From 98f971890e8aa1bddb2587bab82849cfc2070339 Mon Sep 17 00:00:00 2001 From: Matteo Bunino <48362942+matbun@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:51:46 +0200 Subject: [PATCH 1/5] docs: add an SSH tunnel configuration guide Documents the ssh shadow mode: what it does, how it differs from wstunnel and full mesh, both authentication methods, host key pinning, how the compute node reaches the shadow, and the credential replication tradeoff. Kept to its own guide rather than folded into the wstunnel one, since the two are alternatives rather than variations. The cookbook rework is tracked separately in interlink-hq/interLink#547. Signed-off-by: Matteo Bunino <48362942+matbun@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) --- .../guides/14-ssh-tunnel-configuration.mdx | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 docs/docs/guides/14-ssh-tunnel-configuration.mdx diff --git a/docs/docs/guides/14-ssh-tunnel-configuration.mdx b/docs/docs/guides/14-ssh-tunnel-configuration.mdx new file mode 100644 index 00000000..73694126 --- /dev/null +++ b/docs/docs/guides/14-ssh-tunnel-configuration.mdx @@ -0,0 +1,281 @@ +--- +title: "SSH Tunnel Configuration" +description: "Reaching services inside offloaded pods on air-gapped HPC sites over an SSH login node" +sidebar_position: 14 +--- + +# SSH Tunnel Configuration + +Many HPC sites give compute nodes no outbound internet access, and many Kubernetes +clusters have no publicly reachable ingress. The [wstunnel](./10-wstunnel-configuration.mdx) +shadow needs both: the workload dials out to an ingress and runs a wstunnel client +there. On an air-gapped site it cannot. + +The SSH shadow inverts the direction. Instead of the compute node dialing out, the +cluster dials **in** to the site's SSH login node and forwards each exposed port +from the compute node the job landed on: + +``` +browser → Ingress / Service → shadow pod (ssh -L) → HPC login node → compute node (Jupyter) +``` + +The only requirement is outbound SSH from the cluster to the login node. The +offloaded pod runs nothing on its side: no wstunnel client, no WireGuard +configuration, no pre-exec injection. + +:::info +The SSH shadow replaces **wstunnel**, not [full mesh](./13-mesh-network-configuration.mdx). +It exposes the offloaded pod's ports to the cluster; it does not give the pod access +back into the cluster, so an offloaded workload still cannot reach in-cluster object +storage or message buses. Combining the two is rejected at startup and tracked in +[#548](https://github.com/interlink-hq/interLink/issues/548). +::: + +--- + +## Which shadow does what + +| | Direction | Trigger | Workload must run | +| --- | --- | --- | --- | +| `wstunnel` (default) | cluster → pod | `EnableTunnel` + exposed ports | wstunnel client, dials out | +| `ssh` | cluster → pod | `EnableTunnel` + exposed ports | nothing | +| full mesh | bidirectional | `FullMesh: true`, every pod | `mesh.sh` (slirp4netns + WireGuard) | + +--- + +## Configuration + +### Virtual Kubelet + +```yaml +# VirtualKubeletConfig.yaml +Network: + EnableTunnel: true + ShadowMode: ssh + SSH: + LoginHost: login.hpc.example.org + User: alice + KeySecret: hpc-ssh-key +``` + +`EnableTunnel` turns shadow pods on for offloaded pods with exposed ports; +`ShadowMode` picks which shadow is rendered. Both are needed. + +### Helm + +```yaml +virtualNode: + network: + enableTunnel: true + shadowMode: ssh + ssh: + loginHost: login.hpc.example.org + user: alice + keySecret: hpc-ssh-key +``` + +See [`examples/ssh_tunnel.yaml`](https://github.com/interlink-hq/interlink-helm-chart/blob/main/interlink/examples/ssh_tunnel.yaml) +in the chart repository for a complete deployment. + +### Options + +| Option | Default | Description | +| --- | --- | --- | +| `LoginHost` | — | SSH login node to forward through (**required**) | +| `User` | — | Login name on that node (**required**) | +| `Port` | `22` | Login node's SSH port | +| `Image` | `ghcr.io/interlink-hq/interlink/ssh-tunnel:` | Image the shadow runs. Needs an ssh client, plus `kinit` for Kerberos | +| `Auth` | `publickey` | `publickey` or `kerberos` | +| `KeySecret` | — | Secret holding the private key (`publickey`) | +| `KeySecretKey` | `id_ed25519` | Key inside `KeySecret` | +| `KeytabSecret` | — | Secret holding the keytab (`kerberos`) | +| `KeytabSecretKey` | `user.keytab` | Key inside `KeytabSecret` | +| `Principal` | — | Kerberos principal (`kerberos`) | +| `Krb5ConfigMap` | — | ConfigMap with a `krb5.conf`, mounted at `/etc/krb5.conf` | +| `KnownHostsConfigMap` | — | ConfigMap with a `known_hosts` file | +| `ReplicateCredentials` | `true` | Copy the credential into each shadow's namespace | +| `NodeWaitTimeout` | `2h` | How long the shadow waits for the compute node | +| `ExtraOptions` | `[]` | Extra ssh options, each passed as `-o