Skip to content
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
27 changes: 27 additions & 0 deletions docs/source/compute_config/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,33 @@ k8s:
|k8s | runtime_memory | 512 |no | Memory limit in MB. Default 512MB |
|k8s | runtime_timeout | 600 |no | Runtime timeout in seconds. Default 600 seconds |
|k8s | master_timeout | 600 |no | Master pod timeout in seconds. Default 600 seconds |
|k8s | container_security_context | PSS Baseline (drop ALL caps, no privilege escalation, RuntimeDefault seccomp) | no | Mapping injected as the container `securityContext` on every Lithops pod. Set to `null` to disable. |
|k8s | pod_security_context | | no | Mapping injected as the pod-level `securityContext`. Required for clusters enforcing Pod Security Standards Restricted (e.g. EGI Rancher, GKE Autopilot, OpenShift). Requires a non-root runtime image. |

## Running on Pod Security Standards Restricted clusters

Clusters enforcing the [Pod Security Standards "Restricted"](https://kubernetes.io/docs/concepts/security/pod-security-standards/) profile (Rancher with EGI policies, GKE Autopilot, OpenShift, AKS with Azure Policy, EKS with admission controllers) require pods to run as a non-root user with additional hardening. Set `pod_security_context` and use a runtime image that has a non-root `USER` directive:

```yaml
k8s:
runtime: <your_user>/<non_root_runtime>:<tag>
pod_security_context:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
container_security_context:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
```

Providing `container_security_context` fully replaces the defaults — copy the snippet above and adjust if you want to extend rather than override.

## Test Lithops

Expand Down
17 changes: 17 additions & 0 deletions lithops/serverless/backends/k8s/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
'docker_server': 'docker.io'
}

# Pod Security Standards "Baseline"-aligned container defaults; safe to
# enable without runtime image changes. Override via `container_security_context`.
DEFAULT_CONTAINER_SECURITY_CONTEXT = {
'allowPrivilegeEscalation': False,
'capabilities': {'drop': ['ALL']},
'seccompProfile': {'type': 'RuntimeDefault'},
}

DEFAULT_GROUP = "batch"
DEFAULT_VERSION = "v1"
MASTER_NAME = "lithops-master"
Expand Down Expand Up @@ -142,6 +150,15 @@ def load_config(config_data):
if key not in config_data['k8s']:
config_data['k8s'][key] = DEFAULT_CONFIG_KEYS[key]

if 'container_security_context' not in config_data['k8s']:
config_data['k8s']['container_security_context'] = DEFAULT_CONTAINER_SECURITY_CONTEXT
config_data['k8s'].setdefault('pod_security_context', None)

for key in ('container_security_context', 'pod_security_context'):
value = config_data['k8s'][key]
if value is not None and not isinstance(value, dict):
raise Exception(f"'{key}' under 'k8s' must be a mapping or null, got {type(value).__name__}")

if 'runtime' in config_data['k8s']:
runtime = config_data['k8s']['runtime']
registry = config_data['k8s']['docker_server']
Expand Down
16 changes: 16 additions & 0 deletions lithops/serverless/backends/k8s/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ def _get_default_runtime_image_name(self):
self.name, self.k8s_config, 'lithops-kubernetes-default'
)

def _apply_security_context(self, job_res):
"""Inject pod- and container-level securityContext from config (if any)."""
pod_spec = job_res['spec']['template']['spec']
pod_sc = self.k8s_config.get('pod_security_context')
if pod_sc:
pod_spec['securityContext'] = pod_sc
container_sc = self.k8s_config.get('container_security_context')
if container_sc:
pod_spec['containers'][0]['securityContext'] = container_sc

def build_runtime(self, docker_image_name, dockerfile, extra_args=[]):
"""
Builds a new runtime from a Docker file and pushes it to the registry
Expand Down Expand Up @@ -458,6 +468,8 @@ def _start_master(self, docker_image_name):
master_res['metadata']['labels']['user'] = self.user
master_res['spec']['activeDeadlineSeconds'] = self.k8s_config['master_timeout']

self._apply_security_context(master_res)

container = master_res['spec']['template']['spec']['containers'][0]
container['image'] = docker_image_name
container['env'][0]['value'] = 'run_master'
Expand Down Expand Up @@ -648,6 +660,8 @@ def invoke(self, docker_image_name, runtime_memory, job_payload):
job_res['spec']['activeDeadlineSeconds'] = self.k8s_config['runtime_timeout']
job_res['spec']['parallelism'] = total_workers

self._apply_security_context(job_res)

container = job_res['spec']['template']['spec']['containers'][0]
container['image'] = docker_image_name
if not docker_image_name.endswith(':latest'):
Expand Down Expand Up @@ -694,6 +708,8 @@ def _generate_runtime_meta(self, docker_image_name):
job_res['metadata']['labels']['version'] = 'lithops_v' + __version__
job_res['metadata']['labels']['user'] = self.user

self._apply_security_context(job_res)

container = job_res['spec']['template']['spec']['containers'][0]
container['image'] = docker_image_name
container['imagePullPolicy'] = 'Always'
Expand Down
Loading