diff --git a/charts/praxis-gateway/README.md b/charts/praxis-gateway/README.md index f6e5d88..e4f8404 100644 --- a/charts/praxis-gateway/README.md +++ b/charts/praxis-gateway/README.md @@ -64,7 +64,7 @@ AI image; these values may advance independently. | `commonLabels` | object | `{}` | Labels added to all resources. | | `podLabels` | object | `{}` | Additional pod labels. Selector labels cannot be overridden. | | `podAnnotations` | object | `{}` | Pod annotations. | -| `podSecurityContext` | object | `{}` | Extra pod securityContext (`runAsUser`, `runAsGroup`, `fsGroup`, `supplementalGroups`). | +| `podSecurityContext` | object | `{runAsUser: 100, runAsGroup: 101}` | Pod-level securityContext matching the official Praxis AI 0.3.0 image; override the IDs for a compatible image or an OpenShift-assigned UID range. | | `args` | list | `["--config", "/etc/praxis/praxis.yaml"]` | Container arguments. | | `config.existingConfigMap` | string | **required** | Name of an existing ConfigMap with the Praxis config. | | `config.key` | string | `praxis.yaml` | Key in the ConfigMap. | @@ -103,15 +103,34 @@ AI image; these values may advance independently. ## Security -The chart enforces Kubernetes restricted security defaults: +The chart enforces Kubernetes restricted security defaults and defaults to the +numeric identity used by the official Praxis AI 0.3.0 image (`praxis:praxis`, +UID 100, GID 101). Numeric IDs are required because Kubernetes cannot verify +that a named image user is non-root when `runAsNonRoot: true` is set. -- `runAsNonRoot: true` (no fixed UID) +- `runAsNonRoot: true` +- `runAsUser: 100`, `runAsGroup: 101` (official AI 0.3.0 defaults) - `readOnlyRootFilesystem: true` - `allowPrivilegeEscalation: false` - All Linux capabilities dropped - `seccompProfile.type: RuntimeDefault` - `automountServiceAccountToken: false` +For a compatible image with a different non-root identity, override +`podSecurityContext.runAsUser` and `podSecurityContext.runAsGroup`. On OpenShift, +allow the restricted SCC to assign IDs from the namespace range by clearing the +fixed defaults: + +```yaml +podSecurityContext: + runAsUser: null + runAsGroup: null +``` + +The chart then omits those fields while retaining `runAsNonRoot` and the other +restricted controls. Do not disable `runAsNonRoot`, add capabilities, or enable +privilege escalation. + When overlay-sync is enabled, the pod uses a dedicated ServiceAccount, but automatic token mounting remains disabled. A short-lived projected token is mounted only into the overlay-sync init and sidecar containers. The Praxis diff --git a/charts/praxis-gateway/values.yaml b/charts/praxis-gateway/values.yaml index 66e57ac..168ce67 100644 --- a/charts/praxis-gateway/values.yaml +++ b/charts/praxis-gateway/values.yaml @@ -27,11 +27,14 @@ podLabels: {} # -- Annotations on the gateway pod template. podAnnotations: {} -# -- Extra pod-level securityContext fields (e.g. runAsUser, runAsGroup). -# runAsNonRoot and seccompProfile are always set by the chart. -podSecurityContext: {} -# runAsUser: 100 -# runAsGroup: 101 +# -- Pod-level securityContext fields. Defaults match the official Praxis AI +# 0.3.0 image (`praxis:praxis`, UID 100, GID 101) so Kubernetes can verify +# runAsNonRoot before creating the container. Override these IDs for a +# compatible image or a platform such as OpenShift that assigns a namespace +# UID range. runAsNonRoot and seccompProfile remain chart-owned. +podSecurityContext: + runAsUser: 100 + runAsGroup: 101 # -- Container arguments. Defaults to the config file path. args: diff --git a/docs/release.md b/docs/release.md index 94ed14d..59ae86a 100644 --- a/docs/release.md +++ b/docs/release.md @@ -70,7 +70,7 @@ clusters and Docker networks whose names can otherwise collide. | Area | Command | Topology/config path | Classification | Required when | |---|---|---|---|---| -| Provider traffic selection and round-robin | `cargo xtask env run-grid-provider-traffic-qualification --forge-config tests/e2e/topologies/grid-provider-traffic/forge.yaml --full --teardown --evidence-dir "$EVIDENCE_DIR"` | `tests/e2e/topologies/grid-provider-traffic/forge.yaml` | Release qualification | Routing, provider candidates, overlay selection policy, provider attribution, or gateway compatibility changes | +| Provider traffic selection and round-robin | `cargo xtask env run-grid-provider-traffic-qualification --forge-config tests/e2e/topologies/grid-provider-traffic/forge.yaml --quick --teardown --evidence-dir "$EVIDENCE_DIR"` | `tests/e2e/topologies/grid-provider-traffic/forge.yaml` | Release qualification | Routing, provider candidates, overlay selection policy, provider attribution, or gateway compatibility changes | | Distributed token quota | `cargo xtask env run-grid-token-rate-limit-qualification --forge-config tests/e2e/topologies/grid-token-rate-limit/forge.yaml --image-tag "$IMAGE_TAG" --evidence-dir "$EVIDENCE_DIR"` | `tests/e2e/topologies/grid-token-rate-limit/forge.yaml` | Experimental integration qualification | Quota, identity, Basic Auth, Valkey, gateway image, or shared-consumer changes | | Single-cluster multi-gateway | `cargo xtask env run-grid-single-cluster-multi-gateway-qualification --forge-config tests/e2e/topologies/grid-single-cluster-multi-gateway/forge.yaml --evidence-dir "$EVIDENCE_DIR"` | `tests/e2e/topologies/grid-single-cluster-multi-gateway/forge.yaml` | Integration qualification | Same-site gateway lifecycle, overlay delivery, provider selection, concurrency, or NetworkPolicy behavior changes | | Combined-site lifecycle | `cargo xtask env run-grid-combined-site-demo --forge-config tests/e2e/topologies/grid-combined-site/forge.yaml --full --teardown --evidence-dir "$EVIDENCE_DIR"` | `tests/e2e/topologies/grid-combined-site/forge.yaml` | Experimental lifecycle qualification | Combined-site routing, provider add/remove/re-add, session fallback, rollout, trust, or lifecycle changes | @@ -85,7 +85,7 @@ topologies require the filters documented in their READMEs and may use the official compatible AI image where applicable. ```console -cargo xtask env run-grid-provider-traffic-qualification --forge-config tests/e2e/topologies/grid-provider-traffic/forge.yaml --full --teardown --evidence-dir "$EVIDENCE_DIR" +cargo xtask env run-grid-provider-traffic-qualification --forge-config tests/e2e/topologies/grid-provider-traffic/forge.yaml --quick --teardown --evidence-dir "$EVIDENCE_DIR" cargo xtask env run-grid-token-rate-limit-qualification --forge-config tests/e2e/topologies/grid-token-rate-limit/forge.yaml --image-tag "$IMAGE_TAG" --evidence-dir "$EVIDENCE_DIR" cargo xtask env run-grid-single-cluster-multi-gateway-qualification --forge-config tests/e2e/topologies/grid-single-cluster-multi-gateway/forge.yaml --evidence-dir "$EVIDENCE_DIR" cargo xtask env run-grid-combined-site-demo --forge-config tests/e2e/topologies/grid-combined-site/forge.yaml --full --teardown --evidence-dir "$EVIDENCE_DIR" diff --git a/scripts/verify-helm-chart.sh b/scripts/verify-helm-chart.sh index bfd17d0..6d1776e 100755 --- a/scripts/verify-helm-chart.sh +++ b/scripts/verify-helm-chart.sh @@ -214,6 +214,29 @@ if grep -Fq "image: ${DEFAULT_GATEWAY_IMAGE}" /tmp/helm-rendered-gateway.yaml; t else fail "gateway default image is not ${DEFAULT_GATEWAY_IMAGE}" fi +DEFAULT_SECURITY=$(helm template verify-gw-security "$GW_DIR" "${GW_REQ[@]}" \ + --namespace grid-system --show-only templates/deployment.yaml 2>/dev/null) +if echo "$DEFAULT_SECURITY" | grep -q '^ runAsUser: 100$' && \ + echo "$DEFAULT_SECURITY" | grep -q '^ runAsGroup: 101$' && \ + echo "$DEFAULT_SECURITY" | grep -q '^ runAsNonRoot: true$' && \ + echo "$DEFAULT_SECURITY" | grep -q '^ allowPrivilegeEscalation: false$' && \ + echo "$DEFAULT_SECURITY" | grep -q '^ - ALL$'; then + pass "gateway default identity matches official AI 0.3.0 (100:101) with restricted controls" +else + fail "gateway default identity/security context does not match official AI 0.3.0" +fi +OPENSHIFT_SECURITY=$(helm template verify-gw-openshift "$GW_DIR" "${GW_REQ[@]}" \ + --namespace grid-system \ + --set podSecurityContext.runAsUser=null \ + --set podSecurityContext.runAsGroup=null \ + --show-only templates/deployment.yaml 2>/dev/null) +if ! echo "$OPENSHIFT_SECURITY" | grep -q '^ runAsUser:' && \ + ! echo "$OPENSHIFT_SECURITY" | grep -q '^ runAsGroup:' && \ + echo "$OPENSHIFT_SECURITY" | grep -q '^ runAsNonRoot: true$'; then + pass "gateway fixed identity can be cleared for OpenShift-assigned IDs" +else + fail "gateway OpenShift identity override did not preserve the expected security context" +fi # ── Variant renderings ────────────────────────────────────────────── try_template "$GW_DIR" "edge gateway" "${GW_REQ[@]}" \