From 3fde891551eccd772398870c454b4ab06f290a0d Mon Sep 17 00:00:00 2001 From: Manuel Lorenzo Date: Thu, 5 Feb 2026 17:33:45 +0100 Subject: [PATCH 1/2] Prepare RHTPA chart to use Entra ID Signed-off-by: Manuel Lorenzo --- .../trustedprofileanalyzer-template.yaml | 5 +- charts/rhtpa-operator/templates/_helpers.tpl | 91 +++++++++++++++-- .../rhtpa-operator/templates/auth-config.yaml | 98 +++++++++++++++++++ .../templates/oidc-cli-secret.yaml | 15 +-- .../templates/trusted-profile-analyzer.yaml | 39 ++------ charts/rhtpa-operator/values.yaml | 28 +++--- charts/supply-chain/files/rhtpa.sh | 21 +++- .../templates/pipeline-qtodo.yaml | 10 ++ .../templates/secrets/qtodo-rhtpa-pass.yaml | 2 +- .../templates/tasks/upload-sbom.yaml | 9 ++ charts/supply-chain/values.yaml | 3 +- values-secret.yaml.template | 11 +++ 12 files changed, 268 insertions(+), 64 deletions(-) create mode 100644 charts/rhtpa-operator/templates/auth-config.yaml diff --git a/charts/rhtpa-operator/files/trustedprofileanalyzer-template.yaml b/charts/rhtpa-operator/files/trustedprofileanalyzer-template.yaml index d4b56f2a..fc9da237 100644 --- a/charts/rhtpa-operator/files/trustedprofileanalyzer-template.yaml +++ b/charts/rhtpa-operator/files/trustedprofileanalyzer-template.yaml @@ -15,7 +15,8 @@ spec: appDomain: "APP_DOMAIN" TLS_CONFIG - OIDC_CONFIG +OIDC_CONFIG +AUTHENTICATOR_CONFIG createDatabase: name: DATABASE_NAME username: rhtpa @@ -25,7 +26,7 @@ name: rhtpa-db-secret key: password migrateDatabase: {} - MODULES_CONFIG +MODULES_CONFIG SIGSTORE_CONFIG database: host: rhtpa-db.RHTPA_NAMESPACE.svc.cluster.local diff --git a/charts/rhtpa-operator/templates/_helpers.tpl b/charts/rhtpa-operator/templates/_helpers.tpl index 0817dc53..7433a24b 100644 --- a/charts/rhtpa-operator/templates/_helpers.tpl +++ b/charts/rhtpa-operator/templates/_helpers.tpl @@ -49,12 +49,91 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* -Generate the Keycloak OIDC Issuer URL -This evaluates any template variables (like {{ $.Values.global.clusterDomain }}) -and appends the realm name. +Generate the URL of the OIDC service */}} -{{- define "rhtpa-operator.keycloakOIDCIssuer" -}} -{{- $keycloakUrl := tpl .Values.rhtpa.zeroTrust.keycloak.url . -}} -{{- printf "%s/realms/%s" $keycloakUrl .Values.rhtpa.zeroTrust.keycloak.realm -}} +{{- define "rhtpa-operator.oidc.url" -}} +{{- if not .Values.rhtpa.zeroTrust.oidc.authServerUrl }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.rhtpa.zeroTrust.oidc.realm -}} +{{- else }} +{{- printf "%s" .Values.rhtpa.zeroTrust.oidc.authServerUrl -}} +{{- end }} +{{- end }} + +{{/* +Generate the OIDC configuration +*/}} +{{- define "rhtpa-operator.oidc.config" -}} +oidc: + issuerUrl: {{ include "rhtpa-operator.oidc.url" . }} +{{- if ne .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId "" }} + uiScope: >- + openid profile email offline_access + api://{{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }}/create:document + api://{{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }}/read:document + api://{{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }}/update:document + api://{{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }}/delete:document + loadUser: false +{{- end }} + clients: + frontend: + clientId: {{ .Values.rhtpa.zeroTrust.oidc.clients.frontend.clientId }} + cli: + clientId: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.clientId }} + clientSecret: + valueFrom: + secretKeyRef: + name: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.secretName }} + key: client-secret +{{- end }} + +{{/* +Generate the authenticator configuration +*/}} +{{- define "rhtpa-operator.authenticator.config" -}} +{{- if ne .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId "" }} +authenticator: + configMapRef: + name: server-entra-auth + key: auth.yaml +{{- end }} {{- end }} +{{/* +Generate the modules configuration +*/}} +{{- define "rhtpa-operator.modules.config" -}} +modules: + migrateDatabase: + enabled: true + createDatabase: + enabled: {{ .Values.rhtpa.modules.createDatabase.enabled | default true }} + createImporters: + enabled: {{ .Values.rhtpa.modules.createImporters.enabled | default true }} + importers: +{{- if .Values.rhtpa.modules.createImporters.importers }} +{{- toYaml .Values.rhtpa.modules.createImporters.importers | nindent 6 }} +{{- end }} + importer: + replicas: {{ .Values.rhtpa.modules.importer.replicas | default 1 | int }} + resources: + requests: + cpu: {{ .Values.rhtpa.modules.importer.resources.requests.cpu | default 0.5 | toString | quote }} + memory: {{ .Values.rhtpa.modules.importer.resources.requests.memory | default "4Gi" | toString }} + enabled: true + server: + replicas: {{ .Values.rhtpa.modules.server.replicas | default 1 | int }} + resources: + requests: + cpu: {{ .Values.rhtpa.modules.server.resources.requests.cpu | default 0.5 | toString | quote }} + memory: {{ .Values.rhtpa.modules.server.resources.requests.memory | default "4Gi" | toString }} + enabled: true +{{- if or (ne .Values.rhtpa.modules.server.rust.logFilter "info") .Values.rhtpa.modules.server.rust.backtrace }} + rust: +{{- if ne .Values.rhtpa.modules.server.rust.logFilter "info" }} + logFilter: {{ .Values.rhtpa.modules.server.rust.logFilter }} +{{- end }} +{{- if .Values.rhtpa.modules.server.rust.backtrace }} + backtrace: {{ .Values.rhtpa.modules.server.rust.backtrace }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/rhtpa-operator/templates/auth-config.yaml b/charts/rhtpa-operator/templates/auth-config.yaml new file mode 100644 index 00000000..a04960b1 --- /dev/null +++ b/charts/rhtpa-operator/templates/auth-config.yaml @@ -0,0 +1,98 @@ +{{- if and (eq .Values.rhtpa.zeroTrust.oidc.enabled true) (ne .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId "") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: server-entra-auth + namespace: {{ .Release.Namespace }} +data: + auth.yaml: | + authentication: + clients: + # Microsoft Entra ID Frontend Client (for user sign-in) + - clientId: {{ .Values.rhtpa.zeroTrust.oidc.clients.frontend.clientId }} + issuerUrl: {{ .Values.rhtpa.zeroTrust.oidc.authServerUrl }} + requiredAudience: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }} + scopeMappings: + "read:document": + - "ai" + - "read.sbom" + - "read.advisory" + - "read.importer" + - "read.metadata" + - "read.sbomGroup" + - "read.weakness" + - "read.systemInformation" + "create:document": + - "create.sbom" + - "create.advisory" + - "create.importer" + - "create.metadata" + - "create.sbomGroup" + - "create.weakness" + - "update.sbom" + - "update.advisory" + - "update.importer" + - "update.metadata" + - "update.sbomGroup" + - "update.weakness" + - "upload.dataset" + "update:document": + - "update.sbom" + - "update.advisory" + - "update.importer" + - "update.metadata" + - "update.sbomGroup" + - "update.weakness" + "delete:document": + - "delete.sbom" + - "delete.advisory" + - "delete.importer" + - "delete.metadata" + - "delete.sbomGroup" + - "delete.vulnerability" + - "delete.weakness" + # Microsoft Entra ID CLI/API Client (for client credentials) + - clientId: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.clientId }} + issuerUrl: {{ .Values.rhtpa.zeroTrust.oidc.authServerUrl }} + requiredAudience: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.apiId }} + scopeSelector: "$['scope','scp','roles']" + scopeMappings: + "App.Read.Document": + - "ai" + - "read.sbom" + - "read.advisory" + - "read.importer" + - "read.metadata" + - "read.sbomGroup" + - "read.weakness" + - "read.systemInformation" + "App.Create.Document": + - "create.sbom" + - "create.advisory" + - "create.importer" + - "create.metadata" + - "create.sbomGroup" + - "create.weakness" + - "update.sbom" + - "update.advisory" + - "update.importer" + - "update.metadata" + - "update.sbomGroup" + - "update.weakness" + - "upload.dataset" + "App.Update.Document": + - "update.sbom" + - "update.advisory" + - "update.importer" + - "update.metadata" + - "update.sbomGroup" + - "update.weakness" + "App.Delete.Document": + - "delete.sbom" + - "delete.advisory" + - "delete.importer" + - "delete.metadata" + - "delete.sbomGroup" + - "delete.vulnerability" + - "delete.weakness" +{{- end }} \ No newline at end of file diff --git a/charts/rhtpa-operator/templates/oidc-cli-secret.yaml b/charts/rhtpa-operator/templates/oidc-cli-secret.yaml index 01fb306a..f9de9927 100644 --- a/charts/rhtpa-operator/templates/oidc-cli-secret.yaml +++ b/charts/rhtpa-operator/templates/oidc-cli-secret.yaml @@ -1,9 +1,9 @@ -{{- if .Values.rhtpa.zeroTrust.keycloak.enabled }} +{{- if .Values.rhtpa.zeroTrust.oidc.enabled }} --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: - name: {{ .Values.rhtpa.zeroTrust.keycloak.clients.cli.secretName | default "rhtpa-oidc-cli-secret" }} + name: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.secretName | default "rhtpa-oidc-cli-secret" }} namespace: {{ .Values.rhtpa.namespace }} labels: {{- include "rhtpa-operator.labels" . | nindent 4 }} @@ -15,12 +15,15 @@ spec: name: {{ .Values.global.secretStore.name }} kind: {{ .Values.global.secretStore.kind }} target: - name: {{ .Values.rhtpa.zeroTrust.keycloak.clients.cli.secretName | default "rhtpa-oidc-cli-secret" }} - creationPolicy: Owner + name: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.secretName | default "rhtpa-oidc-cli-secret" }} + template: + type: Opaque + data: + client-secret: "{{ `{{ .client_secret | trim }}` }}" data: - - secretKey: client-secret + - secretKey: client_secret remoteRef: - key: {{ .Values.rhtpa.zeroTrust.keycloak.clients.cli.secretVaultPath }} + key: {{ .Values.rhtpa.zeroTrust.oidc.clients.cli.secretVaultPath }} property: client-secret {{- end }} diff --git a/charts/rhtpa-operator/templates/trusted-profile-analyzer.yaml b/charts/rhtpa-operator/templates/trusted-profile-analyzer.yaml index bfce55eb..32402d65 100644 --- a/charts/rhtpa-operator/templates/trusted-profile-analyzer.yaml +++ b/charts/rhtpa-operator/templates/trusted-profile-analyzer.yaml @@ -45,45 +45,18 @@ spec: {{- $template = replace "TLS_CONFIG" "" $template }} {{- end }} {{- /* OIDC Config */ -}} -{{- if and .Values.rhtpa.zeroTrust .Values.rhtpa.zeroTrust.keycloak .Values.rhtpa.zeroTrust.keycloak.enabled }} -{{- /* Construct Keycloak URL from global.localClusterDomain if not provided */ -}} -{{- $keycloakUrl := .Values.rhtpa.zeroTrust.keycloak.url }} -{{- if not $keycloakUrl }} -{{- $keycloakUrl = printf "https://keycloak.%s" .Values.global.localClusterDomain }} -{{- end }} -{{- $oidcConfig := printf `oidc: - clients: - frontend: - clientId: %s - issuerUrl: %s/realms/%s - cli: - clientId: %s - issuerUrl: %s/realms/%s - clientSecret: - valueFrom: - secretKeyRef: - name: %s - key: client-secret` .Values.rhtpa.zeroTrust.keycloak.clients.frontend.clientId $keycloakUrl .Values.rhtpa.zeroTrust.keycloak.realm .Values.rhtpa.zeroTrust.keycloak.clients.cli.clientId $keycloakUrl .Values.rhtpa.zeroTrust.keycloak.realm .Values.rhtpa.zeroTrust.keycloak.clients.cli.secretName }} +{{- if and .Values.rhtpa.zeroTrust .Values.rhtpa.zeroTrust.oidc .Values.rhtpa.zeroTrust.oidc.enabled }} +{{- $oidcConfig := include "rhtpa-operator.oidc.config" . | indent 6 }} {{- $template = replace "OIDC_CONFIG" $oidcConfig $template }} +{{- $authenticatorConfig := include "rhtpa-operator.authenticator.config" . | indent 6 }} +{{- $template = replace "AUTHENTICATOR_CONFIG" $authenticatorConfig $template }} {{- else }} {{- $template = replace "OIDC_CONFIG" "" $template }} +{{- $template = replace "AUTHENTICATOR_CONFIG" "" $template }} {{- end }} {{- /* Modules Config */ -}} {{- if and .Values.rhtpa.modules .Values.rhtpa.modules.createImporters .Values.rhtpa.modules.createImporters.enabled }} -{{- $createDB := .Values.rhtpa.modules.createDatabase.enabled | default true }} -{{- $createImporters := .Values.rhtpa.modules.createImporters.enabled | default true }} -{{- $importerReplicas := .Values.rhtpa.modules.importer.replicas | default 1 | int }} -{{- $importerCPU := .Values.rhtpa.modules.importer.resources.requests.cpu | default 0.5 | toString }} -{{- $importerMemory := .Values.rhtpa.modules.importer.resources.requests.memory | default "4Gi" | toString }} -{{- $serverReplicas := .Values.rhtpa.modules.server.replicas | default 1 | int }} -{{- $serverCPU := .Values.rhtpa.modules.server.resources.requests.cpu | default 0.5 | toString }} -{{- $serverMemory := .Values.rhtpa.modules.server.resources.requests.memory | default "4Gi" | toString }} -{{- $modulesConfig := printf "modules:\n migrateDatabase:\n enabled: true\n createDatabase:\n enabled: %t\n createImporters:\n enabled: %t\n importers:" $createDB $createImporters }} -{{- if .Values.rhtpa.modules.createImporters.importers }} -{{- $importersYaml := toYaml .Values.rhtpa.modules.createImporters.importers | nindent 12 }} -{{- $modulesConfig = printf "%s\n%s" $modulesConfig $importersYaml }} -{{- end }} -{{- $modulesConfig = printf "%s\n importer:\n replicas: %d\n resources:\n requests:\n cpu: \"%s\"\n memory: %s\n enabled: true\n server:\n replicas: %d\n resources:\n requests:\n cpu: \"%s\"\n memory: %s\n enabled: true" $modulesConfig $importerReplicas $importerCPU $importerMemory $serverReplicas $serverCPU $serverMemory }} +{{- $modulesConfig := include "rhtpa-operator.modules.config" . | indent 6 }} {{- $template = replace "MODULES_CONFIG" $modulesConfig $template }} {{- else }} {{- $template = replace "MODULES_CONFIG" "" $template }} diff --git a/charts/rhtpa-operator/values.yaml b/charts/rhtpa-operator/values.yaml index 5613f7e6..d2a82f10 100644 --- a/charts/rhtpa-operator/values.yaml +++ b/charts/rhtpa-operator/values.yaml @@ -5,19 +5,19 @@ rhtpa: # Enable/disable RHTPA deployment enabled: true - + # TrustedProfileAnalyzer CR name name: "trusted-profile-analyzer" - + # Namespace for RHTPA deployment namespace: trusted-profile-analyzer - + # Route hostname prefix # This prefix is prepended to the cluster domain to form the route hostname # NOTE: Due to RHTPA operator bug, "server" is prepended to the result # Example: "trustify" with domain "apps.domain.com" generates: servertrustify.apps.domain.com routePrefix: "trustify" - + # Zero Trust Integration zeroTrust: vault: @@ -28,10 +28,10 @@ rhtpa: policy: "rhtpa-secrets" # RHTPA DB password path (infra) secretPath: "secret/data/hub/infra/rhtpa/rhtpa-db" - keycloak: + oidc: enabled: true - url: "" # Constructed dynamically from global.localClusterDomain in templates - realm: "ztvp" + authServerUrl: "" # Constructed dynamically from global.localClusterDomain in templates + realm: "ztvp" # Used to construct the authentication URL if authServerUrl is not provided namespace: "keycloak-system" # Namespace where Keycloak is deployed instanceName: "keycloak" # Name of the Keycloak CR # User credentials - stored in infra users path @@ -43,9 +43,10 @@ rhtpa: cli: clientId: "rhtpa-cli" # Confidential client for RHTPA CLI/API secretName: "rhtpa-oidc-cli-secret" # Kubernetes secret containing client secret + apiId: "" # API client ID, used in Azure Entra ID integration # RHTPA OIDC CLI secret path (infra) secretVaultPath: "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" - + # TLS Configuration tls: ingressCA: @@ -126,7 +127,10 @@ rhtpa: limits: cpu: 1.0 memory: 8Gi - + rust: + logFilter: "info" + backtrace: false + # PostgreSQL Database Configuration database: create: true @@ -137,7 +141,7 @@ rhtpa: passwordVaultKey: "secret/data/hub/infra/rhtpa/rhtpa-db" storageSize: "10Gi" image: "registry.redhat.io/rhel8/postgresql-16" - + # Object Storage Configuration (NooBaa MCG) objectStorage: enabled: true @@ -147,11 +151,11 @@ rhtpa: name: "rhtpa-s3-storage" bucketName: "trustify" storageClass: "openshift-storage.noobaa.io" - + # Monitoring configuration monitoring: enabled: true - + # External access configuration externalAccess: enabled: true diff --git a/charts/supply-chain/files/rhtpa.sh b/charts/supply-chain/files/rhtpa.sh index ba27baf1..bec97c23 100755 --- a/charts/supply-chain/files/rhtpa.sh +++ b/charts/supply-chain/files/rhtpa.sh @@ -16,8 +16,15 @@ log_msg() { } get_oidc_token() { - # Get the OIDC Issuer URL from Keycloak route - export OIDC_TOKEN_URL="${OIDC_URL}/protocol/openid-connect/token" + if [ -v OIDC_API_ID ]; then + # Set the OIDC token URL for Azure Entra ID + export OIDC_TOKEN_URL="${OIDC_URL}/oauth2/v2.0/token" + export OIDC_SCOPE="api://${OIDC_API_ID}/.default" + scope_param+=("-d" "scope=${OIDC_SCOPE}") + else + # Set the OIDC token URL for Keycloak (RHBK) + export OIDC_TOKEN_URL="${OIDC_URL}/protocol/openid-connect/token" + fi # Request a new access token curl -sSf -X POST "${OIDC_TOKEN_URL}" \ @@ -25,6 +32,7 @@ get_oidc_token() { -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'grant_type=client_credentials' \ -d "client_id=${OIDC_CLIENT_ID}" \ + "${scope_param[@]}" \ -d "client_secret=${OIDC_CLIENT_SECRET}" | jq -r .access_token } @@ -33,9 +41,16 @@ upload_sbom() { log_msg "Getting OIDC token" log_msg "OIDC_URL: ${OIDC_URL}" - log_msg "OIDC_TOKEN_URL: ${OIDC_URL}/protocol/openid-connect/token" log_msg "OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}" + if [ -v OIDC_API_ID ]; then + log_msg "OIDC_API_ID: ${OIDC_API_ID}" + log_msg "OIDC_TOKEN_URL: ${OIDC_URL}/oauth2/v2.0/token" + log_msg "OIDC_SCOPE: api://${OIDC_API_ID}/.default" + else + log_msg "OIDC_TOKEN_URL: ${OIDC_URL}/protocol/openid-connect/token" + fi + TOKEN=$(get_oidc_token) log_msg "Uploading SBOM to RHTPA" diff --git a/charts/supply-chain/templates/pipeline-qtodo.yaml b/charts/supply-chain/templates/pipeline-qtodo.yaml index 1db94743..a2118584 100644 --- a/charts/supply-chain/templates/pipeline-qtodo.yaml +++ b/charts/supply-chain/templates/pipeline-qtodo.yaml @@ -89,6 +89,12 @@ spec: type: string description: The RHTPA OIDC client Secret name to use for authentication default: {{ .Values.rhtpa.oidc.clientSecretName }} +{{- if ne .Values.rhtpa.oidc.apiId "" }} + - name: rhtpa-oidc-api-id + type: string + description: The Azure Entra ID API ID to use for authentication + default: {{ .Values.rhtpa.oidc.apiId }} +{{- end }} {{- end }} workspaces: @@ -249,6 +255,10 @@ spec: value: $(params.rhtpa-oidc-client-id) - name: rhtpa-oidc-client-secret value: $(params.rhtpa-oidc-client-secret) +{{- if ne .Values.rhtpa.oidc.apiId "" }} + - name: rhtpa-oidc-api-id + value: $(params.rhtpa-oidc-api-id) +{{- end }} {{- end }} - name: qtodo-verify-image diff --git a/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml b/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml index 424052a5..5329712f 100644 --- a/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml +++ b/charts/supply-chain/templates/secrets/qtodo-rhtpa-pass.yaml @@ -15,7 +15,7 @@ spec: template: type: Opaque data: - password: "{{ `{{ .password }}` }}" + password: "{{ `{{ .password | trim }}` }}" data: - secretKey: password remoteRef: diff --git a/charts/supply-chain/templates/tasks/upload-sbom.yaml b/charts/supply-chain/templates/tasks/upload-sbom.yaml index d660c48d..ebd82532 100644 --- a/charts/supply-chain/templates/tasks/upload-sbom.yaml +++ b/charts/supply-chain/templates/tasks/upload-sbom.yaml @@ -29,6 +29,11 @@ spec: - description: Secret name containing the OIDC client secret to use for authentication name: rhtpa-oidc-client-secret type: string +{{- if ne .Values.rhtpa.oidc.apiId "" }} + - description: Azure Entra ID API ID to use for authentication + name: rhtpa-oidc-api-id + type: string +{{- end }} workspaces: - name: source @@ -58,6 +63,10 @@ spec: secretKeyRef: name: $(params.rhtpa-oidc-client-secret) key: password +{{- if ne .Values.rhtpa.oidc.apiId "" }} + - name: OIDC_API_ID + value: $(params.rhtpa-oidc-api-id) +{{- end }} - name: SBOM_FILE value: $(workspaces.source.path)/$(params.sbom-file) volumeMounts: diff --git a/charts/supply-chain/values.yaml b/charts/supply-chain/values.yaml index 129e29a0..817e2793 100644 --- a/charts/supply-chain/values.yaml +++ b/charts/supply-chain/values.yaml @@ -32,7 +32,8 @@ rhtpa: oidc: enabled: true url: "" - realm: "ztvp" + realm: "ztvp" # Keycloak realm to use for authentication + apiId: "" # Azure Entra ID API ID to use for authentication clientId: "rhtpa-cli" clientSecretName: "qtodo-rhtpa-cli-password" # RHTPA OIDC CLI secret path (infra) diff --git a/values-secret.yaml.template b/values-secret.yaml.template index f6afe138..a5b715ce 100644 --- a/values-secret.yaml.template +++ b/values-secret.yaml.template @@ -163,6 +163,17 @@ secrets: onMissingValue: generate vaultPolicy: alphaNumericPolicy + # Microsoft Entra ID (Azure AD) OIDC for RHTPA + # This secret supplies the client secret for the Entra app registration + # that backs zeroTrust.oidc.clients.cli The value is read from a local file at 'path' + # Create the client secret in Azure Portal and store it in that file + #- name: rhtpa-oidc-cli + # vaultPrefixes: + # - hub/infra/rhtpa + # fields: + # - name: client-secret + # path: ~/.azure/ztvp-entraid-secret + # =========================================================================== # USER CREDENTIALS (hub/infra/users/) # User passwords managed by Keycloak for application access From 36feef8d2d7c358173e1c3575411eeba01cfb386 Mon Sep 17 00:00:00 2001 From: Manuel Lorenzo Date: Tue, 5 May 2026 16:35:19 +0200 Subject: [PATCH 2/2] Add Entra ID documentation for RHTPA component Signed-off-by: Manuel Lorenzo --- docs/oidc/README.md | 2 +- docs/oidc/entraid.md | 247 ++++++++++++++++++++++++++++++++- scripts/features/entra-id.yaml | 19 ++- 3 files changed, 262 insertions(+), 6 deletions(-) diff --git a/docs/oidc/README.md b/docs/oidc/README.md index 0d9e41d4..8519cf4c 100644 --- a/docs/oidc/README.md +++ b/docs/oidc/README.md @@ -3,4 +3,4 @@ | 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: | +| [Azure Entra ID](entraid.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/docs/oidc/entraid.md b/docs/oidc/entraid.md index f9dfaed6..fe42e69c 100644 --- a/docs/oidc/entraid.md +++ b/docs/oidc/entraid.md @@ -4,9 +4,7 @@ This document describes the steps required to integrate the **Zero Trust Validat * 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. +* Red Hat Trusted Profile Analyzer (RHTPA) ## Configuration @@ -235,3 +233,246 @@ When the pipeline reaches any of these tasks, we will need to follow these steps 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. + +### RHTPA + +#### RHTPA Azure setup + +For RHTPA, we will create two App Registrations in Microsoft Entra ID: + +1. **API:** Exposes the Trustify API and defines scopes/permissions +2. **Frontend:** Browser-based authentication for the React frontend + +##### RHTPA API configuration + +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**: `rhtpa-api` + * **Supported account types**: Choose based on your needs + * **Single tenant**: Only users in your organization + * **Multi-tenant**: Users from any organization + * **Redirect URI**: Leave it blank (not needed for API) +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 expose our API: + +1. In the left menu, click **Expose an API** +2. Click **Add** next to "Application ID URI" +3. Accept the default value: `api://{API_CLIENT_ID}` +4. Click **Save** + +We will continue to define the permissions that clients can request: + +1. Still in **Expose an API**, click **Add a scope** +2. Create scopes for different operations: + 1. Create Documents + * Scope name: `create:document` + * Who can consent: Admins and users (or Admins only) + * Admin consent display name: `Create documents in Trustify` + * Admin consent description: `Allows the application to create documents` + * User consent display name: `Create documents in Trustify` + * User consent description: `Allows the application to create documents` + * State: Enabled + * Click _Add scope_ + 2. Read Documents + * Scope name: `read:document` + * Who can consent: Admins and users (or Admins only) + * Admin consent display name: `Read documents in Trustify` + * Admin consent description: `Allows the application to read documents` + * User consent display name: `Read documents in Trustify` + * User consent description: `Allows the application to read documents` + * State: Enabled + * Click _Add scope_ + 3. Update Documents + * Scope name: `update:document` + * Who can consent: Admins and users (or Admins only) + * Admin consent display name: `Update documents in Trustify` + * Admin consent description: `Allows the application to update documents` + * User consent display name: `Update documents in Trustify` + * User consent description: `Allows the application to update documents` + * State: Enabled + * Click _Add scope_ + 4. Delete Documents + * Scope name: `delete:document` + * Who can consent: Admins and users (or Admins only) + * Admin consent display name: `Delete documents in Trustify` + * Admin consent description: `Allows the application to delete documents` + * User consent display name: `Delete documents in Trustify` + * User consent description: `Allows the application to delete documents` + * State: Enabled + * Click _Add scope_ + +After creating all scopes, you'll have: + +* `api://{API_CLIENT_ID}/create:document` +* `api://{API_CLIENT_ID}/read:document` +* `api://{API_CLIENT_ID}/update:document` +* `api://{API_CLIENT_ID}/delete:document` + +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: `rhtpa-api 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. + +Let's configure the token version: + +1. Click **Manifest** in the left menu +2. Find `"requestedAccessTokenVersion"`, within the `api` attribute, in the JSON +3. Change it from `null` to `2`: + + ```json + "requestedAccessTokenVersion": 2 + ``` + +4. Click **Save** + +And last but not least, we add application roles for admin consent: + +1. Click **App roles** in the left menu +2. Click **Create app role** +3. Create roles for each permission: + 1. **App.Read.Document** + * Display name: `App.Read.Document` + * Allowed member types: `Applications` + * Value: `App.Read.Document` + * Description: `Allows the application to read documents` + * Enable the role in the checkbox + * Click **Apply** + 2. **App.Create.Document** + * Display name: `App.Create.Document` + * Allowed member types: `Applications` + * Value: `App.Create.Document` + * Description: `Allows the application to create documents` + * Enable the role in the checkbox + * Click **Apply** + 3. **App.Update.Document** + * Display name: `App.Update.Document` + * Allowed member types: `Applications` + * Value: `App.Update.Document` + * Description: `Allows the application to update documents` + * Enable the role in the checkbox + * Click **Apply** + 4. **App.Delete.Document** + * Display name: `App.Delete.Document` + * Allowed member types: `Applications` + * Value: `App.Delete.Document` + * Description: `Allows the application to delete documents` + * Enable the role in the checkbox + * Click **Apply** +4. In the left menu, click **API permissions** +5. Click **Add a permission** +6. Go to **My APIs** and select the API application registration you created earlier +7. Select **Application permissions** +8. Check the boxes for: + * `App.Read.Document` + * `App.Create.Document` + * `App.Update.Document` + * `App.Delete.Document` +9. Click **Add permissions** +10. Click **Grant admin consent** for the application roles we just added. This is **mandatory** + +##### RHTPA Frontend configuration + +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**: `rhtpa-frontend` + * **Supported account types**: Choose based on your needs + * **Single tenant**: Only users in your organization + * **Multi-tenant**: Users from any organization + * **Redirect URI**: + * Platform: `Single-page application (SPA)` + * URI: Add the URL with your custom domain here (for example `https://trustify.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. + +The next step is configure the authentication. + +1. Click **Authentication** in the left menu + * **Redirect URI configuration Tab** + ** * **Single-page application** should be selected. + * **Settings Tab + * **Implicit grant and hybrid flows:** + * **DO NOT** check "Access tokens" or "ID tokens" - not needed for SPA with PKCE + * **Allow public client flows**: Disabled +2. Click **Save** + +To grant the frontend permission to call your API: + +1. Click **API permissions** in the left menu +2. You'll see "Microsoft Graph" with "User.Read" - this is fine to keep +3. Click **Add a permission** +4. Click **My APIs** tab +5. Select **rhtpa-api** (the API app you created in Step 1) +6. Click **Delegated permissions** +7. Check all the scopes you created: + * `create:document` + * `read:document` + * `update:document` + * `delete:document` +8. Click **Add permissions** + +And finally, let's configure the token version: + +1. Click **Manifest** in the left menu +2. Find `"requestedAccessTokenVersion"`, within the `api` attribute, in the JSON +3. Change it from `null` to `2`: + + ```json + "requestedAccessTokenVersion": 2 + ``` + +4. Click **Save** + +#### RHTPA ZTVP setup + +In the `values-hub.yaml` file, we add the following configuration for the **trusted-profile-analyzer** application: + +```yaml + trusted-profile-analyzer: + overrides: + - name: rhtpa.zeroTrust.oidc.authServerUrl + value: https://login.microsoftonline.com//v2.0 + - name: rhtpa.zeroTrust.oidc.clients.frontend.clientId + value: + - name: rhtpa.zeroTrust.oidc.clients.cli.clientId + value: + - name: rhtpa.zeroTrust.oidc.clients.cli.apiId + value: + supply-chain: + overrides: + - name: rhtpa.oidc.enabled + value: true + - name: rhtpa.oidc.url + value: https://login.microsoftonline.com/ # Do not include /v2.0 here, the URL for the OAuth token will be generated from this base URL + - name: rhtpa.oidc.clientId + value: + - name: rhtpa.oidc.apiId + value: +``` + +In the `values-secret.yaml` file, make sure that the secret `rhtpa-oidc-cli` uses the file with the secret associated with the _App Registration_ `rhtpa-api` instead of generating it dynamically. diff --git a/scripts/features/entra-id.yaml b/scripts/features/entra-id.yaml index 5712590d..07773509 100644 --- a/scripts/features/entra-id.yaml +++ b/scripts/features/entra-id.yaml @@ -18,6 +18,16 @@ clusterGroup: value: "https://login.microsoftonline.com//v2.0" - name: rhtas.fulcio.oidcIssuers.email.clientID value: "" + trusted-profile-analyzer: + overrides: + - name: rhtpa.zeroTrust.oidc.authServerUrl + value: "https://login.microsoftonline.com//v2.0" + - name: rhtpa.zeroTrust.oidc.clients.frontend.clientId + value: "" + - name: rhtpa.zeroTrust.oidc.clients.cli.clientId + value: "" + - name: rhtpa.zeroTrust.oidc.clients.cli.apiId + value: "" supply-chain: overrides: - name: rhtas.spire.enabled @@ -34,5 +44,10 @@ clusterGroup: value: "https://login.microsoftonline.com//v2.0" - name: rhtas.oidc.identity value: "" - - name: rhtpa.enabled - value: "false" + - name: rhtpa.oidc.url + value: "https://login.microsoftonline.com/" # Do not include /v2.0 + - name: rhtpa.oidc.clientId + value: "" + - name: rhtpa.oidc.apiId + value: "" +