variant: fcos version: 1.6.0 # cirrus: 5001 admin sshd | 5002 sish SSH | 80 HTTP by Host | 443 TLS by SNI (passthrough) # 22, 20000-20099 raw TCP forwards passwd: users: - name: core ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILL4RXmGVhbcCdh3a6TdgR+7EER250UUDk0VtrwUvb9E philipp@philipp-laptop storage: directories: - path: /var/lib/sish/keys mode: 0700 user: { id: 65532 } group: { id: 65532 } files: - path: /etc/hostname mode: 0644 contents: inline: cirrus.traberph.de # netcup: IPv4 via DHCP, IPv6 static (no router advertisements, gateway is always fe80::1) - path: /etc/NetworkManager/system-connections/ens3.nmconnection mode: 0600 contents: inline: | [connection] id=ens3 type=ethernet interface-name=ens3 [ipv4] method=auto [ipv6] method=manual address1=2a03:4000:2:83c::1/64 gateway=fe80::1 # Edge SSH host key (connectors pin its public half). Kept only locally in coreos/.secrets/ # (gitignored), so back it up outside this folder. Build: butane --files-dir coreos ... - path: /var/lib/sish/keys/ssh_host_ed25519_key mode: 0400 user: { id: 65532 } group: { id: 65532 } contents: local: .secrets/ssh_host_ed25519_key - path: /var/lib/sish/pubkeys/clients # k8s tunnel client keys (authorized_keys format) mode: 0644 contents: inline: | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEsHP4H4HLHCEhycaAV+YZZV/HOr7HSiDkgpMA+WLO56 connector-cumulus - path: /etc/ssh/sshd_config.d/10-cirrus.conf mode: 0644 contents: inline: | Port 5001 AuthenticationMethods publickey PermitRootLogin no AllowUsers core - path: /etc/cirrus/sshd_port_5001.cil # 5001 is commplex_link_port_t mode: 0644 contents: inline: | (allow sshd_t commplex_link_port_t (tcp_socket (name_bind))) # Same sish config as kubernetes/base/sish/config.yml (+ the cirrus overlay values), # only the SSH port differs (5002 instead of 2222). - path: /etc/sish/config.yml mode: 0644 contents: inline: | # Listeners ssh-address: ":5002" http-address: ":80" https: false # sish never terminates TLS; :443 is an SNI passthrough listener # Single tenant: connectors may claim any hostname containing a dot, wildcards included # (*.example.com). Only a name without a dot falls back to .. bind-any-host: true verify-dns: false # _sish TXT ownership checks, pointless with bind-any-host domain: tunnel.traberph.de # the edge's own name (A/AAAA -> VPS) # SNI passthrough + multiple connectors per hostname sni-proxy: true sni-load-balancer: true tcp-load-balancer: true http-load-balancer: true # Connectors get exactly what they ask for, or the bind fails bind-random-ports: false bind-random-subdomains: false bind-random-aliases: false force-requested-subdomains: true force-requested-ports: true bind-wildcards: true # Ports connectors may claim. Must match the nftables rule below, otherwise a claimed # port is silently unreachable. # 22 gitea ssh, 443 SNI, 20000-20099 reserved for raw tcp forwards. port-bind-range: "22,443,20000-20099" # PROXY header version for connectors that request it (sish-client default: v2) proxy-protocol: true proxy-protocol-version: "2" # Default is 5s, which kills idle websockets/SSE/slow uploads idle-connection-timeout: 1h # Auth: public keys only authentication: true authentication-keys-directory: /pubkeys private-keys-directory: /keys # No web UI / consoles redirect-root: false admin-console: false service-console: false load-templates: false # Default -1 makes the HTTP muxer io.ReadAll() every request/response body into memory # (for the console), even with consoles disabled: large uploads OOM-kill sish. # 0 = never buffer, stream bodies through. service-console-max-content-length: 0 log-to-stdout: true log-to-file: false - path: /etc/containers/systemd/sish.container mode: 0644 contents: inline: | [Unit] Description=sish tunnel server [Container] ContainerName=sish Image=ghcr.io/antoniomika/sish:v2.24.0 Network=host User=65532 Group=65532 DropCapability=all AddCapability=CAP_NET_BIND_SERVICE NoNewPrivileges=true ReadOnly=true # sish creates a temp file per forward. The automatic read-only /tmp tmpfs copies the # image's /tmp (root, 755), so uid 65532 can't write there: give it a plain sticky /tmp. Tmpfs=/tmp:rw,nosuid,nodev,noexec,size=16m,mode=1777,notmpcopyup Volume=/etc/sish:/config:ro,Z Volume=/var/lib/sish/keys:/keys:ro,Z Volume=/var/lib/sish/pubkeys:/pubkeys:ro,Z Exec=--config=/config/config.yml [Service] Restart=always RestartSec=5 MemoryMax=256M [Install] WantedBy=multi-user.target # OS updates: Zincati stages new FCOS releases automatically, this limits the reboot to a window - path: /etc/zincati/config.d/55-updates-strategy.toml mode: 0644 contents: inline: | [updates] strategy = "periodic" [[updates.periodic.window]] days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] start_time = "03:00" # UTC length_minutes = 60 # sish updates: no floating tags upstream (only vX.Y.Z), so podman auto-update can't follow a # version line. This bumps Image= to the newest tag within TRACK and rolls back if sish # doesn't come up again. - path: /usr/local/bin/sish-update mode: 0755 contents: inline: | #!/bin/bash set -euo pipefail TRACK=v2. # "v2." = minor + patch releases, "v2.24." = patch releases only unit=/etc/containers/systemd/sish.container repo=ghcr.io/antoniomika/sish current=$(sed -n "s|^Image=$repo:||p" "$unit") latest=$(podman search --list-tags --limit 10000 --format '{{.Tag}}' "$repo" \ | grep -E "^${TRACK//./\\.}[0-9]+(\.[0-9]+)*$" | sort -V | tail -n1) if [[ -z $latest || $(printf '%s\n' "$current" "$latest" | sort -V | tail -n1) == "$current" ]]; then exit 0 fi set_image() { sed -i "s|^Image=.*|Image=$repo:$1|" "$unit" systemctl daemon-reload systemctl restart sish.service } echo "sish: $current -> $latest" podman pull -q "$repo:$latest" >/dev/null set_image "$latest" sleep 30 if ! ss -Htln 'sport = :5002' | grep -q .; then echo "sish $latest not listening on :5002, rolling back to $current" >&2 set_image "$current" exit 1 fi podman image prune -af >/dev/null - path: /etc/sysconfig/nftables.conf mode: 0600 overwrite: true contents: inline: | table inet cirrus delete table inet cirrus table inet cirrus { chain input { type filter hook input priority filter; policy drop; ct state established,related accept ct state invalid drop iif "lo" accept meta l4proto { icmp, ipv6-icmp } accept udp sport 67 udp dport 68 accept ip6 saddr fe80::/10 udp sport 547 udp dport 546 accept tcp dport { 22, 80, 443, 5001, 5002, 20000-20099 } accept # sish ports: see port-bind-range } } systemd: units: - name: sshd-port-selinux.service enabled: true contents: | [Unit] Description=Allow sshd to bind 5001/tcp (SELinux) Before=sshd.service [Service] Type=oneshot RemainAfterExit=yes ExecCondition=/bin/sh -c '! /usr/sbin/semodule -l | grep -qx sshd_port_5001' ExecStart=/usr/sbin/semodule -i /etc/cirrus/sshd_port_5001.cil [Install] WantedBy=multi-user.target - name: nftables.service enabled: true - name: sish-update.service contents: | [Unit] Description=Update sish within its major version Wants=network-online.target After=network-online.target sish.service [Service] Type=oneshot ExecStart=/usr/local/bin/sish-update - name: sish-update.timer enabled: true contents: | [Unit] Description=Daily sish update check [Timer] OnCalendar=*-*-* 05:00:00 UTC RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target