ssh-proxy is an SSH gateway for k8shell workspaces. It terminates SSH connections, authenticates clients via the identity service, resolves and provisions their workspace via the provisioner service, then proxies all channel traffic — shell, exec, sftp, port-forwards, and Unix socket forwarding — to the in-workspace k8shelld daemon over gRPC.
Supported channel types:
| Channel | SSH client usage |
|---|---|
session |
Interactive shell, exec, sftp subsystem |
direct-tcpip |
Port forwarding (-L), ProxyCommand -W |
direct-streamlocal |
Unix socket forwarding |
Authentication: public key (via identity service), keyboard-interactive (onboarding flow).
Not supported: RemoteForward (-R), DynamicForward (-D), X11 forwarding — all global requests are rejected.
- Client connects and authenticates (public key or keyboard-interactive)
- ssh-proxy resolves the user's workspace via the identity + provisioner services
- A gRPC connection is established to
k8shelldinside the workspace - Each channel action (shell, exec, sftp, agent-forward) is evaluated by the authz service (if configured)
- Channel requests are proxied through k8shelld; sessions are tracked via the session service
When an authz service address is configured, every SSH action is evaluated before the operation begins. The authz service can also return a RecordObligation that overrides the static per-channel recording settings for that session. When authz is not configured, the server falls back to the static recording config.
When server.forking: true, ssh-proxy spawns a subprocess per connection, passing the socket via fd 3:
SSH Client → acceptConnections() → startSubProcess()
└── spawns: ./ssh-proxy --child --config config.yaml
└── HandleConnectionChildProcess() → handleConnection()
Prerequisites: Go 1.24+, access to identity/session/provisioner/k8shelld services.
# Build
make build
# Run (uses config/config.yaml by default)
./bin/ssh-proxy
# Flags
./bin/ssh-proxy --config config/config.yaml # config path
./bin/ssh-proxy --logtext # human-readable log output
./bin/ssh-proxy -v # print version and exitConfiguration is a YAML file. Secrets and connection parameters can be injected via environment variables or !file references. See config/config.yaml for the full reference.
| Target | Description |
|---|---|
make build |
Compile binary to bin/ssh-proxy |
make test |
Run unit tests with coverage |
make test-static |
Run golangci-lint and gosec |
make test-self |
Static analysis + build + smoke tests (used in CI) |
make image |
Build Docker image (Alpine by default) |
RUNTIME=distroless make image |
Build production-hardened distroless image |
make vendor |
Vendor Go dependencies |
Two runtime stages are available in docker/ssh-proxy/Dockerfile:
| Stage | Base | Use case |
|---|---|---|
alpine |
alpine:3.21.3 |
Development, debugging (has a shell; restarts on exit) |
distroless |
distroless/static-debian12:nonroot |
Production (no shell, runs as non-root) |
Both stages use the same statically compiled binary (CGO_ENABLED=0). The alpine stage includes debug symbols; the distroless stage strips them (-ldflags="-s -w").
Deployment configuration and Helm charts for ssh-proxy and the other k8shell services are maintained in the k8shell-io/charts repository.
Host my-workspace
Hostname ssh-internal.k8shell-test # workspace-internal hostname
User alice
IdentityFile ~/.ssh/id_rsa
ProxyCommand ssh -i ~/.ssh/id_rsa -W %h:%p -p 22 alice@app.k8shell.dev
ForwardAgent yes
ServerAliveInterval 30
StrictHostKeyChecking accept-newAGPL-3.0-or-later. See LICENSE.