Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9274825
feat: BYO container registry support
minmzzhang Feb 6, 2026
b1203c1
feat: unified registry configuration with multi-registry support
minmzzhang Feb 18, 2026
13a08c9
Merge remote-tracking branch 'upstream/main' into byo-container-regis…
minmzzhang Mar 27, 2026
d6a5748
Merge remote-tracking branch 'upstream/main' into byo-container-regis…
minmzzhang Apr 2, 2026
2c00ce4
feat: add script to generate registry option test variants
minmzzhang Apr 2, 2026
af5d854
fix(acs-central): handle CA trust race on fresh cluster deployment
minmzzhang Mar 23, 2026
0d93e08
refactor: centralize registry config in global.registry and derive qt…
minmzzhang Apr 4, 2026
da24280
fix: align vault-utils JWT placeholders and ACS init RBAC
minmzzhang Apr 7, 2026
78229ad
feat(supply-chain): embedded OCP registry token refresh and Vault JWT
minmzzhang Apr 7, 2026
3444e02
fix(gen-variants): fix subject regex and imagePullTrust matching
minmzzhang Apr 7, 2026
808914a
docs: clarify registry secret is only needed for BYO registry
minmzzhang Apr 7, 2026
e3d7f26
refactor: address PR #99 review feedback
minmzzhang Apr 14, 2026
cc9b142
fix: address PR review feedback (naming, sync-wave, docs)
minmzzhang Apr 16, 2026
876f945
fix: rename OCP to OpenShift in gen-byo-container-registry-variants.py
minmzzhang Apr 16, 2026
a8a5660
fix: apply remaining OCP to OpenShift renames in values-hub.yaml
minmzzhang Apr 16, 2026
9c68d62
feat: seed image job, pipeline auto-trigger, PVC health check, and oc…
minmzzhang Apr 17, 2026
580d880
fix: make registry-seed-image job best-effort to prevent ArgoCD retry…
minmzzhang Apr 17, 2026
facfacd
fix: add registry readiness check to pipeline launcher job
minmzzhang Apr 17, 2026
bf010f1
fix: quote boolean override values as strings in values-hub.yaml
minmzzhang Apr 17, 2026
9e76da0
docs: update supply-chain.md with new automation features
minmzzhang Apr 17, 2026
c30b5bc
fix: use placeholder repository path for BYO registry option
minmzzhang Apr 17, 2026
5b4d8c8
fix: address PR review feedback
minmzzhang Apr 18, 2026
0196776
fix: pin clustergroup chart to 0.9.47
minmzzhang Apr 18, 2026
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
168 changes: 95 additions & 73 deletions charts/acs-central/templates/jobs/create-auth-provider.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,51 +45,50 @@ spec:
- |
#!/usr/bin/env bash

echo "🔄 Configuring Keycloak OIDC authentication provider..."
echo "Configuring Keycloak OIDC authentication provider..."

wait_for_central() {
local max_retries=30
local retry_count=0
echo "Waiting for ACS Central API to be available..."
until curl -sk -u "admin:$PASSWORD" https://central/v1/ping > /dev/null 2>&1; do
retry_count=$((retry_count + 1))
if [ $retry_count -ge $max_retries ]; then
echo "ERROR: Timeout waiting for ACS Central API"
return 1
fi
echo " Retry $retry_count/$max_retries..."
sleep 10
done
echo "ACS Central API is ready"
}

# Wait for ACS Central to be ready
echo "⏳ Waiting for ACS Central API to be available..."
max_retries=30
retry_count=0
until curl -sk -u "admin:$PASSWORD" https://central/v1/ping > /dev/null 2>&1; do
retry_count=$((retry_count + 1))
if [ $retry_count -ge $max_retries ]; then
echo "❌ Timeout waiting for ACS Central API"
exit 1
fi
echo " Retry $retry_count/$max_retries..."
sleep 10
done
echo "✅ ACS Central API is ready"
wait_for_central || exit 1

