Compare commits

...
Author SHA1 Message Date
Cédric VerstraetenandCopilot 6f1dcf8f00 Release Hub 0.167.3 with hardened Admin routing and probes
Publish opt-in hardened runtime support with named Admin Service routing, configurable health probes, legacy defaults and runtime regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-28 18:27:10 +00:00
Cédric Verstraeten 371408e5eb Merge pull request #207 from kerberos-io/security/image-hardening-rollout
Add opt-in hardened Hub worker runtime contexts
2026-10-27 14:43:33 +01:00
7 changed files with 122 additions and 13 deletions
+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.167.2
version: 0.167.3
# 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
+25 -8
View File
@@ -73,15 +73,16 @@ 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.
### Hardened Admin image (opt-in, unreleased chart support)
### Hardened Admin image (opt-in, chart 0.167.3)
`charts/hub` deploys Admin in `all` and `ui` modes. The shell-free image proposed
by `uug-ai/admin#59` runs as `65532:65532` and listens on `8080`, unlike the legacy
image on `80`. When selecting a verified compatible image, set these values
together; the chart does not select or upgrade that image automatically:
`charts/hub` deploys Admin in `all` and `ui` modes. Shell-free Admin `v1.4.3`
runs as `65532:65532` and listens on `8080`, unlike the legacy image on `80`.
Set these values together; the chart does not upgrade the image automatically:
```yaml
admin:
repository: ghcr.io/uug-ai/admin
tag: v1.4.3
containerPort: 8080
securityContext:
runAsNonRoot: true
@@ -101,17 +102,33 @@ admin:
volumeMounts:
- name: admin-tmp
mountPath: /tmp
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
```
Merge the scratch volume with any existing mounts. Only `/tmp` is writable;
application files remain read-only. Memory-backed scratch counts against the
pod memory limit. The `admin-svc` Service and Admin ingress still use port `80`;
only the Service target and container port change. OAuth2-proxy is unchanged.
Defaults remain port `80` with no security context or added volumes. Coordinate
the Service targets the named `http` container port. OAuth2-proxy is unchanged.
Defaults remain port `80` with no security context, added volumes, or probes. Coordinate
the image, values and a chart revision containing these settings: published
`0.167.2` does not contain Admin runtime support.
### Hardened Hub workers (opt-in, unreleased chart support)
Rollbacks to legacy images must restore port `80`, remove the hardened
security context, and disable `/healthz` probes together. Run
`bash scripts/check-admin-runtime.sh` from the repository root to verify
legacy defaults, hardened runtime/probes, and unchanged OAuth2 routing.
### Hardened Hub workers (opt-in, chart 0.167.3)
The worker templates accept **per-component** `securityContext` (container) and
`podSecurityContext` (pod) maps. Both default to `{}` and are omitted from the
+9 -1
View File
@@ -40,8 +40,16 @@ spec:
{{- end }}
ports:
- name: http
containerPort: {{ .Values.admin.containerPort }}
containerPort: {{ .Values.admin.containerPort | default 80 }}
protocol: TCP
{{- with .Values.admin.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.admin.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
- name: LOG_LEVEL
value: "{{ .Values.admin.logLevel }}"
+1 -1
View File
@@ -10,7 +10,7 @@ spec:
ports:
- protocol: TCP
port: 80
targetPort: {{ .Values.admin.containerPort }}
targetPort: http
name: http
selector:
app: admin
+2
View File
@@ -271,6 +271,8 @@ admin:
# Opt in to 8080 and a securityContext only with a compatible hardened image.
containerPort: 80
securityContext: {}
readinessProbe: {}
livenessProbe: {}
pullPolicy: IfNotPresent
tag: "v1.3.0"
replicas: 2
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail
CHART_DIR="${1:-charts/hub}"
resource() {
local source="$1"
awk -v source="# Source: hub/templates/admin/$source.yaml" '
/^---/ { selected=0 }
/^# Source:/ { selected=($0 == source); next }
selected { print }
'
}
assert_contains() {
local rendered="$1" pattern="$2"
if ! grep -Eq -- "$pattern" <<< "$rendered"; then
printf 'Missing expected Admin setting: %s\n' "$pattern" >&2
return 1
fi
}
assert_absent() {
local rendered="$1" pattern="$2"
if grep -Eq -- "$pattern" <<< "$rendered"; then
printf 'Unexpected Admin setting: %s\n' "$pattern" >&2
return 1
fi
}
HARDENED_FLAGS=(
--set admin.containerPort=8080
--set-json 'admin.securityContext={"runAsNonRoot":true,"runAsUser":65532,"runAsGroup":65532,"readOnlyRootFilesystem":true,"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"seccompProfile":{"type":"RuntimeDefault"}}'
--set-json 'admin.volumes=[{"name":"admin-tmp","emptyDir":{"medium":"Memory","sizeLimit":"16Mi"}}]'
--set-json 'admin.volumeMounts=[{"name":"admin-tmp","mountPath":"/tmp"}]'
--set-json 'admin.readinessProbe={"httpGet":{"path":"/healthz","port":"http"},"periodSeconds":10}'
--set-json 'admin.livenessProbe={"httpGet":{"path":"/healthz","port":"http"},"initialDelaySeconds":10,"periodSeconds":10}'
)
for mode in all ui; do
default_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true)"
default_deployment="$(resource deployment <<< "$default_render")"
default_service="$(resource service <<< "$default_render")"
default_ingress="$(resource ingress <<< "$default_render")"
default_oauth="$(resource oauth2-proxy <<< "$default_render")"
assert_contains "$default_deployment" 'containerPort: 80$'
assert_absent "$default_deployment" 'securityContext:|readinessProbe:|livenessProbe:'
assert_contains "$default_service" 'port: 80$'
assert_contains "$default_service" 'targetPort: http$'
hardened_render="$(helm template hub "$CHART_DIR" --set "mode=$mode" --set admin.oauth2Proxy.enabled=true "${HARDENED_FLAGS[@]}")"
hardened_deployment="$(resource deployment <<< "$hardened_render")"
assert_contains "$hardened_deployment" 'containerPort: 8080$'
assert_absent "$hardened_deployment" 'containerPort: 80$'
for setting in 'runAsNonRoot: true' 'runAsUser: 65532' 'runAsGroup: 65532' \
'readOnlyRootFilesystem: true' 'allowPrivilegeEscalation: false' \
'type: RuntimeDefault' 'drop:' '- ALL' 'medium: Memory' \
'sizeLimit: 16Mi' 'mountPath: /tmp' 'name: admin-tmp'; do
assert_contains "$hardened_deployment" "^[[:space:]]*(- )?$setting$"
done
for probe in readinessProbe livenessProbe; do
probe_render="$(awk -v probe="$probe:" '
/^ [a-zA-Z]/ { selected=($1 == probe) }
selected { print }
' <<< "$hardened_deployment")"
assert_contains "$probe_render" 'path: /healthz$'
assert_contains "$probe_render" 'port: http$'
done
[[ "$(resource service <<< "$hardened_render")" == "$default_service" ]]
[[ "$(resource ingress <<< "$hardened_render")" == "$default_ingress" ]]
[[ "$(resource oauth2-proxy <<< "$hardened_render")" == "$default_oauth" ]]
assert_contains "$default_ingress" 'nginx.ingress.kubernetes.io/auth-url:'
assert_contains "$default_ingress" 'number: 80$'
echo "OK ($mode): legacy defaults; hardened runtime/probes; stable Service, ingress and OAuth2"
done
pipeline_render="$(helm template hub "$CHART_DIR" --set mode=pipeline "${HARDENED_FLAGS[@]}")"
assert_absent "$pipeline_render" '^# Source: hub/templates/admin/'
echo "All Admin runtime checks passed."
+2 -2
View File
@@ -171,9 +171,9 @@ for mode in all ui pipeline; do
done
old_service="$(resource Service admin-svc <<< "$legacy")"
service="$(resource Service admin-svc <<< "$configured")"
assert_line "$old_service" '^[ ]+targetPort: 80$'
assert_line "$old_service" '^[ ]+targetPort: http$'
assert_line "$service" '^[ ]+port: 80$'
assert_line "$service" '^[ ]+targetPort: 8080$'
assert_line "$service" '^[ ]+targetPort: http$'
[[ "$(resource Ingress admin-ingress <<< "$legacy")" == "$(resource Ingress admin-ingress <<< "$configured")" ]]
[[ "$(resource Deployment oauth2-proxy-admin <<< "$legacy")" == "$(resource Deployment oauth2-proxy-admin <<< "$configured")" ]]
[[ "$(resource Service oauth2-proxy-admin <<< "$legacy")" == "$(resource Service oauth2-proxy-admin <<< "$configured")" ]]