Skip to content

feat: add a ssh proxy that can connect to running sessions - #4586

Open
eikek wants to merge 23 commits into
release-2.22.0from
build/renku-ssh-proxy
Open

eikek wants to merge 23 commits into
release-2.22.0from
build/renku-ssh-proxy

Conversation

@eikek

@eikek eikek commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

Adds support for SSH in sessions that use images built from code.
Currently not supported on apps, jobs or on remote clusters or sessions on CSCS/slurm.

/deploy extra-values=sshProxy.enabled=true,dataService.imageBuilders.enabled=true,dataService.imageBuilders.strategyName=renku-buildpacks-v3,dataService.imageBuilders.outputImagePrefix=harbor.dev.renku.ch/renku-build/,dataService.imageBuilders.nodeSelector.renku.io/node-purpose=user,dataService.imageBuilders.tolerations[0].effect=NoSchedule,dataService.imageBuilders.tolerations[0].key=renku.io/dedicated,dataService.imageBuilders.tolerations[0].operator=Equal,dataService.imageBuilders.tolerations[0].value=user,sshProxy.domain=ssh.ci-renku-4586.dev.renku.ch

@RenkuBot

RenkuBot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

You can access the deployment of this PR at https://ci-renku-4586.dev.renku.ch

@eikek
eikek force-pushed the build/renku-ssh-proxy branch from 73e8c3c to 64a3457 Compare September 9, 2026 07:28

@SalimKayal SalimKayal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok a few things on the helm side.

Comment thread helm-chart/renku/templates/ssh-proxy/deployment.yaml
Comment thread helm-chart/renku/templates/ssh-proxy/secret.yaml Outdated
Comment thread helm-chart/renku/templates/ssh-proxy/deployment.yaml Outdated
Comment thread helm-chart/renku/values.yaml Outdated
Comment thread helm-chart/renku/values.yaml Outdated
Comment thread helm-chart/renku/values.yaml Outdated
Comment thread helm-chart/renku/templates/ssh-proxy/service.yaml Outdated
Comment thread helm-chart/renku/templates/ssh-proxy/deployment.yaml Outdated
Comment thread helm-chart/renku/templates/ssh-proxy/service.yaml Outdated
Comment thread helm-chart/renku/templates/network-policies.yaml

@SalimKayal SalimKayal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oups, approved instead of requesting changes, sorry.

@eikek
eikek force-pushed the build/renku-ssh-proxy branch from e52784e to c701fef Compare September 10, 2026 15:59
@eikek
eikek requested a review from SalimKayal September 11, 2026 12:32
@eikek
eikek force-pushed the build/renku-ssh-proxy branch from 18a26c4 to 4c04672 Compare September 11, 2026 12:40
Comment thread renku-ssh-proxy/src/main.rs Outdated
Comment thread renku-ssh-proxy/src/proxy_server.rs Outdated
Comment thread renku-ssh-proxy/src/proxy_server.rs Outdated
Comment thread renku-ssh-proxy/src/proxy_server.rs Outdated
Comment thread renku-ssh-proxy/src/proxy_server.rs Outdated
Comment thread renku-ssh-proxy/src/proxy_server.rs Outdated
Comment thread renku-ssh-proxy/src/config.rs
Comment thread renku-ssh-proxy/src/config.rs Outdated

@sgaist sgaist left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some suggestion around labeling this new backend that should allow for improved handling in the near future.

