diff --git a/charts/qtodo/templates/_helpers.tpl b/charts/qtodo/templates/_helpers.tpl index 866e5d3e..bd2b94d2 100644 --- a/charts/qtodo/templates/_helpers.tpl +++ b/charts/qtodo/templates/_helpers.tpl @@ -16,3 +16,25 @@ quay.io/ztvp/qtodo) so no VP --set override is needed. {{- printf "%s:%s" $name (tpl .value.version .context) -}} {{- end -}} {{- end -}} + +{{/* +Generate the URL of the OIDC service +*/}} +{{- define "qtodo.oidc.url" }} +{{- if not .Values.app.oidc.authServerUrl }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.oidc.realm }} +{{- else }} +{{- print .Values.app.oidc.authServerUrl }} +{{- end }} +{{- end }} + +{{/* +Generate the JWT Audience +*/}} +{{- define "qtodo.jwt.audience" }} +{{- if not .Values.app.vault.audience }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.oidc.realm }} +{{- else }} +{{- print .Values.app.vault.audience }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/qtodo/templates/app-config-env.yaml b/charts/qtodo/templates/app-config-env.yaml index de978bc2..01da1e1f 100644 --- a/charts/qtodo/templates/app-config-env.yaml +++ b/charts/qtodo/templates/app-config-env.yaml @@ -6,13 +6,13 @@ metadata: data: {{- if eq .Values.app.spire.enabled true }} QUARKUS_OIDC_ENABLED: "true" - QUARKUS_OIDC_AUTH_SERVER_URL: "{{ default (printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.keycloak.realm) .Values.app.oidc.authServerUrl }}" + QUARKUS_OIDC_AUTH_SERVER_URL: "{{ include "qtodo.oidc.url" . }}" QUARKUS_OIDC_CLIENT_ID: "{{ .Values.app.oidc.clientId }}" QUARKUS_OIDC_APPLICATION_TYPE: "{{ .Values.app.oidc.applicationType }}" QUARKUS_HTTP_AUTH_PERMISSION_AUTHENTICATED_PATHS: "{{ .Values.app.oidc.authenticatedPaths }}" QUARKUS_HTTP_AUTH_PERMISSION_AUTHENTICATED_POLICY: "{{ .Values.app.oidc.authenticatedPolicy }}" QUARKUS_OIDC_AUTHENTICATION_FORCE_REDIRECT_HTTPS_SCHEME: "true" -{{- if .Values.app.oidc.clientAssertion.enabled }} +{{- if and .Values.app.oidc.clientAssertion.enabled (not .Values.app.oidc.clientSecret.enabled) }} QUARKUS_OIDC_CREDENTIALS_JWT_SOURCE: "bearer" QUARKUS_OIDC_CREDENTIALS_JWT_TOKEN_PATH: "{{ .Values.app.oidc.clientAssertion.jwtTokenPath }}" {{- end }} diff --git a/charts/qtodo/templates/app-deployment.yaml b/charts/qtodo/templates/app-deployment.yaml index 8586221e..2c78c92f 100644 --- a/charts/qtodo/templates/app-deployment.yaml +++ b/charts/qtodo/templates/app-deployment.yaml @@ -55,6 +55,7 @@ spec: - name: qtodo-truststore-java mountPath: /usr/local/bin {{- end }} +{{- if and .Values.app.oidc.enabled (contains "keycloak" (include "qtodo.oidc.url" .)) }} - name: wait-for-keycloak image: registry.redhat.io/openshift4/ose-tools-rhel9:latest imagePullPolicy: IfNotPresent @@ -63,7 +64,7 @@ spec: - -c - | echo "Waiting for Keycloak OIDC endpoint to be available..." - KEYCLOAK_URL="{{ default (printf "https://keycloak.%s/realms/ztvp/.well-known/openid-configuration" .Values.global.localClusterDomain) .Values.app.oidc.authServerUrl }}/.well-known/openid-configuration" + KEYCLOAK_URL="{{ include "qtodo.oidc.url" . }}/.well-known/openid-configuration" # Remove duplicate .well-known if authServerUrl already contains realm KEYCLOAK_URL=$(echo "$KEYCLOAK_URL" | sed 's|/.well-known/openid-configuration/.well-known/openid-configuration|/.well-known/openid-configuration|') @@ -87,6 +88,7 @@ spec: - name: ztvp-trusted-ca mountPath: /etc/pki/ca-trust/extracted/pem readOnly: true +{{- end }} - name: init-spiffe-helper image: {{ template "qtodo.image" (dict "value" .Values.app.images.spiffeHelper "context" $) }} imagePullPolicy: {{ .Values.app.images.spiffeHelper.pullPolicy }} @@ -123,7 +125,7 @@ spec: - name: CREDENTIALS_FILE value: /run/secrets/db-credentials/credentials.properties - name: JWT_TOKEN_FILE - value: /svids/jwt.token + value: {{ .Values.app.oidc.clientAssertion.jwtTokenPath }} - name: ZTVP_CA_BUNDLE value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem volumeMounts: @@ -176,7 +178,7 @@ spec: - name: CREDENTIALS_FILE value: /run/secrets/db-credentials/credentials.properties - name: JWT_TOKEN_FILE - value: /svids/jwt.token + value: {{ .Values.app.oidc.clientAssertion.jwtTokenPath }} - name: ZTVP_CA_BUNDLE value: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem volumeMounts: @@ -234,11 +236,11 @@ spec: {{- else }} - name: QUARKUS_CONFIG_LOCATIONS value: file:/run/secrets/db-credentials/credentials.properties -{{- if not .Values.app.oidc.clientAssertion.enabled }} +{{- if .Values.app.oidc.clientSecret.enabled }} - name: QUARKUS_OIDC_CREDENTIALS_SECRET valueFrom: secretKeyRef: - name: oidc-client-secret + name: {{ .Values.app.oidc.clientSecret.name }} key: client-secret {{- end }} {{- if .Values.app.truststore.enabled }} diff --git a/charts/qtodo/templates/oidc-client-secret-external-secret.yaml b/charts/qtodo/templates/oidc-client-secret-external-secret.yaml index 79927aa5..193095af 100644 --- a/charts/qtodo/templates/oidc-client-secret-external-secret.yaml +++ b/charts/qtodo/templates/oidc-client-secret-external-secret.yaml @@ -1,8 +1,8 @@ -{{- if .Values.app.oidcSecret.enabled }} +{{- if .Values.app.oidc.clientSecret.enabled }} apiVersion: "external-secrets.io/v1beta1" kind: ExternalSecret metadata: - name: {{ .Values.app.oidcSecret.name }} + name: {{ .Values.app.oidc.clientSecret.name }} namespace: {{ .Release.Namespace }} spec: refreshInterval: 15s @@ -10,14 +10,14 @@ spec: name: {{ .Values.global.secretStore.name }} kind: {{ .Values.global.secretStore.kind }} target: - name: {{ .Values.app.oidcSecret.name }} + name: {{ .Values.app.oidc.clientSecret.name }} template: type: Opaque data: - client-secret: "{{ `{{ .client_secret }}` }}" + client-secret: "{{ `{{ .client_secret | trim }}` }}" data: - secretKey: client_secret remoteRef: - key: {{ .Values.app.oidcSecret.vaultPath }} + key: {{ .Values.app.oidc.clientSecret.vaultPath }} property: client-secret {{- end }} diff --git a/charts/qtodo/templates/spiffe-helper-config.yaml b/charts/qtodo/templates/spiffe-helper-config.yaml index 5a3b42fc..b0854474 100644 --- a/charts/qtodo/templates/spiffe-helper-config.yaml +++ b/charts/qtodo/templates/spiffe-helper-config.yaml @@ -1,5 +1,4 @@ {{- if eq .Values.app.spire.enabled true }} -{{- $keycloakRealmUrl := default (printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.keycloak.realm) .Values.app.keycloak.realmUrl }} kind: ConfigMap apiVersion: v1 metadata: @@ -16,6 +15,6 @@ data: svid_file_name = "svid.pem" svid_key_file_name = "svid_key.pem" svid_bundle_file_name = "svid_bundle.pem" - jwt_svids = [{jwt_audience="{{ $keycloakRealmUrl }}", jwt_svid_file_name="jwt.token"}] + jwt_svids = [{jwt_audience="{{ include "qtodo.jwt.audience" . }}", jwt_svid_file_name="jwt.token"}] jwt_bundle_file_name = "jwt_bundle.json" {{- end }} \ No newline at end of file diff --git a/charts/qtodo/values.yaml b/charts/qtodo/values.yaml index cd70b59b..d8ba6ac5 100644 --- a/charts/qtodo/values.yaml +++ b/charts/qtodo/values.yaml @@ -48,7 +48,8 @@ app: oidc: enabled: false # Will be enabled when SPIFFE is enabled authServerUrl: "" - clientId: qtodo-app + realm: "ztvp" + clientId: "qtodo-app" applicationType: "web-app" authenticatedPaths: "/*" authenticatedPolicy: "authenticated" @@ -56,12 +57,10 @@ app: clientAssertion: enabled: true jwtTokenPath: "/svids/jwt.token" - - # Keycloak realm configuration - keycloak: - realm: "ztvp" - # Keycloak realm URL (auto-generated from global.localClusterDomain and realm if empty) - realmUrl: "" + clientSecret: + enabled: false + name: "oidc-client-secret" + vaultPath: "secret/data/apps/qtodo/qtodo-oidc-client" spire: enabled: true # Enable SPIFFE + OIDC integration by default @@ -72,17 +71,11 @@ app: vault: url: "" role: "qtodo" + # JWT Audience (auto-generated if not set) + # audience: "api://client-id" # QTodo secrets path (app-level isolation) secretPath: "secret/data/apps/qtodo/qtodo-db" - # OIDC External Secret configuration - # Disabled when using federated client assertion (no client secret needed) - oidcSecret: - enabled: false - name: "oidc-client-secret" - # QTodo OIDC secret path (app-level isolation) - vaultPath: "secret/data/apps/qtodo/qtodo-oidc-client" - # Seed image Job: mirrors the upstream qtodo image into the configured # registry so the deployment can pull before the supply-chain pipeline runs. seedImage: diff --git a/docs/entraid.md b/docs/entraid.md new file mode 100644 index 00000000..03bcc8fb --- /dev/null +++ b/docs/entraid.md @@ -0,0 +1,81 @@ +# 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) +* Red Hat Trusted Profile Analyzer (RHTPA) + +## 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 +``` + +### RHTAS + +### RHTPA + +#### API + +#### UI