From 22f11c0bdb4a507b15ae219c327780ebef54e9aa Mon Sep 17 00:00:00 2001 From: cwiklik Date: Wed, 19 Aug 2026 10:22:10 -0400 Subject: [PATCH 1/3] fix(release): pin AuthBridge injection images + guard against :latest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operator chart pinned the cortex-built AuthBridge injection images (authbridge, -envoy, -lite, proxy-init) at :latest — non-reproducible, and release.yml only pins the controller-manager image, so every release shipped :latest (#508). Pin them to cortex v0.7.0-alpha.3, and add a release.yml guard that fails the release if any injected image is a floating tag so it can't regress silently. Closes #508 Assisted-By: Claude (Anthropic AI) Signed-off-by: cwiklik --- .github/workflows/release.yml | 12 ++++++++++++ charts/operator/values.yaml | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 455b4149..7c0a20a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,6 +103,18 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Guard — injected AuthBridge images must be version-pinned (no floating tags) + # release.yml pins the controller-manager image (below) but NOT the cortex-built + # AuthBridge injection images; they were shipping at :latest (rossoctl/rossoctl#508). + # Fail the release if any is a floating tag so it can never regress silently. + run: | + cd ${{ env.CHARTS_PATH }}/operator + if grep -nE 'ghcr\.io/rossoctl/cortex/[a-z-]+:(latest|main|master)([^0-9a-zA-Z.-]|$)' values.yaml; then + echo "::error::injected AuthBridge image(s) pinned to a floating tag — pin to a cortex release tag before releasing (rossoctl/rossoctl#508)" + exit 1 + fi + echo "AuthBridge injected images are version-pinned OK" + - name: Package and push Helm chart run: | chartVersion=$(echo "${{ github.ref_name }}" | cut -c 2-) diff --git a/charts/operator/values.yaml b/charts/operator/values.yaml index e266c949..7d2551ab 100644 --- a/charts/operator/values.yaml +++ b/charts/operator/values.yaml @@ -246,10 +246,10 @@ featureGates: # proxy-sidecar / lite mode (always-on enforce-redirect egress capture). defaults: images: - envoyProxy: ghcr.io/rossoctl/cortex/authbridge-envoy:latest - authbridge: ghcr.io/rossoctl/cortex/authbridge:latest - authbridgeLite: ghcr.io/rossoctl/cortex/authbridge-lite:latest - proxyInit: ghcr.io/rossoctl/cortex/proxy-init:latest + envoyProxy: ghcr.io/rossoctl/cortex/authbridge-envoy:v0.7.0-alpha.3 + authbridge: ghcr.io/rossoctl/cortex/authbridge:v0.7.0-alpha.3 + authbridgeLite: ghcr.io/rossoctl/cortex/authbridge-lite:v0.7.0-alpha.3 + proxyInit: ghcr.io/rossoctl/cortex/proxy-init:v0.7.0-alpha.3 pullPolicy: IfNotPresent # Proxy settings From b72cfc0888f39f821fbe8253e382719a0789ce08 Mon Sep 17 00:00:00 2001 From: cwiklik Date: Wed, 19 Aug 2026 11:33:21 -0400 Subject: [PATCH 2/3] fix(release): pin compiled default + e2e AuthBridge images (#508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart-layer pin (values.yaml) didn't cover the compiled fallbacks in config/defaults.go, which loader.go overlays the platform-config ConfigMap on top of. Any deploy without that ConfigMap (kustomize make deploy, or webhook.enable=false) therefore still injected :latest — the exact paths the values.yaml grep guard can't see. Pin all four compiled defaults to v0.7.0-alpha.3 to match the chart, with a keep-in-sync note. Also pin the e2e sidecar images (they still pulled :latest, so e2e never exercised the shipped tags) and add the missing authbridge-lite image. Addresses review feedback on #512. Assisted-By: Claude (Anthropic AI) Signed-off-by: cwiklik --- operator/internal/webhook/config/defaults.go | 13 +++++++++---- operator/test/e2e/e2e_suite_test.go | 12 ++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/operator/internal/webhook/config/defaults.go b/operator/internal/webhook/config/defaults.go index 87cb0d9e..79187e12 100644 --- a/operator/internal/webhook/config/defaults.go +++ b/operator/internal/webhook/config/defaults.go @@ -29,20 +29,25 @@ func CompiledDefaults() *PlatformConfig { // Compiled defaults are overridden at runtime by the platform-config // ConfigMap (rossoctl-platform-config). These serve as fallbacks only. Images: ImageConfig{ + // Keep in sync with charts/operator/values.yaml (defaults.images.*). + // These compiled fallbacks are used when the platform-config ConfigMap + // is absent (e.g. kustomize `make deploy`, or webhook.enable=false), so + // they must be version-pinned too — an unpinned fallback would inject + // :latest on exactly the deploy paths the chart-layer pin doesn't cover. // authbridge-envoy: combined image for envoy-sidecar mode // (Envoy + ext_proc authbridge + spiffe-helper bundled). - EnvoyProxy: "ghcr.io/rossoctl/cortex/authbridge-envoy:latest", + EnvoyProxy: "ghcr.io/rossoctl/cortex/authbridge-envoy:v0.7.0-alpha.3", // authbridge: combined image for proxy-sidecar mode (default // deployment shape) — authbridge-proxy + spiffe-helper // bundled, no Envoy, no gRPC. - AuthBridge: "ghcr.io/rossoctl/cortex/authbridge:latest", + AuthBridge: "ghcr.io/rossoctl/cortex/authbridge:v0.7.0-alpha.3", // authbridge-lite: size-optimized variant for the "lite" // mode. Same listener layout as AuthBridge but parsers // (a2a/mcp/inference) are dropped. - AuthBridgeLite: "ghcr.io/rossoctl/cortex/authbridge-lite:latest", + AuthBridgeLite: "ghcr.io/rossoctl/cortex/authbridge-lite:v0.7.0-alpha.3", // proxy-init: iptables init container, used by // envoy-sidecar mode only. - ProxyInit: "ghcr.io/rossoctl/cortex/proxy-init:latest", + ProxyInit: "ghcr.io/rossoctl/cortex/proxy-init:v0.7.0-alpha.3", PullPolicy: corev1.PullIfNotPresent, }, Proxy: ProxyConfig{ diff --git a/operator/test/e2e/e2e_suite_test.go b/operator/test/e2e/e2e_suite_test.go index 4f1e5e66..3d695ca1 100644 --- a/operator/test/e2e/e2e_suite_test.go +++ b/operator/test/e2e/e2e_suite_test.go @@ -57,15 +57,19 @@ var ( signerImage = "ghcr.io/rossoctl/operator/agentcard-signer:e2e-test" // sidecarImages are the AuthBridge sidecar images to pull and load into Kind. - // cortex ships two combined images plus proxy-init: + // cortex ships three combined images plus proxy-init: // * authbridge-envoy: envoy-sidecar mode (Envoy + ext_proc + bundled spiffe-helper) // * authbridge: proxy-sidecar mode (authbridge-proxy + bundled spiffe-helper) + // * authbridge-lite: lite mode (jwt-validation + token-exchange only) // * proxy-init: iptables init container, envoy-sidecar mode only // Spiffe-helper and client-registration are no longer separate images. + // Keep tags in sync with charts/operator/values.yaml (defaults.images.*) so + // e2e exercises the images the chart actually ships, not :latest. sidecarImages = []string{ - "ghcr.io/rossoctl/cortex/authbridge-envoy:latest", - "ghcr.io/rossoctl/cortex/authbridge:latest", - "ghcr.io/rossoctl/cortex/proxy-init:latest", + "ghcr.io/rossoctl/cortex/authbridge-envoy:v0.7.0-alpha.3", + "ghcr.io/rossoctl/cortex/authbridge:v0.7.0-alpha.3", + "ghcr.io/rossoctl/cortex/authbridge-lite:v0.7.0-alpha.3", + "ghcr.io/rossoctl/cortex/proxy-init:v0.7.0-alpha.3", } ) From 14dc842dcb57591a5019319e776bcb244a259a95 Mon Sep 17 00:00:00 2001 From: cwiklik Date: Wed, 19 Aug 2026 11:46:02 -0400 Subject: [PATCH 3/3] ci(release): pin-guard at PR time + positive assertion incl. defaults.go (#508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback on #512 (findings #1 and #2): - #1 (guard runs too late): the release.yml guard only fired after build-and-push had already pushed the operator images, risking a half-published release. Add the same check to the Helm Chart Lint job in security-scans.yaml, which runs on every PR, so a :latest regression is caught at review time. - #2 (deny-list too loose): replace the 3-tag deny-list with a positive :vX.Y.Z assertion over the four defaults.images.* keys (via yq), which also rejects untagged/:dev/:edge and registry moves. - Both checks now also cover operator/internal/webhook/config/defaults.go (loader.go overlays the ConfigMap on top of these compiled fallbacks), so a regression in the Go defaults is caught too — not just values.yaml. Verified locally: guard passes on the pinned state and errors on :latest and untagged images; both workflow files parse. Assisted-By: Claude (Anthropic AI) Signed-off-by: cwiklik --- .github/workflows/release.yml | 29 ++++++++++++++++++++----- .github/workflows/security-scans.yaml | 31 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c0a20a9..8a483cbe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,14 +106,33 @@ jobs: - name: Guard — injected AuthBridge images must be version-pinned (no floating tags) # release.yml pins the controller-manager image (below) but NOT the cortex-built # AuthBridge injection images; they were shipping at :latest (rossoctl/rossoctl#508). - # Fail the release if any is a floating tag so it can never regress silently. + # Positive assertion (tighter than a deny-list, which passes untagged/:dev/etc): + # every injection image must carry a :vX.Y.Z tag — in BOTH the chart values and + # the compiled Go fallbacks (config/defaults.go), which loader.go overlays the + # platform-config ConfigMap on top of, so a no-ConfigMap deploy (kustomize + # `make deploy`, webhook.enable=false) would otherwise still inject :latest. + # This is a release-time backstop; the same check runs per-PR in security-scans.yaml. run: | - cd ${{ env.CHARTS_PATH }}/operator - if grep -nE 'ghcr\.io/rossoctl/cortex/[a-z-]+:(latest|main|master)([^0-9a-zA-Z.-]|$)' values.yaml; then - echo "::error::injected AuthBridge image(s) pinned to a floating tag — pin to a cortex release tag before releasing (rossoctl/rossoctl#508)" + set -euo pipefail + fail=0 + for k in envoyProxy authbridge authbridgeLite proxyInit; do + img=$(yq ".defaults.images.$k" ${{ env.CHARTS_PATH }}/operator/values.yaml) + if [[ ! "$img" =~ :v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::defaults.images.$k not version-pinned in values.yaml: $img" + fail=1 + fi + done + while IFS= read -r img; do + if [[ ! "$img" =~ :v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::compiled default not version-pinned in config/defaults.go: $img" + fail=1 + fi + done < <(grep -oE 'ghcr\.io/rossoctl/cortex/[a-z-]+:[^"]+' operator/internal/webhook/config/defaults.go) + if [ "$fail" -ne 0 ]; then + echo "::error::pin the flagged AuthBridge injection image(s) to a cortex release tag before releasing (rossoctl/rossoctl#508)" exit 1 fi - echo "AuthBridge injected images are version-pinned OK" + echo "AuthBridge injection images are version-pinned OK (values.yaml + config/defaults.go)" - name: Package and push Helm chart run: | diff --git a/.github/workflows/security-scans.yaml b/.github/workflows/security-scans.yaml index 61f2cdc8..38c83782 100644 --- a/.github/workflows/security-scans.yaml +++ b/.github/workflows/security-scans.yaml @@ -150,6 +150,37 @@ jobs: fi done + - name: Install yq + uses: mikefarah/yq@1b9b4ac5187171d2e5e3129be0cfa827c7f9d53d # v4 + + - name: Guard — AuthBridge injection images must be version-pinned (no floating tags) + # Catches a :latest / floating-tag regression at PR-review time rather than + # mid-release (rossoctl/rossoctl#508). Positive assertion: every injection image + # must carry a :vX.Y.Z tag, in BOTH the chart values and the compiled Go fallbacks + # (config/defaults.go, which the platform-config ConfigMap overlays on top of — a + # no-ConfigMap deploy would otherwise still inject :latest). + run: | + set -euo pipefail + fail=0 + for k in envoyProxy authbridge authbridgeLite proxyInit; do + img=$(yq ".defaults.images.$k" charts/operator/values.yaml) + if [[ ! "$img" =~ :v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error file=charts/operator/values.yaml::defaults.images.$k not version-pinned: $img" + fail=1 + fi + done + while IFS= read -r img; do + if [[ ! "$img" =~ :v[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error file=operator/internal/webhook/config/defaults.go::compiled default not version-pinned: $img" + fail=1 + fi + done < <(grep -oE 'ghcr\.io/rossoctl/cortex/[a-z-]+:[^"]+' operator/internal/webhook/config/defaults.go) + if [ "$fail" -ne 0 ]; then + echo "::error::pin the flagged AuthBridge injection image(s) to a cortex release tag (rossoctl/rossoctl#508)" + exit 1 + fi + echo "AuthBridge injection images are version-pinned OK (values.yaml + config/defaults.go)" + # ============================================================================ # Phase B: Container/IaC Security # ============================================================================