# Wait for Keycloak OIDC discovery endpoint to be available
echo "⏳ Waiting for Keycloak OIDC discovery endpoint..."
echo "Waiting for Keycloak OIDC discovery endpoint..."
max_retries=30
retry_count=0
until curl -sk "$KEYCLOAK_ISSUER/.well-known/openid-configuration" > /dev/null 2>&1; do
retry_count=$((retry_count + 1))
if [ $retry_count -ge $max_retries ]; then
echo " Timeout waiting for Keycloak OIDC discovery endpoint"
echo " Tried: $KEYCLOAK_ISSUER/.well-known/openid-configuration"
echo "ERROR: Timeout waiting for Keycloak OIDC discovery endpoint"
echo " Tried: $KEYCLOAK_ISSUER/.well-known/openid-configuration"
exit 1
fi
echo " Retry $retry_count/$max_retries..."
sleep 10
done
echo "Keycloak OIDC discovery endpoint is ready"
echo "Keycloak OIDC discovery endpoint is ready"

# Check if auth provider already exists
AUTH_PROVIDERS=$(curl -sk -u "admin:$PASSWORD" https://central/v1/authProviders)
if echo "$AUTH_PROVIDERS" | grep -q "OIDC"; then
echo "OIDC provider already configured"
echo "OIDC provider already configured -- nothing to do"
exit 0
fi

# Get ACS Central hostname (without https://)
ACS_CENTRAL_HOSTNAME="$(oc get route central -n stackrox -o jsonpath='{.spec.host}')"
echo "ACS Central hostname: $ACS_CENTRAL_HOSTNAME"

# Create OIDC provider JSON
cat > /tmp/oidc-config.json << 'OIDCEOF'
{
"name": "OIDC",
Expand All @@ -111,69 +110,92 @@ spec:
}
OIDCEOF

# Remove leading spaces from JSON
sed -i 's/^ //g' /tmp/oidc-config.json

# Replace placeholders using printf and sed to safely handle special characters
# Escape special sed characters in variables: & \ / and newlines
ACS_CENTRAL_HOSTNAME_ESC=$(printf '%s\n' "$ACS_CENTRAL_HOSTNAME" | sed 's:[&\\/]:\\&:g')
KEYCLOAK_ISSUER_ESC=$(printf '%s\n' "$KEYCLOAK_ISSUER" | sed 's:[&\\/]:\\&:g')
KEYCLOAK_CLIENT_ID_ESC=$(printf '%s\n' "$KEYCLOAK_CLIENT_ID" | sed 's:[&\\/]:\\&:g')
KEYCLOAK_CLIENT_SECRET_ESC=$(printf '%s\n' "$KEYCLOAK_CLIENT_SECRET" | sed 's:[&\\/]:\\&:g')
CLAIM_NAME_ESC=$(printf '%s\n' "$CLAIM_NAME" | sed 's:[&\\/]:\\&:g')
CLAIM_EMAIL_ESC=$(printf '%s\n' "$CLAIM_EMAIL" | sed 's:[&\\/]:\\&:g')
CLAIM_GROUPS_ESC=$(printf '%s\n' "$CLAIM_GROUPS" | sed 's:[&\\/]:\\&:g')
CLAIM_ROLES_ESC=$(printf '%s\n' "$CLAIM_ROLES" | sed 's:[&\\/]:\\&:g')

sed -i "s|UI_ENDPOINT_PLACEHOLDER|$ACS_CENTRAL_HOSTNAME_ESC|g" /tmp/oidc-config.json
sed -i "s|ISSUER_PLACEHOLDER|$KEYCLOAK_ISSUER_ESC|g" /tmp/oidc-config.json
sed -i "s|CLIENT_ID_PLACEHOLDER|$KEYCLOAK_CLIENT_ID_ESC|g" /tmp/oidc-config.json
sed -i "s|CLIENT_SECRET_PLACEHOLDER|$KEYCLOAK_CLIENT_SECRET_ESC|g" /tmp/oidc-config.json
sed -i "s|CLAIM_NAME_PLACEHOLDER|$CLAIM_NAME_ESC|g" /tmp/oidc-config.json
sed -i "s|CLAIM_EMAIL_PLACEHOLDER|$CLAIM_EMAIL_ESC|g" /tmp/oidc-config.json
sed -i "s|CLAIM_GROUPS_PLACEHOLDER|$CLAIM_GROUPS_ESC|g" /tmp/oidc-config.json
sed -i "s|CLAIM_ROLES_PLACEHOLDER|$CLAIM_ROLES_ESC|g" /tmp/oidc-config.json

# Debug: Show the JSON payload
echo "📝 OIDC Configuration JSON:"
escape_sed() { printf '%s\n' "$1" | sed 's:[&\\/]:\\&:g'; }

sed -i "s|UI_ENDPOINT_PLACEHOLDER|$(escape_sed "$ACS_CENTRAL_HOSTNAME")|g" /tmp/oidc-config.json
sed -i "s|ISSUER_PLACEHOLDER|$(escape_sed "$KEYCLOAK_ISSUER")|g" /tmp/oidc-config.json
sed -i "s|CLIENT_ID_PLACEHOLDER|$(escape_sed "$KEYCLOAK_CLIENT_ID")|g" /tmp/oidc-config.json
sed -i "s|CLIENT_SECRET_PLACEHOLDER|$(escape_sed "$KEYCLOAK_CLIENT_SECRET")|g" /tmp/oidc-config.json
sed -i "s|CLAIM_NAME_PLACEHOLDER|$(escape_sed "$CLAIM_NAME")|g" /tmp/oidc-config.json
sed -i "s|CLAIM_EMAIL_PLACEHOLDER|$(escape_sed "$CLAIM_EMAIL")|g" /tmp/oidc-config.json
sed -i "s|CLAIM_GROUPS_PLACEHOLDER|$(escape_sed "$CLAIM_GROUPS")|g" /tmp/oidc-config.json
sed -i "s|CLAIM_ROLES_PLACEHOLDER|$(escape_sed "$CLAIM_ROLES")|g" /tmp/oidc-config.json

echo "OIDC Configuration JSON:"
cat /tmp/oidc-config.json
echo ""

# Verify Keycloak discovery endpoint one more time before creating provider
echo "🔍 Verifying Keycloak discovery endpoint..."
curl -sk "$KEYCLOAK_ISSUER/.well-known/openid-configuration" | head -20
echo ""
# --- Create OIDC auth provider with TLS retry logic ---
# On fresh clusters the proxy trustedCA injection may not have
# propagated to Central's mounted CA bundle yet. If Central
# rejects the Keycloak issuer with "certificate signed by unknown
# authority", restart the Central deployment so it reloads the
# (now-correct) CA bundle, then retry.
create_oidc_provider() {
local http_code
http_code=$(curl -X POST -u "admin:$PASSWORD" -k https://central/v1/authProviders \
-H "Content-Type: application/json" \
--data @/tmp/oidc-config.json \
-w "%{http_code}" \
-o /tmp/output.json)

if [ "$http_code" = "200" ]; then
return 0
fi

echo "📤 Creating auth provider in ACS..."
HTTP_CODE=$(curl -X POST -u "admin:$PASSWORD" -k https://central/v1/authProviders \
-H "Content-Type: application/json" \
--data @/tmp/oidc-config.json \
-w "%{http_code}" \
-o /tmp/output.json)
local body
body=$(cat /tmp/output.json)
echo "Auth provider creation returned HTTP $http_code: $body"

echo "📥 Response HTTP Code: $HTTP_CODE"
echo "📥 Response Body:"
cat /tmp/output.json
echo ""
if echo "$body" | grep -q "certificate signed by unknown authority"; then
return 2
fi
return 1
}

MAX_TLS_RETRIES=3
tls_attempt=0

echo "Creating auth provider in ACS..."
while true; do
create_oidc_provider
rc=$?

if [ "$HTTP_CODE" != "200" ]; then
echo "❌ Failed to create auth provider (HTTP $HTTP_CODE)"
if [ $rc -eq 0 ]; then
break
fi

if [ $rc -eq 2 ]; then
tls_attempt=$((tls_attempt + 1))
if [ $tls_attempt -gt $MAX_TLS_RETRIES ]; then
echo "ERROR: Central still does not trust the ingress CA after $MAX_TLS_RETRIES restart(s)"
exit 1
fi
echo "Central does not trust the ingress CA yet (attempt $tls_attempt/$MAX_TLS_RETRIES)"
echo "Restarting Central to reload CA bundle..."
oc rollout restart deployment/central -n stackrox
oc rollout status deployment/central -n stackrox --timeout=180s
wait_for_central || exit 1
continue
fi

echo "ERROR: Failed to create auth provider"
exit 1
fi
done

# Extract provider ID
AUTH_PROVIDER_ID=$(sed 's/,/\n/g' /tmp/output.json | grep -w id | awk -F\" '{ print $4 }')

if [ -z "$AUTH_PROVIDER_ID" ]; then
echo " Failed to extract auth provider ID"
echo "ERROR: Failed to extract auth provider ID"
cat /tmp/output.json
exit 1
fi

echo "Auth provider created with ID: $AUTH_PROVIDER_ID"
echo "Auth provider created with ID: $AUTH_PROVIDER_ID"

# Create admin role mapping for acs-admin role
echo "📝 Creating role mapping: acs-admin → Admin"
echo "Creating role mapping: acs-admin -> Admin"
JSON_PAYLOAD="{\"roleName\":\"Admin\",\"props\":{\"authProviderId\":\"$AUTH_PROVIDER_ID\",\"key\":\"roles\",\"value\":\"acs-admin\"}}"

HTTP_CODE=$(curl -X POST -u "admin:$PASSWORD" -k https://central/v1/groups \
Expand All @@ -182,17 +204,17 @@ spec:
-w "%{http_code}" \
-o /tmp/role-mapping.json)

echo "📥 Role mapping response (HTTP $HTTP_CODE):"
echo "Role mapping response (HTTP $HTTP_CODE):"
cat /tmp/role-mapping.json
echo ""

if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
echo "Admin role mapping created for acs-admin"
echo "Admin role mapping created for acs-admin"
else
echo "⚠️ Warning: Failed to create admin role mapping (HTTP $HTTP_CODE, may already exist)"
echo "WARNING: Failed to create admin role mapping (HTTP $HTTP_CODE, may already exist)"
fi

echo "🎉 Keycloak OIDC configuration complete"
echo "Keycloak OIDC configuration complete"
name: create-auth-provider
dnsPolicy: ClusterFirst
restartPolicy: Never
Expand Down
11 changes: 10 additions & 1 deletion charts/acs-central/templates/rbac/cluster-init-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,13 @@ rules:
- routes
verbs:
- get
- list
- list
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- watch
- patch
14 changes: 11 additions & 3 deletions charts/qtodo/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
{{/*
Create the image path for the passed in image field
Create the image path for the passed in image field.
When global.registry is enabled with domain and repository, the image
reference is derived from global.registry.domain/repository (e.g.
quay.io/ztvp/qtodo) so no VP --set override is needed.
*/}}
{{- define "qtodo.image" -}}
{{- $name := tpl .value.name .context -}}
{{- $useRegistry := default false .useRegistry -}}
{{- if and $useRegistry .context.Values.global.registry.enabled .context.Values.global.registry.domain .context.Values.global.registry.repository -}}
{{- $name = printf "%s/%s" (tpl .context.Values.global.registry.domain .context) .context.Values.global.registry.repository -}}
{{- end -}}
{{- if eq (substr 0 7 (tpl .value.version .context)) "sha256:" -}}
{{- printf "%s@%s" (tpl .value.name .context) (tpl .value.version .context) -}}
{{- printf "%s@%s" $name (tpl .value.version .context) -}}
{{- else -}}
{{- printf "%s:%s" (tpl .value.name .context) (tpl .value.version .context) -}}
{{- printf "%s:%s" $name (tpl .value.version .context) -}}
{{- end -}}
{{- end -}}
2 changes: 1 addition & 1 deletion charts/qtodo/templates/app-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ spec:
readOnly: true
{{- end }}
- name: qtodo
image: {{ template "qtodo.image" (dict "value" .Values.app.images.main "context" $) }}
image: {{ template "qtodo.image" (dict "value" .Values.app.images.main "context" $ "useRegistry" true) }}
imagePullPolicy: {{ .Values.app.images.main.pullPolicy }}
ports:
- containerPort: 8080
Expand Down
2 changes: 1 addition & 1 deletion charts/qtodo/templates/app-serviceaccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ metadata:
app: qtodo
name: qtodo
namespace: qtodo
{{- if .Values.app.images.main.registry.auth }}
{{- if or .Values.app.images.main.registry.auth (and .Values.global.registry.enabled .Values.global.registry.vaultPath) }}
imagePullSecrets:
- name: {{ .Values.app.images.main.registry.secretName }}
{{- end }}
19 changes: 13 additions & 6 deletions charts/qtodo/templates/registry-external-secret.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
{{- if .Values.app.images.main.registry.auth }}
{{- $regAuth := or .Values.app.images.main.registry.auth (and .Values.global.registry.enabled .Values.global.registry.vaultPath) }}
{{- $regDomain := .Values.app.images.main.registry.domain | default .Values.global.registry.domain }}
{{- $regUser := .Values.app.images.main.registry.user | default .Values.global.registry.user }}
{{- $regVaultPath := .Values.app.images.main.registry.vaultPath | default .Values.global.registry.vaultPath }}
{{- $regPasswordKey := .Values.app.images.main.registry.passwordVaultKey | default .Values.global.registry.passwordVaultKey }}
{{- if $regAuth }}
---
apiVersion: "external-secrets.io/v1beta1"
kind: ExternalSecret
metadata:
name: {{ .Values.app.images.main.registry.secretName }}
namespace: {{ .Release.Namespace | default .Values.global.namespace }}
annotations:
argocd.argoproj.io/sync-wave: "36"
spec:
refreshInterval: 15s
secretStoreRef:
Expand All @@ -18,14 +25,14 @@ spec:
.dockerconfigjson: |
{
"auths": {
"{{ .Values.app.images.main.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }}": {
"auth": "{{ `{{ printf "%s:%s" "` }}{{ .Values.app.images.main.registry.user }}{{ `" .password | b64enc }}` }}"
"{{ tpl (required "registry domain is required (set app.images.main.registry.domain or global.registry.domain)" $regDomain) $ }}": {
"auth": "{{ `{{ printf "%s:%s" "` }}{{ $regUser }}{{ `" .password | b64enc }}` }}"
}
}
}
data:
- secretKey: password
remoteRef:
key: {{ .Values.app.images.main.registry.vaultPath }}
property: {{ .Values.app.images.main.registry.passwordVaultKey }}
{{- end }}
key: {{ required "registry vaultPath is required (set app.images.main.registry.vaultPath or global.registry.vaultPath)" $regVaultPath }}
property: {{ required "registry passwordVaultKey is required (set app.images.main.registry.passwordVaultKey or global.registry.passwordVaultKey)" $regPasswordKey }}
{{- end }}
Loading
Loading