initial commit: cirrus edge (Talos + sish)

- talos/: generated base config (gitignored) + patches for control-plane
  scheduling, unprivileged ports and the ingress firewall
- kubernetes/: sish base and cirrus-dev overlay, applied with kubectl
- READMEs incl. production rollout plan

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 12:16:27 +02:00
co-authored by Claude Opus 5.5
commit 3d3ddc98e9
14 changed files with 487 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# sish configuration (keys mirror the CLI flags, see `sish --help`).
# Cluster-specific values (domain, bind-hosts) are injected as SISH_* env vars
# from the `sish-env` ConfigMap in each overlay. Env takes precedence over this file.
# Listeners
ssh-address: ":2222"
http-address: ":80"
https: false # sish never terminates TLS; :443 is an SNI passthrough listener
# 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 sish-public rule in
# talos/patches/firewall.yaml, otherwise a claimed port is silently unreachable.
# 22 gitea ssh, 443 SNI, 20000-20099 reserved for raw tcp forwards.
# Ports below 80 also need talos/patches/unprivileged-ports.yaml.
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
+94
View File
@@ -0,0 +1,94 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sish
labels:
app.kubernetes.io/name: sish
spec:
replicas: 1
# Host ports cannot be shared, so the old pod must be gone before the new one starts.
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: sish
template:
metadata:
labels:
app.kubernetes.io/name: sish
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
enableServiceLinks: false
automountServiceAccountToken: false
# Binding :22/:80/:443 as non-root relies on the node sysctl
# net.ipv4.ip_unprivileged_port_start=22 (talos/patches/unprivileged-ports.yaml).
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: sish
image: docker.io/antoniomika/sish:v2.23.0
args:
- --config=/config/config.yml
envFrom:
- configMapRef:
name: sish-env
optional: true
ports:
- name: ssh
containerPort: 2222
- name: http
containerPort: 80
- name: https
containerPort: 443
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
memory: 256Mi
readinessProbe:
tcpSocket:
port: ssh
periodSeconds: 10
livenessProbe:
tcpSocket:
port: ssh
initialDelaySeconds: 10
periodSeconds: 20
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: hostkey
mountPath: /keys
readOnly: true
- name: pubkeys
mountPath: /pubkeys
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: config
configMap:
name: sish-config
- name: hostkey
secret:
secretName: sish-hostkey
defaultMode: 0440
- name: pubkeys
configMap:
name: sish-pubkeys
- name: tmp
emptyDir:
sizeLimit: 16Mi
+14
View File
@@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: sish
resources:
- namespace.yaml
- deployment.yaml
configMapGenerator:
- name: sish-config
files:
- config.yml
# Provided by each overlay:
# ConfigMap sish-env (SISH_DOMAIN, SISH_BIND_HOSTS)
# ConfigMap sish-pubkeys (authorized connector public keys)
# Secret sish-hostkey (pinned SSH host key)
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: sish
labels:
# hostNetwork is only permitted by the privileged profile.
# The pod itself still runs non-root with all capabilities dropped.
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: baseline