Skip to content

Commit

Permalink
feat: ssh access to sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
olevski committed Jan 18, 2023
1 parent 4cbcb14 commit bd7979d
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 0 deletions.
6 changes: 6 additions & 0 deletions chartpress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ charts:
valuesPath: k8sWatcher.image
paths:
- k8s-watcher
ssh-jump-host:
contextPath: ssh-jump-host
dockerfilePath: ssh-jump-host/Dockerfile
valuesPath: ssh.image
paths:
- ssh-jump-host
109 changes: 109 additions & 0 deletions helm-chart/renku-notebooks/templates/ssh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{{- if .Values.ssh.enabled }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "notebooks.fullname" . }}-ssh
labels:
app: {{ template "notebooks.name" . }}-ssh
chart: {{ template "notebooks.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
{{- if not .Values.ssh.autoscaling.enabled }}
replicas: {{ .Values.ssh.replicaCount }}
{{- end }}
selector:
matchLabels:
app: {{ template "notebooks.name" . }}-ssh
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "notebooks.name" . }}-ssh
chart: {{ template "notebooks.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ if .Values.rbac.create }}"{{ template "notebooks.fullname" . }}"{{ else }}"{{ .Values.rbac.serviceAccountName }}"{{ end }}
securityContext:
fsGroup: 1000
containers:
- name: ssh
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
image: "{{ .Values.ssh.image.repository }}:{{ .Values.ssh.image.tag }}"
imagePullPolicy: {{ .Values.ssh.image.pullPolicy }}
ports:
- name: ssh
containerPort: 2022
protocol: TCP
resources:
{{- toYaml .Values.ssh.resources | nindent 12 }}
{{- if not (kindIs "invalid" .Values.ssh.hostKeySecret) }}
volumeMounts:
- name: ssh-host-key
mountPath: /opt/ssh/ssh_host_keys
readOnly: true
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if not (kindIs "invalid" .Values.ssh.hostKeySecret) }}
volumes:
- name: ssh-host-key
secret:
secretName: {{ .Values.ssh.hostKeySecret | quote }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ template "notebooks.fullname" . }}-ssh
labels:
app: {{ template "notebooks.name" . }}-ssh
chart: {{ template "notebooks.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- name: ssh
{{- if eq .Values.ssh.service.type "NodePort" }}
nodePort: {{ .Values.ssh.service.port }}
{{- end }}
port: {{ .Values.ssh.service.port }}
protocol: TCP
targetPort: ssh
selector:
app: {{ template "notebooks.name" . }}-ssh
release: {{ .Release.Name }}
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: {{ template "notebooks.fullname" . }}-ssh
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ template "notebooks.fullname" . }}-ssh
minReplicas: {{ .Values.ssh.autoscaling.minReplicas }}
maxReplicas: {{ .Values.ssh.autoscaling.maxReplicas }}
targetCPUUtilizationPercentage: {{ .Values.ssh.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
6 changes: 6 additions & 0 deletions helm-chart/renku-notebooks/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ spec:
- name: NB_KEYCLOAK_REALM
value: {{ .Values.global.keycloak.realm | quote }}
{{ end }}
- name: NB_SESSIONS__SSH__ENABLED
value: {{ .Values.ssh.enabled | quote }}
{{- if not (kindIs "invalid" .Values.ssh.hostKeySecret) }}
- name: NB_SESSIONS__SSH__HOST_KEY_SECRET
value: {{ .Values.ssh.hostKeySecret | quote }}
{{- end }}
ports:
- name: http
containerPort: 8000
Expand Down
6 changes: 6 additions & 0 deletions helm-chart/renku-notebooks/templates/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ spec:
- name: NB_KEYCLOAK_REALM
value: {{ $.Values.global.keycloak.realm | quote }}
{{ end }}
- name: NB_SESSIONS__SSH__ENABLED
value: {{ $.Values.ssh.enabled | quote }}
{{- if not (kindIs "invalid" $.Values.ssh.hostKeySecret) }}
- name: NB_SESSIONS__SSH__HOST_KEY_SECRET
value: {{ $.Values.ssh.hostKeySecret | quote }}
{{- end }}
command:
- poetry
- run
Expand Down
32 changes: 32 additions & 0 deletions helm-chart/renku-notebooks/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,35 @@ k8sWatcher:
targetCPUUtilizationPercentage: 50
minReplicas: 2
maxReplicas: 5

ssh:
enabled: true
image:
repository: renku/renku-notebooks-ssh
tag: "latest"
pullPolicy: IfNotPresent
resources: {}
replicaCount: 1
service:
type: ClusterIP
port: 22
autoscaling:
enabled: false
targetCPUUtilizationPercentage: 50
minReplicas: 1
maxReplicas: 3
## If defined the keys in the secret will be mounted as SSH host keys.
## This is useful to make sure that the host can be properly recognized
## when connecting to a session. If left unset then the host keys are
## likely to change causing ssh connections to fail and require removing the
## old keys from ~/.ssh/known_hosts. Therefore it is reccomended that this is
## set for production. The required keys are:
## - ssh_host_dsa_key
## - ssh_host_dsa_key.pub
## - ssh_host_rsa_key
## - ssh_host_rsa_key.pub
## - ssh_host_ecdsa_key
## - ssh_host_ecdsa_key.pub
## - ssh_host_ed25519_key
## - ssh_host_ed25519_key.pub
hostKeySecret:
60 changes: 60 additions & 0 deletions renku_notebooks/api/amalthea_patches/ssh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from typing import Any, Dict, List
from ...config import config


def main() -> List[Dict[str, Any]]:
if not config.sessions.ssh.enabled:
return []
patches = [
{
"type": "application/json-patch+json",
"patch": [
{
"op": "add",
"path": "/service/spec/ports/-",
"value": {
"name": "ssh",
"port": config.sessions.ssh.service_port,
"protocol": "TCP",
"targetPort": "ssh",
},
},
{
"op": "add",
"path": "/statefulset/spec/template/spec/containers/0/ports",
"value": [
{
"name": "ssh",
"containerPort": config.sessions.ssh.container_port,
"protocol": "TCP",
},
],
},
],
}
]
if config.sessions.ssh.host_key_secret:
patches.append(
{
"type": "application/json-patch+json",
"patch": [
{
"op": "add",
"path": "/statefulset/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "ssh-host-keys",
"mountPath": config.sessions.ssh.host_key_location,
},
},
{
"op": "add",
"path": "/statefulset/spec/template/spec/volumes/-",
"value": {
"name": "ssh-host-keys",
"secret": {"secretName": config.sessions.ssh.host_key_secret},
},
},
],
}
)
return patches
2 changes: 2 additions & 0 deletions renku_notebooks/api/classes/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..amalthea_patches import init_containers as init_containers_patches
from ..amalthea_patches import inject_certificates as inject_certificates_patches
from ..amalthea_patches import jupyter_server as jupyter_server_patches
from ..amalthea_patches import ssh as ssh_patches
from ...config import config
from ...errors.programming import ConfigurationError
from ...errors.user import MissingResourceError
Expand Down Expand Up @@ -274,6 +275,7 @@ def _get_session_manifest(self):
git_sidecar_patches.main(self),
general_patches.oidc_unverified_email(self),
cloudstorage_patches.main(self),
ssh_patches.main(),
# init container for certs must come before all other init containers
# so that it runs first before all other init containers
init_containers_patches.certificates(),
Expand Down
1 change: 1 addition & 0 deletions renku_notebooks/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def get_config(default_config: str) -> _NotebooksConfig:
git-sidecar,
]
}
ssh {}
enforce_cpu_limits: false
autosave_minimum_lfs_file_size_bytes: 1000000
termination_grace_period_seconds: 600
Expand Down
15 changes: 15 additions & 0 deletions renku_notebooks/config/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ class _SessionContainers:
registered: List[Text]


