diff --git a/k8s/deployment/build_context b/k8s/deployment/build_context index cb539de8..c7a09ed8 100755 --- a/k8s/deployment/build_context +++ b/k8s/deployment/build_context @@ -36,6 +36,15 @@ fi IMAGE_PULL_SECRETS="${IMAGE_PULL_SECRETS:-"{}"}" IMAGE_PULL_SECRETS=$(echo "$IMAGE_PULL_SECRETS" | jq .) +# Non-root configuration with defaults +NON_ROOT="${NON_ROOT:-"{}"}" +NON_ROOT=$(echo "$NON_ROOT" | jq '{ + enabled: .ENABLED // false, + user_id: .USER_ID // 1000, + group_id: .GROUP_ID // 1000, + fs_group: .FS_GROUP // 1000 +}') + SCOPE_TRAFFIC_PROTOCOL=$(echo "$CONTEXT" | jq -r .scope.capabilities.protocol) TRAFFIC_CONTAINER_VERSION="latest" @@ -44,6 +53,15 @@ if [[ "$SCOPE_TRAFFIC_PROTOCOL" == "web_sockets" ]]; then TRAFFIC_CONTAINER_VERSION="websocket2" fi +NON_ROOT_ENABLED=$(echo "$NON_ROOT" | jq -r '.enabled') +if [[ "$NON_ROOT_ENABLED" == "true" ]]; then + if [[ "$TRAFFIC_CONTAINER_VERSION" == "websocket2" ]]; then + TRAFFIC_CONTAINER_VERSION="websocket2-non-root" + else + TRAFFIC_CONTAINER_VERSION="latest-non-root" + fi +fi + TRAFFIC_CONTAINER_IMAGE=${TRAFFIC_CONTAINER_IMAGE:-"public.ecr.aws/nullplatform/k8s-traffic-manager:$TRAFFIC_CONTAINER_VERSION"} # Pod Disruption Budget configuration @@ -59,6 +77,7 @@ CONTEXT=$(echo "$CONTEXT" | jq \ --argjson pull_secrets "$IMAGE_PULL_SECRETS" \ --arg pdb_enabled "$PDB_ENABLED" \ --arg pdb_max_unavailable "$PDB_MAX_UNAVAILABLE" \ + --argjson non_root "$NON_ROOT" \ '. + {blue_deployment_id: $blue_deployment_id, blue_replicas: $blue_replicas, green_replicas: $green_replicas, @@ -66,7 +85,8 @@ CONTEXT=$(echo "$CONTEXT" | jq \ pull_secrets: $pull_secrets, traffic_image: $traffic_image, pdb_enabled: $pdb_enabled, - pdb_max_unavailable: $pdb_max_unavailable + pdb_max_unavailable: $pdb_max_unavailable, + non_root: $non_root }') DEPLOYMENT_ID=$(echo "$CONTEXT" | jq -r '.deployment.id') diff --git a/k8s/deployment/templates/deployment.yaml.tpl b/k8s/deployment/templates/deployment.yaml.tpl index d16fdf0d..8b118070 100644 --- a/k8s/deployment/templates/deployment.yaml.tpl +++ b/k8s/deployment/templates/deployment.yaml.tpl @@ -117,17 +117,40 @@ spec: {{ data.ToYAML $nodeSelector | indent 8 }} {{- end }} {{- end }} + {{- if .non_root.enabled }} + securityContext: + runAsUser: {{ .non_root.user_id | default 1000 }} + runAsGroup: {{ .non_root.group_id | default 1000 }} + fsGroup: {{ .non_root.fs_group | default 1000 }} + runAsNonRoot: true + fsGroupChangePolicy: "OnRootMismatch" + {{- end }} containers: - name: http + image: {{ .traffic_image }} securityContext: + {{- if .non_root.enabled }} + runAsUser: {{ .non_root.user_id | default 1000 }} + runAsGroup: {{ .non_root.group_id | default 1000 }} + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + {{- else }} runAsUser: 0 - image: {{ .traffic_image }} + {{- end }} ports: - - containerPort: 80 + - containerPort: {{ if .non_root.enabled }}8888{{ else }}80{{ end }} protocol: TCP env: - name: HEALTH_CHECK_TYPE value: http + - name: PORT + value: "{{ if .non_root.enabled }}8888{{ else }}80{{ end }}" - name: GRACE_PERIOD value: '15' - name: LISTENER_PROTOCOL @@ -142,25 +165,25 @@ spec: cpu: 31m livenessProbe: {{- if and (has .scope.capabilities.health_check "type") (eq .scope.capabilities.health_check.type "TCP") }} - {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" 80 "app_port" 8080 }} + {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" (if .non_root.enabled 8888 80) "app_port" 8080 }} {{- else }} - {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" 80 }} + {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" (if .non_root.enabled 8888 80) }} {{- end }} {{- template "probe.base" dict "healthCheck" .scope.capabilities.health_check }} failureThreshold: 9 readinessProbe: {{- if and (has .scope.capabilities.health_check "type") (eq .scope.capabilities.health_check.type "TCP") }} - {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" 80 "app_port" 8080 }} + {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" (if .non_root.enabled 8888 80) "app_port" 8080 }} {{- else }} - {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" 80 }} + {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" (if .non_root.enabled 8888 80) }} {{- end }} {{- template "probe.base" dict "healthCheck" .scope.capabilities.health_check }} failureThreshold: 3 startupProbe: {{- if and (has .scope.capabilities.health_check "type") (eq .scope.capabilities.health_check.type "TCP") }} - {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" 80 "app_port" 8080 }} + {{- template "probe.tcp" dict "healthCheck" .scope.capabilities.health_check "traffic_port" (if .non_root.enabled 8888 80) "app_port" 8080 }} {{- else }} - {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" 80 }} + {{- template "probe.http" dict "healthCheck" .scope.capabilities.health_check "port" (if .non_root.enabled 8888 80) }} {{- end }} {{- template "probe.base" dict "healthCheck" .scope.capabilities.health_check }} failureThreshold: 90 @@ -172,9 +195,22 @@ spec: {{ range .scope.capabilities.additional_ports }} {{ if eq .type "GRPC" }} - name: grpc-{{ .port }} + image: {{ $.traffic_image }} securityContext: + {{- if $.non_root.enabled }} + runAsUser: {{ $.non_root.user_id | default 1000 }} + runAsGroup: {{ $.non_root.group_id | default 1000 }} + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE + {{- else }} runAsUser: 0 - image: {{ $.traffic_image }} + {{- end }} ports: - containerPort: {{ .port }} protocol: TCP @@ -231,7 +267,18 @@ spec: image: >- {{ .asset.url }} securityContext: + {{- if .non_root.enabled }} + runAsUser: {{ .non_root.user_id | default 1000 }} + runAsGroup: {{ .non_root.group_id | default 1000 }} + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + {{- else }} runAsUser: 0 + {{- end }} ports: - containerPort: 8080 protocol: TCP diff --git a/k8s/deployment/templates/service.yaml.tpl b/k8s/deployment/templates/service.yaml.tpl index 7d369d43..eea41b06 100644 --- a/k8s/deployment/templates/service.yaml.tpl +++ b/k8s/deployment/templates/service.yaml.tpl @@ -58,7 +58,7 @@ spec: ports: - protocol: TCP port: 8080 - targetPort: 80 + targetPort: {{ if .non_root.enabled }}8888{{ else }}80{{ end }} selector: nullplatform: "true" account: {{ .account.slug }} diff --git a/k8s/values.yaml b/k8s/values.yaml index 03b1e45a..b56b5e57 100644 --- a/k8s/values.yaml +++ b/k8s/values.yaml @@ -58,6 +58,11 @@ configuration: # POD_DISRUPTION_BUDGET: # ENABLED: false # MAX_UNAVAILABLE: "25%" # Can be percentage or number (e.g., "2") + NON_ROOT: + ENABLED: false + USER_ID: 1000 + GROUP_ID: 1000 + FS_GROUP: 1000 # K8S_MODIFIERS: # global: # annotations: