diff --git a/.github/workflows/scripts/validate-helm-templates.sh b/.github/workflows/scripts/validate-helm-templates.sh index 03a8b87eaa..f2045972ae 100755 --- a/.github/workflows/scripts/validate-helm-templates.sh +++ b/.github/workflows/scripts/validate-helm-templates.sh @@ -426,15 +426,20 @@ echo "" echo -e "${CYAN}🔒 7/7 - Validating OpenShift-compatible Security Contexts...${NC}" echo "----------------------------------------------------------------" -test_name="default Bifrost pod does not set runAsUser (SCC assigns UID)" +# Images before v1.6.4 use a non-numeric `USER appuser`, so kubelet can only +# verify runAsNonRoot when the chart pins runAsUser. The default render must +# carry runAsUser: 1000; OpenShift users unset it with explicit nulls (tested +# below). +test_name="default Bifrost pod sets runAsUser: 1000 (kubelet runAsNonRoot verification)" if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ + -s templates/stateful.yaml \ > /tmp/helm-template-output.yaml 2>&1; then - if grep -Eq '^[[:space:]]*runAsUser:' /tmp/helm-template-output.yaml; then - report_result "$test_name" 1 - echo -e "${YELLOW} runAsUser found in default render (must stay unset so OpenShift can assign a UID)${NC}" - else + if grep -Eq '^[[:space:]]*runAsUser:[[:space:]]*1000$' /tmp/helm-template-output.yaml; then report_result "$test_name" 0 + else + report_result "$test_name" 1 + echo -e "${YELLOW} runAsUser: 1000 missing from default render (pre-v1.6.4 images have non-numeric USER, so runAsNonRoot fails without it)${NC}" fi else report_result "$test_name" 1 @@ -443,17 +448,39 @@ else fi # Postgres mode renders a Deployment (not the sqlite StatefulSet); assert the -# pinned-UID regression can't sneak in on that code path either. -test_name="postgres-mode Bifrost pod does not set runAsUser (SCC assigns UID)" +# UID pin holds on that code path too. +test_name="postgres-mode Bifrost pod sets runAsUser: 1000 (kubelet runAsNonRoot verification)" if helm template bifrost ./helm-charts/bifrost \ --set image.tag=v1.0.0 \ --set storage.mode=postgres \ --set postgresql.enabled=true \ --set postgresql.auth.password=testpass \ + -s templates/deployment.yaml \ + > /tmp/helm-template-output.yaml 2>&1; then + if grep -Eq '^[[:space:]]*runAsUser:[[:space:]]*1000$' /tmp/helm-template-output.yaml; then + report_result "$test_name" 0 + else + report_result "$test_name" 1 + echo -e "${YELLOW} runAsUser: 1000 missing from postgres render (pre-v1.6.4 images have non-numeric USER, so runAsNonRoot fails without it)${NC}" + fi +else + report_result "$test_name" 1 + echo -e "${YELLOW} Error output:${NC}" + head -10 /tmp/helm-template-output.yaml | sed 's/^/ /' +fi + +# OpenShift path: explicit nulls must unset the UID pins so the SCC can +# assign an arbitrary UID. +test_name="OpenShift override (runAsUser/fsGroup null) removes UID pins" +if helm template bifrost ./helm-charts/bifrost \ + --set image.tag=v1.0.0 \ + --set podSecurityContext.runAsUser=null \ + --set podSecurityContext.fsGroup=null \ + --set securityContext.runAsUser=null \ > /tmp/helm-template-output.yaml 2>&1; then - if grep -Eq '^[[:space:]]*runAsUser:' /tmp/helm-template-output.yaml; then + if grep -Eq '^[[:space:]]*(runAsUser|fsGroup):' /tmp/helm-template-output.yaml; then report_result "$test_name" 1 - echo -e "${YELLOW} runAsUser found in postgres render (must stay unset so OpenShift can assign a UID)${NC}" + echo -e "${YELLOW} runAsUser/fsGroup still rendered with null overrides (OpenShift SCC cannot assign a UID)${NC}" else report_result "$test_name" 0 fi diff --git a/docs/changelogs/helm-v2.1.27.mdx b/docs/changelogs/helm-v2.1.27.mdx index 61283c0a08..e2c3b44310 100644 --- a/docs/changelogs/helm-v2.1.27.mdx +++ b/docs/changelogs/helm-v2.1.27.mdx @@ -5,6 +5,10 @@ description: "Helm v2.1.27 changelog - 2026-07-09" + +**Known issue - use v2.1.28 instead.** This version dropped `runAsUser: 1000` from the default security contexts. With any image before v1.6.4 (including the chart's default), kubelet cannot verify `runAsNonRoot: true` against the image's non-numeric `USER appuser`, and pods fail with `CreateContainerConfigError: container has runAsNonRoot and image has non-numeric user (appuser)`. If you must stay on v2.1.27, set `podSecurityContext.runAsUser: 1000` and `securityContext.runAsUser: 1000` in your values, or use image v1.6.4+. On OpenShift (restricted-v2), v2.1.27 works as-is since the SCC injects a numeric UID; pair it with image v1.6.4+ and `podSecurityContext.fsGroup: null`. + + ## Changelog - `bifrost.schemaUrl` — override the generated `config.json` `$schema` location for isolated or air-gapped deployments. Accepts an HTTP(S) URL, `file://` URL, or filesystem path. When set, the value is also exported as `BIFROST_SCHEMA_URL` in the pod; when empty (default), the env var is not injected and the public schema URL is used. Renders into `$schema`. diff --git a/docs/changelogs/helm-v2.1.28.mdx b/docs/changelogs/helm-v2.1.28.mdx new file mode 100644 index 0000000000..3cd6857f91 --- /dev/null +++ b/docs/changelogs/helm-v2.1.28.mdx @@ -0,0 +1,14 @@ +--- +title: "v2.1.28" +description: "Helm v2.1.28 changelog - 2026-07-14" +--- + + + +## Changelog + +- Restored `runAsUser: 1000` defaults in `podSecurityContext` and `securityContext` (dropped in 2.1.27). Images before v1.6.4 use a non-numeric `USER appuser`, so kubelet could not verify `runAsNonRoot: true` and pods failed with CreateContainerConfigError. OpenShift (restricted-v2) users unset the pins with explicit nulls: `podSecurityContext.runAsUser: null`, `podSecurityContext.fsGroup: null`, `securityContext.runAsUser: null`. +- Added `project_id` to `bifrost.providers.bedrock.keys[*].bedrock_key_config` (renders into `bedrock_key_config.project_id`) and `bifrost.providers.bedrock_mantle.keys[*].bedrock_mantle_key_config` (renders into `bedrock_mantle_key_config.project_id`) for AWS project scoping via the OpenAI-Project / anthropic-workspace-id headers. +- Updated the per-alias `project_id` description: it is now a shared cross-provider override (Vertex GCP project; Bedrock/Bedrock Mantle AWS project header). + + diff --git a/docs/docs.json b/docs/docs.json index 146af10391..df57b79739 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1003,6 +1003,7 @@ "item": "Helm", "icon": "box", "pages": [ + "changelogs/helm-v2.1.28", "changelogs/helm-v2.1.27", "changelogs/helm-v2.1.26", "changelogs/helm-v2.1.25", diff --git a/helm-charts/bifrost/Chart.yaml b/helm-charts/bifrost/Chart.yaml index 45bbd5f0a8..343b7953c2 100644 --- a/helm-charts/bifrost/Chart.yaml +++ b/helm-charts/bifrost/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: bifrost description: A Helm chart for deploying Bifrost - AI Gateway with unified interface for multiple providers type: application -version: 2.1.27 +version: 2.1.28 appVersion: "1.5.12" keywords: - ai diff --git a/helm-charts/bifrost/README.md b/helm-charts/bifrost/README.md index c894f3fbe1..1b9f6a9c7a 100644 --- a/helm-charts/bifrost/README.md +++ b/helm-charts/bifrost/README.md @@ -4,12 +4,30 @@ Official Helm charts for deploying [Bifrost](https://github.com/maximhq/bifrost) - a high-performance AI gateway with unified interface for multiple providers. -**Latest Version:** 2.1.27 +**Latest Version:** 2.1.28 ## Changelog +### 2.1.28 + +- Restored `runAsUser: 1000` defaults in `podSecurityContext` and `securityContext` (dropped in 2.1.27). Images before v1.6.4 use a non-numeric `USER appuser`, so kubelet could not verify `runAsNonRoot: true` and pods failed with CreateContainerConfigError. OpenShift (restricted-v2) users unset the pins with explicit nulls: `podSecurityContext.runAsUser: null`, `podSecurityContext.fsGroup: null`, `securityContext.runAsUser: null`. +- Added `project_id` to `bifrost.providers.bedrock.keys[*].bedrock_key_config` (renders into `bedrock_key_config.project_id`) and `bifrost.providers.bedrock_mantle.keys[*].bedrock_mantle_key_config` (renders into `bedrock_mantle_key_config.project_id`) for AWS project scoping via the OpenAI-Project / anthropic-workspace-id headers. +- Updated the per-alias `project_id` description: it is now a shared cross-provider override (Vertex GCP project; Bedrock/Bedrock Mantle AWS project header). + ### 2.1.27 +> **Known issue - use 2.1.28 instead.** This version dropped `runAsUser: 1000` +> from the default security contexts. With any image before v1.6.4 (including +> the chart's default), kubelet cannot verify `runAsNonRoot: true` against the +> image's non-numeric `USER appuser` and pods fail with +> `CreateContainerConfigError: container has runAsNonRoot and image has +> non-numeric user (appuser)`. If you must stay on 2.1.27, set +> `podSecurityContext.runAsUser: 1000` and `securityContext.runAsUser: 1000` +> in your values, or use image v1.6.4+. On OpenShift (restricted-v2), 2.1.27 +> works as-is since the SCC injects a numeric UID; pair it with image v1.6.4+ +> and `podSecurityContext.fsGroup: null` (see the OpenShift section under +> Installation). + - Added `bifrost.auditLogs.objectStorage` for archiving audit events to S3/GCS. Supports `type` (s3/gcs), `bucket`, `prefix`, `compress`, and full S3 credential fields (`region`, `endpoint`, `accessKeyId`, `secretAccessKey`, `sessionToken`, `roleArn`, `forcePathStyle`) and GCS fields (`projectId`, `credentialsJson`). Renders into `audit_logs.object_storage`. - Added `bifrost.schemaUrl` to override the generated `config.json` `$schema` location for isolated deployments. It accepts HTTP(S), `file://`, or filesystem paths. When set, it is also exported as `BIFROST_SCHEMA_URL` in the pod; when empty (default), the env var is not injected and the public schema URL is used. - Added `force_single_region` to `bifrost.providers.vertex.keys[*].vertex_key_config`. When `true`, skips automatic promotion of multi-region-only models to a multi-region endpoint. Enable for provisioned throughput. Renders into `vertex_key_config.force_single_region`. @@ -458,24 +476,54 @@ cd bifrost/helm-charts/bifrost ### OpenShift (restricted-v2 SCC) -The default install sets `podSecurityContext.fsGroup: 1000`. OpenShift's -`restricted-v2` SCC enforces `MustRunAs` against the namespace's allocated -group range and rejects that value at admission: +The default install pins `runAsUser: 1000` (pod and container level) and +`podSecurityContext.fsGroup: 1000`. The UID pin is required on vanilla +Kubernetes: images before v1.6.4 declare a non-numeric `USER appuser`, so +kubelet cannot verify `runAsNonRoot: true` without an explicit numeric UID and +rejects the container with: + +```text +container has runAsNonRoot and image has non-numeric user (appuser), cannot verify user is non-root +``` + +OpenShift's `restricted-v2` SCC enforces `MustRunAsRange` / `MustRunAs` +against the namespace's allocated UID/GID ranges and rejects those pinned +values at admission: ```text fsGroup: Invalid value: []int64{1000}: 1000 is not an allowed group ``` -To deploy on OpenShift, clear the default `fsGroup` so the SCC can assign an +To deploy on OpenShift, clear all three pins so the SCC can assign an in-range UID/GID: ```yaml podSecurityContext: - # Helm merges maps, so `{}` does NOT clear this — you must use null. + # Helm merges maps, so `{}` does NOT clear these - you must use null. + runAsUser: null fsGroup: null runAsNonRoot: true + +securityContext: + runAsUser: null ``` +Or equivalently on the command line: + +```bash +helm install bifrost bifrost/bifrost \ + --set image.tag=v1.6.4 \ + --set podSecurityContext.runAsUser=null \ + --set podSecurityContext.fsGroup=null \ + --set securityContext.runAsUser=null +``` + +Use image v1.6.4 or later on OpenShift. The SCC injects an arbitrary in-range +UID at admission (so `runAsNonRoot` verification always passes there), but +only v1.6.4+ images make the data directory group-0-owned and group-writable +at build time; earlier images assume UID 1000 owns `/app/data` and fail to +write `config.db` under an arbitrary UID. + The Bifrost image supports arbitrary UIDs with group 0: the data directory is owned by group 0 and group-writable at build time, so the restricted-v2 UID (with GID 0) can write `config.db` and `logs.db` — no custom SCC or `anyuid` is diff --git a/helm-charts/bifrost/values.schema.json b/helm-charts/bifrost/values.schema.json index bd9b160ba9..d27b684ddb 100644 --- a/helm-charts/bifrost/values.schema.json +++ b/helm-charts/bifrost/values.schema.json @@ -5054,6 +5054,10 @@ "type": "string", "description": "Role session name for AssumeRole (can use env. prefix)" }, + "project_id": { + "type": "string", + "description": "Bedrock project ID scoping inference and model listing. Sent as the OpenAI-Project header on the OpenAI-compatible surface and the anthropic-workspace-id header on the native-Anthropic (Claude) surface. When empty, AWS routes to the account's default project (can use env. prefix)" + }, "deployments": { "type": "object", "additionalProperties": { @@ -5125,6 +5129,10 @@ "session_name": { "type": "string", "description": "Role session name for AssumeRole (can use env. prefix)" + }, + "project_id": { + "type": "string", + "description": "Bedrock project ID scoping the Mantle sub-surface (OpenAI-compatible gpt-*/Gemma routing) via the OpenAI-Project header. When empty, AWS routes to the account's default project. No effect on the Converse/bedrock-runtime paths (can use env. prefix)" } }, "required": ["region"], @@ -5192,7 +5200,7 @@ }, "project_id": { "type": "string", - "description": "Per-alias Vertex project ID override (can use env. prefix)." + "description": "Per-alias project override shared across providers: Vertex uses it as the GCP project; Bedrock and Bedrock Mantle send it as the AWS project via the OpenAI-Project / anthropic-workspace-id header (can use env. prefix)." }, "project_number": { "type": "string", diff --git a/helm-charts/bifrost/values.yaml b/helm-charts/bifrost/values.yaml index eafec3b829..e6bc1beda5 100644 --- a/helm-charts/bifrost/values.yaml +++ b/helm-charts/bifrost/values.yaml @@ -57,13 +57,20 @@ deploymentLabels: {} podAnnotations: {} podLabels: {} -# Pod-level security context. Defaults chown freshly provisioned PVCs (fsGroup) -# and forbid running as root, which vanilla Kubernetes needs for restrictive -# storage classes. On OpenShift (restricted-v2), set `podSecurityContext.fsGroup: null` -# (Helm merges maps, so `{}` would keep this default) — the image supports -# arbitrary UIDs with group 0, so no custom SCC is needed. +# Pod-level security context. Defaults pin UID 1000 and chown freshly +# provisioned PVCs (fsGroup), which vanilla Kubernetes needs for restrictive +# storage classes. runAsUser is also required by kubelet to verify +# runAsNonRoot against images whose USER is non-numeric (bifrost images +# before v1.6.4 use `USER appuser`). +# On OpenShift (restricted-v2), unset the UID pins so the SCC can assign an +# arbitrary UID: set `podSecurityContext.runAsUser: null`, +# `podSecurityContext.fsGroup: null`, and `securityContext.runAsUser: null` +# (Helm merges maps, so explicit `null` is required; `{}` keeps these +# defaults). The image supports arbitrary UIDs with group 0, so no custom +# SCC is needed. podSecurityContext: fsGroup: 1000 + runAsUser: 1000 runAsNonRoot: true securityContext: @@ -71,6 +78,7 @@ securityContext: drop: - ALL readOnlyRootFilesystem: false + runAsUser: 1000 runAsNonRoot: true service: @@ -421,6 +429,7 @@ bifrost: # region: "us-east-1" # access_key: "env.AWS_ACCESS_KEY_ID" # secret_key: "env.AWS_SECRET_ACCESS_KEY" + # project_id: "" # Optional: Bedrock project ID scoping inference and model listing (can use env. prefix) # # # AWS Bedrock Mantle example (requires bedrock_mantle_key_config) # bedrock_mantle: @@ -432,6 +441,7 @@ bifrost: # region: "us-east-1" # Required # access_key: "env.AWS_ACCESS_KEY_ID" # secret_key: "env.AWS_SECRET_ACCESS_KEY" + # project_id: "" # Optional: Bedrock project ID sent as the OpenAI-Project header (can use env. prefix) # # session_token: "env.AWS_SESSION_TOKEN" # # role_arn: "" # For AssumeRole # # external_id: ""