Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ssh access to sessions with jump host #1389

Merged
merged 6 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
47 changes: 47 additions & 0 deletions helm-chart/renku-notebooks/templates/network-policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,50 @@ spec:
ports:
- protocol: TCP
port: http
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ template "notebooks.fullname" . }}-ssh-sessions
spec:
podSelector:
matchLabels:
app.kubernetes.io/component: jupyterserver
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/name: amalthea
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: {{ template "notebooks.name" . }}-ssh
ports:
- port: ssh
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ template "notebooks.fullname" . }}-ssh-sessions-egress
spec:
podSelector:
matchLabels:
app: {{ template "notebooks.name" . }}-ssh
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/component: jupyterserver
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/name: amalthea
ports:
- port: ssh
protocol: TCP
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
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 }}
8 changes: 8 additions & 0 deletions helm-chart/renku-notebooks/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ spec:
value: "{{ .Values.gitClone.image.name }}:{{ .Values.gitClone.image.tag }}"
- name: NB_ANONYMOUS_SESSIONS_ENABLED
value: {{ .Values.global.anonymousSessions.enabled | quote }}
- name: NB_SSH_ENABLED
value: {{ .Values.ssh.enabled | quote }}
- name: NB_SESSIONS__CULLING__REGISTERED__IDLE_SECONDS
value: {{ .Values.culling.idleThresholdSeconds.registered | quote }}
- name: NB_SESSIONS__CULLING__ANONYMOUS__IDLE_SECONDS
Expand Down Expand Up @@ -182,6 +184,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:
62 changes: 62 additions & 0 deletions renku_notebooks/api/amalthea_patches/ssh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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, DuplicateEnvironmentVariableError
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/api/notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def version():
"s3": config.cloud_storage.s3.enabled,
"azure_blob": config.cloud_storage.azure_blob.enabled,
},
"sshEnabled": config.ssh_enabled,
},
}
],
Expand Down
4 changes: 4 additions & 0 deletions renku_notebooks/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class _NotebooksConfig:
cloud_storage: _CloudStorage
current_resource_schema_version: int = 1
anonymous_sessions_enabled: Union[Text, bool] = False
ssh_enabled: Union[Text, bool] = False
service_prefix: str = "/notebooks"
version: str = "0.0.0"
keycloak_realm: str = "Renku"
Expand All @@ -36,6 +37,7 @@ def __post_init__(self):
self.anonymous_sessions_enabled = _parse_str_as_bool(
self.anonymous_sessions_enabled
)
self.ssh_enabled = _parse_str_as_bool(self.ssh_enabled)
self.session_get_endpoint_annotations = _ServersGetEndpointAnnotations()
if not self.k8s.enabled:
return
Expand Down Expand Up @@ -158,6 +160,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 Expand Up @@ -196,6 +199,7 @@ def get_config(default_config: str) -> _NotebooksConfig:
mount_folder = /cloudstorage
}
anonymous_sessions_enabled = false
ssh_enabled = false
service_prefix = /notebooks
version = 0.0.0
keycloak_realm = Renku
Expand Down
Loading