Comment thread helm-chart/renku/templates/ssh-proxy/deployment.yaml
Comment thread helm-chart/renku/templates/ssh-proxy/deployment.yaml
Comment thread helm-chart/renku/templates/network-policies.yaml Outdated
Comment thread helm-chart/renku/values.yaml
Comment thread helm-chart/renku/values.yaml Outdated
Comment thread helm-chart/renku/values.yaml Outdated
Comment thread helm-chart/renku/templates/ssh-proxy/secret.yaml Outdated
Comment thread renku-ssh-proxy/src/config.rs Outdated
Comment on lines +696 to +716
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-to-ssh-proxy
labels:
app: {{ template "renku.name" . }}
chart: {{ template "renku.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
podSelector:
matchLabels:
app: renku-ssh-proxy
release: {{ .Release.Name }}
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
---

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this with the external-dns annotation ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your comment is on the wrong file. I think you are talking about the annotation of the service, right?
This part of the service, right?

"external-dns.kubernetes.io/hostname": {{ include "renku.sshDomain" . | quote }}

That is how the load balancer type service gets assigned a hostname for ingress in azure I believe. But that annotation is general and not azure specific. It just depends on the networking/ingress controller of your cluster. In some cases the controller may do nothing with it. I opened a PR to clean some of this up. I changed a few things around this - take a look there. #4680

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I did in my cleanup is changed the values file so that you can disable the load balancer service. Then you have to setup the ingress outside of the helm chart. Also I added the option to specify additional annotations.

SalimKayal added a commit that referenced this pull request Oct 8, 2026
…, default to port 22 (#4675)

## Summary

Cleanup of the SSH proxy: the proxy-to-session target is now fixed rather than configurable, the hop-2 keygen is gated behind `sshProxy.enabled`, the proxy runs two hardened replicas, and the public entry defaults to port **22** so `ssh <session>@ssh.<host>` works with no `-p`. Stacked on #4586.

## Motivation and context

- The hop-2 target is fixed by the amalthea contract. The session Service port is `80` and the login user is `renku`. Exposing `sshProxy.targetPort`/`targetUser` in values invites an operator to change a value that silently breaks every session; the proxy's own fallback was also stale at `2222`.
- The `platform-init` hook generated the two hop-2 Secrets unconditionally, even when the proxy is disabled.
- A single replica with no PodDisruptionBudget is not HA for an internet-facing entry point.
- The entry defaulted to `2222`, so the copy-paste command needed a non-standard port. data-services already omits `:22` from the `ssh://` URL, so defaulting to 22 makes the plain command correct.

## Behavior

- `ssh <session>@ssh.<host>` connects on the default port 22; `sshProxy.service.port` remains operator-overridable.
- With `sshProxy.enabled=false`, the hop-2 key Secrets are no longer generated.
- Two proxy replicas are spread across nodes and a PDB keeps one available during node drains.
- The external ingress NetworkPolicy admits only the proxy's pod port (2222) instead of all ports.

## Changes

- `renku-ssh-proxy/src/config.rs`: hop-2 default target port `2222 → 80`.
- `helm-chart/renku/templates/ssh-proxy/deployment.yaml`: drop the `RENKU_SSH_PROXY_TARGET_PORT`/`_TARGET_USER` env; default to preferred hostname pod anti-affinity (overridable via `sshProxy.affinity`).
- `helm-chart/renku/templates/ssh-proxy/pdb.yaml` (new): `policy/v1` PDB, `maxUnavailable: 50%`, created only when enabled and `replicaCount > 1`.
- `helm-chart/renku/values.yaml`: remove `targetPort`/`targetUser`; `replicaCount: 1 → 2`; `service.port: 2222 → 22`.
- `helm-chart/renku/templates/network-policies.yaml`: pin `allow-external-to-ssh-proxy` to pod port `2222`.
- `helm-chart/renku/templates/setup-job-platform-init.yaml` + `scripts/platform-init/platform-init.py`: pass `SSH_PROXY_ENABLED` into the hook and guard `ensure_ssh_proxy_session_keys` on it.

## Notes

- The NetworkPolicy pins the **pod** port `2222`, which assumes the CNI evaluates ingress policy post-DNAT (Calico/Cilium do). Verify on the first cluster that enables the proxy; a pre-DNAT CNI would need the Service port as well.
@olevski
olevski changed the base branch from master to release-2.22.0 October 12, 2026 20:45
@olevski

olevski commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

We should see if we can change the ssh command shown in the ui. ssh://... does not work in all places. For example I was testing with positron - like a reskinned vscode that R users like. And the ssh:// prefix does not work there.

bethcg and others added 5 commits October 14, 2026 15:58
* Deleted old references to apps

* resolved broken links and how to configure frontends

* Run Prettier

* resolve links

* resolve broken link

* resolve first link

* resolve broken links
* chore: add renku cli docs

* address comments
- allow to connect to a fixed target host
- include nix testing vm
- authenticate with fixed public key
- Add dockerfile and workflow for build/publish
- Add building ssh-proxy image to chartpress
* Enable local port-forwarding
* Do not allow forwarding outside the session host
* Don't break the session if port-forwarding fails
* Don't drop connection when data-service connection fails
* chore(helm): drop unused internal-authn mount from ssh-proxy

* fix(helm): generate ssh-proxy host key when unset

* feat(ssh-proxy-session): add ssh-proxy-session-keys generator image

* feat(ssh-proxy-session): generate static proxy-to-session keys via hook job

* refactor(ssh-proxy-session): generate proxy-to-session keys in platform-init
SalimKayal and others added 17 commits October 14, 2026 15:58
* feat(ssh-proxy-session): add ssh-proxy-session-keys generator image

* feat(ssh-proxy-session): generate static proxy-to-session keys via hook job

* refactor(ssh-proxy-session): generate proxy-to-session keys in platform-init

* feat(ssh-proxy-session): load session host/auth keys from config

* feat(ssh-proxy-session): pin session host key and use publickey auth

* feat(ssh-proxy-session): mount session host/auth keys in ssh-proxy

* chore(ssh-proxy-session): devshell and dev VM support for session keys

* feat(ssh-proxy-session): pass session key secret names to data-services

* refactor(ssh-proxy): remove b64 decode fallback from load_private_key_from_file

* squashme: Update helm-chart/renku/templates/data-service/session-env-secret.yaml

---------

Co-authored-by: Tasko Olevski <16360283+olevski@users.noreply.github.com>
* feat(ssh-proxy-session): add ssh-proxy-session-keys generator image

* feat(ssh-proxy-session): generate static proxy-to-session keys via hook job

* refactor(ssh-proxy-session): generate proxy-to-session keys in platform-init

* feat(ssh-proxy-session): load session host/auth keys from config

* refactor(ssh-proxy): remove b64 decode fallback from load_private_key_from_file

* feat(ssh-proxy): set explicit SSH auth and keepalive limits

* feat(ssh-proxy): harden proxy pod security context

* feat(ssh-proxy): restrict proxy egress to required peers

* fix(ssh-proxy): disable unused ServiceAccount token projection

* squashme: Apply batched suggestions from code review

Co-authored-by: Samuel Gaist <samuel.gaist@idiap.ch>

* Update helm-chart/renku/values.yaml

---------

Co-authored-by: Tasko Olevski <16360283+olevski@users.noreply.github.com>
Co-authored-by: Samuel Gaist <samuel.gaist@idiap.ch>
* feat(ssh-proxy-session): add ssh-proxy-session-keys generator image

* feat(ssh-proxy-session): generate static proxy-to-session keys via hook job

* refactor(ssh-proxy-session): generate proxy-to-session keys in platform-init

* feat(ssh-proxy-session): load session host/auth keys from config

* refactor(ssh-proxy): remove b64 decode fallback from load_private_key_from_file

* ci(ssh-proxy): audit Cargo.lock for known vulnerabilities

* ci(ssh-proxy): track cargo dependencies with dependabot

* docs(ssh-proxy): document the dependency audit cadence
- also pass some information into the data service
…, default to port 22 (#4675)

## Summary

Cleanup of the SSH proxy: the proxy-to-session target is now fixed rather than configurable, the hop-2 keygen is gated behind `sshProxy.enabled`, the proxy runs two hardened replicas, and the public entry defaults to port **22** so `ssh <session>@ssh.<host>` works with no `-p`. Stacked on #4586.

## Motivation and context

- The hop-2 target is fixed by the amalthea contract. The session Service port is `80` and the login user is `renku`. Exposing `sshProxy.targetPort`/`targetUser` in values invites an operator to change a value that silently breaks every session; the proxy's own fallback was also stale at `2222`.
- The `platform-init` hook generated the two hop-2 Secrets unconditionally, even when the proxy is disabled.
- A single replica with no PodDisruptionBudget is not HA for an internet-facing entry point.
- The entry defaulted to `2222`, so the copy-paste command needed a non-standard port. data-services already omits `:22` from the `ssh://` URL, so defaulting to 22 makes the plain command correct.

## Behavior

- `ssh <session>@ssh.<host>` connects on the default port 22; `sshProxy.service.port` remains operator-overridable.
- With `sshProxy.enabled=false`, the hop-2 key Secrets are no longer generated.
- Two proxy replicas are spread across nodes and a PDB keeps one available during node drains.
- The external ingress NetworkPolicy admits only the proxy's pod port (2222) instead of all ports.

## Changes

- `renku-ssh-proxy/src/config.rs`: hop-2 default target port `2222 → 80`.
- `helm-chart/renku/templates/ssh-proxy/deployment.yaml`: drop the `RENKU_SSH_PROXY_TARGET_PORT`/`_TARGET_USER` env; default to preferred hostname pod anti-affinity (overridable via `sshProxy.affinity`).
- `helm-chart/renku/templates/ssh-proxy/pdb.yaml` (new): `policy/v1` PDB, `maxUnavailable: 50%`, created only when enabled and `replicaCount > 1`.
- `helm-chart/renku/values.yaml`: remove `targetPort`/`targetUser`; `replicaCount: 1 → 2`; `service.port: 2222 → 22`.
- `helm-chart/renku/templates/network-policies.yaml`: pin `allow-external-to-ssh-proxy` to pod port `2222`.
- `helm-chart/renku/templates/setup-job-platform-init.yaml` + `scripts/platform-init/platform-init.py`: pass `SSH_PROXY_ENABLED` into the hook and guard `ensure_ssh_proxy_session_keys` on it.

## Notes

- The NetworkPolicy pins the **pod** port `2222`, which assumes the CNI evaluates ingress policy post-DNAT (Calico/Cilium do). Verify on the first cluster that enables the proxy; a pre-DNAT CNI would need the Service port as well.
@olevski
olevski force-pushed the build/renku-ssh-proxy branch from 07b9a35 to 0b5abda Compare October 14, 2026 13:59
@olevski
olevski requested a review from Panaetius October 14, 2026 13:59

@olevski olevski left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested in this deployment. Things look good to me. There is some small followup issues and work. But we can get those in a separate PR into release-2.22.0. This PR is already large enough.

@olevski
olevski marked this pull request as ready for review October 14, 2026 15:19
@olevski
olevski requested review from a team as code owners October 14, 2026 15:19

@leafty leafty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to have the UI display the server fingerprint or include it in the ssh command? SSH being Trust-on-first-use is bad for security and it would be nice to avoid people trusting SSH servers just by habit.

@leafty leafty left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to have the UI display the server fingerprint or include it in the ssh command? SSH being Trust-on-first-use is bad for security and it would be nice to avoid people trusting SSH servers just by habit.

@lorenzo-cavazzi lorenzo-cavazzi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm! 🚀

P.S. I agree with the suggestion from @leafty

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants