coreos: sish edge config for production

- sish config mirrors kubernetes/base/sish (HTTP by Host on :80, SNI passthrough
  on :443, raw TCP on 22 and 20000-20099), single tenant via bind-any-host,
  domain tunnel.traberph.de
- host key and connector key provisioned from gitignored coreos/.secrets/
- MemoryMax=256M for sish
- Zincati reboot window 03:00-04:00 UTC, daily sish update within v2 with rollback
- ignore *.ign (contains the host key)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 19:11:30 +02:00
co-authored by Claude Opus 5.5
parent 8688bd91d3
commit adb68d5146
2 changed files with 150 additions and 22 deletions
+147 -22
View File
@@ -1,29 +1,37 @@
variant: fcos
version: 1.6.0
# cirrus: 5001 admin sshd | 5002 sish SSH | 22,80,443 raw TCP -> k8s gateway
# 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 AAAA_REPLACE_ADMIN_KEY admin
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILL4RXmGVhbcCdh3a6TdgR+7EER250UUDk0VtrwUvb9E philipp@philipp-laptop
storage:
directories:
- path: /var/lib/sish/keys # sish host key, generated on first start
- path: /var/lib/sish/keys
mode: 0700
user: { id: 65532 }
group: { id: 65532 }
files:
# 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 AAAA_REPLACE_CLIENT_KEY k8s-tunnel
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
@@ -34,6 +42,65 @@ storage:
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 <name>.<domain>.
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:
@@ -51,30 +118,65 @@ storage:
AddCapability=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ReadOnly=true
Volume=/var/lib/sish/keys:/keys:Z
Volume=/etc/sish:/config:ro,Z
Volume=/var/lib/sish/keys:/keys:ro,Z
Volume=/var/lib/sish/pubkeys:/pubkeys:ro,Z
Exec=--ssh-address=:5002 \
--domain=cirrus.example.com \
--private-keys-directory=/keys \
--authentication-keys-directory=/pubkeys \
--http-address=127.0.0.1:5080 \
--http-request-port-override=5080 \
--https-request-port-override=5080 \
--port-bind-range=22,80,443 \
--bind-random-ports=false \
--force-requested-ports=true \
--force-tcp-address=true \
--tcp-load-balancer=true \
--idle-connection=false \
--proxy-protocol=true \
--proxy-protocol-version=userdefined
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
@@ -91,7 +193,7 @@ storage:
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 } accept
tcp dport { 22, 80, 443, 5001, 5002, 20000-20099 } accept # sish ports: see port-bind-range
}
}
@@ -114,4 +216,27 @@ systemd:
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