diff --git a/charts/qtodo/templates/app-config-env.yaml b/charts/qtodo/templates/app-config-env.yaml index 01da1e1f..348c19b1 100644 --- a/charts/qtodo/templates/app-config-env.yaml +++ b/charts/qtodo/templates/app-config-env.yaml @@ -4,7 +4,7 @@ metadata: name: qtodo-config-env namespace: {{ .Release.Namespace }} data: -{{- if eq .Values.app.spire.enabled true }} +{{- if eq .Values.app.oidc.enabled true }} QUARKUS_OIDC_ENABLED: "true" QUARKUS_OIDC_AUTH_SERVER_URL: "{{ include "qtodo.oidc.url" . }}" QUARKUS_OIDC_CLIENT_ID: "{{ .Values.app.oidc.clientId }}" diff --git a/charts/supply-chain/files/rhtas.sh b/charts/supply-chain/files/rhtas.sh index 1653d6c9..c6d68bfb 100755 --- a/charts/supply-chain/files/rhtas.sh +++ b/charts/supply-chain/files/rhtas.sh @@ -88,9 +88,28 @@ sign_artifact() { log_msg "REKOR_URL: ${REKOR_URL}" log_msg "bundle: ${bundle}" + if [ -v OIDC_CLIENT_ID ]; then + log_msg "OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}" + fi + if [ -v OIDC_ISSUER ]; then + log_msg "OIDC_ISSUER: ${OIDC_ISSUER}" + fi + if [ -v OIDC_IDENTITY ]; then + log_msg "OIDC_IDENTITY: ${OIDC_IDENTITY}" + fi + if [ -v OIDC_CLIENT_SECRET_FILE ]; then + log_msg "OIDC_CLIENT_SECRET_FILE: ${OIDC_CLIENT_SECRET_FILE}" + if ! [ -f "${OIDC_CLIENT_SECRET_FILE}" ]; then + log_msg "ERROR: ${OIDC_CLIENT_SECRET_FILE} not found" + exit 1 + fi + fi + cosign sign-blob "${1}" \ --fulcio-url "${FULCIO_URL}" \ --rekor-url "${REKOR_URL}" \ + ${OIDC_CLIENT_ID:+"--oidc-client-id=${OIDC_CLIENT_ID}"} \ + ${OIDC_CLIENT_SECRET_FILE:+"--oidc-client-secret-file=${OIDC_CLIENT_SECRET_FILE}"} \ --bundle "${bundle}" \ --yes } @@ -107,9 +126,28 @@ sign_image() { log_msg "REKOR_URL: ${REKOR_URL}" log_msg "image_ref: ${image_ref}" + if [ -v OIDC_CLIENT_ID ]; then + log_msg "OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}" + fi + if [ -v OIDC_ISSUER ]; then + log_msg "OIDC_ISSUER: ${OIDC_ISSUER}" + fi + if [ -v OIDC_IDENTITY ]; then + log_msg "OIDC_IDENTITY: ${OIDC_IDENTITY}" + fi + if [ -v OIDC_CLIENT_SECRET_FILE ]; then + log_msg "OIDC_CLIENT_SECRET_FILE: ${OIDC_CLIENT_SECRET_FILE}" + if ! [ -f "${OIDC_CLIENT_SECRET_FILE}" ]; then + log_msg "ERROR: ${OIDC_CLIENT_SECRET_FILE} not found" + exit 1 + fi + fi + cosign sign "${image_ref}" \ --fulcio-url "${FULCIO_URL}" \ --rekor-url "${REKOR_URL}" \ + ${OIDC_CLIENT_ID:+"--oidc-client-id=${OIDC_CLIENT_ID}"} \ + ${OIDC_CLIENT_SECRET_FILE:+"--oidc-client-secret-file=${OIDC_CLIENT_SECRET_FILE}"} \ --yes } diff --git a/charts/supply-chain/templates/_helpers.tpl b/charts/supply-chain/templates/_helpers.tpl new file mode 100644 index 00000000..e3d23e02 --- /dev/null +++ b/charts/supply-chain/templates/_helpers.tpl @@ -0,0 +1,178 @@ +{{/* +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" $name (tpl .value.version .context) -}} +{{- else -}} +{{- printf "%s:%s" $name (tpl .value.version .context) -}} +{{- end -}} +{{- end -}} + +{{/* +Generate OIDC issuer +*/}} +{{- define "rhtas.oidc.issuer" }} +{{- if not .Values.rhtas.oidc.enabled }} +{{- printf "https://spire-spiffe-oidc-discovery-provider.%s" .Values.global.hubClusterDomain }} +{{- else }} +{{- print .Values.rhtas.oidc.issuer }} +{{- end }} +{{- end }} + +{{/* +Generate OIDC identity +*/}} +{{- define "rhtas.oidc.identity" }} +{{- if not .Values.rhtas.oidc.enabled }} +{{- printf "spiffe://%s/ns/%s/sa/pipeline" .Values.global.hubClusterDomain .Values.global.namespace }} +{{- else }} +{{- print .Values.rhtas.oidc.identity }} +{{- end }} +{{- end }} + +{{/* +Generate the RHTPA URL +*/}} +{{- define "rhtpa.url" }} +{{- if not .Values.rhtpa.url }} +{{- printf "https://servertrustify.%s" .Values.global.hubClusterDomain }} +{{- else }} +{{- print .Values.rhtpa.url }} +{{- end }} +{{- end }} + +{{/* +Generate the URL of the OIDC service used by RHTPA +*/}} +{{- define "rhtpa.oidc.url" }} +{{- if not .Values.rhtpa.oidc.url }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.hubClusterDomain .Values.rhtpa.oidc.realm }} +{{- else }} +{{- print .Values.rhtpa.oidc.url }} +{{- end }} +{{- end }} + +{{/* +Sigstore environment variables used in Pipeline Tasks +*/}} +{{- define "rhtas.tasks.sigstoreEnvVars" -}} +{{- if .Values.rhtas.spire.enabled }} +- name: SPIFFE_ENDPOINT_SOCKET + value: {{ .Values.spire.endpointSocketPath }} +{{- end }} +- name: CLI_SERVER_URL + value: $(params.cli-server-url) +- name: TUF_URL + value: $(params.tuf-url) +- name: COSIGN_MIRROR + value: $(params.tuf-url) +- name: COSIGN_ROOT + value: $(params.tuf-url)/root.json +- name: FULCIO_URL + value: $(params.fulcio-url) +- name: COSIGN_FULCIO_URL + value: $(params.fulcio-url) +- name: SIGSTORE_FULCIO_URL + value: $(params.fulcio-url) +- name: REKOR_URL + value: $(params.rekor-url) +- name: COSIGN_REKOR_URL + value: $(params.rekor-url) +- name: SIGSTORE_REKOR_URL + value: $(params.rekor-url) +- name: CA_FILE + 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 + value: $(params.oidc-issuer) +- name: OIDC_ISSUER_URL + value: $(params.oidc-issuer) +- name: COSIGN_OIDC_ISSUER + value: $(params.oidc-issuer) +- name: COSIGN_CERTIFICATE_OIDC_ISSUER + value: $(params.oidc-issuer) +- name: SIGSTORE_OIDC_ISSUER + value: $(params.oidc-issuer) +- name: OIDC_CLIENT_ID + value: $(params.rhtas-oidc-client-id) +- name: COSIGN_OIDC_CLIENT_ID + value: $(params.rhtas-oidc-client-id) +- name: SIGTORE_OIDC_CLIENT_ID + value: $(params.rhtas-oidc-client-id) +{{- if ne .Values.rhtas.oidc.clientSecretName "" }} +- name: OIDC_CLIENT_SECRET_FILE + value: /run/secrets/rhtas/oidc/client-secret +- name: COSIGN_OIDC_CLIENT_SECRET_FILE + value: /run/secrets/rhtas/oidc/client-secret +{{- end }} +{{- end }} +{{- end }} + +{{/* +Sigstore params used in Pipeline Tasks +*/}} +{{- define "rhtas.tasks.sigstoreParams" -}} +- name: ca-file + value: $(params.ca-file) +- name: fulcio-url + value: $(params.fulcio-url) +- name: rekor-url + value: $(params.rekor-url) +- name: tuf-url + 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) +- name: rhtas-oidc-client-id + value: $(params.rhtas-oidc-client-id) +{{- end }} +{{- end }} + +{{/* +Sigstore params descriptions used in Pipeline Tasks +*/}} +{{- define "rhtas.tasks.sigstoreParamsDesc" -}} +- description: PEM encoded file containing the CA certificate to validate RHTAS services + name: ca-file + type: string +- description: Fulcio service URL + name: fulcio-url + type: string +- description: Rekor service URL + name: rekor-url + type: string +- description: TUF service URL + name: tuf-url + type: string +- 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 +- description: RHTAS OIDC client ID + name: rhtas-oidc-client-id + type: string +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/supply-chain/templates/pipeline-qtodo.yaml b/charts/supply-chain/templates/pipeline-qtodo.yaml index 5c4b6385..1db94743 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: {{ tpl (required "registry.domain (or global.registry.domain) is required" (.Values.registry.domain | default .Values.global.registry.domain)) $ }}/{{ .Values.registry.repository | default .Values.global.registry.repository }}:{{ .Values.qtodo.tag }} + default: {{ template "qtodo.image" (dict "value" .Values.qtodo.image "context" $ "useRegistry" true) }} - name: image-tls-verify type: string description: Whether to verify TLS when pushing to the OCI registry @@ -57,16 +57,22 @@ spec: - name: oidc-identity type: string description: The OIDC identity to use for verification - default: spiffe://{{ .Values.global.hubClusterDomain }}/ns/{{ .Values.global.namespace }}/sa/pipeline + default: {{ include "rhtas.oidc.identity" . }} - name: oidc-issuer type: string description: The OIDC issuer to use for verification - default: https://spire-spiffe-oidc-discovery-provider.{{ .Values.global.hubClusterDomain }} + default: {{ include "rhtas.oidc.issuer" . }} +{{- if eq .Values.rhtas.oidc.enabled true }} + - name: rhtas-oidc-client-id + type: string + description: The RHTAS OIDC client ID to use for signing + default: {{ .Values.rhtas.oidc.clientId }} +{{- end }} {{- if eq .Values.rhtpa.enabled true }} - name: rhtpa-url type: string description: The URL of the RHTPA service - default: https://servertrustify.{{ .Values.global.hubClusterDomain }} + default: {{ include "rhtpa.url" . }} - name: sbom-file type: string description: The SBOM file to upload to RHTPA @@ -74,15 +80,15 @@ spec: - name: rhtpa-oidc-url type: string description: The URL of the RHTPA OIDC service - default: https://keycloak.{{ .Values.global.hubClusterDomain }}/realms/{{ .Values.rhtpa.oidcRealm }} + default: {{ include "rhtpa.oidc.url" . }} - name: rhtpa-oidc-client-id type: string description: The RHTPA OIDC client ID to use for authentication - default: {{ .Values.rhtpa.clientId }} + default: {{ .Values.rhtpa.oidc.clientId }} - name: rhtpa-oidc-client-secret type: string description: The RHTPA OIDC client Secret name to use for authentication - default: {{ .Values.rhtpa.clientSecretName }} + default: {{ .Values.rhtpa.oidc.clientSecretName }} {{- end }} workspaces: @@ -132,16 +138,7 @@ spec: - name: source workspace: qtodo-source params: - - name: ca-file - value: $(params.ca-file) - - name: fulcio-url - value: $(params.fulcio-url) - - name: rekor-url - value: $(params.rekor-url) - - name: tuf-url - value: $(params.tuf-url) - - name: cli-server-url - value: $(params.cli-server-url) +{{- include "rhtas.tasks.sigstoreParams" . | nindent 8 }} - name: qtodo-artifact-path value: $(params.qtodo-artifact-path) @@ -155,20 +152,7 @@ spec: - name: source workspace: qtodo-source params: - - name: ca-file - value: $(params.ca-file) - - name: fulcio-url - value: $(params.fulcio-url) - - name: rekor-url - value: $(params.rekor-url) - - name: tuf-url - value: $(params.tuf-url) - - name: cli-server-url - value: $(params.cli-server-url) - - name: oidc-identity - value: $(params.oidc-identity) - - name: oidc-issuer - value: $(params.oidc-issuer) +{{- include "rhtas.tasks.sigstoreParams" . | nindent 8 }} - name: qtodo-artifact-path value: $(params.qtodo-artifact-path) @@ -209,18 +193,9 @@ spec: name: qtodo-sign-image kind: Task params: +{{- include "rhtas.tasks.sigstoreParams" . | nindent 8 }} - name: image value: $(params.image-target) - - name: ca-file - value: $(params.ca-file) - - name: fulcio-url - value: $(params.fulcio-url) - - name: rekor-url - value: $(params.rekor-url) - - name: tuf-url - value: $(params.tuf-url) - - name: cli-server-url - value: $(params.cli-server-url) - name: qtodo-generate-sbom runAfter: @@ -247,18 +222,9 @@ spec: - name: source workspace: qtodo-source params: +{{- include "rhtas.tasks.sigstoreParams" . | nindent 8 }} - name: image value: $(params.image-target) - - name: ca-file - value: $(params.ca-file) - - name: fulcio-url - value: $(params.fulcio-url) - - name: rekor-url - value: $(params.rekor-url) - - name: tuf-url - value: $(params.tuf-url) - - name: cli-server-url - value: $(params.cli-server-url) {{- if eq .Values.rhtpa.enabled true }} - name: qtodo-upload-sbom @@ -293,22 +259,9 @@ spec: name: qtodo-verify-image kind: Task params: +{{- include "rhtas.tasks.sigstoreParams" . | nindent 8 }} - name: image value: $(params.image-target) - - name: ca-file - value: $(params.ca-file) - - name: fulcio-url - value: $(params.fulcio-url) - - name: rekor-url - value: $(params.rekor-url) - - name: tuf-url - value: $(params.tuf-url) - - name: cli-server-url - value: $(params.cli-server-url) - - name: oidc-identity - value: $(params.oidc-identity) - - name: oidc-issuer - value: $(params.oidc-issuer) finally: - name: restart-qtodo @@ -318,4 +271,4 @@ spec: values: ["Succeeded"] taskRef: name: restart-qtodo - kind: Task \ No newline at end of file + kind: Task diff --git a/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml b/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml index 8570ff12..424052a5 100644 --- a/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml +++ b/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml @@ -1,9 +1,9 @@ -{{- if eq .Values.rhtpa.enabled true }} +{{- if and .Values.rhtpa.enabled .Values.rhtpa.oidc.enabled }} --- apiVersion: "external-secrets.io/v1beta1" kind: ExternalSecret metadata: - name: qtodo-rhtpa-cli-password + name: {{ .Values.rhtpa.oidc.clientSecretName }} namespace: {{ .Release.Namespace | default .Values.global.namespace }} spec: refreshInterval: 15s @@ -11,7 +11,7 @@ spec: name: {{ .Values.global.secretStore.name }} kind: {{ .Values.global.secretStore.kind }} target: - name: {{ .Values.rhtpa.clientSecretName }} + name: {{ .Values.rhtpa.oidc.clientSecretName }} template: type: Opaque data: @@ -19,6 +19,6 @@ spec: data: - secretKey: password remoteRef: - key: {{ .Values.rhtpa.clientSecretVaultPath }} - property: {{ .Values.rhtpa.clientSecretVaultKey }} + key: {{ .Values.rhtpa.oidc.clientSecretVaultPath }} + property: {{ .Values.rhtpa.oidc.clientSecretVaultKey }} {{- end }} \ No newline at end of file diff --git a/charts/supply-chain/templates/secrets/rhtas-client-secret.yaml b/charts/supply-chain/templates/secrets/rhtas-client-secret.yaml new file mode 100644 index 00000000..3cc78bb8 --- /dev/null +++ b/charts/supply-chain/templates/secrets/rhtas-client-secret.yaml @@ -0,0 +1,24 @@ +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} +--- +apiVersion: "external-secrets.io/v1beta1" +kind: ExternalSecret +metadata: + name: {{ .Values.rhtas.oidc.clientSecretName }} + namespace: {{ .Release.Namespace | default .Values.global.namespace }} +spec: + refreshInterval: 15s + secretStoreRef: + name: {{ .Values.global.secretStore.name }} + kind: {{ .Values.global.secretStore.kind }} + target: + name: {{ .Values.rhtas.oidc.clientSecretName }} + template: + type: Opaque + data: + client-secret: "{{ `{{ .password }}` }}" + data: + - secretKey: password + remoteRef: + key: {{ .Values.rhtas.oidc.clientSecretVaultPath }} + property: {{ .Values.rhtas.oidc.clientSecretVaultKey }} +{{- end }} \ No newline at end of file diff --git a/charts/supply-chain/templates/tasks/sbom-attest.yaml b/charts/supply-chain/templates/tasks/sbom-attest.yaml index 5fed7ce3..850f5374 100644 --- a/charts/supply-chain/templates/tasks/sbom-attest.yaml +++ b/charts/supply-chain/templates/tasks/sbom-attest.yaml @@ -8,24 +8,10 @@ spec: description: This Task can be used to attest the SBOM for the qtodo image. params: +{{- include "rhtas.tasks.sigstoreParamsDesc" . | nindent 4 }} - description: Image to generate the SBOM for name: image type: string - - description: PEM encoded file containing the CA certificate to validate RHTAS services - name: ca-file - type: string - - description: Fulcio service URL - name: fulcio-url - type: string - - description: Rekor service URL - name: rekor-url - type: string - - description: TUF service URL - name: tuf-url - type: string - - description: Cosign CLI server URL - name: cli-server-url - type: string - description: SBOM file path to attach to the attestation name: sbom-file type: string @@ -40,43 +26,47 @@ spec: description: The workspace where the SBOM file will be found. volumes: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true +{{- end }} - name: rhtas-functions configMap: name: rhtas-functions defaultMode: 0755 +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + secret: + secretName: {{ .Values.rhtas.oidc.clientSecretName }} + defaultMode: 0400 +{{- end }} steps: - image: {{ .Values.tasks.images.cosign }} name: sbom-attestation resources: {} env: - - name: SPIFFE_ENDPOINT_SOCKET - value: {{ .Values.spire.endpointSocketPath }} - - name: CLI_SERVER_URL - value: $(params.cli-server-url) - - name: TUF_URL - value: $(params.tuf-url) - - name: FULCIO_URL - value: $(params.fulcio-url) - - name: REKOR_URL - value: $(params.rekor-url) - - name: CA_FILE - value: $(params.ca-file) +{{- include "rhtas.tasks.sigstoreEnvVars" . | nindent 8 }} - name: SBOM_FILE value: $(workspaces.source.path)/$(params.sbom-file) - name: SBOM_FORMAT value: $(params.sbom-format) volumeMounts: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api mountPath: /spiffe-workload-api readOnly: true +{{- end }} - name: rhtas-functions mountPath: /usr/local/lib readOnly: true +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + mountPath: /run/secrets/rhtas/oidc + readOnly: true +{{- end }} script: | #!/usr/bin/env bash diff --git a/charts/supply-chain/templates/tasks/sign-artifact.yaml b/charts/supply-chain/templates/tasks/sign-artifact.yaml index 76b22089..3bfbaedd 100644 --- a/charts/supply-chain/templates/tasks/sign-artifact.yaml +++ b/charts/supply-chain/templates/tasks/sign-artifact.yaml @@ -10,59 +10,49 @@ spec: - name: source description: The workspace where the source code is cloned. params: +{{- include "rhtas.tasks.sigstoreParamsDesc" . | nindent 4 }} - description: Path to the artifact to sign name: qtodo-artifact-path type: string - - description: PEM encoded file containing the CA certificate to validate RHTAS services - name: ca-file - type: string - - description: Fulcio service URL - name: fulcio-url - type: string - - description: Rekor service URL - name: rekor-url - type: string - - description: TUF service URL - name: tuf-url - type: string - - description: Cosign CLI server URL - name: cli-server-url - type: string volumes: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true +{{- end }} - name: rhtas-functions configMap: name: rhtas-functions defaultMode: 0755 +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + secret: + secretName: {{ .Values.rhtas.oidc.clientSecretName }} + defaultMode: 0400 +{{- end }} steps: - image: {{ .Values.tasks.images.cosign }} name: sign-artifact resources: {} env: - - name: SPIFFE_ENDPOINT_SOCKET - value: {{ .Values.spire.endpointSocketPath }} - - name: CLI_SERVER_URL - value: $(params.cli-server-url) - - name: TUF_URL - value: $(params.tuf-url) - - name: FULCIO_URL - value: $(params.fulcio-url) - - name: REKOR_URL - value: $(params.rekor-url) - - name: CA_FILE - value: $(params.ca-file) +{{- include "rhtas.tasks.sigstoreEnvVars" . | nindent 8 }} volumeMounts: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api mountPath: /spiffe-workload-api readOnly: true +{{- end }} - name: rhtas-functions mountPath: /usr/local/lib readOnly: true +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + mountPath: /run/secrets/rhtas/oidc + readOnly: true +{{- end }} script: | #!/usr/bin/env bash diff --git a/charts/supply-chain/templates/tasks/sign-image.yaml b/charts/supply-chain/templates/tasks/sign-image.yaml index 2132aac5..dc820538 100644 --- a/charts/supply-chain/templates/tasks/sign-image.yaml +++ b/charts/supply-chain/templates/tasks/sign-image.yaml @@ -8,24 +8,10 @@ spec: description: This Task can be used to sign the qtodo image. params: +{{- include "rhtas.tasks.sigstoreParamsDesc" . | nindent 4 }} - description: Image to sign name: image type: string - - description: PEM encoded file containing the CA certificate to validate RHTAS services - name: ca-file - type: string - - description: Fulcio service URL - name: fulcio-url - type: string - - description: Rekor service URL - name: rekor-url - type: string - - description: TUF service URL - name: tuf-url - type: string - - description: Cosign CLI server URL - name: cli-server-url - type: string volumes: - name: spiffe-workload-api @@ -36,31 +22,33 @@ spec: configMap: name: rhtas-functions defaultMode: 0755 +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + secret: + secretName: {{ .Values.rhtas.oidc.clientSecretName }} + defaultMode: 0400 +{{- end }} steps: - image: {{ .Values.tasks.images.cosign }} name: sign-image resources: {} env: - - name: SPIFFE_ENDPOINT_SOCKET - value: {{ .Values.spire.endpointSocketPath }} - - name: CLI_SERVER_URL - value: $(params.cli-server-url) - - name: TUF_URL - value: $(params.tuf-url) - - name: FULCIO_URL - value: $(params.fulcio-url) - - name: REKOR_URL - value: $(params.rekor-url) - - name: CA_FILE - value: $(params.ca-file) +{{- include "rhtas.tasks.sigstoreEnvVars" . | nindent 8 }} volumeMounts: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api mountPath: /spiffe-workload-api readOnly: true +{{- end }} - name: rhtas-functions mountPath: /usr/local/lib readOnly: true +{{- if and .Values.rhtas.oidc.enabled (ne .Values.rhtas.oidc.clientSecretName "") }} + - name: rhtas-oidc-client-secret + mountPath: /run/secrets/rhtas/oidc + readOnly: true +{{- end }} script: | #!/usr/bin/env bash diff --git a/charts/supply-chain/templates/tasks/verify-artifact.yaml b/charts/supply-chain/templates/tasks/verify-artifact.yaml index 1e0068cc..68e7186f 100644 --- a/charts/supply-chain/templates/tasks/verify-artifact.yaml +++ b/charts/supply-chain/templates/tasks/verify-artifact.yaml @@ -10,36 +10,18 @@ spec: - name: source description: The workspace where the source code is cloned. params: +{{- include "rhtas.tasks.sigstoreParamsDesc" . | nindent 4 }} - description: Path to the artifact to verify name: qtodo-artifact-path type: string - - description: PEM encoded file containing the CA certificate to validate RHTAS services - name: ca-file - type: string - - description: Fulcio service URL - name: fulcio-url - type: string - - description: Rekor service URL - name: rekor-url - type: string - - description: TUF service URL - name: tuf-url - type: string - - description: Cosign CLI server URL - name: cli-server-url - type: string - - description: OIDC identity to use for verification - name: oidc-identity - type: string - - description: OIDC issuer to use for verification - name: oidc-issuer - type: string volumes: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true +{{- end }} - name: rhtas-functions configMap: name: rhtas-functions @@ -50,26 +32,13 @@ spec: name: verify-artifact resources: {} env: - - name: SPIFFE_ENDPOINT_SOCKET - value: {{ .Values.spire.endpointSocketPath }} - - name: CLI_SERVER_URL - value: $(params.cli-server-url) - - name: TUF_URL - value: $(params.tuf-url) - - name: FULCIO_URL - value: $(params.fulcio-url) - - name: REKOR_URL - value: $(params.rekor-url) - - name: CA_FILE - value: $(params.ca-file) - - name: OIDC_IDENTITY - value: $(params.oidc-identity) - - name: OIDC_ISSUER - value: $(params.oidc-issuer) +{{- include "rhtas.tasks.sigstoreEnvVars" . | nindent 8 }} volumeMounts: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api mountPath: /spiffe-workload-api readOnly: true +{{- end }} - name: rhtas-functions mountPath: /usr/local/lib readOnly: true diff --git a/charts/supply-chain/templates/tasks/verify-image.yaml b/charts/supply-chain/templates/tasks/verify-image.yaml index f67de6b0..c19d22e0 100644 --- a/charts/supply-chain/templates/tasks/verify-image.yaml +++ b/charts/supply-chain/templates/tasks/verify-image.yaml @@ -8,37 +8,18 @@ spec: description: This Task can be used to verify the qtodo image. params: +{{- include "rhtas.tasks.sigstoreParamsDesc" . | nindent 4 }} - description: Image to verify name: image type: string - - description: PEM encoded file containing the CA certificate to validate RHTAS services - name: ca-file - type: string - default: /run/secrets/kubernetes.io/serviceaccount/ca.crt - - description: Fulcio service URL - name: fulcio-url - type: string - - description: Rekor service URL - name: rekor-url - type: string - - description: TUF service URL - name: tuf-url - type: string - - description: Cosign CLI server URL - name: cli-server-url - type: string - - description: OIDC identity to use for verification - name: oidc-identity - type: string - - description: OIDC issuer to use for verification - name: oidc-issuer - type: string volumes: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api csi: driver: csi.spiffe.io readOnly: true +{{- end }} - name: rhtas-functions configMap: name: rhtas-functions @@ -49,26 +30,13 @@ spec: name: verify-image resources: {} env: - - name: SPIFFE_ENDPOINT_SOCKET - value: {{ .Values.spire.endpointSocketPath }} - - name: CLI_SERVER_URL - value: $(params.cli-server-url) - - name: TUF_URL - value: $(params.tuf-url) - - name: FULCIO_URL - value: $(params.fulcio-url) - - name: REKOR_URL - value: $(params.rekor-url) - - name: CA_FILE - value: $(params.ca-file) - - name: OIDC_IDENTITY - value: $(params.oidc-identity) - - name: OIDC_ISSUER - value: $(params.oidc-issuer) +{{ include "rhtas.tasks.sigstoreEnvVars" . | nindent 8 }} volumeMounts: +{{- if .Values.rhtas.spire.enabled }} - name: spiffe-workload-api mountPath: /spiffe-workload-api readOnly: true +{{- end }} - name: rhtas-functions mountPath: /usr/local/lib readOnly: true diff --git a/charts/supply-chain/values.yaml b/charts/supply-chain/values.yaml index fb8b3170..129e29a0 100644 --- a/charts/supply-chain/values.yaml +++ b/charts/supply-chain/values.yaml @@ -15,21 +15,37 @@ global: rhtas: enabled: false + spire: + enabled: true + oidc: + enabled: false + clientId: "" + clientSecretName: "rhtas-oidc-client-secret" + clientSecretVaultPath: "secret/data/hub/infra/rhtas/rhtas-oidc-client-secret" + clientSecretVaultKey: "client-secret" + issuer: "" + identity: "" rhtpa: enabled: false - oidcRealm: "ztvp" - clientId: "rhtpa-cli" - clientSecretName: "qtodo-rhtpa-cli-password" - # RHTPA OIDC CLI secret path (infra) - clientSecretVaultPath: "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" - clientSecretVaultKey: "client-secret" + url: "" + oidc: + enabled: true + url: "" + realm: "ztvp" + clientId: "rhtpa-cli" + clientSecretName: "qtodo-rhtpa-cli-password" + # RHTPA OIDC CLI secret path (infra) + clientSecretVaultPath: "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" + clientSecretVaultKey: "client-secret" # qtodo repository configuration qtodo: repository: "https://github.com/validatedpatterns-demos/qtodo.git" revision: "main" - tag: "latest" + image: + name: "quay.io/validatedpatterns/qtodo" + version: "latest" buildCmd: "./mvnw -s settings.xml package -DskipTests -Dquarkus.package.jar.type=uber-jar" containerfile: "./Containerfile" @@ -127,4 +143,4 @@ tasks: cosign: "registry.access.redhat.com/ubi9/ubi:9.7-1764794285" mandrel: "registry.redhat.io/quarkus/mandrel-for-jdk-21-rhel8:23.1-36" syft: "registry.redhat.io/rh-syft-tech-preview/syft-rhel9:1.29.0" - rhtpa: "registry.access.redhat.com/ubi9/ubi:9.7-1764794285" \ No newline at end of file + rhtpa: "registry.access.redhat.com/ubi9/ubi:9.7-1764794285" diff --git a/docs/entraid.md b/docs/entraid.md deleted file mode 100644 index 4f6d16ae..00000000 --- a/docs/entraid.md +++ /dev/null @@ -1,74 +0,0 @@ -# Azure Entra ID integration - -This document describes the steps required to integrate the **Zero Trust Validated Pattern** (ZTVP) with **Azure Entra ID**, trusting this service as the Identity Provider for the following components: - -* Qtodo demo application - -> [!WARNING] -> The integration of Azure Entra ID into the pattern is still **in progress** and does not cover all components. This document describes those that are supported. For components not supported by Entra ID, **Red Hat Build of Keycloak (RHBK)** will continue to be used as the default OIDC. - -## Configuration - -To configure the components we will need access to Azure Portal with permissions to create App Registrations and a Microsoft Entra ID tenant. - -### Qtodo - -#### Azure setup - -1. Go to [Azure Portal](https://portal.azure.com) -2. Navigate to **Microsoft Entra ID** -3. Click **App registrations** in the left menu -4. Click **New registration** -5. Fill in the details: - * **Name**: `qtodo` - * **Supported account types**: Choose based on your needs - * **Single tenant**: Only users in your organization - * **Multi-tenant**: Users from any organization - * **Redirect URI**: Add the URL of the qtodo application here (for example `https://qtodo-qtodo.apps.ztvp.example.com`)/ -6. Click **Register** - -After the creation, you will see the _Overview_ page: - -* **Application (client) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` -* **Directory (tenant) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` - -**Save these values** - you will need them later. - -Let's create a new secret for our app: - -1. Click **Certificates & secrets** in the left menu -2. Click **New client secret** -3. Add a description: `qtodo secret` -4. Choose expiration: 6 months, 12 months, 24 months, or custom -5. Click **Add** -6. **IMPORTANT**: Copy the **Value** immediately - it will not be shown again - -**Save this value securely** - We will need to add this secret to the Hashicorp Vault in the OpenShift cluster. - -#### ZTVP setup - -In the `values-secret.yaml` file, we add a new entry with the secret we generated in the Azure portal. For example: - -```yaml - - name: qtodo-oidc-entraid - vaultPrefixes: - - apps/qtodo - fields: - - name: client-secret - path: ~/.azure/ztvp-qtodo-entraid-secret -``` - -In the `values-hub.yaml file`, we add the following configuration for the qtodo application: - -```yaml - qtodo: - overrides: - - name: app.oidc.authServerUrl - value: https://login.microsoftonline.com//v2.0 - - name: app.oidc.clientId - value: - - name: app.oidc.clientSecret.enabled - value: true - - name: app.oidc.clientSecret.vaultPath - value: secret/data/apps/qtodo/qtodo-oidc-entraid -``` diff --git a/docs/oidc/README.md b/docs/oidc/README.md new file mode 100644 index 00000000..0d9e41d4 --- /dev/null +++ b/docs/oidc/README.md @@ -0,0 +1,6 @@ +# Zero Touch Validated Pattern OIDC support status + +| OIDC | Red Hat Trusted Artifact Signer (RHTAS) | Red Hat Trusted Profile Analyzer (RHTPA) | Qtodo demo application | +| :---: | :-----: | :-----: | :-----: | +| Red Hat Build of Keycloak (RHBK) | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Azure Entra ID | :white_check_mark: | :x: | :white_check_mark: | diff --git a/docs/oidc/entraid.md b/docs/oidc/entraid.md new file mode 100644 index 00000000..f9dfaed6 --- /dev/null +++ b/docs/oidc/entraid.md @@ -0,0 +1,237 @@ +# Azure Entra ID integration + +This document describes the steps required to integrate the **Zero Trust Validated Pattern** (ZTVP) with **Azure Entra ID**, trusting this service as the Identity Provider for the following components: + +* Qtodo demo application +* Red Hat Trusted Artifact Signer (RHTAS) + +> [!WARNING] +> The integration of Azure Entra ID into the pattern is still **in progress** and does not cover all components. This document describes those that are supported. For components not supported by Entra ID, **Red Hat Build of Keycloak (RHBK)** will continue to be used as the default OIDC. + +## Configuration + +To configure the components we will need access to Azure Portal with permissions to create App Registrations and a Microsoft Entra ID tenant. + +### Qtodo + +#### Qtodo Azure setup + +1. Go to [Azure Portal](https://portal.azure.com) +2. Navigate to **Microsoft Entra ID** +3. Click **App registrations** in the left menu +4. Click **New registration** +5. Fill in the details: + * **Name**: `qtodo` + * **Supported account types**: Choose based on your needs + * **Single tenant**: Only users in your organization + * **Multi-tenant**: Users from any organization + * **Redirect URI**: Add the URL of the qtodo application here (for example `https://qtodo-qtodo.apps.ztvp.example.com/`) +6. Click **Register** + +After the creation, you will see the _Overview_ page: + +* **Application (client) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +* **Directory (tenant) ID**: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` + +**Save these values** - you will need them later. + +Let's create a new secret for our app: + +1. Click **Certificates & secrets** in the left menu +2. Click **New client secret** +3. Add a description: `qtodo secret` +4. Choose expiration: 6 months, 12 months, 24 months, or custom +5. Click **Add** +6. **IMPORTANT**: Copy the **Value** immediately - it will not be shown again + +**Save this value securely** - We will need to add this secret to the Hashicorp Vault in the OpenShift cluster. + +#### Qtodo ZTVP setup + +In the `values-secret.yaml` file, we add a new entry with the secret we generated in the Azure portal. For example: + +```yaml + - name: qtodo-oidc-entraid + vaultPrefixes: + - apps/qtodo + fields: + - name: client-secret + path: ~/.azure/ztvp-qtodo-entraid-secret +``` + +In the `values-hub.yaml file`, we add the following configuration for the qtodo application: + +```yaml + qtodo: + overrides: + - name: app.oidc.authServerUrl + value: https://login.microsoftonline.com//v2.0 + - name: app.oidc.clientId + value: + - name: app.oidc.clientSecret.enabled + value: true + - name: app.oidc.clientSecret.vaultPath + value: secret/data/apps/qtodo/qtodo-oidc-entraid +``` + +### RHTAS + +#### RHTAS Azure setup + +Since in this case we are going to configure some objects that are not visible through the Azure portal, and also require editing within the _App Registration_ Manifest, we will do the configuration using [Azure CLI](https://github.com/Azure/azure-cli). + +This configuration uses the [Device code flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code) for interactive user authentication in Entra ID. + +1. Open a new terminal. +2. Create a new **App Registration**. + + ```shell + export RHTAS_APP_REGISTRATION=$( + az ad app create \ + --display-name=rhtas \ + --web-redirect-uris=https://qtodo-qtodo.apps.ztvp.example.com/auth/callback \ + --enable-id-token-issuance \ + --query appId \ + -o tsv \ + | tr -d '\t\n\r') + ``` + +3. Create a new **Client Secret** for our _App Registration_. Save this value securely. + + ```shell + az ad app credential reset \ + --id="$RHTAS_APP_REGISTRATION" \ + --display-name="RHTAS Client Secret" \ + --query 'password' \ + -o tsv + ``` + +4. Create a new **Claim Mapping Policy** to define a new JWT claim called `email_verified`. + + ```shell + az rest -m post \ + --headers Content-Type=application/json \ + --uri https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies \ + --body '{"definition": ["{\"ClaimsMappingPolicy\":{\"Version\":1,\"IncludeBasicClaimSet\":\"true\", \"ClaimsSchema\":[{\"value\":\"true\",\"JwtClaimType\":\"email_verified\"}]}}"],"displayName": "EmailVerified"}' + ``` + +5. Get the _App Registration_ object ID. + + ```shell + export RHTAS_APP_OBJECT_ID=$(az ad app list --display-name "rhtas" --query "[0].id" -o tsv | tr -d '\t\n\r') + ``` + +6. Enable **Mapped Claims** in the _App Registration_ **manifest**. + + ```shell + az rest --method patch \ + --uri "https://graph.microsoft.com/v1.0/applications/$RHTAS_APP_OBJECT_ID" \ + --headers 'Content-Type=application/json' \ + --body '{"api":{"acceptMappedClaims":true}}' + ``` + +7. Create a new **Service Principal** and associate it with the _App Registration_: + + ```shell + export SERVICE_PRINCIPAL_ID=$( + az ad sp create --id="$RHTAS_APP_REGISTRATION" -o tsv --query 'id' \ + | tr -d '\t\n\r') + ``` + +8. Get the _Claim Mapping Policy_ ID: + + ```shell + export CLAIM_MAPPING_POLICY_ID=$( + az rest --uri https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies \ + --query "value[?displayName=='EmailVerified'] | [0].id" \ + -o tsv) + ``` + +9. Associate the _Claim Mapping Policy_ with the _Service Principal_: + + ```shell + az rest -m post \ + --headers Content-Type=application/json \ + --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${SERVICE_PRINCIPAL_ID}/claimsMappingPolicies/\$ref" \ + --body "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/${CLAIM_MAPPING_POLICY_ID}\"}" + ``` + +10. Enable **public client flow** authentication. + + ```shell + az ad app update --id="$RHTAS_APP_OBJECT_ID" --set isFallbackPublicClient=true + ``` + +#### RHTAS ZTVP setup + +In the `values-hub.yaml` file, we add the following configuration for the **trusted-artifact-signer** and **supply-chain** applications: + +```yaml + trusted-artifact-signer: + overrides: + - name: rhtas.zeroTrust.email.issuer + value: https://login.microsoftonline.com//v2.0 + - name: rhtas.fulcio.oidcIssuers.email.clientID + value: + supply-chain: + overrides: + - name: rhtas.spire.enabled + value: false + - name: rhtas.oidc.enabled + value: true + - name: rhtas.oidc.url + value: https://login.microsoftonline.com//v2.0 + - name: rhtas.oidc.clientId + value: + - name: rhtas.oidc.clientSecretName + value: "" + - name: rhtas.oidc.issuer + value: https://login.microsoftonline.com//v2.0 + - name: rhtas.oidc.identity + value: +``` + +The **device code flow** involves user interaction, so the following tasks in the `qtodo-supply-chain` pipeline require reviewing the output and validating the generated code: + +* `qtodo-sign-artifact` +* `qtodo-sign-image` +* `qtodo-sbom-attestation` + +When the pipeline reaches any of these tasks, we will need to follow these steps to authorize the signature with our _Azure Entra ID_ credentials: + +##### Using the OpenShift Web UI + +1. Select **Pipelines -> Pipelines** from the left hand navigation bar. +2. Locate the **qtodo-supply-chain** pipeline. It's within the **layered-zero-trust-hub** project. +3. Select the last `PipelineRun` or start a new one. +4. On the logs tab, select the signing task: `qtodo-sign-artifact`, `qtodo-sign-image` or `qtodo-sbom-attestation` +5. In the log box, on the last lines, you can get the verification code. +6. Go to [https://login.microsoft.com/device](https://login.microsoft.com/device) and enter the verification code +7. Pick your _Microsoft Azure_ account. +8. Authorize the signature by pressing the **Continue** button. + +##### Using the OpenShift CLI + +1. List the `TaskRuns` in the namespace **layered-zero-trust-hub**. + + ```shell + oc get taskrun -n layered-zero-trust-hub + ``` + +2. Continue monitoring the status of the tasks until one of the signing tasks (`qtodo-sign-artifact`, `qtodo-sign-image` or `qtodo-sbom-attestation`) starts. +3. For each signing task, obtain the verification code and verify it using a Web browser: + + ```shell + export SIGN_ARTIFACT_POD=$(oc get taskrun -n layered-zero-trust-hub -l tekton.dev/pipelineTask=qtodo-sign-artifact -o jsonpath='{ .items[0].status.podName }') + oc logs -n layered-zero-trust-hub ${SIGN_ARTIFACT_POD} | grep 'verification code' + + export SIGN_IMAGE_POD=$(oc get taskrun -n layered-zero-trust-hub -l tekton.dev/pipelineTask=qtodo-sign-image -o jsonpath='{ .items[0].status.podName }') + oc logs -n layered-zero-trust-hub ${SIGN_IMAGE_POD} | grep 'verification code' + + export SIGN_SBOM_POD=$(oc get taskrun -n layered-zero-trust-hub -l tekton.dev/pipelineTask=qtodo-sbom-attestation -o jsonpath='{ .items[0].status.podName }') + oc logs -n layered-zero-trust-hub ${SIGN_SBOM_POD} | grep 'verification code' + ``` + +4. Go to [https://login.microsoft.com/device](https://login.microsoft.com/device) and enter the verification code +5. Pick your _Microsoft Azure_ account. +6. Authorize the signature by pressing the **Continue** button. diff --git a/scripts/features/entra-id.yaml b/scripts/features/entra-id.yaml new file mode 100644 index 00000000..5712590d --- /dev/null +++ b/scripts/features/entra-id.yaml @@ -0,0 +1,38 @@ +# Azure Entra ID integration +# Depends on: supply-chain +clusterGroup: + merge_into_applications: + qtodo: + overrides: + - name: app.oidc.authServerUrl + value: "https://login.microsoftonline.com//v2.0" + - name: app.oidc.clientId + value: "" + - name: app.oidc.clientSecret.enabled + value: "true" + - name: app.oidc.clientSecret.vaultPath + value: "secret/data/apps/qtodo/qtodo-oidc-entraid" + trusted-artifact-signer: + overrides: + - name: rhtas.zeroTrust.email.issuer + value: "https://login.microsoftonline.com//v2.0" + - name: rhtas.fulcio.oidcIssuers.email.clientID + value: "" + supply-chain: + overrides: + - name: rhtas.spire.enabled + value: "false" + - name: rhtas.oidc.enabled + value: "true" + - name: rhtas.oidc.url + value: "https://login.microsoftonline.com//v2.0" + - name: rhtas.oidc.clientId + value: "" + - name: rhtas.oidc.clientSecretName + value: "" + - name: rhtas.oidc.issuer + value: "https://login.microsoftonline.com//v2.0" + - name: rhtas.oidc.identity + value: "" + - name: rhtpa.enabled + value: "false" diff --git a/scripts/features/features.yaml b/scripts/features/features.yaml index 192b03ff..ee35f2cf 100644 --- a/scripts/features/features.yaml +++ b/scripts/features/features.yaml @@ -29,6 +29,10 @@ features: org: ztvp image_name: qtodo + entra-id: + description: "Azure Entra ID integration" + depends_on: [supply-chain] + # Registry options (only used with supply-chain feature) # Each maps to a file under registry/ subdirectory. registry_options: diff --git a/values-secret.yaml.template b/values-secret.yaml.template index 945279fd..f6afe138 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -85,6 +85,17 @@ secrets: # onMissingValue: generate # vaultPolicy: alphaNumericPolicy + # qtodo-oidc-entraid — Microsoft Entra ID (Azure AD) OIDC for QTodo + # This secret supplies the client secret for the Entra app registration + # that backs app.oidc.clientId. The value is read from a local file at 'path' + # Create the client secret in Azure Portal and store it in that file + #- name: qtodo-oidc-entraid + # vaultPrefixes: + # - apps/qtodo + # fields: + # - name: client-secret + # path: ~/.azure/ztvp-entraid-secret + - name: qtodo-truststore vaultPrefixes: - apps/qtodo