Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,37 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Guard — injected AuthBridge images must be version-pinned (no floating tags)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard sits in the release job, which is needs: build-and-push + if: ref_type == 'tag'. By the time it fails, kagenti-operator and agentcard-signer are already built and pushed to ghcr with the release tag — half-published release (images out, chart not), manual cleanup.

Two cheaper spots: a pre-flight job that build-and-push depends on, or the Helm Chart Lint job in security-scans.yaml which runs on every PR — then a :latest regression is caught at review time, not mid-release.

# release.yml pins the controller-manager image (below) but NOT the cortex-built
# AuthBridge injection images; they were shipping at :latest (rossoctl/rossoctl#508).
# 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: |
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 injection images are version-pinned OK (values.yaml + config/defaults.go)"

- name: Package and push Helm chart
run: |
chartVersion=$(echo "${{ github.ref_name }}" | cut -c 2-)
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/security-scans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ============================================================================
Expand Down
8 changes: 4 additions & 4 deletions charts/operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chart layer pinned, compiled fallback not: operator/internal/webhook/config/defaults.go:34-45 still has all four at :latest, and loader.go:45 overlays the ConfigMap on top of CompiledDefaults() (fields absent from the file keep the compiled value). So any deploy without kagenti-platform-config — kustomize make deploy, or webhook.enable=false where the ConfigMap isn't rendered — still injects :latest. The new grep only reads values.yaml, so it will never catch it.

defaults.go already carries a Keep in sync with charts/operator/values.yaml comment for the spiffe-helper config; same treatment fits here.

Related: operator/test/e2e/e2e_suite_test.go:66-69 still pulls :latest (and is missing authbridge-lite), so e2e never exercises the images the chart actually ships.

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
Expand Down
13 changes: 9 additions & 4 deletions operator/internal/webhook/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
12 changes: 8 additions & 4 deletions operator/test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
)

Expand Down
Loading