diff --git a/charts/qtodo/templates/registry-external-secret.yaml b/charts/qtodo/templates/registry-external-secret.yaml index 8646909d..0fe1e1ad 100644 --- a/charts/qtodo/templates/registry-external-secret.yaml +++ b/charts/qtodo/templates/registry-external-secret.yaml @@ -18,7 +18,7 @@ spec: .dockerconfigjson: | { "auths": { - "{{ .Values.app.images.main.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }}": { + "{{ tpl (required "app.images.main.registry.domain is required when registry.auth is enabled" .Values.app.images.main.registry.domain) $ }}": { "auth": "{{ `{{ printf "%s:%s" "` }}{{ .Values.app.images.main.registry.user }}{{ `" .password | b64enc }}` }}" } } @@ -26,6 +26,6 @@ spec: data: - secretKey: password remoteRef: - key: {{ .Values.app.images.main.registry.vaultPath }} - property: {{ .Values.app.images.main.registry.passwordVaultKey }} -{{- end }} \ No newline at end of file + key: {{ required "app.images.main.registry.vaultPath is required when registry.auth is enabled" .Values.app.images.main.registry.vaultPath }} + property: {{ required "app.images.main.registry.passwordVaultKey is required when registry.auth is enabled" .Values.app.images.main.registry.passwordVaultKey }} +{{- end }} diff --git a/charts/qtodo/values.yaml b/charts/qtodo/values.yaml index 90494f40..60732643 100644 --- a/charts/qtodo/values.yaml +++ b/charts/qtodo/values.yaml @@ -15,13 +15,14 @@ app: # Modified to Always to force a pull so we can test changes to the container image without requiring manual deletion of images or restarts of argo pullPolicy: Always registry: + # Set to true to create registry auth secret for image pulls auth: false secretName: qtodo-registry-auth - user: quay-user - # domain: quay-registry-quay-quay-enterprise.apps.example.com - # Registry credentials - stored in quay path - vaultPath: secret/data/hub/infra/quay/quay-users - passwordVaultKey: quay-user-password + user: registry-user + # domain: registry.example.com # REQUIRED when auth is enabled + # Vault path and key for registry password (set for your scenario) + vaultPath: "" + passwordVaultKey: "" spiffeHelper: name: registry.redhat.io/zero-trust-workload-identity-manager/spiffe-helper-rhel9 version: v0.10.0 diff --git a/charts/supply-chain/templates/pipeline-qtodo.yaml b/charts/supply-chain/templates/pipeline-qtodo.yaml index 13ae2c8c..add87310 100644 --- a/charts/supply-chain/templates/pipeline-qtodo.yaml +++ b/charts/supply-chain/templates/pipeline-qtodo.yaml @@ -25,7 +25,7 @@ spec: - name: image-target type: string description: qtodo image push destination (e.g. quay.io/ztvp/qtodo:latest) - default: {{ .Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }}/{{ .Values.registry.org }}/{{ .Values.registry.repo }}:{{ .Values.qtodo.tag }} + default: {{ tpl (required "registry.domain is required" .Values.registry.domain) $ }}/{{ .Values.registry.org }}/{{ .Values.registry.repo }}:{{ .Values.qtodo.tag }} - name: image-tls-verify type: string description: Whether to verify TLS when pushing to the OCI registry diff --git a/charts/supply-chain/templates/pipelinerun-qtodo.yaml b/charts/supply-chain/templates/pipelinerun-qtodo.yaml new file mode 100644 index 00000000..820c8da4 --- /dev/null +++ b/charts/supply-chain/templates/pipelinerun-qtodo.yaml @@ -0,0 +1,21 @@ +{{- if .Values.pipelinerun.enabled }} +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: qtodo-supply-chain- + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + pipelineRef: + name: qtodo-supply-chain + workspaces: + - name: qtodo-source + persistentVolumeClaim: + claimName: qtodo-workspace-source + - name: registry-auth-config + secret: + secretName: {{ .Values.registry.authSecretName }} +{{- end }} diff --git a/charts/supply-chain/templates/quay/quay-user-job.yaml b/charts/supply-chain/templates/quay/quay-user-job.yaml index 417afcc7..4fa0f7c4 100644 --- a/charts/supply-chain/templates/quay/quay-user-job.yaml +++ b/charts/supply-chain/templates/quay/quay-user-job.yaml @@ -19,7 +19,7 @@ spec: command: ["python3", "/app/create_user.py"] env: - name: QUAY_HOST - value: {{ .Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }} + value: {{ tpl (.Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain)) $ }} - name: QUAY_ADMIN_USER value: {{ .Values.registry.user }} - name: QUAY_ADMIN_PASSWORD diff --git a/charts/supply-chain/templates/rbac/registry-image-namespace.yaml b/charts/supply-chain/templates/rbac/registry-image-namespace.yaml new file mode 100644 index 00000000..ebd5bf45 --- /dev/null +++ b/charts/supply-chain/templates/rbac/registry-image-namespace.yaml @@ -0,0 +1,119 @@ +{{- if and (index .Values.registry "embeddedOCP") (index .Values.registry.embeddedOCP "ensureImageNamespaceRBAC") }} +# When using the embedded OCP image registry, the pipeline pushes to a namespace +# that matches registry.org (e.g. ztvp). This ensures that namespace exists and +# the pipeline SA has system:image-builder so the push succeeds (transparent to the user). +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.registry.org }} + annotations: + argocd.argoproj.io/sync-wave: "0" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: pipeline-image-builder + namespace: {{ .Values.registry.org }} + annotations: + argocd.argoproj.io/sync-wave: "0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:image-builder +subjects: + - kind: ServiceAccount + name: pipeline + namespace: {{ .Values.global.namespace }} +--- +# Enable the default route on the embedded OCP image registry so that +# the pipeline can push and external clients can pull images via the route. +# Uses a Job because the imageregistry config is a cluster-singleton managed +# by the image-registry operator; declarative ownership would conflict. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: registry-route-enabler + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "0" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Values.global.namespace }}-registry-route-enabler + annotations: + argocd.argoproj.io/sync-wave: "0" +rules: +- apiGroups: ["imageregistry.operator.openshift.io"] + resources: ["configs"] + verbs: ["get", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Values.global.namespace }}-registry-route-enabler + annotations: + argocd.argoproj.io/sync-wave: "0" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .Values.global.namespace }}-registry-route-enabler +subjects: +- kind: ServiceAccount + name: registry-route-enabler + namespace: {{ .Values.global.namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: enable-registry-default-route + namespace: {{ .Values.global.namespace }} + annotations: + argocd.argoproj.io/sync-wave: "1" + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: HookSucceeded +spec: + backoffLimit: 3 + template: + spec: + serviceAccountName: registry-route-enabler + restartPolicy: Never + containers: + - name: enable-route + image: registry.access.redhat.com/ubi9/ubi:9.7-1764794285 + command: + - /bin/sh + - -ce + - | + APISERVER="https://kubernetes.default.svc" + TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" + CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + RESOURCE_URL="${APISERVER}/apis/imageregistry.operator.openshift.io/v1/configs/cluster" + AUTH_HEADER="Authorization: Bearer ${TOKEN}" + + echo "Checking current defaultRoute status..." + BODY=$(curl -sS --cacert "${CACERT}" -H "${AUTH_HEADER}" "${RESOURCE_URL}") + rc=$?; if [ $rc -ne 0 ]; then echo "ERROR: GET failed (curl rc=${rc})"; exit 1; fi + + # Parse defaultRoute from JSON without jq/grep dependency + case "${BODY}" in + *'"defaultRoute":true'*) echo "Default route already enabled, nothing to do."; exit 0 ;; + esac + + echo "Enabling default route on embedded OCP image registry..." + RESP=$(curl -sS -w "\n%{http_code}" --cacert "${CACERT}" \ + -H "${AUTH_HEADER}" \ + -H "Content-Type: application/merge-patch+json" \ + -X PATCH -d '{"spec":{"defaultRoute":true}}' \ + "${RESOURCE_URL}") + HTTP_CODE=$(echo "${RESP}" | tail -1) + + if [ "${HTTP_CODE}" -ge 200 ] 2>/dev/null && [ "${HTTP_CODE}" -lt 300 ] 2>/dev/null; then + echo "Default route enabled successfully (HTTP ${HTTP_CODE})." + else + echo "ERROR: PATCH failed (HTTP ${HTTP_CODE})." + echo "${RESP}" | head -5 + exit 1 + fi +{{- end }} diff --git a/charts/supply-chain/templates/secrets/qtodo-registry-pass.yaml b/charts/supply-chain/templates/secrets/qtodo-quay-pass.yaml similarity index 64% rename from charts/supply-chain/templates/secrets/qtodo-registry-pass.yaml rename to charts/supply-chain/templates/secrets/qtodo-quay-pass.yaml index 65406f8d..2f42cedd 100644 --- a/charts/supply-chain/templates/secrets/qtodo-registry-pass.yaml +++ b/charts/supply-chain/templates/secrets/qtodo-quay-pass.yaml @@ -1,3 +1,10 @@ +{{/* + Quay User Provisioner Secret + Purpose: Provides password for the Quay user provisioner job to create/update users in built-in Quay + Used by: quay-user-job.yaml (CronJob that provisions Quay users) + Only created when: quay.enabled=true (built-in Quay registry) + Uses unified registry.vaultPath and registry.passwordVaultKey +*/}} {{- if eq .Values.quay.enabled true }} --- apiVersion: "external-secrets.io/v1beta1" @@ -21,4 +28,4 @@ spec: remoteRef: key: {{ .Values.registry.vaultPath }} property: {{ .Values.registry.passwordVaultKey }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/supply-chain/templates/secrets/qtodo-registry-auth.yaml b/charts/supply-chain/templates/secrets/qtodo-registry-auth.yaml index 416e8020..ea3e1e2b 100644 --- a/charts/supply-chain/templates/secrets/qtodo-registry-auth.yaml +++ b/charts/supply-chain/templates/secrets/qtodo-registry-auth.yaml @@ -1,8 +1,17 @@ +{{/* + Pipeline Registry Auth Secret + Purpose: Provides dockerconfigjson for pipeline to push/pull images + Used by: Tekton pipeline tasks (build-image, sign-image, verify-image) + Created when: registry.enabled=true + Registry-agnostic: works for built-in Quay, BYO (quay.io, ghcr.io), or embedded OCP. + Set registry.domain, registry.vaultPath, and registry.passwordVaultKey for your scenario. +*/}} +{{- if .Values.registry.enabled }} --- apiVersion: "external-secrets.io/v1beta1" kind: ExternalSecret metadata: - name: qtodo-registry-auth + name: {{ .Values.registry.authSecretName }} namespace: {{ .Release.Namespace | default .Values.global.namespace }} spec: refreshInterval: 15s @@ -10,14 +19,14 @@ spec: name: {{ .Values.global.secretStore.name }} kind: {{ .Values.global.secretStore.kind }} target: - name: qtodo-registry-auth + name: {{ .Values.registry.authSecretName }} template: type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: | { "auths": { - "{{ .Values.registry.domain | default (printf "quay-registry-quay-quay-enterprise.%s" .Values.global.hubClusterDomain) }}": { + "{{ tpl (required "registry.domain is required when registry.enabled=true" .Values.registry.domain) $ }}": { "auth": "{{ `{{ printf "%s:%s" "` }}{{ .Values.registry.user }}{{ `" .password | b64enc }}` }}" } } @@ -25,5 +34,6 @@ spec: data: - secretKey: password remoteRef: - key: {{ .Values.registry.vaultPath }} - property: {{ .Values.registry.passwordVaultKey }} \ No newline at end of file + key: {{ required "registry.vaultPath is required when registry.enabled=true" .Values.registry.vaultPath }} + property: {{ required "registry.passwordVaultKey is required when registry.enabled=true" .Values.registry.passwordVaultKey }} +{{- end }} diff --git a/charts/supply-chain/values.yaml b/charts/supply-chain/values.yaml index 4a54d048..0b6be272 100644 --- a/charts/supply-chain/values.yaml +++ b/charts/supply-chain/values.yaml @@ -26,26 +26,67 @@ qtodo: buildCmd: "./mvnw -s settings.xml package -DskipTests -Dquarkus.package.jar.type=uber-jar" containerfile: "./Containerfile" -# quay registry configuration -# used to create a new user in quay. Generic registry configuration is below. +# =========================================================================== +# QUAY USER PROVISIONER (only for built-in Quay registry) +# When enabled, runs a CronJob that provisions users in the built-in Quay instance. +# This is Quay-specific and not needed for BYO or embedded OCP registries. +# =========================================================================== quay: - enabled: true + enabled: false email: "quay-user@example.com" job: image: registry.access.redhat.com/ubi9/ubi:9.7-1764794285 schedule: "*/5 * * * *" -# container registry configuration +# =========================================================================== +# REGISTRY CONFIGURATION (option-agnostic) +# Works for all registry types: built-in Quay, BYO (quay.io, ghcr.io, etc.), +# or embedded OCP image registry. Set the values for your scenario. +# +# Scenario-specific values (set in values-hub.yaml overrides): +# Built-in Quay: +# domain: quay-registry-quay-quay-enterprise.apps. +# vaultPath: secret/data/hub/infra/quay/quay-users +# passwordVaultKey: quay-user-password +# BYO (quay.io, ghcr.io, etc.): +# domain: quay.io (or your registry hostname) +# vaultPath: secret/data/hub/infra/registry/registry-user +# passwordVaultKey: registry-password +# Embedded OCP: +# domain: default-route-openshift-image-registry.apps. +# vaultPath: secret/data/hub/infra/registry/registry-user +# passwordVaultKey: registry-password +# embeddedOCP.ensureImageNamespaceRBAC: true +# =========================================================================== registry: - # Commented to generate it dynamically - # domain: "quay-registry-quay-quay-enterprise.hub.example.com" + # Set to true to create the registry auth secret (dockerconfigjson) + enabled: false + # Registry hostname (REQUIRED when enabled) + domain: "" + # Organization/namespace within the registry org: "ztvp" + # Repository name repo: "qtodo" + # Whether to verify TLS when pushing to the registry tlsVerify: "true" - user: "quay-user" - passwordVaultKey: "quay-user-password" - # Infrastructure secrets - stored in quay path - vaultPath: "secret/data/hub/infra/quay/quay-users" + # Registry username + user: "registry-user" + # Vault path to the secret containing the registry password + vaultPath: "" + # Key within the Vault secret that holds the password + passwordVaultKey: "" + # Secret name for registry auth (dockerconfigjson) + authSecretName: "qtodo-registry-auth" + # Embedded OCP registry only: create image namespace (registry.org) and grant + # pipeline SA system:image-builder so the pipeline can push. Set to true only when + # using the in-cluster OpenShift image registry; leave false for other registries. + embeddedOCP: + ensureImageNamespaceRBAC: false + +# pipeline run configuration +pipelinerun: + # Set to true to automatically trigger a pipeline run on ArgoCD sync + enabled: false # spire configuration spire: diff --git a/charts/ztvp-certificates/files/extract-certificates.sh.tpl b/charts/ztvp-certificates/files/extract-certificates.sh.tpl index e54bab1b..ebd85f89 100644 --- a/charts/ztvp-certificates/files/extract-certificates.sh.tpl +++ b/charts/ztvp-certificates/files/extract-certificates.sh.tpl @@ -398,7 +398,79 @@ fi {{- end }} # =================================================================== -# PHASE 9: Automatic Rollout (if enabled) +# PHASE 9: Configure Node-Level Image Pull Trust (if enabled) +# Creates a ConfigMap with registry-hostname keys containing the ingress CA, +# then patches image.config.openshift.io/cluster to reference it. +# This allows kubelet to pull images from registries behind the cluster ingress +# (e.g. built-in Quay) without "x509: certificate signed by unknown authority". +# =================================================================== + +{{- if .Values.imagePullTrust.enabled }} +{{- if .Values.imagePullTrust.registries }} +log "Configuring node-level image pull trust" + +if [[ "$INGRESS_CA_FOUND" != "true" ]]; then + error "imagePullTrust is enabled but no ingress CA was extracted. Cannot configure image pull trust." + error "Ensure autoDetect is true or provide a custom ingress CA source." + exit 1 +fi + +# Build the ConfigMap data with registry hostnames as keys +# Each key is a registry hostname, value is the ingress CA PEM +REGISTRY_CM_DATA="" +{{- range .Values.imagePullTrust.registries }} +log "Adding registry trust: {{ tpl . $ }}" +{{- end }} + +log "Creating ConfigMap: {{ .Values.global.namespace }}/{{ .Values.imagePullTrust.configMapName }}" + +# Combine all ingress CA files into one PEM for registry trust +COMBINED_INGRESS_CA="${TEMP_DIR}/combined-ingress-ca.pem" +> "${COMBINED_INGRESS_CA}" +for f in "${TEMP_DIR}"/ingress-ca-*.crt; do + [[ -f "$f" ]] || continue + cat "$f" >> "${COMBINED_INGRESS_CA}" + echo "" >> "${COMBINED_INGRESS_CA}" +done + +# Create the ConfigMap with registry hostnames as keys +cat <<'CMEOF' > "${TEMP_DIR}/registry-cas-cm.yaml" +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.imagePullTrust.configMapName }} + namespace: {{ .Values.global.namespace }} + labels: + app.kubernetes.io/name: ztvp-certificates + app.kubernetes.io/component: image-pull-trust + app.kubernetes.io/managed-by: ztvp-certificate-manager +data: {} +CMEOF + +oc apply -f "${TEMP_DIR}/registry-cas-cm.yaml" + +# Patch each registry hostname as a key with the ingress CA PEM +{{- range .Values.imagePullTrust.registries }} +log "Patching ConfigMap key: {{ tpl . $ }}" +oc create configmap {{ $.Values.imagePullTrust.configMapName }} \ + -n {{ $.Values.global.namespace }} \ + --from-file="{{ tpl . $ }}=${COMBINED_INGRESS_CA}" \ + --dry-run=client -o yaml | oc apply -f - +{{- end }} + +# Patch image.config.openshift.io/cluster to reference the ConfigMap +log "Patching image.config.openshift.io/cluster additionalTrustedCA" +oc patch image.config.openshift.io/cluster --type merge \ + -p "{\"spec\":{\"additionalTrustedCA\":{\"name\":\"{{ .Values.imagePullTrust.configMapName }}\"}}}" + +log "Node-level image pull trust configured successfully" +log "Note: MCO will roll this out to nodes (may take a few minutes)" + +{{- end }} +{{- end }} + +# =================================================================== +# PHASE 10: Automatic Rollout (if enabled) # =================================================================== {{- if .Values.rollout.enabled }} diff --git a/charts/ztvp-certificates/templates/rbac.yaml b/charts/ztvp-certificates/templates/rbac.yaml index e2ce3cbc..5ca51fe4 100644 --- a/charts/ztvp-certificates/templates/rbac.yaml +++ b/charts/ztvp-certificates/templates/rbac.yaml @@ -84,6 +84,39 @@ subjects: - kind: ServiceAccount name: {{ include "ztvp-certificates.serviceAccountName" . }} namespace: {{ .Values.global.namespace }} +{{- if .Values.imagePullTrust.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-image-config + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +rules: +# Patch image.config.openshift.io/cluster to set additionalTrustedCA +- apiGroups: ["config.openshift.io"] + resources: ["images"] + verbs: ["get", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "ztvp-certificates.fullname" . }}-image-config + annotations: + argocd.argoproj.io/sync-wave: "-9" + labels: + {{- include "ztvp-certificates.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "ztvp-certificates.fullname" . }}-image-config +subjects: +- kind: ServiceAccount + name: {{ include "ztvp-certificates.serviceAccountName" . }} + namespace: {{ .Values.global.namespace }} +{{- end }} {{- if .Values.rollout.enabled }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/charts/ztvp-certificates/values.yaml b/charts/ztvp-certificates/values.yaml index 6b45fd74..7fe49813 100644 --- a/charts/ztvp-certificates/values.yaml +++ b/charts/ztvp-certificates/values.yaml @@ -185,6 +185,25 @@ distribution: # Requires: ManagedClusterSetBinding in the namespace method: "acm-policy" +# Node-level image pull trust for kubelet +# Configures image.config.openshift.io/cluster additionalTrustedCA so that +# kubelet can pull images from registries behind the cluster's ingress (e.g. +# built-in Quay). Without this, kubelet image pulls fail with +# "x509: certificate signed by unknown authority" when the ingress uses a +# self-signed or cluster-internal CA. +imagePullTrust: + # Set to true to create the registry-CA ConfigMap and patch image.config + enabled: false + # ConfigMap name created in openshift-config for image.config additionalTrustedCA + configMapName: ztvp-registry-cas + # Registry hostnames that need the ingress CA for image pulls. + # Each becomes a key in the ConfigMap with the ingress CA as the value. + # Use {{ .Values.global.hubClusterDomain }} in values-hub.yaml overrides. + registries: [] + # Example (built-in Quay): + # registries: + # - quay-registry-quay-quay-enterprise.apps.example.com + # Debugging options debug: # Enable verbose logging in extraction job diff --git a/docs/supply-chain.md b/docs/supply-chain.md index d2459fad..5e92aa73 100644 --- a/docs/supply-chain.md +++ b/docs/supply-chain.md @@ -20,6 +20,155 @@ In our demo, we will use a number of additional ZTVP components. These component * [Multicloud Object Gateway](https://docs.redhat.com/en/documentation/red_hat_openshift_container_storage/4.8/html/managing_hybrid_and_multicloud_resources/index) is a data service for OpenShift that provides an S3-compatible object storage. In our case, this component is necessary to provide a storage system to Quay. * [Red Hat OpenShift Pipelines](https://docs.redhat.com/en/documentation/red_hat_openshift_pipelines/1.20) is a cloud-native CI/CD solution built on the Tekton framework. We will use this product to automate our secure supply chain process, but you could use your own CI/CD solution if one exists. +## Bring Your Own (BYO) Container Registry + +By default, ZTVP deploys a built-in Red Hat Quay registry. However, you can use your own container registry (e.g., quay.io, Docker Hub, GitHub Container Registry, or a private registry) instead. + +### Configuration Steps + +1. **Disable built-in Quay registry** (optional - if not using Quay): Comment out the Quay-related applications in `values-hub.yaml`: `quay-enterprise` namespace, `quay-operator` subscription, and `quay-registry` application. + +2. **Configure registry credentials in Vault**: Per VP rule, add your registry credentials to `~/values-secrets.yaml` (or `~/values-secret.yaml` / `~/values-secret-layered-zero-trust.yaml` per VP lookup order): + + ```bash + # Copy template to local file if not already done + cp values-secret.yaml.template ~/values-secrets.yaml + ``` + + Add the registry-user secret (same format for **BYO external registry** and **embedded OCP registry**): + + ```yaml + - name: registry-user + vaultPrefixes: + - hub/infra/registry + fields: + - name: registry-password + value: "REPLACE_WITH_REGISTRY_TOKEN" + onMissingValue: error + ``` + + Replace `REPLACE_WITH_REGISTRY_TOKEN` with: + * **Embedded OCP registry:** output of `oc whoami -t` (after `oc login`). + * **External registry (BYO):** your registry token or password (e.g. quay.io, ghcr.io). + + > **Note**: Never commit `~/values-secrets.yaml` (or your local values-secret file) to git. This file contains sensitive credentials and should remain local. + +3. **Set registry configuration in values-hub.yaml**: For the supply-chain application, add these overrides: + + ```yaml + overrides: + - name: registry.enabled + value: "true" + - name: registry.domain + value: "your-registry.example.com" + - name: registry.user + value: "your-username" + - name: registry.org + value: "your-org" + - name: registry.vaultPath + value: "secret/data/hub/infra/registry/registry-user" + - name: registry.passwordVaultKey + value: "registry-password" + ``` + +4. **Configure qtodo for custom registry** (if pulling from custom registry): + + ```yaml + overrides: + - name: app.images.main.registry.auth + value: true + - name: app.images.main.registry.domain + value: "your-registry.example.com" + - name: app.images.main.registry.user + value: "your-username" + ``` + +### Required Configuration + +| Parameter | Description | Example | +| --------- | ----------- | ------- | +| `registry.enabled` | Enable registry auth secret creation | `true` | +| `registry.domain` | Registry hostname (REQUIRED) | `quay.io`, `ghcr.io`, `registry.example.com` | +| `registry.org` | Organization/namespace | `my-org` | +| `registry.repo` | Repository name | `qtodo` | +| `registry.user` | Registry username | `my-robot-account` | +| `registry.vaultPath` | Vault path for registry password | `secret/data/hub/infra/registry/registry-user` | +| `registry.passwordVaultKey` | Key within the Vault secret | `registry-password` | + +> **Note**: All registry types (built-in Quay, BYO, embedded OCP) use the same parameters. Set `registry.domain`, `registry.vaultPath`, and `registry.passwordVaultKey` to the appropriate values for your scenario. See the Vault Paths table below for scenario-specific values. + +### Vault Paths + +Registry credentials are stored at different paths based on registry type: + +| Registry Type | Vault Path | Password Key | +| ------------------ | ---------------------------------------------- | -------------------- | +| Built-in Quay | `secret/data/hub/infra/quay/quay-users` | `quay-user-password` | +| BYO Registry | `secret/data/hub/infra/registry/registry-user` | `registry-password` | +| Embedded OCP | `secret/data/hub/infra/registry/registry-user` | `registry-password` | + +Set `registry.vaultPath` and `registry.passwordVaultKey` in your `values-hub.yaml` overrides to match your scenario. When `registry.enabled=false` (default), no registry auth secret is created (fresh install state). + +The Vault policy `hub-supply-chain-jwt-secret` grants read access to both paths for the pipeline service account. + +### Embedded OCP Registry + +To use the in-cluster OpenShift image registry instead of an external registry: + +1. **Enable `registry.embeddedOCP.ensureImageNamespaceRBAC`** in the supply-chain overrides. The chart will automatically: + * Create the image namespace matching `registry.org` (e.g. `ztvp`) + * Grant the pipeline ServiceAccount `system:image-builder` in that namespace + * Enable the default route on the image registry (via a one-time Job) + +2. **Set the registry domain** to `default-route-openshift-image-registry.apps.`. + +3. **Set the registry user** to `admin` (or a user with push permissions). + +4. **Store the token in Vault**: Use `oc whoami -t` output as the `registry-password` value in `~/values-secrets.yaml`. + +Example supply-chain overrides: + +```yaml +overrides: + - name: registry.enabled + value: "true" + - name: registry.domain + value: default-route-openshift-image-registry.apps. + - name: registry.org + value: ztvp + - name: registry.user + value: admin + - name: registry.vaultPath + value: "secret/data/hub/infra/registry/registry-user" + - name: registry.passwordVaultKey + value: "registry-password" + - name: registry.embeddedOCP.ensureImageNamespaceRBAC + value: "true" +``` + +### Node-Level Image Pull Trust + +When using a registry behind the cluster ingress (Option 1: Built-in Quay or Option 3: Embedded OCP Registry), kubelet cannot pull images by default because the ingress certificate is self-signed and not trusted at the node level. + +The `ztvp-certificates` application handles this by patching `image.config.openshift.io/cluster` with the ingress CA certificate for the configured registry hostnames. Enable it by uncommenting the `imagePullTrust` overrides in `values-hub.yaml`: + +```yaml +# ztvp-certificates overrides +- name: imagePullTrust.enabled + value: "true" +- name: imagePullTrust.registries[0] + value: +``` + +Set `` to match your registry option: + +| Option | Registry Hostname | +| ------ | ----------------- | +| Option 1: Built-in Quay | `quay-registry-quay-quay-enterprise.apps.` | +| Option 3: Embedded OCP | `default-route-openshift-image-registry.apps.` | + +> **Note**: Option 2 (BYO/External Registry) does not require `imagePullTrust` because external registries like quay.io and ghcr.io use publicly trusted certificates. + ## Automatic approach To automate the application building and certifying process, we will use _Red Hat OpenShift Pipelines_. @@ -78,12 +227,57 @@ Using the previously created definition, start a new execution of the pipeline u oc create -f qtodo-pipeline.yaml ``` +#### Using Helm Template + +You can also trigger a pipeline run using the Helm template included in the chart. + +**For Built-in Quay Registry:** + +```shell +helm template supply-chain charts/supply-chain \ + --set pipelinerun.enabled=true \ + --set registry.enabled=true \ + --set registry.domain=quay-registry-quay-quay-enterprise.apps.example.com \ + --set registry.vaultPath=secret/data/hub/infra/quay/quay-users \ + --set registry.passwordVaultKey=quay-user-password \ + --set global.namespace=layered-zero-trust-hub \ + --show-only templates/pipelinerun-qtodo.yaml | oc create -f - +``` + +**For BYO/External Registry:** + +```shell +helm template supply-chain charts/supply-chain \ + --set pipelinerun.enabled=true \ + --set registry.enabled=true \ + --set registry.domain=quay.io \ + --set registry.vaultPath=secret/data/hub/infra/registry/registry-user \ + --set registry.passwordVaultKey=registry-password \ + --set global.namespace=layered-zero-trust-hub \ + --show-only templates/pipelinerun-qtodo.yaml | oc create -f - +``` + +This renders the PipelineRun template with the correct PVC and secret workspace bindings, then creates it in the cluster. + You can review the current pipeline logs using the [Tekton CLI](https://tekton.dev/docs/cli/). ```shell tkn pipeline logs -n layered-zero-trust-hub -L -f ``` +Or use `oc` commands to monitor progress: + +```shell +# List pipeline runs +oc get pipelinerun -n layered-zero-trust-hub + +# Check task status for a specific run +oc get taskruns -n layered-zero-trust-hub -l tekton.dev/pipelineRun= + +# View logs for a specific task +oc logs -n layered-zero-trust-hub -l tekton.dev/pipelineRun=,tekton.dev/pipelineTask= +``` + ### Pipeline tasks The pipeline we have prepared has the following steps: diff --git a/scripts/gen-byo-container-registry-variants.py b/scripts/gen-byo-container-registry-variants.py new file mode 100755 index 00000000..ee12e3fe --- /dev/null +++ b/scripts/gen-byo-container-registry-variants.py @@ -0,0 +1,534 @@ +#!/usr/bin/env python3 +"""Generate values-hub.yaml variants for BYO container registry options. + +Reads the default values-hub.yaml (all supply-chain components commented out) +and produces up to 3 variants with the chosen registry option enabled: + + Option 1: Built-in Quay Registry + Option 2: BYO / External Registry (e.g. quay.io, ghcr.io) + Option 3: Embedded OCP Image Registry + +Each variant also enables the common supply-chain stack (OpenShift Pipelines, +ODF, NooBaa, RHTAS, RHTPA, and their namespaces/subscriptions/vault roles). + +Usage: + # Generate all 3 variants under /tmp + python3 scripts/gen-byo-container-registry-options.py + + # Generate a single variant + python3 scripts/gen-byo-container-registry-options.py --option 2 + + # Custom base file and output directory + python3 scripts/gen-byo-container-registry-options.py \\ + --base my-values-hub.yaml --outdir /tmp/variants +""" + +import argparse +import os +import re +import sys + + +def uncomment_line(line): + """Remove one layer of comment: ' # foo' -> ' foo'.""" + return re.sub(r"^(\s*)# ?", r"\1", line, count=1) + + +def uncomment_lines_matching(lines, patterns): + """Uncomment individual lines matching any of the given patterns.""" + result = [] + for line in lines: + matched = False + for pat in patterns: + if re.search(pat, line): + result.append(uncomment_line(line)) + matched = True + break + if not matched: + result.append(line) + return result + + +def _uncomment_multiline_block(lines, trigger_re, body_re): + """Uncomment a contiguous block: first line matches *trigger_re*, + subsequent lines match *body_re*. Both the trigger and body + lines are uncommented.""" + new = [] + i = 0 + while i < len(lines): + if re.search(trigger_re, lines[i]): + while i < len(lines) and re.search(body_re, lines[i]): + new.append(uncomment_line(lines[i])) + i += 1 + continue + new.append(lines[i]) + i += 1 + return new + + +def _uncomment_until_sentinel(lines, trigger_re, sentinel_re, prev_re=None): + """Uncomment from trigger line until a sentinel (exclusive).""" + new = [] + i = 0 + while i < len(lines): + prev_ok = prev_re is None or (i > 0 and re.search(prev_re, lines[i - 1])) + if re.search(trigger_re, lines[i]) and prev_ok: + while i < len(lines): + if re.match(r"^\s*$", lines[i]): + break + if re.match(r"^\s{4}\w", lines[i]): + break + if re.search(sentinel_re, lines[i]): + break + new.append(uncomment_line(lines[i])) + i += 1 + continue + new.append(lines[i]) + i += 1 + return new + + +# --------------------------------------------------------------------------- +# Common supply-chain components (shared by all 3 options) +# --------------------------------------------------------------------------- +def apply_common_supply_chain(lines): + """Uncomment all components common to every supply-chain option.""" + + # Namespace: openshift-pipelines + lines = uncomment_lines_matching(lines, [r"^\s*# - openshift-pipelines\s*$"]) + + # Namespace: openshift-storage + lines = _uncomment_multiline_block( + lines, + r"# - openshift-storage:", + r"#\s+(- openshift-storage:|operatorGroup:|targetNamespace:" + r"|annotations:|labels:" + r"|openshift\.io/cluster-monitoring" + r"|argocd\.argoproj\.io/sync-wave.*26)", + ) + + # Namespace: trusted-artifact-signer + lines = _uncomment_multiline_block( + lines, + r"# - trusted-artifact-signer:", + r"#\s+(- trusted-artifact-signer:" + r"|annotations:|labels:" + r"|argocd\.argoproj\.io/sync-wave.*32.*Auto-created" + r"|openshift\.io/cluster-monitoring)", + ) + + # Namespace: rhtpa-operator + lines = _uncomment_multiline_block( + lines, + r"# - rhtpa-operator:", + r"#\s+(- rhtpa-operator:|operatorGroup:" + r"|targetNamespace: rhtpa" + r"|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*26.*Create before operator)", + ) + + # Namespace: trusted-profile-analyzer + lines = _uncomment_multiline_block( + lines, + r"# - trusted-profile-analyzer:", + r"#\s+(- trusted-profile-analyzer:" + r"|annotations:|labels:" + r"|argocd\.argoproj\.io/sync-wave.*32.*Create before RHTPA" + r"|openshift\.io/cluster-monitoring)", + ) + + # Subscription: openshift-pipelines + new = [] + i = 0 + while i < len(lines): + prev = lines[i - 1] if i > 0 else "" + if re.search(r"# openshift-pipelines:", lines[i]) and re.search( + r"Uncomment to enable OpenShift Pipelines", prev + ): + while i < len(lines) and re.search( + r"#\s*(openshift-pipelines:" + r"|name: openshift-pipelines" + r"|namespace: openshift-operators)", + lines[i], + ): + new.append(uncomment_line(lines[i])) + i += 1 + continue + new.append(lines[i]) + i += 1 + lines = new + + # Subscription: odf + lines = _uncomment_multiline_block( + lines, + r"# odf:", + r"#\s*(odf:|name: odf-operator|namespace: openshift-storage" + r"|channel: stable-4" + r"|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*27.*Install after OperatorGroup)", + ) + + # Subscription: rhtas-operator + lines = _uncomment_multiline_block( + lines, + r"# rhtas-operator:", + r"#\s*(rhtas-operator:|name: rhtas-operator" + r"|namespace: openshift-operators|channel: stable\s*$" + r"|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*29" + r"|catalogSource: redhat-operators)", + ) + + # Subscription: rhtpa-operator + new = [] + i = 0 + while i < len(lines): + prev2 = lines[i - 2] if i > 1 else "" + if re.search(r"# rhtpa-operator:", lines[i]) and re.search(r"Channel:", prev2): + while i < len(lines) and re.search( + r"#\s*(rhtpa-operator:|name: rhtpa-operator" + r"|namespace: rhtpa-operator" + r"|channel: stable-v1\.1" + r"|catalogSource: redhat-operators" + r"|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*27" + r".*Install after OperatorGroup.*before applications)", + lines[i], + ): + new.append(uncomment_line(lines[i])) + i += 1 + continue + new.append(lines[i]) + i += 1 + lines = new + + # Vault JWT roles: rhtpa and supply-chain + lines = uncomment_lines_matching( + lines, + [ + r"#\s+- name: rhtpa\s*$", + r"#\s+audience: rhtpa", + r"#\s+subject: spiffe://.*ns/trusted-profile-analyzer", + r"#\s+policies:\s*$", + r"#\s+- hub-infra-rhtpa-jwt-secret", + r"#\s+- name: supply-chain\s*$", + r"#\s+audience: supply-chain", + r"#\s+subject: spiffe://.*ns/pipeline", + r"#\s+- hub-supply-chain-jwt-secret", + ], + ) + + # Application: noobaa-mcg + lines = _uncomment_multiline_block( + lines, + r"# noobaa-mcg:", + r"#\s*(noobaa-mcg:|name: noobaa-mcg|namespace: openshift-storage" + r"|project: hub|path: charts/noobaa-mcg|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*36)", + ) + + # Application: trusted-artifact-signer + lines = _uncomment_until_sentinel( + lines, + r"# trusted-artifact-signer:", + r"# RHTPA \(Red Hat", + prev_re=r"Depends on:", + ) + + # Application: trusted-profile-analyzer + lines = _uncomment_until_sentinel( + lines, + r"# trusted-profile-analyzer:", + r"PLACEHOLDER_NEVER_MATCH", + prev_re=r"Depends on:", + ) + + return lines + + +# --------------------------------------------------------------------------- +# Per-option enablers +# --------------------------------------------------------------------------- +def enable_quay_namespace_and_sub(lines): + """Enable quay-enterprise namespace, quay-operator sub, quay-registry app.""" + + lines = _uncomment_multiline_block( + lines, + r"# - quay-enterprise:", + r"#\s+(- quay-enterprise:" + r"|annotations:|labels:" + r"|argocd\.argoproj\.io/sync-wave.*32.*Create before" + r"|openshift\.io/cluster-monitoring)", + ) + + lines = _uncomment_multiline_block( + lines, + r"# quay-operator:", + r"#\s*(quay-operator:|name: quay-operator" + r"|namespace: openshift-operators|channel: stable-3" + r"|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*28)", + ) + + lines = _uncomment_multiline_block( + lines, + r"# quay-registry:", + r"#\s*(quay-registry:|name: quay-registry" + r"|namespace: quay-enterprise|project: hub" + r"|chart: quay|chartVersion: 0\.1|annotations:" + r"|argocd\.argoproj\.io/sync-wave.*41)", + ) + + return lines + + +def enable_image_pull_trust(lines, hostname): + """Enable imagePullTrust in ztvp-certificates overrides.""" + result = [] + for line in lines: + if re.search(r"# - name: imagePullTrust\.enabled", line): + result.append(uncomment_line(line)) + elif ( + re.search(r'#\s+value: "true"\s*$', line) + and result + and "imagePullTrust.enabled" in result[-1] + ): + result.append(uncomment_line(line)) + elif re.search(r"# - name: imagePullTrust\.registries\[0\]", line): + result.append(uncomment_line(line)) + elif re.search(r"#\s+value: ", line): + result.append( + line.replace("# ", "").replace("", hostname) + ) + else: + result.append(line) + return result + + +def enable_qtodo_option(lines, option_num): + """Uncomment the specified qtodo registry option (1, 2, or 3).""" + option_header = f"OPTION {option_num}:" + result = [] + i = 0 + in_qtodo_options = False + + while i < len(lines): + line = lines[i] + + if re.search(r"# Secure Supply Chain: pull pipeline-built image", line): + in_qtodo_options = True + result.append(line) + i += 1 + continue + + if in_qtodo_options and re.search(r"# DEFAULT: No pipeline image", line): + in_qtodo_options = False + result.append(line) + i += 1 + continue + + if in_qtodo_options and re.search(f"# {re.escape(option_header)}", line): + result.append(line) + i += 1 + while i < len(lines): + line = lines[i] + if re.search(r"# ====", line): + result.append(line) + i += 1 + break + result.append(uncomment_line(line)) + i += 1 + continue + + result.append(line) + i += 1 + return result + + +def enable_supply_chain_option(lines, option_num): + """Enable the supply-chain app with the specified registry option. + + Two-pass approach: + Pass 1 - Remove the outer structural comment layer from every line + in the supply-chain block. Single-commented structural lines + become bare YAML; double-commented option lines become + single-commented. + Pass 2 - Within the now-single-commented option lines, uncomment the + selected option's overrides and the common RHTAS/RHTPA flags. + """ + option_header = f"OPTION {option_num}:" + option_any_re = re.compile(r"OPTION\s+\d+:") + + # --- Pass 1: strip outer comment from all supply-chain lines ---------- + pass1 = [] + in_block = False + block_start = -1 + block_end = -1 + + for idx, line in enumerate(lines): + if re.search(r"# Secure Supply Chain - Uncomment to enable", line): + in_block = True + block_start = idx + 1 + pass1.append(line) + continue + if in_block and re.match(r"^\s{4}#\s*$", line): + in_block = False + block_end = idx + pass1.append(line) + continue + if in_block: + pass1.append(uncomment_line(line)) + else: + pass1.append(line) + + if block_start < 0: + return pass1 + + # --- Pass 2: selectively uncomment option overrides ------------------- + final = [] + in_target_option = False + past_header = False + + for idx, line in enumerate(pass1): + if not (block_start <= idx < block_end): + final.append(line) + continue + + stripped = line.lstrip() + if not stripped.startswith("#"): + final.append(line) + continue + + # Detect OPTION headers + if re.search(option_header, line): + in_target_option = True + past_header = False + final.append(line) + continue + if option_any_re.search(line) and not re.search(option_header, line): + in_target_option = False + past_header = False + final.append(line) + continue + + is_separator = bool(re.search(r"# ====", line)) + + if is_separator: + if in_target_option and not past_header: + past_header = True + elif in_target_option and past_header: + in_target_option = False + final.append(line) + continue + + # Selected option overrides + if in_target_option and past_header: + final.append(uncomment_line(line)) + continue + + # Common overrides after all option blocks (RHTAS / RHTPA) + if not in_target_option and ( + re.search(r"# - name: rhtas\.enabled", line) + or re.search(r"# - name: rhtpa\.enabled", line) + ): + final.append(uncomment_line(line)) + continue + + if not in_target_option and re.search(r"#\s+value:", line): + if final and ("rhtas.enabled" in final[-1] or "rhtpa.enabled" in final[-1]): + final.append(uncomment_line(line)) + continue + + final.append(line) + + return final + + +# --------------------------------------------------------------------------- +# Top-level generator +# --------------------------------------------------------------------------- +OPTION_LABELS = { + 1: "built-in-quay-registry", + 2: "byo-external-registry", + 3: "embedded-ocp-registry", +} + + +def generate_variant(base_path, option_num, output_path): + with open(base_path) as fh: + lines = fh.readlines() + + lines = apply_common_supply_chain(lines) + + if option_num == 1: + lines = enable_quay_namespace_and_sub(lines) + lines = enable_image_pull_trust( + lines, + "quay-registry-quay-quay-enterprise.apps." + "{{ $.Values.global.clusterDomain }}", + ) + + if option_num == 3: + lines = enable_image_pull_trust( + lines, + "default-route-openshift-image-registry.apps." + "{{ $.Values.global.clusterDomain }}", + ) + + lines = enable_qtodo_option(lines, option_num) + lines = enable_supply_chain_option(lines, option_num) + + with open(output_path, "w") as fh: + fh.writelines(lines) + + label = OPTION_LABELS.get(option_num, f"option-{option_num}") + print(f" Option {option_num} ({label}) -> {output_path}") + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--base", + default=None, + help="Base values-hub.yaml to read (default: /values-hub.yaml)", + ) + parser.add_argument( + "--outdir", + default=None, + help="Output directory (default: /tmp)", + ) + parser.add_argument( + "--option", + type=int, + choices=[1, 2, 3], + default=None, + help="Generate only this option (default: all 3)", + ) + args = parser.parse_args() + + repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + base = args.base or os.path.join(repo_root, "values-hub.yaml") + outdir = args.outdir or "/tmp" + + if not os.path.isfile(base): + print(f"ERROR: base file not found: {base}", file=sys.stderr) + sys.exit(1) + + os.makedirs(outdir, exist_ok=True) + + options = [args.option] if args.option else [1, 2, 3] + print(f"Base: {base}") + print(f"Output directory: {outdir}") + for opt in options: + label = OPTION_LABELS[opt] + out = os.path.join(outdir, f"values-hub-{label}.yaml") + generate_variant(base, opt, out) + + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/values-hub.yaml b/values-hub.yaml index e2c5304d..40b4eaeb 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -224,6 +224,15 @@ clusterGroup: - name: rollout.strategy value: labeled + # Node-level image pull trust for kubelet + # Required when pulling images from registries behind the cluster ingress + # (e.g. built-in Quay, embedded OCP registry). Patches image.config.openshift.io/cluster. + # Uncomment and set the registry hostname when enabling a registry option. + # - name: imagePullTrust.enabled + # value: "true" + # - name: imagePullTrust.registries[0] + # value: + # Note: additionalCertificates (complex nested array) temporarily disabled # Need to find proper way to pass complex structures in Validated Patterns acm: @@ -308,6 +317,9 @@ clusterGroup: path "secret/data/hub/infra/quay/*" { capabilities = ["read"] } + path "secret/data/hub/infra/registry/*" { + capabilities = ["read"] + } path "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" { capabilities = ["read"] } @@ -491,41 +503,132 @@ clusterGroup: value: qtodo - name: app.vault.secretPath value: secret/data/apps/qtodo/qtodo-db - # For Secure Supply Chain, we changed the qtodo image to use the one built in the secure supply chain + # ============================================================ + # Secure Supply Chain: pull pipeline-built image from registry + # Uncomment the option matching your supply-chain registry choice. + # ============================================================ + # OPTION 1: Built-in Quay Registry # - name: app.images.main.name # value: quay-registry-quay-quay-enterprise.apps.{{ $.Values.global.clusterDomain }}/ztvp/qtodo # - name: app.images.main.version # value: latest - # Uncomment to enable registry authentication # - name: app.images.main.registry.auth - # value: true + # value: "true" + # - name: app.images.main.registry.domain + # value: quay-registry-quay-quay-enterprise.apps.{{ $.Values.global.clusterDomain }} # - name: app.images.main.registry.user # value: quay-user + # - name: app.images.main.registry.vaultPath + # value: "secret/data/hub/infra/quay/quay-users" # - name: app.images.main.registry.passwordVaultKey - # value: quay-user-password + # value: "quay-user-password" + # ============================================================ + # OPTION 2: BYO/External Registry + # - name: app.images.main.name + # value: quay.io/minzhang/qtodo + # - name: app.images.main.version + # value: latest + # - name: app.images.main.registry.auth + # value: "true" + # - name: app.images.main.registry.domain + # value: quay.io + # - name: app.images.main.registry.user + # value: minzhang + # - name: app.images.main.registry.vaultPath + # value: "secret/data/hub/infra/registry/registry-user" + # - name: app.images.main.registry.passwordVaultKey + # value: "registry-password" + # ============================================================ + # OPTION 3: Embedded OCP Registry + # - name: app.images.main.name + # value: default-route-openshift-image-registry.apps.{{ $.Values.global.clusterDomain }}/ztvp/qtodo + # - name: app.images.main.version + # value: latest + # - name: app.images.main.registry.auth + # value: "true" + # - name: app.images.main.registry.domain + # value: default-route-openshift-image-registry.apps.{{ $.Values.global.clusterDomain }} + # - name: app.images.main.registry.user + # value: admin + # - name: app.images.main.registry.vaultPath + # value: "secret/data/hub/infra/registry/registry-user" + # - name: app.images.main.registry.passwordVaultKey + # value: "registry-password" + # ============================================================ + # DEFAULT: No pipeline image (comment out all options above) + # qtodo uses the upstream image from the chart's default values.yaml + # ============================================================ # Secure Supply Chain - Uncomment to enable # supply-chain: - # name: supply-chain - # project: hub - # path: charts/supply-chain - # annotations: - # argocd.argoproj.io/sync-wave: "48" - # ignoreDifferences: - # - kind: ServiceAccount - # jqPathExpressions: - # - .imagePullSecrets[]|select(.name | contains("-dockercfg-")) - # overrides: - # # Don't forget to uncomment the RHTAS and RHTPA components in this same file - # - name: rhtas.enabled - # value: true - # - name: rhtpa.enabled - # value: true - # - name: registry.tlsVerify - # value: "false" - # - name: registry.user - # value: quay-admin - # - name: registry.passwordVaultKey - # value: quay-admin-password + # name: supply-chain + # project: hub + # path: charts/supply-chain + # annotations: + # argocd.argoproj.io/sync-wave: "48" + # ignoreDifferences: + # - kind: ServiceAccount + # jqPathExpressions: + # - .imagePullSecrets[]|select(.name | contains("-dockercfg-")) + # overrides: + # # ============================================================ + # # OPTION 1: Built-in Quay Registry (comment out Option 2, uncomment below) + # # Requires: quay-enterprise namespace, quay-operator, quay-registry app + # # ============================================================ + # # - name: quay.enabled + # # value: "true" + # # - name: registry.enabled + # # value: "true" + # # - name: registry.domain + # # value: quay-registry-quay-quay-enterprise.apps.{{ $.Values.global.clusterDomain }} + # # - name: registry.tlsVerify + # # value: "false" + # # - name: registry.user + # # value: quay-user + # # - name: registry.vaultPath + # # value: "secret/data/hub/infra/quay/quay-users" + # # - name: registry.passwordVaultKey + # # value: "quay-user-password" + # # ============================================================ + # # OPTION 2: BYO/External Registry (comment out Option 3, uncomment below) + # # Store token in Vault (hub/infra/registry/registry-user, field registry-password) + # # and in ~/values-secrets.yaml. + # # ============================================================ + # # - name: registry.enabled + # # value: "true" + # # - name: registry.domain + # # value: quay.io + # # - name: registry.org + # # value: your-org + # # - name: registry.user + # # value: your-username + # # - name: registry.vaultPath + # # value: "secret/data/hub/infra/registry/registry-user" + # # - name: registry.passwordVaultKey + # # value: "registry-password" + # # ============================================================ + # # OPTION 3: Embedded OCP Registry + # # ============================================================ + # # - name: registry.enabled + # # value: "true" + # # - name: registry.domain + # # value: default-route-openshift-image-registry.apps.{{ $.Values.global.clusterDomain }} + # # - name: registry.org + # # value: ztvp + # # - name: registry.user + # # value: admin + # # - name: registry.vaultPath + # # value: "secret/data/hub/infra/registry/registry-user" + # # - name: registry.passwordVaultKey + # # value: "registry-password" + # # - name: registry.embeddedOCP.ensureImageNamespaceRBAC + # # value: "true" + # # ============================================================ + # # Enable RHTAS signing + # # - name: rhtas.enabled + # # value: "true" + # # Enable RHTPA SBOM upload + # # - name: rhtpa.enabled + # # value: "true" # # ACS Central Services acs-central: diff --git a/values-secret.yaml.template b/values-secret.yaml.template index 9185fc4f..131d554e 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -16,7 +16,8 @@ version: "2.0" # Infrastructure Secrets (hub/infra/*): # hub/infra/keycloak/ - Keycloak infrastructure secrets # hub/infra/rhtpa/ - RHTPA infrastructure secrets -# hub/infra/quay/ - Quay registry credentials +# hub/infra/quay/ - Built-in Quay registry credentials (auto-generated) +# hub/infra/registry/ - BYO container registry credentials (user-provided) # hub/infra/users/ - User credentials managed by IdP # # Framework Secrets: @@ -174,34 +175,38 @@ secrets: vaultPolicy: alphaNumericPolicy # =========================================================================== - # QUAY INFRASTRUCTURE SECRETS (hub/infra/quay/) - # Registry credentials for Quay - # Policy: hub-infra-quay-secret (read access to hub/infra/quay/*) + # BUILT-IN QUAY REGISTRY SECRETS (hub/infra/quay/) + # Auto-generated credentials for built-in Quay registry + # Used by: Quay user provisioner job, supply-chain pipeline (when quay.enabled=true) + # Policy: hub-supply-chain-jwt-secret (read access to hub/infra/quay/*) # =========================================================================== - name: quay-users vaultPrefixes: - hub/infra/quay fields: - - name: quay-admin-password - onMissingValue: generate - vaultPolicy: validatedPatternDefaultPolicy - name: quay-user-password onMissingValue: generate vaultPolicy: validatedPatternDefaultPolicy - # External Registry Credentials (e.g., Quay.io, Docker Hub, GHCR) - # Reserved for future use with container signing workflows - # Uncomment and provide your credentials when needed - #- name: external-registry - # vaultPrefixes: - # - hub/infra - # fields: - # - name: username - # value: "your-registry-username" # Replace with your username - # onMissingValue: error - # - name: password - # value: "your-registry-token" # Replace with your token/password - # onMissingValue: error + # =========================================================================== + # BYO / EMBEDDED OCP REGISTRY SECRETS (hub/infra/registry/) + # User-provided credentials for external or embedded OCP registry. + # Used by: supply-chain pipeline (push), qtodo (pull) when externalRegistry.enabled=true + # Policy: hub-supply-chain-jwt-secret (read access to hub/infra/registry/*) + # + # VP rule: add this (with your token) to ~/values-secrets.yaml (or + # ~/values-secret.yaml / ~/values-secret-layered-zero-trust.yaml per VP lookup). + # Replace REPLACE_WITH_REGISTRY_TOKEN in your local file: + # - Embedded OCP registry: use output of oc whoami -t + # - External registry (BYO): use your registry token/password + # =========================================================================== + - name: registry-user + vaultPrefixes: + - hub/infra/registry + fields: + - name: registry-password + value: "REPLACE_WITH_REGISTRY_TOKEN" + onMissingValue: error # =========================================================================== # COCO (CONFIDENTIAL CONTAINERS) SECRETS