@dataclass
class _SessionSshConfig:
enabled: Union[Text, bool] = False
service_port: Union[Text, int] = 22
container_port: Union[Text, int] = 2022
host_key_secret: Optional[Text] = None
host_key_location: Text = "/opt/ssh/ssh_host_keys"

def __post_init__(self):
self.enabled = _parse_str_as_bool(self.enabled)
self.service_port = _parse_value_as_numeric(self.service_port, int)
self.container_port = _parse_value_as_numeric(self.container_port, int)


@dataclass
class _SessionConfig:
culling: _SessionCullingConfig
Expand All @@ -184,6 +198,7 @@ class _SessionConfig:
oidc: _SessionOidcConfig
storage: _SessionStorageConfig
containers: _SessionContainers
ssh: _SessionSshConfig
default_image: Text = "renku/singleuser:latest"
enforce_cpu_limits: Union[Text, bool] = False
autosave_minimum_lfs_file_size_bytes: Union[int, Text] = 1000000
Expand Down
19 changes: 19 additions & 0 deletions ssh-jump-host/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM alpine:3.17.1

RUN apk add --update --no-cache openssh-server tini && \
mkdir -p /opt/ssh && \
adduser -D jovyan && \
passwd -d jovyan && \
chown -R jovyan:users /opt/ssh && \
addgroup jovyan shadow

USER jovyan
RUN mkdir -p /opt/ssh/sshd_config.d && \
mkdir -p /opt/ssh/ssh_host_keys && \
ssh-keygen -q -N "" -t dsa -f /opt/ssh/ssh_host_keys/ssh_host_dsa_key && \
ssh-keygen -q -N "" -t rsa -b 4096 -f /opt/ssh/ssh_host_keys/ssh_host_rsa_key && \
ssh-keygen -q -N "" -t ecdsa -f /opt/ssh/ssh_host_keys/ssh_host_ecdsa_key && \
ssh-keygen -q -N "" -t ed25519 -f /opt/ssh/ssh_host_keys/ssh_host_ed25519_key
COPY sshd_config /opt/ssh/sshd_config
ENTRYPOINT ["tini", "-g", "--"]
CMD [ "/usr/sbin/sshd", "-D", "-f", "/opt/ssh/sshd_config", "-e" ]
23 changes: 23 additions & 0 deletions ssh-jump-host/sshd_config
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Include /opt/ssh/sshd_config.d/*.conf
Port 2022

HostKey /opt/ssh/ssh_host_keys/ssh_host_dsa_key
HostKey /opt/ssh/ssh_host_keys/ssh_host_rsa_key
HostKey /opt/ssh/ssh_host_keys/ssh_host_ecdsa_key
HostKey /opt/ssh/ssh_host_keys/ssh_host_ed25519_key

ChallengeResponseAuthentication no

X11Forwarding no
PrintMotd no
PidFile /opt/ssh/sshd.pid

AcceptEnv LANG LC_*

Match User jovyan
PermitTTY no
X11Forwarding no
PermitTunnel no
GatewayPorts no
ForceCommand /sbin/nologin
PermitEmptyPasswords yes

0 comments on commit bd7979d

Please sign in to comment.