diff --git a/charts/supply-chain/templates/_helpers.tpl b/charts/supply-chain/templates/_helpers.tpl index e3d23e02..156de036 100644 --- a/charts/supply-chain/templates/_helpers.tpl +++ b/charts/supply-chain/templates/_helpers.tpl @@ -93,7 +93,6 @@ Sigstore environment variables used in Pipeline Tasks value: $(params.ca-file) - name: COSIGN_YES value: "true" -{{- if eq .Values.rhtas.oidc.enabled true }} - name: OIDC_IDENTITY value: $(params.oidc-identity) - name: OIDC_ISSUER @@ -106,6 +105,7 @@ Sigstore environment variables used in Pipeline Tasks value: $(params.oidc-issuer) - name: SIGSTORE_OIDC_ISSUER value: $(params.oidc-issuer) +{{- if eq .Values.rhtas.oidc.enabled true }} - name: OIDC_CLIENT_ID value: $(params.rhtas-oidc-client-id) - name: COSIGN_OIDC_CLIENT_ID @@ -135,11 +135,11 @@ Sigstore params used in Pipeline Tasks value: $(params.tuf-url) - name: cli-server-url value: $(params.cli-server-url) -{{- if eq .Values.rhtas.oidc.enabled true }} - name: oidc-identity value: $(params.oidc-identity) - name: oidc-issuer value: $(params.oidc-issuer) +{{- if eq .Values.rhtas.oidc.enabled true }} - name: rhtas-oidc-client-id value: $(params.rhtas-oidc-client-id) {{- end }} @@ -164,13 +164,13 @@ Sigstore params descriptions used in Pipeline Tasks - description: Cosign CLI server URL name: cli-server-url type: string -{{- if eq .Values.rhtas.oidc.enabled true }} - description: OIDC identity in signatures name: oidc-identity type: string - description: OIDC issuer in signatures name: oidc-issuer type: string +{{- if eq .Values.rhtas.oidc.enabled true }} - description: RHTAS OIDC client ID name: rhtas-oidc-client-id type: string diff --git a/charts/tekton-chains/Chart.yaml b/charts/tekton-chains/Chart.yaml new file mode 100644 index 00000000..858878a5 --- /dev/null +++ b/charts/tekton-chains/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: tekton-chains +description: Tekton Chains configuration for OpenShift Pipelines supply chain security with RHTAS keyless signing +type: application +version: 0.1.0 +appVersion: "1.15.0" +keywords: + - tekton-chains + - supply-chain-security + - sigstore + - in-toto + - slsa + - zero-trust + - openshift-pipelines +maintainers: + - name: Zero Trust Validated Patterns Team + email: ztvp-arch-group@redhat.com diff --git a/charts/tekton-chains/templates/_helpers.tpl b/charts/tekton-chains/templates/_helpers.tpl new file mode 100644 index 00000000..52a669ab --- /dev/null +++ b/charts/tekton-chains/templates/_helpers.tpl @@ -0,0 +1,52 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "tekton-chains.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "tekton-chains.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "tekton-chains.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "tekton-chains.labels" -}} +helm.sh/chart: {{ include "tekton-chains.chart" . }} +{{ include "tekton-chains.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: tekton-chains +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "tekton-chains.selectorLabels" -}} +app.kubernetes.io/name: {{ include "tekton-chains.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/tekton-chains/templates/spire-static-entries.yaml b/charts/tekton-chains/templates/spire-static-entries.yaml new file mode 100644 index 00000000..a6a19457 --- /dev/null +++ b/charts/tekton-chains/templates/spire-static-entries.yaml @@ -0,0 +1,143 @@ +{{- if and .Values.chains.enabled .Values.chains.signers.x509.fulcio.enabled }} +{{- if eq .Values.chains.signers.x509.fulcio.provider "spiffe" }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tekton-chains-spire-config + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/sync-wave: "48" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tekton-chains-spire-config + annotations: + argocd.argoproj.io/sync-wave: "48" +rules: +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +- apiGroups: ["spire.spiffe.io"] + resources: ["clusterstaticentries"] + verbs: ["get", "list", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tekton-chains-spire-config + annotations: + argocd.argoproj.io/sync-wave: "48" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: tekton-chains-spire-config +subjects: +- kind: ServiceAccount + name: tekton-chains-spire-config + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: tekton-chains-spire-entries + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/sync-wave: "48" + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + labels: + {{- include "tekton-chains.labels" . | nindent 4 }} +spec: + backoffLimit: 5 + template: + metadata: + labels: + {{- include "tekton-chains.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: tekton-chains-spire-config + restartPolicy: Never + containers: + - name: create-entries + image: registry.redhat.io/openshift4/ose-cli-rhel9:latest + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + command: ["/bin/bash", "-c"] + args: + - | + set -euo pipefail + + TRUST_DOMAIN=$(oc get configmap spire-controller-manager \ + -n zero-trust-workload-identity-manager \ + -o jsonpath='{.data.controller-manager-config\.yaml}' \ + | grep trustDomain | awk '{print $2}') + CLUSTER_NAME=$(oc get configmap spire-controller-manager \ + -n zero-trust-workload-identity-manager \ + -o jsonpath='{.data.controller-manager-config\.yaml}' \ + | grep 'clusterName:' | awk '{print $2}') + CLASS_NAME=$(oc get configmap spire-controller-manager \ + -n zero-trust-workload-identity-manager \ + -o jsonpath='{.data.controller-manager-config\.yaml}' \ + | grep 'className:' | head -1 | awk '{print $2}') + + echo "Trust domain: $TRUST_DOMAIN" + echo "Cluster name: $CLUSTER_NAME" + echo "Class name: $CLASS_NAME" + + NODES=$(oc get nodes -o jsonpath='{range .items[*]}{.metadata.name} {.metadata.uid}{"\n"}{end}') + echo "" + echo "Nodes:" + echo "$NODES" + + echo "$NODES" | while read -r NODE_NAME NODE_UID; do + [ -z "$NODE_NAME" ] && continue + ENTRY_NAME="tekton-chains-controller-${NODE_NAME}" + PARENT_ID="spiffe://${TRUST_DOMAIN}/spire/agent/k8s_psat/${CLUSTER_NAME}/${NODE_UID}" + SPIFFE_ID="spiffe://${TRUST_DOMAIN}/ns/openshift-pipelines/sa/tekton-chains-controller" + + echo "" + echo "Creating ClusterStaticEntry: $ENTRY_NAME" + echo " parentID: $PARENT_ID" + + oc apply -f - </dev/null || true) + for entry in $EXISTING; do + ENTRY_SHORT=$(echo "$entry" | sed 's|clusterstaticentry.spire.spiffe.io/||') + NODE_PART=$(echo "$ENTRY_SHORT" | sed 's|tekton-chains-controller-||') + if ! echo "$NODES" | grep -q "^${NODE_PART} "; then + echo "Removing stale entry: $ENTRY_SHORT" + oc delete "$entry" || true + fi + done + + echo "" + echo "Done. Current entries:" + oc get clusterstaticentries -l app.kubernetes.io/managed-by=tekton-chains-spire-config +{{- end }} +{{- end }} diff --git a/charts/tekton-chains/templates/tektonconfig-chains.yaml b/charts/tekton-chains/templates/tektonconfig-chains.yaml new file mode 100644 index 00000000..d69ca8e2 --- /dev/null +++ b/charts/tekton-chains/templates/tektonconfig-chains.yaml @@ -0,0 +1,64 @@ +{{- if .Values.chains.enabled }} +--- +apiVersion: operator.tekton.dev/v1alpha1 +kind: TektonConfig +metadata: + name: config + annotations: + argocd.argoproj.io/sync-wave: "47" + argocd.argoproj.io/sync-options: Delete=false,ServerSideApply=true,SkipDryRunOnMissingResource=true +spec: + chain: + artifacts.taskrun.format: {{ .Values.chains.artifacts.taskrun.format }} + artifacts.taskrun.storage: {{ .Values.chains.artifacts.taskrun.storage }} + artifacts.pipelinerun.format: {{ .Values.chains.artifacts.pipelinerun.format }} + artifacts.pipelinerun.storage: {{ .Values.chains.artifacts.pipelinerun.storage }} + {{- if .Values.chains.artifacts.pipelinerun.enableDeepInspection }} + artifacts.pipelinerun.enable-deep-inspection: "true" + {{- end }} + artifacts.oci.storage: {{ .Values.chains.artifacts.oci.storage }} + disabled: false + transparency.enabled: {{ .Values.chains.transparency.enabled | quote }} + transparency.url: https://rekor-server-trusted-artifact-signer.{{ .Values.global.hubClusterDomain }} + signers.x509.fulcio.enabled: {{ .Values.chains.signers.x509.fulcio.enabled }} + signers.x509.fulcio.address: https://fulcio-server-trusted-artifact-signer.{{ .Values.global.hubClusterDomain }} + signers.x509.fulcio.issuer: https://spire-spiffe-oidc-discovery-provider.{{ .Values.global.hubClusterDomain }} + signers.x509.fulcio.provider: {{ .Values.chains.signers.x509.fulcio.provider }} + {{- if .Values.chains.signers.x509.tuf.enabled }} + signers.x509.tuf.mirror.url: https://tuf-trusted-artifact-signer.{{ .Values.global.hubClusterDomain }} + {{- end }} + options: + disabled: false + deployments: + tekton-chains-controller: + spec: + template: + spec: + containers: + - name: tekton-chains-controller + env: + - name: SPIFFE_ENDPOINT_SOCKET + value: {{ .Values.chains.spiffe.socketMount }}/{{ .Values.chains.spiffe.socketFile }} + {{- if .Values.chains.signers.x509.tuf.enabled }} + - name: TUF_ROOT + value: {{ .Values.chains.tuf.mountPath }} + {{- end }} + resources: {} + volumeMounts: + - mountPath: {{ .Values.chains.spiffe.socketMount }} + name: spiffe-workload-api + {{- if .Values.chains.signers.x509.tuf.enabled }} + - mountPath: {{ .Values.chains.tuf.mountPath }} + name: {{ .Values.chains.tuf.name }} + {{- end }} + volumes: + - csi: + driver: csi.spiffe.io + readOnly: true + name: spiffe-workload-api + {{- if .Values.chains.signers.x509.tuf.enabled }} + - emptyDir: {} + name: {{ .Values.chains.tuf.name }} + {{- end }} + targetNamespace: openshift-pipelines +{{- end }} diff --git a/charts/tekton-chains/values.yaml b/charts/tekton-chains/values.yaml new file mode 100644 index 00000000..a6a09f4e --- /dev/null +++ b/charts/tekton-chains/values.yaml @@ -0,0 +1,42 @@ +# Tekton Chains Configuration for ZTVP +# Configures the TektonConfig CR to enable Tekton Chains with RHTAS keyless signing + +global: + hubClusterDomain: apps.example.com + +chains: + enabled: true + + artifacts: + taskrun: + format: in-toto + storage: oci + signer: x509 + pipelinerun: + format: in-toto + storage: oci + signer: x509 + enableDeepInspection: true + oci: + format: simplesigning + storage: oci + signer: x509 + + transparency: + enabled: true + + signers: + x509: + fulcio: + enabled: true + provider: spiffe + tuf: + enabled: true + + tuf: + name: tuf + mountPath: /tuf + + spiffe: + socketMount: /spiffe-workload-api + socketFile: spire-agent.sock diff --git a/docs/SYNC-WAVE-INVENTORY.md b/docs/SYNC-WAVE-INVENTORY.md index fa7a57c1..e2a47083 100644 --- a/docs/SYNC-WAVE-INVENTORY.md +++ b/docs/SYNC-WAVE-INVENTORY.md @@ -70,6 +70,8 @@ Every sync-wave in the repository, in order. **App** = hub-level Argo CD Applica | 46 | └ acs-central | chart | console-link | | 46 | └ acs-secured-cluster | chart | secured-cluster-cr | | 46 | └ rhtas-operator | chart | securesign | +| 47 | tekton-chains | **App** | Tekton Chains config (RHTAS keyless signing) | +| 47 | └ tekton-chains | chart | tektonconfig-chains (TektonConfig CR) | | 48 | supply-chain | **App** | | | 48+0 | └ supply-chain | chart | registry-image-namespace (Namespace, RBAC), pipeline-sa, tasks (incl. restart-qtodo), secrets (quay-pass, rhtpa-pass), quay-user, rhtas/rhtpa-config, pipeline-qtodo-restarter (Role+RoleBinding in qtodo ns) | | 48+1 | └ supply-chain | chart (hook) | enable-registry-default-route (Sync hook Job) | @@ -112,6 +114,7 @@ Every sync-wave in the repository, in order. **App** = hub-level Argo CD Applica | trusted-profile-analyzer | 10 | 41 | Chart resources (OBC, DB, etc.) | | acs-secured-cluster | 15 | 46 | — | | trusted-artifact-signer | 15 | 46 | Deploy after dependencies | +| tekton-chains | — | 47 | After RHTAS, before supply-chain (newly added) | | supply-chain | — | 48 | After RHTAS/ACS, before chart templates (newly added) | | acs-policies | 20 | 51 | After ACS Central + Secured Cluster | @@ -209,6 +212,12 @@ Charts marked **(external)** have been externalized to standalone repositories m | --- | ---: | ---: | | securesign.yaml | 15 | 46 | +### tekton-chains (`charts/tekton-chains/templates/`) — App wave: 47 + +| Resource | Old | Current | +| --- | ---: | ---: | +| tektonconfig-chains.yaml | — | 47 | + ### rhtpa-operator (`charts/rhtpa-operator/templates/`) — App wave: 41 | Resource | Old | Current | diff --git a/scripts/features/features.yaml b/scripts/features/features.yaml index ee35f2cf..49ca349a 100644 --- a/scripts/features/features.yaml +++ b/scripts/features/features.yaml @@ -22,9 +22,13 @@ features: description: "OpenShift Pipelines" depends_on: [] + tekton-chains: + description: "Tekton Chains with RHTAS keyless signing via SPIFFE" + depends_on: [pipelines, rhtas] + supply-chain: description: "Full secure supply chain pipeline" - depends_on: [pipelines, rhtas, rhtpa, storage] + depends_on: [tekton-chains, rhtpa, storage] registry_option_required: true org: ztvp image_name: qtodo diff --git a/scripts/features/quay.yaml b/scripts/features/quay.yaml index bdb44f15..bd29edf0 100644 --- a/scripts/features/quay.yaml +++ b/scripts/features/quay.yaml @@ -25,3 +25,8 @@ clusterGroup: chartVersion: 0.1.* annotations: argocd.argoproj.io/sync-wave: "41" + overrides: + - name: job.image + value: "registry.redhat.io/openshift4/ose-cli-rhel9:latest" + - name: job.resources.limits.memory + value: "512Mi" diff --git a/scripts/features/registry/option-1-quay.yaml b/scripts/features/registry/option-1-quay.yaml index 5dc6b059..8251b773 100644 --- a/scripts/features/registry/option-1-quay.yaml +++ b/scripts/features/registry/option-1-quay.yaml @@ -41,6 +41,8 @@ clusterGroup: overrides: - name: job.image value: "registry.redhat.io/openshift4/ose-cli-rhel9:latest" + - name: job.resources.limits.memory + value: "512Mi" merge_into_applications: supply-chain: diff --git a/scripts/features/tekton-chains.yaml b/scripts/features/tekton-chains.yaml new file mode 100644 index 00000000..f2e9cce4 --- /dev/null +++ b/scripts/features/tekton-chains.yaml @@ -0,0 +1,11 @@ +# Tekton Chains for supply chain security with RHTAS keyless signing +# Depends on: pipelines (OpenShift Pipelines operator), rhtas (RHTAS for Fulcio/Rekor/TUF) +# Applies TektonConfig with ServerSideApply to configure SPIFFE and TUF volumes +clusterGroup: + applications: + tekton-chains: + name: tekton-chains + project: hub + path: charts/tekton-chains + annotations: + argocd.argoproj.io/sync-wave: "47" diff --git a/scripts/gen-feature-variants.md b/scripts/gen-feature-variants.md index d084a79a..fe9235e9 100644 --- a/scripts/gen-feature-variants.md +++ b/scripts/gen-feature-variants.md @@ -48,6 +48,9 @@ python3 scripts/gen-feature-variants.py --features rhtpa # Enable multiple features python3 scripts/gen-feature-variants.py --features rhtpa,rhtas +# Enable Tekton Chains (auto-resolves: pipelines -> rhtas -> tekton-chains) +python3 scripts/gen-feature-variants.py --features tekton-chains + # Full supply chain: pick a registry option (1, 2, or 3) python3 scripts/gen-feature-variants.py --features supply-chain --registry-option 1 diff --git a/values-hub.yaml b/values-hub.yaml index e870fb8f..a5dbd0e6 100644 --- a/values-hub.yaml +++ b/values-hub.yaml @@ -403,6 +403,11 @@ clusterGroup: # chartVersion: 0.1.* # annotations: # argocd.argoproj.io/sync-wave: "41" # Deploy after NooBaa storage backend + # overrides: + # - name: job.image + # value: "registry.redhat.io/openshift4/ose-cli-rhel9:latest" + # - name: job.resources.limits.memory + # value: "512Mi" # RHTAS with SPIFFE Integration # COMMENTED OUT: Uncomment to enable RHTAS with SPIFFE and Email issuers # Depends on: Vault, SPIRE, Keycloak (for Email OIDC issuer if used) @@ -548,6 +553,17 @@ clusterGroup: # before the supply-chain pipeline runs (avoids ImagePullBackOff on first install) # - name: app.seedImage.enabled # value: "true" + # Tekton Chains - Configures Tekton Chains for supply chain security + # Requires: OpenShift Pipelines operator (openshift-pipelines subscription) + # Requires: RHTAS (trusted-artifact-signer) for keyless signing via SPIFFE + # Uses a PostSync Job to patch the operator-managed TektonConfig CR + # tekton-chains: + # name: tekton-chains + # project: hub + # path: charts/tekton-chains + # annotations: + # argocd.argoproj.io/sync-wave: "47" + # # Secure Supply Chain - Uncomment to enable (required for Option 1, 2, or 3 registry flows in docs) # supply-chain: # name: supply-chain