Compare commits

...
Author SHA1 Message Date
Cédric VerstraetenandCopilot 56ea65c6e7 Add hardened Hub API security contexts and writable scratch (0.170.1)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 14:05:22 +00:00
uug.ai af4e1c36a9 A new public release - 1793786033 (#219) 2026-11-04 11:19:24 +01:00
Cédric VerstraetenandCopilot 166b4a31e6 Default Hub frontends to hardened images on port 8080 (0.170.0) (#220)
Coordinate frontend v1.19.28, port 8080, non-root/read-only security defaults and writable scratch for primary and demo. Preserve Service port 80, Admin legacy defaults, and explicit legacy rollback settings. Validate real image startup, health and generated configuration in Kind.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-04 11:16:48 +01:00
10 changed files with 331 additions and 37 deletions
@@ -20,3 +20,9 @@ jobs:
helm lint charts/hub
bash scripts/check-admin-runtime.sh
bash scripts/check-hardened-workers.sh
bash scripts/check-frontend-runtime.sh
bash scripts/check-hub-api-runtime.sh
- name: Smoke test hardened frontend images and Service routing
env:
CLUSTER_SMOKE: "true"
run: bash scripts/check-frontend-runtime.sh
+1 -1
View File
@@ -16,7 +16,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.169.1
version: 0.170.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
+53 -4
View File
@@ -31,12 +31,41 @@ Uninstall the Kerberos Hub chart
helm uninstall hub -n kerberos-hub
### Hardened Hub API
Starting with chart **`0.170.1`**, `kerberoshub.api.securityContext` is rendered
on the Hub API container and `kerberoshub.api.podSecurityContext` on its pod.
The current image runs as `65532:65532` with a read-only root filesystem,
privilege escalation disabled, all capabilities dropped and `RuntimeDefault`
seccomp. A disk-backed `hub-api-tmp` emptyDir mounted at `/tmp`, with pod
`fsGroup: 65532`, keeps attachment uploads and multipart parsing writable.
API Service, ingress and port settings are unchanged.
Append custom mounts and volumes to the default lists rather than replacing
`hub-api-tmp`. Helm replaces lists in overrides. Existing MongoDB TLS, API TLS,
catalog and workflow-configuration mounts remain supported.
For a legacy image that requires its original identity or writable root
filesystem, explicitly set both `kerberoshub.api.securityContext: null` and
`kerberoshub.api.podSecurityContext: null`. Custom security settings are
per-API; other workloads do not inherit them. When upgrading, check retained
values (`--reuse-values` or GitOps overrides) so the security context and
writable scratch mount stay aligned.
Run `bash scripts/check-hub-api-runtime.sh` to validate defaults, overrides,
legacy opt-out, TLS/configuration mounts and deployment modes.
### Hardened frontend images
New shell-free frontend images run as `65532:65532` and listen on port `8080`.
Before selecting such an image tag, set the following values together. Defaults
remain compatible with legacy images on port `80`; this chart does not upgrade
the frontend image automatically. The Service and ingress remain on port `80`.
Starting with chart **`0.170.0`**, both frontends default to shell-free
`ghcr.io/uug-ai/hub-frontend:v1.19.28`, running as `65532:65532` on port `8080`
with a read-only root filesystem and writable `/tmp`. The Service and ingress
remain on port `80`. Admin retains its legacy image and port `80`; its hardened
runtime remains opt-in.
The following frontend runtime settings are now the defaults. When upgrading,
remove stale port `80` overrides and coordinate image/runtime values; Helm
`--reuse-values` and GitOps overrides may retain the old settings.
Use chart `0.167.2` or newer when the demo frontend is enabled; `0.167.1`
supports these settings only on the primary frontend.
@@ -73,6 +102,26 @@ the primary and demo frontends, which share the frontend image tag. Update the
live Argo CD Application's chart pin as well as its values: an image-only
upgrade leaves older charts forwarding to port `80` and causes gateway errors.
For legacy frontend images or a rollback, explicitly restore **all** runtime
settings together (including removing inherited security defaults):
```yaml
kerberoshub:
frontend:
tag: v1.13.9
containerPort: 80
securityContext: null
volumes: []
volumeMounts: []
```
Keep any application-specific branding mounts when adjusting these lists.
Chart upgrades do not rewrite separately managed NodePort Services: ensure they
target the named `http` port, not numeric `80`. Follow the duplicate-port recovery
instructions below if the existing Deployment cannot be patched to `8080`.
Run `bash scripts/check-frontend-runtime.sh` for default, demo, legacy override,
and deployment-values regression coverage.
### Hardened Admin image (opt-in, chart 0.167.3)
`charts/hub` deploys Admin in `all` and `ui` modes. Shell-free Admin `v1.4.3`
@@ -140,6 +140,10 @@ spec:
labels:
app: hub-api
spec:
{{- with .Values.kerberoshub.api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.kerberoshub.serviceAccount.create }}
serviceAccountName: {{ default (printf "%s-%s-sa" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-") .Values.kerberoshub.serviceAccount.name }}
{{- end }}
@@ -229,6 +233,10 @@ spec:
- name: hub-api
image: "{{ .Values.global.imageRegistry }}{{ .Values.kerberoshub.api.repository }}:{{ .Values.kerberoshub.api.tag }}"
imagePullPolicy: {{ .Values.kerberoshub.api.pullPolicy }}
{{- with .Values.kerberoshub.api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.kerberoshub.api.resources }}
resources:
{{- toYaml . | nindent 12 }}
@@ -110,7 +110,7 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 80 }}
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
name: http
{{- with .Values.kerberoshub.frontend.volumeMounts}}
volumeMounts:
@@ -191,7 +191,7 @@ spec:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 80 }}
- containerPort: {{ .Values.kerberoshub.frontend.containerPort | default 8080 }}
name: http
{{- with .Values.kerberoshub.frontend.volumeMounts}}
volumeMounts:
+59 -25
View File
@@ -50,7 +50,6 @@ mongodb:
# hub-pipeline-monitor, ...) instead of the one in uri, so the Atlas query
# profiler and currentOp show which service issued an operation.
appNamePerService: true
# Self-hosted mongodb, you could also use the uri property above.
host: mongodb.mongodb
adminDatabase: admin
@@ -73,7 +72,6 @@ mongodb:
caFileName: ""
mountPath: "/etc/mongodb/tls"
insecureSkipVerify: false
# Classifications supported by Hub filters, alerts, markers, and other
# downstream processing. Classifier output keys must match these values.
# Disable this to use the legacy classification catalog stored in MongoDB.
@@ -115,7 +113,6 @@ deviceTags:
# - key: anpr-pilot
# label: ANPR pilot
# description: Runs licence plate recognition on this camera
# Configuration packaging only: consuming contracts requires a compatible
# API/frontend. Enabling this mounts files but does not register stage workers.
workflowConfiguration:
@@ -339,8 +336,27 @@ kerberoshub:
api:
repository: ghcr.io/uug-ai/hub-api
pullPolicy: IfNotPresent
tag: "v1.9.51"
tag: "v1.11.31"
replicas: 2
podSecurityContext:
fsGroup: 65532
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
# Keep writable scratch space for attachment uploads and multipart parsing.
volumes:
- name: hub-api-tmp
emptyDir: {}
volumeMounts:
- name: hub-api-tmp
mountPath: /tmp
audit:
enabled: true
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
@@ -375,6 +391,7 @@ kerberoshub:
memory: 100Mi
cpu: 250m
# E-mail templates
# Append custom mounts/volumes to the lists above; retain hub-api-tmp.
#volumeMounts:
# - name: custom-email-templates
# mountPath: /mail
@@ -482,11 +499,28 @@ kerberoshub:
frontend:
repository: ghcr.io/uug-ai/hub-frontend
pullPolicy: IfNotPresent
tag: "v1.13.9"
tag: "v1.19.28"
replicas: 2
# Hardened frontend images listen on 8080; retain 80 for existing images.
containerPort: 80
securityContext: {}
containerPort: 8080
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
# Keep these entries when adding custom branding/translation mounts.
volumes:
- name: frontend-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
volumeMounts:
- name: frontend-tmp
mountPath: /tmp
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Set to false to skip rendering the hub-frontend Service (e.g. when an
# external/shared Service is managed elsewhere).
@@ -776,7 +810,7 @@ kerberoshub:
podSecurityContext: {}
repository: ghcr.io/uug-ai/hub-cleanup
pullPolicy: IfNotPresent
tag: "v1.4.19"
tag: "v1.4.26"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1141,7 +1175,7 @@ kerberoshub:
podSecurityContext: {}
repository: ghcr.io/uug-ai/hub-monitor-device
pullPolicy: IfNotPresent
tag: "v1.4.2"
tag: "v1.4.10"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
logLevel: "info" # possible values: trace, debug, info, warn, error
@@ -1229,7 +1263,7 @@ kerberospipeline:
# argocd.argoproj.io/sync-wave: "10"
repository: ghcr.io/uug-ai/hub-pipeline-event
pullPolicy: IfNotPresent
tag: "v1.3.1"
tag: "v1.3.9"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1250,7 +1284,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "70"
pullPolicy: IfNotPresent
tag: "v1.3.13"
tag: "v1.3.29"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1271,7 +1305,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "60"
pullPolicy: IfNotPresent
tag: "v1.6.26"
tag: "v1.7.14"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1289,12 +1323,12 @@ kerberospipeline:
throttler:
securityContext: {}
podSecurityContext: {}
repository: uugai/hub-pipeline-throttler
repository: ghcr.io/uug-ai/hub-pipeline-throttler
deploymentAnnotations: {}
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "30"
pullPolicy: IfNotPresent
tag: "v1.2.1"
tag: "v1.2.10"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1317,7 +1351,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "20"
pullPolicy: IfNotPresent
tag: "v1.3.18"
tag: "v1.3.46"
replicas: 1 # Number of pods for the service.
audit:
enabled: true
@@ -1337,9 +1371,9 @@ kerberospipeline:
# persistentVolumeClaim:
# claimName: custom-layout-claim
notifyTest:
repository: uugai/hub-pipeline-notification-test
repository: ghcr.io/uug-ai/hub-pipeline-notification-test
pullPolicy: IfNotPresent
tag: "v1.2.2"
tag: "v1.2.6"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
resources:
@@ -1360,7 +1394,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "40"
pullPolicy: IfNotPresent
tag: "v1.8.22"
tag: "v1.8.29"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1398,7 +1432,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "50"
pullPolicy: IfNotPresent
tag: "v2.0.3"
tag: "v2.0.9"
replicas: 3 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1424,7 +1458,7 @@ kerberospipeline:
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "50"
pullPolicy: IfNotPresent
tag: "v1.3.15"
tag: "v1.3.22"
replicas: 2 # Number of pods for the service.
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
@@ -1452,12 +1486,12 @@ kerberospipeline:
counting:
securityContext: {}
podSecurityContext: {}
repository: uugai/hub-pipeline-counting
repository: ghcr.io/uug-ai/hub-pipeline-counting
deploymentAnnotations: {}
# deploymentAnnotations:
# argocd.argoproj.io/sync-wave: "50"
pullPolicy: IfNotPresent
tag: "v2.0.0"
tag: "v2.0.6"
replicas: 1 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
# Optional extra volumes / volumeMounts for this deployment (empty = none).
@@ -1479,7 +1513,7 @@ kerberospipeline:
# argocd.argoproj.io/sync-wave: "50"
repository: ghcr.io/uug-ai/hub-pipeline-sprite
pullPolicy: IfNotPresent
tag: "v1.1.21"
tag: "v1.1.27"
replicas: 5 # Number of pods for the service.
prefetchCount: 0 # Max unacknowledged RabbitMQ messages per pod; 0 = service default (5).
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
@@ -1506,7 +1540,7 @@ kerberospipeline:
export:
repository: ghcr.io/uug-ai/hub-pipeline-export
pullPolicy: IfNotPresent
tag: "v1.2.10"
tag: "v1.2.20"
replicas: 2 # Number of pods for the service.
topologySpreadConstraints: [] # Optional pod topology spread constraints (empty = none).
logLevel: "info" # possible values: trace, debug, info, warn, error
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
values=()
if [[ $# -ge 2 ]]; then values=(--values "$2"); fi
resource() {
local kind="$1" name="$2"
awk -v kind="$kind" -v name="$name" '
function emit() { if (matchedKind && matchedName) printf "%s", block }
/^---$/ { emit(); block=""; matchedKind=0; matchedName=0 }
{ block=block $0 "\n" }
$0 == "kind: " kind { matchedKind=1 }
$0 == " name: " name { matchedName=1 }
END { emit() }
'
}
assert_contains() {
if ! grep -Eq -- "$2" <<< "$1"; then
printf 'Missing expected frontend setting: %s\n' "$2" >&2
exit 1
fi
}
for mode in all ui; do
rendered="$(helm template hub "$CHART_DIR" "${values[@]}" --set "mode=$mode" \
--set kerberoshub.frontend.demoEnabled=true)"
for name in hub-frontend hub-frontend-demo; do
deployment="$(resource Deployment "$name" <<< "$rendered")"
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.19.28"$'
assert_contains "$deployment" 'containerPort: 8080$'
[[ "$(grep -c 'containerPort:' <<< "$deployment")" == 1 ]]
for setting in 'name: http' 'runAsNonRoot: true' 'runAsUser: 65532' \
'runAsGroup: 65532' 'readOnlyRootFilesystem: true' \
'allowPrivilegeEscalation: false' 'type: RuntimeDefault' 'drop:' '- ALL' \
'medium: Memory' 'sizeLimit: 16Mi' 'mountPath: /tmp' 'name: frontend-tmp'; do
assert_contains "$deployment" "^[[:space:]]*(- )?$setting$"
done
service="$(resource Service "$name-svc" <<< "$rendered")"
assert_contains "$service" 'port: 80$'
assert_contains "$service" 'targetPort: http$'
echo "OK ($mode): $name hardened image, security, scratch and named routing"
done
admin="$(resource Deployment admin <<< "$rendered")"
assert_contains "$admin" 'containerPort: 80$'
legacy="$(helm template hub "$CHART_DIR" "${values[@]}" --set "mode=$mode" \
--set kerberoshub.frontend.demoEnabled=true \
--set kerberoshub.frontend.tag=v1.13.9 \
--set kerberoshub.frontend.containerPort=80 \
--set kerberoshub.frontend.securityContext=null \
--set-json 'kerberoshub.frontend.volumes=[]' \
--set-json 'kerberoshub.frontend.volumeMounts=[]')"
for name in hub-frontend hub-frontend-demo; do
deployment="$(resource Deployment "$name" <<< "$legacy")"
assert_contains "$deployment" 'containerPort: 80$'
assert_contains "$deployment" 'image: "ghcr.io/uug-ai/hub-frontend:v1.13.9"$'
if grep -Eq 'securityContext:|mountPath: /tmp' <<< "$deployment"; then
echo "Legacy frontend retains incompatible hardened runtime settings" >&2
exit 1
fi
done
done
if [[ "${CLUSTER_SMOKE:-false}" == true ]]; then
namespace=frontend-runtime-test
kubectl create namespace "$namespace"
helm template runtime "$CHART_DIR" "${values[@]}" --namespace "$namespace" \
--set mode=ui --set ingress="" --set kerberoshub.frontend.demoEnabled=true \
--show-only templates/kerberos-hub/hub-frontend.yaml \
--show-only templates/kerberos-hub/hub-frontend-demo.yaml | kubectl apply -f -
pid=""
cleanup() {
if [[ -n "$pid" ]]; then kill "$pid"; fi
if [[ "$1" != 0 ]]; then
kubectl -n "$namespace" get pods
kubectl -n "$namespace" describe pods
kubectl -n "$namespace" logs -l app=hub-frontend --all-containers=true
kubectl -n "$namespace" logs -l app=hub-frontend-demo --all-containers=true
fi
}
trap 'cleanup "$?"' EXIT
for name in hub-frontend hub-frontend-demo; do
kubectl -n "$namespace" rollout status "deployment/$name" --timeout=180s
kubectl -n "$namespace" port-forward "service/$name-svc" 18080:80 &
pid=$!
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:18080/healthz > /dev/null; then break; fi
sleep 1
done
[[ "$(curl --fail --silent http://127.0.0.1:18080/healthz)" == ok ]]
curl --fail --silent http://127.0.0.1:18080/assets/env.js | grep 'window' > /dev/null
kill "$pid"
wait "$pid" || [[ "$?" == 143 ]]
pid=""
echo "OK: $name serves health and generated runtime configuration through Service port 80"
done
fi
echo "Frontend runtime checks passed."
+14 -5
View File
@@ -60,10 +60,19 @@ assert_line() {
fi
}
without_frontends() {
awk '
/^---$/ { skip=0 }
/^# Source: hub\/templates\/kerberos-hub\/(hub-frontend(-demo)?|hub-api)\.yaml$/ { skip=1 }
!skip { print }
'
}
for mode in all pipeline ui; do
defaults="$(helm template hub "$CHART_DIR" --set "mode=$mode")"
if grep -q 'securityContext:' <<< "$defaults"; then
echo "Default $mode mode unexpectedly enables security settings" >&2
worker_defaults="$(without_frontends <<< "$defaults")"
if grep -q 'securityContext:' <<< "$worker_defaults"; then
echo "Default $mode mode unexpectedly enables worker security settings" >&2
exit 1
fi
@@ -111,8 +120,8 @@ for mode in all pipeline ui; do
fi
done
expected="${#names[@]}"
[[ "$(grep -c 'runAsUser: 65532' <<< "$hardened")" == "$expected" ]]
[[ "$(grep -c 'fsGroup: 65532' <<< "$hardened")" == "$expected" ]]
[[ "$(without_frontends <<< "$hardened" | grep -c 'runAsUser: 65532')" == "$expected" ]]
[[ "$(without_frontends <<< "$hardened" | grep -c 'fsGroup: 65532')" == "$expected" ]]
# Image selection, Services, env and TLS mounts must not change.
for pattern in '^[ ]+image:' '^[ ]+(port|targetPort|containerPort):' 'mongodb-tls'; do
@@ -179,7 +188,7 @@ for mode in all ui pipeline; do
[[ "$(resource Service oauth2-proxy-admin <<< "$legacy")" == "$(resource Service oauth2-proxy-admin <<< "$configured")" ]]
[[ "$(grep 'image:' <<< "$legacy")" == "$(grep 'image:' <<< "$configured")" ]]
# No other workload inherits Admin's container port or security settings.
[[ "$(grep -c 'runAsUser: 65532' <<< "$configured")" == 1 ]]
[[ "$(without_frontends <<< "$configured" | grep -c 'runAsUser: 65532')" == 1 ]]
echo "OK ($mode): Admin opt-in port/security/tmpfs; legacy image, Service port, ingress and OAuth2-proxy preserved"
done
echo "All hardened Admin checks passed."
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
resource() {
local kind="$1" name="$2"
awk -v kind="$kind" -v name="$name" '
function emit() { if (matchedKind && matchedName) printf "%s", block }
/^---$/ { emit(); block=""; matchedKind=0; matchedName=0 }
{ block=block $0 "\n" }
$0 == "kind: " kind { matchedKind=1 }
$0 == " name: " name { matchedName=1 }
END { emit() }
'
}
assert_line() {
if ! grep -Eq -- "$2" <<< "$1"; then
printf 'Missing expected Hub API setting: %s\n' "$2" >&2
exit 1
fi
}
fixtures=(
--set workflowConfiguration.enabled=true
--set mongodb.tls.enabled=true
--set mongodb.tls.existingSecret=mongodb-ca
--set kerberoshub.api.serverTLS.enabled=true
--set kerberoshub.api.serverTLS.secretName=api-tls
)
for mode in all ui pipeline; do
defaults="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}")"
legacy="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}" \
--set kerberoshub.api.securityContext=null \
--set kerberoshub.api.podSecurityContext=null)"
configured="$(helm template hub "$CHART_DIR" --set "mode=$mode" "${fixtures[@]}" \
--set kerberoshub.api.securityContext.runAsUser=1000 \
--set kerberoshub.api.securityContext.runAsGroup=1000 \
--set kerberoshub.api.podSecurityContext.fsGroup=1000)"
helm lint "$CHART_DIR" --quiet --set "mode=$mode" "${fixtures[@]}"
if [[ "$mode" == pipeline ]]; then
[[ -z "$(resource Deployment hub-api <<< "$defaults")" ]]
[[ "$defaults" == "$legacy" && "$defaults" == "$configured" ]]
echo "OK (pipeline): no Hub API resources or unrelated changes"
continue
fi
api="$(resource Deployment hub-api <<< "$defaults")"
for pattern in \
'^[ ]{6}securityContext:$' '^[ ]{8}fsGroup: 65532$' \
'^[ ]{10}securityContext:$' '^[ ]{12}runAsUser: 65532$' \
'^[ ]{12}runAsGroup: 65532$' '^[ ]{12}runAsNonRoot: true$' \
'^[ ]{12}readOnlyRootFilesystem: true$' \
'^[ ]{12}allowPrivilegeEscalation: false$' '^[ ]{12}capabilities:$' \
'^[ ]{14}drop:$' '^[ ]{14}- ALL$' \
'^[ ]{12}seccompProfile:$' '^[ ]{14}type: RuntimeDefault$' \
'^[ ]+(- )?name: hub-api-tmp$' '^[ ]+(- )?emptyDir: \{\}$' \
'^[ ]+(- )?mountPath: /tmp$' '^[ ]+secretName: mongodb-ca$' \
'^[ ]+secretName: api-tls$' '^[ ]+mountPath: /etc/mongodb/tls$' \
'^[ ]+mountPath: /etc/hub-api/tls$' \
'^[ ]+mountPath: /etc/kerberos/configuration$' \
'^[ ]+mountPath: /etc/kerberos/classifications$'; do
assert_line "$api" "$pattern"
done
[[ "$(grep -c 'securityContext:' <<< "$api")" == 2 ]]
old_api="$(resource Deployment hub-api <<< "$legacy")"
if grep -q 'securityContext:' <<< "$old_api"; then
echo "Legacy Hub API opt-out still renders a security context" >&2
exit 1
fi
custom_api="$(resource Deployment hub-api <<< "$configured")"
for field in runAsUser runAsGroup fsGroup; do
assert_line "$custom_api" "^[ ]+$field: 1000$"
done
# Only security settings change; preserve env, mounts, images and routing.
for pattern in '^[ ]+image:' '^[ ]+(port|targetPort|containerPort):' \
'^[ ]+(- )?(name|mountPath|secretName|value):'; do
[[ "$(grep -E "$pattern" <<< "$defaults")" == "$(grep -E "$pattern" <<< "$legacy")" ]]
[[ "$(grep -E "$pattern" <<< "$defaults")" == "$(grep -E "$pattern" <<< "$configured")" ]]
done
echo "OK ($mode): API security defaults, overrides, opt-out, scratch and TLS/config mounts"
done