diff --git a/charts/qtodo/templates/_helpers.tpl b/charts/qtodo/templates/_helpers.tpl index 1260c996..420a1325 100644 --- a/charts/qtodo/templates/_helpers.tpl +++ b/charts/qtodo/templates/_helpers.tpl @@ -21,21 +21,36 @@ quay.io/ztvp/qtodo) so no VP --set override is needed. 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 }} +{{- if .Values.app.oidc.authServerUrl }} {{- print .Values.app.oidc.authServerUrl }} +{{- else if eq .Values.app.oidc.provider "entraid" }} +{{- if not .Values.app.oidc.entraid.tenantId }} +{{- fail "app.oidc.entraid.tenantId is required when using Entra ID provider" }} +{{- end }} +{{- printf "https://login.microsoftonline.com/%s/v2.0" .Values.app.oidc.entraid.tenantId }} +{{- else }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.oidc.realm }} {{- end }} {{- end }} {{/* -Generate the JWT Audience +Generate the JWT Audience for SPIFFE authentication */}} {{- define "qtodo.jwt.audience" }} -{{- if not .Values.app.vault.audience }} -{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.oidc.realm }} -{{- else }} +{{- if .Values.app.vault.audience }} {{- print .Values.app.vault.audience }} +{{- else if eq .Values.app.oidc.provider "entraid" }} +{{- if .Values.app.oidc.entraid.audience }} +{{- if hasPrefix "api://" .Values.app.oidc.entraid.audience }} +{{- print .Values.app.oidc.entraid.audience }} +{{- else }} +{{- printf "api://%s" .Values.app.oidc.entraid.audience }} +{{- end }} +{{- else }} +{{- fail "app.oidc.entraid.audience is required when using Entra ID provider" }} +{{- end }} +{{- else }} +{{- printf "https://keycloak.%s/realms/%s" .Values.global.localClusterDomain .Values.app.oidc.realm }} {{- end }} {{- end }} diff --git a/charts/qtodo/templates/app-config-env.yaml b/charts/qtodo/templates/app-config-env.yaml index 348c19b1..6e3e6b86 100644 --- a/charts/qtodo/templates/app-config-env.yaml +++ b/charts/qtodo/templates/app-config-env.yaml @@ -15,5 +15,8 @@ data: {{- 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 }}" +{{- if eq .Values.app.oidc.provider "entraid" }} + QUARKUS_OIDC_CREDENTIALS_JWT_AUDIENCE: "{{ include "qtodo.jwt.audience" . }}" +{{- end }} {{- end }} {{- end }} diff --git a/charts/qtodo/values.yaml b/charts/qtodo/values.yaml index 0cadeddf..953ff2ec 100644 --- a/charts/qtodo/values.yaml +++ b/charts/qtodo/values.yaml @@ -49,6 +49,8 @@ app: oidc: enabled: false # Will be enabled when SPIFFE is enabled + # OIDC Provider: keycloak or entraid + provider: "keycloak" authServerUrl: "" realm: "ztvp" clientId: "qtodo-app" @@ -63,6 +65,10 @@ app: enabled: false name: "oidc-client-secret" vaultPath: "secret/data/apps/qtodo/qtodo-oidc-client" + # Entra ID specific configuration + entraid: + tenantId: "" + audience: "api://AzureADTokenExchange" # Route configuration route: @@ -83,7 +89,7 @@ app: url: "" role: "qtodo" # JWT Audience (auto-generated if not set) - # audience: "api://client-id" + # audience: "" # QTodo secrets path (app-level isolation) secretPath: "secret/data/apps/qtodo/qtodo-db" diff --git a/charts/rhtpa-operator/values.yaml b/charts/rhtpa-operator/values.yaml index d2a82f10..d04bb0df 100644 --- a/charts/rhtpa-operator/values.yaml +++ b/charts/rhtpa-operator/values.yaml @@ -43,7 +43,7 @@ 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 + apiId: "" # Azure Entra ID Application (client) ID - UUID only # RHTPA OIDC CLI secret path (infra) secretVaultPath: "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" diff --git a/charts/supply-chain/values.yaml b/charts/supply-chain/values.yaml index d7e7cbc8..efd4d186 100644 --- a/charts/supply-chain/values.yaml +++ b/charts/supply-chain/values.yaml @@ -33,7 +33,7 @@ rhtpa: enabled: true url: "" realm: "ztvp" # Keycloak realm to use for authentication - apiId: "" # Azure Entra ID API ID to use for authentication + apiId: "" # Azure Entra ID Application (client) ID - UUID only clientId: "rhtpa-cli" clientSecretName: "qtodo-rhtpa-cli-password" # RHTPA OIDC CLI secret path (infra) diff --git a/docs/oidc/entraid.md b/docs/oidc/entraid.md index 71b1d1d1..e32b7197 100644 --- a/docs/oidc/entraid.md +++ b/docs/oidc/entraid.md @@ -12,7 +12,128 @@ To configure the components we will need access to Azure Portal with permissions ### Qtodo -#### Qtodo Azure setup +The qtodo application supports two authentication methods with Azure Entra ID: + +1. **Client Assertion (Recommended)**: Uses SPIFFE JWT tokens for workload identity. No client secrets needed +2. **Client Secret**: Traditional secret-based authentication + +We recommend the **Client Assertion** method, which aligns with zero-trust principles by eliminating static credentials. + +#### Qtodo Azure setup with Client Assertion + +> **⚠️ Important: SSL Certificate Requirement** +> +> For client assertion authentication to work with Azure Entra ID, Microsoft's platform must be able to reach the SPIRE OIDC discovery endpoints over HTTPS with a **valid SSL certificate**: +> +> * `https://spire-spiffe-oidc-discovery-provider.apps./.well-known/openid-configuration` +> * `https://spire-spiffe-oidc-discovery-provider.apps./keys` +> +> The hostname in these URLs matches the `issuer` value in the SPIFFE JWT token. If Azure Entra ID cannot reach these endpoints or encounters an SSL certificate error (self-signed, expired, untrusted CA, etc.), the federated credential validation will fail and authentication will not work. +> +> **Requirements:** +> +> * The SPIRE OIDC Discovery Provider route must be publicly accessible from the internet +> * The SSL certificate must be issued by a trusted Certificate Authority +> * The certificate must be valid (not expired, correct hostname, complete certificate chain) +> +> Self-signed certificates or certificates from internal/private CAs will cause authentication failures. + +You can configure qtodo with Azure Entra ID using either the **Azure Portal** (GUI) or **Azure CLI** (automated). + +##### Option A: Automated Setup with Azure CLI + +For a quick, automated setup, use the provided script: + +```bash +# Set your environment variables +export CLUSTER_DOMAIN="ztvp.example.com" +export QTODO_REDIRECT_URI="https://qtodo-qtodo.apps.ztvp.example.com/" + +# Run the setup script +./scripts/entraid/setup-qtodo-entraid.sh +``` + +The script will: + +1. Create the app registration +2. Set up federated credentials with SPIFFE (using generic audience) +3. Add optional claims +4. Disable user assignment requirement +5. Output configuration data + +**Manual Azure CLI Commands:** + +If you prefer to run commands individually: + +```bash +# Login to Azure +az login + +# Get your tenant ID +export TENANT_ID=$(az account show --query tenantId -o tsv | tr -d '\t\n\r') + +# Set configuration +export APP_NAME="qtodo" +export CLUSTER_DOMAIN="ztvp.example.com" +export APPS_DOMAIN="apps.${CLUSTER_DOMAIN}" +export QTODO_REDIRECT_URI="https://qtodo-qtodo.${APPS_DOMAIN}/" +export SPIRE_ISSUER="https://spire-spiffe-oidc-discovery-provider.${APPS_DOMAIN}" +export SPIFFE_SUBJECT="spiffe://${APPS_DOMAIN}/ns/qtodo/sa/qtodo" +export AUDIENCE="api://AzureADTokenExchange" + +# Create app registration +export CLIENT_ID=$(az ad app create \ + --display-name="${APP_NAME}" \ + --web-redirect-uris="${QTODO_REDIRECT_URI}" \ + --enable-id-token-issuance \ + --query appId \ + -o tsv | tr -d '\t\n\r') + +echo "Client ID: ${CLIENT_ID}" + +# Get object ID +export OBJECT_ID=$(az ad app show --id="${CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') + +# Create federated credential +az ad app federated-credential create \ + --id="${CLIENT_ID}" \ + --parameters "{ + \"name\": \"qtodo-spiffe-federation\", + \"issuer\": \"${SPIRE_ISSUER}\", + \"subject\": \"${SPIFFE_SUBJECT}\", + \"audiences\": [\"${AUDIENCE}\"], + \"description\": \"SPIFFE workload identity for qtodo application\" + }" + +# Add optional claims +az rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{ + "optionalClaims": { + "idToken": [ + {"name": "email", "source": null, "essential": false, "additionalProperties": []}, + {"name": "preferred_username", "source": null, "essential": false, "additionalProperties": []} + ] + } + }' + +# Create service principal and disable assignment requirement +export SP_ID=$(az ad sp create --id="${CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +az rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${SP_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{"appRoleAssignmentRequired": false}' + +echo "Setup complete!" +echo "Tenant ID: ${TENANT_ID}" +echo "Client ID: ${CLIENT_ID}" +echo "Audience: ${AUDIENCE}" +``` + +##### Option B: Manual Setup via Azure Portal + +##### Step 1: Create the App Registration 1. Go to [Azure Portal](https://portal.azure.com) 2. Navigate to **Microsoft Entra ID** @@ -21,57 +142,225 @@ To configure the components we will need access to Azure Portal with permissions 5. Fill in the details: * **Name**: `qtodo` * **Supported account types**: Choose based on your needs - * **Single tenant**: Only users in your organization + * **Single tenant**: Only users in your organization (recommended for enterprise deployments) * **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/`) + * **Redirect URI**: Add the URL of the qtodo application (for example `https://qtodo-qtodo.apps.ztvp.example.com/`) 6. Click **Register** -After the creation, you will see the _Overview_ page: +After creation, note down these values from 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. +**Save these values** - you will need them for the ZTVP configuration. -Let's create a new secret for our app: +##### Step 2: Configure Federated Credentials + +Federated credentials allow Azure Entra ID to trust SPIFFE JWT tokens issued by your SPIRE server. 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 +2. Click the **Federated credentials** tab +3. Click **Add credential** +4. Select **Other issuer** as the federated credential scenario +5. Fill in the credential details: + * **Issuer**: Your SPIRE OIDC Discovery Provider URL + * Format: `https://spire-spiffe-oidc-discovery-provider.` + * Example: `https://spire-spiffe-oidc-discovery-provider.apps.ztvp.example.com` + * **Subject identifier**: SPIFFE ID of the qtodo workload + * Format: `spiffe:///ns//sa/` + * Example: `spiffe://apps.ztvp.example.com/ns/qtodo/sa/qtodo` + * **Name**: `qtodo-spiffe-federation` (or any descriptive name) + * **Description**: `SPIFFE workload identity for qtodo application` + * **Audience**: Use the generic audience value + * Value: `api://AzureADTokenExchange` +6. Click **Add** + +**Important Notes**: + +* The **Issuer** must match exactly what your SPIRE server publishes in its OIDC discovery document +* The **Subject** must match the SPIFFE ID assigned to the qtodo pod's service account +* The **Audience** must match the value you'll configure in `app.oidc.entraid.audience` (`api://AzureADTokenExchange`) + +To verify your SPIRE OIDC issuer URL, run: + +```bash +oc get cm -n zero-trust-workload-identity-manager spire-server -o jsonpath='{.data.server\.conf}' | jq -r '.server.jwt_issuer' +``` + +##### Step 3: Configure Authentication settings + +1. Click **Authentication** in the left menu +2. Under **Platform configurations**, verify your redirect URI is listed +3. Under **Implicit grant and hybrid flows**: + * Check **ID tokens** (used for user authentication) +4. Click **Save** + +##### Step 4: Configure Token settings + +1. Click **Token configuration** in the left menu +2. Click **Add optional claim** +3. Select **ID** token type +4. Add the following claims (check the boxes): + * `email` + * `preferred_username` 5. Click **Add** -6. **IMPORTANT**: Copy the **Value** immediately - it will not be shown again +6. If prompted about Microsoft Graph permissions, check the box to grant them -**Save this value securely** - We will need to add this secret to the Hashicorp Vault in the OpenShift cluster. +##### Step 5: Disable User Assignment Requirement (Optional) + +By default, Azure Entra ID requires users to be explicitly assigned to an application before they can access it. To allow all users in your tenant to access qtodo without explicit assignment: + +1. Go to **Enterprise applications** in the left menu (not App registrations) +2. Find and click on your **qtodo** application + * If you don't see it, make sure "Application type" filter is set to "All applications" +3. Click **Properties** in the left menu +4. Find the setting **Assignment required?** +5. Toggle it to **No** +6. Click **Save** + +This allows all users in your Azure AD tenant to authenticate to qtodo without requiring explicit role assignments. + +**Note:** If you need to restrict access to specific users or groups, keep this setting as **Yes** and use the **Users and groups** section to assign users. + +#### Qtodo ZTVP setup with Client Assertion -#### Qtodo ZTVP setup +Now configure your ZTVP deployment to use the Entra ID app registration with client assertion authentication. -In the `values-secret.yaml` file, we add a new entry with the secret we generated in the Azure portal. For example: +In the `values-hub.yaml` file, add the following configuration for the qtodo application: ```yaml - - name: qtodo-oidc-entraid - vaultPrefixes: - - apps/qtodo - fields: - - name: client-secret - path: ~/.azure/ztvp-qtodo-entraid-secret + qtodo: + overrides: + # Enable OIDC authentication + - name: app.oidc.enabled + value: true + # Set Entra ID as the OIDC provider + - name: app.oidc.provider + value: entraid + # Entra ID tenant ID (from Azure portal) + - name: app.oidc.entraid.tenantId + value: + # Application (client) ID from Azure portal + - name: app.oidc.clientId + value: + # Audience for SPIFFE JWT token validation + - name: app.oidc.entraid.audience + value: api://AzureADTokenExchange + # Enable client assertion (SPIFFE JWT) + - name: app.oidc.clientAssertion.enabled + value: true + # Disable client secret + - name: app.oidc.clientSecret.enabled + value: false ``` -In the `values-hub.yaml file`, we add the following configuration for the qtodo application: +#### How it works + +When using client assertion with Entra ID for a **web application**, there are TWO authentication flows: + +##### User Authentication (Web Browser) + +1. **User visits qtodo**: User opens `https://qtodo-qtodo.apps.example.com` in their web browser +2. **Redirect to Entra ID**: qtodo redirects the browser to Entra IDs authorization endpoint +3. **User authenticates**: User enters credentials (username/password, MFA, etc.) in Entra ID +4. **Redirect back with code**: Entra ID redirects back to qtodo with an authorization code +5. **Token exchange**: qtodo exchanges the code for tokens (see Backend Authentication below) +6. **User logged in**: User can now access qtodo with an active session + +##### Backend Authentication (Client Assertion) + +When qtodo needs to exchange the authorization code for tokens: + +1. **SPIRE issues JWT token**: The SPIRE agent running on the qtodo pod issues a JWT token (SVID) with: + * `iss` (issuer): Your SPIRE OIDC Discovery Provider URL + * `sub` (subject): The qtodo workload's SPIFFE ID + * `aud` (audience): The generic audience value `api://AzureADTokenExchange` + +2. **Qtodo uses JWT for authentication**: Instead of a client secret, qtodo presents the SPIFFE JWT token to Entra ID when requesting access tokens: + + ```text + POST /token + grant_type=authorization_code + code={AUTHORIZATION_CODE} + client_id={CLIENT_ID} + client_assertion={SPIFFE_JWT} + client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer + ``` + +3. **Entra ID validates the token**: Azure verifies: + * The JWT is signed by the trusted SPIRE server (via OIDC discovery) + * The subject matches a registered federated credential + * The audience matches the configured value + +4. **Access granted**: If validation succeeds, Entra ID issues tokens (ID token, access token, refresh token) + +**Key Point**: Client assertion replaces the client secret in the token exchange step. Users still log in via browser redirect - nothing changes from their perspective. + +This eliminates the need to store and rotate client secrets, reducing the attack surface and following zero-trust principles. + +#### Qtodo Vault JWT Role Configuration + +To enable the qtodo application to authenticate to HashiCorp Vault using SPIFFE JWT tokens, you need to configure a Vault JWT auth role in `overrides/values-vault-jwt.yaml`: + +```yaml +vault_jwt_roles: +- name: qtodo + audience: api://AzureADTokenExchange + subject: spiffe://apps.{{ $.Values.global.clusterDomain }}/ns/qtodo/sa/qtodo + policies: + - apps-qtodo-jwt-secret +``` + +**Key Parameters**: + +* **audience**: Must match the value configured in `app.oidc.entraid.audience` (e.g., `api://AzureADTokenExchange`) +* **subject**: The SPIFFE ID of the qtodo workload (`spiffe:///ns//sa/`) +* **policies**: Vault policies granting access to qtodo secrets (e.g., `apps-qtodo-jwt-secret` grants read access to `secret/data/apps/qtodo/*`) + +This role allows qtodo to exchange its SPIFFE JWT token for a Vault token with the specified policies, enabling secure access to database credentials and other secrets stored in Vault. + +#### Alternative: Using Client Secret (Not Recommended) + +If you need to use traditional client secret authentication instead of client assertion, follow these steps: + +1. In Azure Portal, go to your app registration +2. Click **Certificates & secrets** → **Client secrets** tab +3. Click **New client secret** +4. Add description and expiration, then click **Add** +5. **Copy the secret value immediately** - it won't be shown again + +Then update your ZTVP configuration: ```yaml qtodo: overrides: - - name: app.oidc.authServerUrl - value: https://login.microsoftonline.com//v2.0 + - name: app.oidc.enabled + value: true + - name: app.oidc.provider + value: entraid + - name: app.oidc.entraid.tenantId + value: - name: app.oidc.clientId value: + - name: app.oidc.clientAssertion.enabled + value: false - name: app.oidc.clientSecret.enabled value: true - name: app.oidc.clientSecret.vaultPath value: secret/data/apps/qtodo/qtodo-oidc-entraid ``` +And add to `values-secret.yaml`: + +```yaml + - name: qtodo-oidc-entraid + vaultPrefixes: + - apps/qtodo + fields: + - name: client-secret + path: ~/.azure/ztvp-qtodo-entraid-secret +``` + ### RHTAS #### RHTAS Azure setup @@ -132,7 +421,7 @@ This configuration uses the [Device code flow](https://learn.microsoft.com/en-us ```shell export SERVICE_PRINCIPAL_ID=$( - az ad sp create --id="$RHTAS_APP_REGISTRATION" -o tsv --query 'id' \ + az ad sp create --id="$RHTAS_APP_REGISTRATION" --query 'id' -o tsv \ | tr -d '\t\n\r') ``` @@ -170,7 +459,7 @@ In the `values-hub.yaml` file, we add the following configuration for the **trus - name: rhtas.zeroTrust.email.issuer value: https://login.microsoftonline.com//v2.0 - name: rhtas.fulcio.oidcIssuers.email.clientID - value: + value: supply-chain: overrides: - name: rhtas.spire.enabled @@ -180,7 +469,7 @@ In the `values-hub.yaml` file, we add the following configuration for the **trus - name: rhtas.oidc.url value: https://login.microsoftonline.com//v2.0 - name: rhtas.oidc.clientId - value: + value: - name: rhtas.oidc.clientSecretName value: "" - name: rhtas.oidc.issuer @@ -415,7 +704,7 @@ The next step is configure the authentication. 1. Click **Authentication** in the left menu * **Redirect URI configuration Tab** - ** * **Single-page application** should be selected. + * **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 @@ -463,7 +752,7 @@ In the `values-hub.yaml` file, we add the following configuration for the **trus - name: rhtpa.zeroTrust.oidc.clients.cli.clientId value: - name: rhtpa.zeroTrust.oidc.clients.cli.apiId - value: + value: # UUID only - Helm template prepends api:// supply-chain: overrides: - name: rhtpa.oidc.enabled @@ -473,7 +762,7 @@ In the `values-hub.yaml` file, we add the following configuration for the **trus - name: rhtpa.oidc.clientId value: - name: rhtpa.oidc.apiId - value: + value: # UUID only - script prepends api:// ``` 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. @@ -498,3 +787,81 @@ In the `values-secret.yaml` file, make sure that the secret `rhtpa-oidc-cli` use - name: client-secret path: ~/.azure/ztvp-entraid-secret ``` + +#### RHTPA Vault JWT Role Configuration + +To enable the RHTPA application to authenticate to HashiCorp Vault using SPIFFE JWT tokens, configure a Vault JWT auth role in `overrides/values-vault-jwt.yaml`: + +```yaml +vault_jwt_roles: +- name: rhtpa + audience: rhtpa + subject: "spiffe://apps.{{ $.Values.global.clusterDomain }}/ns/trusted-profile-analyzer/sa/rhtpa" + policies: + - hub-infra-rhtpa-jwt-secret +``` + +**Key Parameters**: + +* **audience**: Internal identifier for RHTPA's Vault authentication (`rhtpa`) +* **subject**: The SPIFFE ID of the RHTPA workload +* **policies**: Vault policies granting access to RHTPA secrets (e.g., `hub-infra-rhtpa-jwt-secret` grants read access to `secret/data/hub/infra/rhtpa/*`) + +This role allows RHTPA to exchange its SPIFFE JWT token for a Vault token with the specified policies, enabling secure access to the Entra ID client secret and database credentials stored in Vault. + +### Supply Chain + +#### Supply Chain Vault JWT Role Configuration + +To enable the supply chain pipelines to authenticate to HashiCorp Vault using SPIFFE JWT tokens, configure a Vault JWT auth role in `overrides/values-vault-jwt.yaml`: + +```yaml +vault_jwt_roles: +- name: supply-chain + audience: supply-chain + subject: "spiffe://apps.{{ $.Values.global.clusterDomain }}/ns/{{ $.Values.global.pattern }}-hub/sa/pipeline" + policies: + - hub-supply-chain-jwt-secret +``` + +**Key Parameters**: + +* **audience**: Internal identifier for supply chain Vault authentication (`supply-chain`) +* **subject**: The SPIFFE ID of the Tekton pipeline service account (e.g., `spiffe://apps./ns/layered-zero-trust-hub/sa/pipeline`) +* **policies**: Vault policies granting access to supply chain secrets + +**Vault Policy for Supply Chain**: + +The `hub-supply-chain-jwt-secret` policy grants access to: + +* **Quay registry credentials**: `secret/data/hub/infra/quay/*` (read) +* **BYO registry credentials**: `secret/data/hub/infra/registry/*` (read, create, update) +* **RHTPA OIDC credentials**: `secret/data/hub/infra/rhtpa/rhtpa-oidc-cli` (read) +* **Git credentials**: `secret/data/hub/supply-chain/*` (read) + +Example policy definition in `overrides/values-vault-jwt.yaml`: + +```yaml +vault_jwt_policies: +- name: hub-supply-chain-jwt-secret + policy: | + path "secret/data/hub/infra/quay/*" { + capabilities = ["read"] + } + path "secret/data/hub/infra/registry/*" { + capabilities = ["read", "create", "update"] + } + path "secret/data/hub/infra/rhtpa/rhtpa-oidc-cli" { + capabilities = ["read"] + } + path "secret/data/hub/supply-chain/*" { + capabilities = ["read"] + } +``` + +This configuration enables the Tekton pipeline to: + +1. Authenticate to Vault using its SPIFFE JWT token +2. Retrieve registry credentials for pushing container images +3. Retrieve RHTPA OIDC credentials for SBOM upload +4. Retrieve Git credentials for cloning protected repositories diff --git a/scripts/entraid/cleanup-entraid.sh b/scripts/entraid/cleanup-entraid.sh new file mode 100755 index 00000000..f6468c42 --- /dev/null +++ b/scripts/entraid/cleanup-entraid.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# Cleanup script for Azure Entra ID integration +# This script removes all App Registrations and related configurations created by the setup scripts +# +# WARNING: This will permanently delete: +# - qtodo App Registration and its Service Principal +# - rhtas-ztvp App Registration and its Service Principal +# - Claims Mapping Policy for RHTAS (EmailVerified-RHTAS) +# - rhtpa-api App Registration and its Service Principal +# - rhtpa-frontend App Registration and its Service Principal + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check for az command +if command -v az &> /dev/null; then + # Use native az cli + az_cmd="az" +elif podman ps &> /dev/null 2>&1 && podman image exists azcli &> /dev/null 2>&1; then + # Use containerized az cli + az_cmd="podman run --rm -h az --net host -w /root -v $HOME/.azure:/root/.azure:Z -v $HOME/.ssh:/root/.ssh:Z azcli az" +else + echo -e "${RED}ERROR: Azure CLI not found${NC}" + echo "Please install Azure CLI or create an 'azcli' container image" + echo " Native: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + echo " Container: podman pull mcr.microsoft.com/azure-cli && podman tag mcr.microsoft.com/azure-cli azcli" + exit 1 +fi + +# Configuration variables - use same names as setup scripts +QTODO_APP_NAME="${APP_NAME:-qtodo}" +RHTAS_APP_NAME="${RHTAS_APP_NAME:-rhtas-ztvp}" +RHTPA_API_APP_NAME="${API_APP_NAME:-rhtpa-api}" +RHTPA_FRONTEND_APP_NAME="${FRONTEND_APP_NAME:-rhtpa-frontend}" +RHTAS_POLICY_NAME="EmailVerified-RHTAS" + +echo -e "${RED}========================================${NC}" +echo -e "${RED}Azure Entra ID Cleanup${NC}" +echo -e "${RED}WARNING: This will delete all ZTVP App Registrations${NC}" +echo -e "${RED}========================================${NC}" +echo "" + +# Check if logged in +if ! $az_cmd account show &> /dev/null; then + echo -e "${RED}ERROR: Not logged in to Azure${NC}" + echo "Run: az login" + exit 1 +fi + +TENANT_ID=$($az_cmd account show --query tenantId -o tsv | tr -d '\t\n\r') +echo -e "${GREEN}✓ Logged in to Azure${NC}" +echo -e " Tenant ID: ${TENANT_ID}" +echo "" + +# Function to find app by display name +find_app_id() { + local app_name="$1" + $az_cmd ad app list --display-name "${app_name}" --query "[0].appId" -o tsv 2>/dev/null | tr -d '\t\n\r' +} + +# Function to find service principal by app ID +find_sp_id() { + local app_id="$1" + $az_cmd ad sp list --filter "appId eq '${app_id}'" --query "[0].id" -o tsv 2>/dev/null | tr -d '\t\n\r' +} + +# Function to find claims mapping policy +find_policy_id() { + local policy_name="$1" + $az_cmd rest --uri https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies \ + --query "value[?displayName=='${policy_name}'] | [0].id" -o tsv 2>/dev/null +} + +echo -e "${YELLOW}Scanning for ZTVP resources...${NC}" +echo "" + +# Scan for qtodo +QTODO_APP_ID=$(find_app_id "${QTODO_APP_NAME}") +if [ -n "$QTODO_APP_ID" ]; then + QTODO_SP_ID=$(find_sp_id "${QTODO_APP_ID}") + echo -e "${YELLOW}Found: ${QTODO_APP_NAME}${NC}" + echo " App ID: ${QTODO_APP_ID}" + [ -n "$QTODO_SP_ID" ] && echo " Service Principal ID: ${QTODO_SP_ID}" + QTODO_FOUND=true +else + echo -e "${GREEN}Not found: ${QTODO_APP_NAME} (already deleted or never created)${NC}" + QTODO_FOUND=false +fi +echo "" + +# Scan for rhtas-ztvp +RHTAS_APP_ID=$(find_app_id "${RHTAS_APP_NAME}") +if [ -n "$RHTAS_APP_ID" ]; then + RHTAS_SP_ID=$(find_sp_id "${RHTAS_APP_ID}") + echo -e "${YELLOW}Found: ${RHTAS_APP_NAME}${NC}" + echo " App ID: ${RHTAS_APP_ID}" + [ -n "$RHTAS_SP_ID" ] && echo " Service Principal ID: ${RHTAS_SP_ID}" + RHTAS_FOUND=true +else + echo -e "${GREEN}Not found: ${RHTAS_APP_NAME} (already deleted or never created)${NC}" + RHTAS_FOUND=false +fi +echo "" + +# Scan for Claims Mapping Policy +POLICY_ID=$(find_policy_id "${RHTAS_POLICY_NAME}") +if [ -n "$POLICY_ID" ]; then + echo -e "${YELLOW}Found: Claims Mapping Policy '${RHTAS_POLICY_NAME}'${NC}" + echo " Policy ID: ${POLICY_ID}" + POLICY_FOUND=true +else + echo -e "${GREEN}Not found: Claims Mapping Policy '${RHTAS_POLICY_NAME}' (already deleted or never created)${NC}" + POLICY_FOUND=false +fi +echo "" + +# Scan for rhtpa-api +RHTPA_API_APP_ID=$(find_app_id "${RHTPA_API_APP_NAME}") +if [ -n "$RHTPA_API_APP_ID" ]; then + RHTPA_API_SP_ID=$(find_sp_id "${RHTPA_API_APP_ID}") + echo -e "${YELLOW}Found: ${RHTPA_API_APP_NAME}${NC}" + echo " App ID: ${RHTPA_API_APP_ID}" + [ -n "$RHTPA_API_SP_ID" ] && echo " Service Principal ID: ${RHTPA_API_SP_ID}" + RHTPA_API_FOUND=true +else + echo -e "${GREEN}Not found: ${RHTPA_API_APP_NAME} (already deleted or never created)${NC}" + RHTPA_API_FOUND=false +fi +echo "" + +# Scan for rhtpa-frontend +RHTPA_FRONTEND_APP_ID=$(find_app_id "${RHTPA_FRONTEND_APP_NAME}") +if [ -n "$RHTPA_FRONTEND_APP_ID" ]; then + RHTPA_FRONTEND_SP_ID=$(find_sp_id "${RHTPA_FRONTEND_APP_ID}") + echo -e "${YELLOW}Found: ${RHTPA_FRONTEND_APP_NAME}${NC}" + echo " App ID: ${RHTPA_FRONTEND_APP_ID}" + [ -n "$RHTPA_FRONTEND_SP_ID" ] && echo " Service Principal ID: ${RHTPA_FRONTEND_SP_ID}" + RHTPA_FRONTEND_FOUND=true +else + echo -e "${GREEN}Not found: ${RHTPA_FRONTEND_APP_NAME} (already deleted or never created)${NC}" + RHTPA_FRONTEND_FOUND=false +fi +echo "" + +# Check if anything was found +if [ "$QTODO_FOUND" = false ] && [ "$RHTAS_FOUND" = false ] && [ "$POLICY_FOUND" = false ] && \ + [ "$RHTPA_API_FOUND" = false ] && [ "$RHTPA_FRONTEND_FOUND" = false ]; then + echo -e "${GREEN}No ZTVP resources found. Nothing to clean up.${NC}" + exit 0 +fi + +echo -e "${RED}========================================${NC}" +echo -e "${RED}The following resources will be DELETED:${NC}" +echo -e "${RED}========================================${NC}" +echo "" + +[ "$QTODO_FOUND" = true ] && echo -e "${RED}✗ ${QTODO_APP_NAME} App Registration${NC}" +[ "$RHTAS_FOUND" = true ] && echo -e "${RED}✗ ${RHTAS_APP_NAME} App Registration${NC}" +[ "$POLICY_FOUND" = true ] && echo -e "${RED}✗ Claims Mapping Policy '${RHTAS_POLICY_NAME}'${NC}" +[ "$RHTPA_API_FOUND" = true ] && echo -e "${RED}✗ ${RHTPA_API_APP_NAME} App Registration${NC}" +[ "$RHTPA_FRONTEND_FOUND" = true ] && echo -e "${RED}✗ ${RHTPA_FRONTEND_APP_NAME} App Registration${NC}" + +echo "" +echo -e "${YELLOW}NOTE: Deleting App Registrations will also delete:${NC}" +echo " - Service Principals" +echo " - Client Secrets" +echo " - Federated Credentials" +echo " - App Roles" +echo " - API Scopes" +echo " - Optional Claims" +echo "" +echo -e "${RED}This action cannot be undone!${NC}" +echo "" + +read -p "Are you sure you want to delete these resources? Type 'yes' to confirm: " -r +echo +if [ "$REPLY" != "yes" ]; then + echo "Cleanup cancelled." + exit 0 +fi + +echo "" +echo -e "${YELLOW}Starting cleanup...${NC}" +echo "" + +# Delete qtodo +if [ "$QTODO_FOUND" = true ]; then + echo -e "${YELLOW}Deleting ${QTODO_APP_NAME}...${NC}" + + # Delete service principal first (if exists) + if [ -n "$QTODO_SP_ID" ]; then + echo " Deleting service principal..." + $az_cmd ad sp delete --id "${QTODO_SP_ID}" 2>/dev/null || echo " Warning: Service principal already deleted" + fi + + # Delete app registration + echo " Deleting app registration..." + $az_cmd ad app delete --id "${QTODO_APP_ID}" + + echo -e "${GREEN}✓ ${QTODO_APP_NAME} deleted${NC}" + echo "" +fi + +# Delete rhtas-ztvp +if [ "$RHTAS_FOUND" = true ]; then + echo -e "${YELLOW}Deleting ${RHTAS_APP_NAME}...${NC}" + + # Delete service principal first (if exists) + if [ -n "$RHTAS_SP_ID" ]; then + echo " Deleting service principal..." + $az_cmd ad sp delete --id "${RHTAS_SP_ID}" 2>/dev/null || echo " Warning: Service principal already deleted" + fi + + # Delete app registration + echo " Deleting app registration..." + $az_cmd ad app delete --id "${RHTAS_APP_ID}" + + echo -e "${GREEN}✓ ${RHTAS_APP_NAME} deleted${NC}" + echo "" +fi + +# Delete Claims Mapping Policy +if [ "$POLICY_FOUND" = true ]; then + echo -e "${YELLOW}Deleting Claims Mapping Policy '${RHTAS_POLICY_NAME}'...${NC}" + + $az_cmd rest -m delete \ + --uri "https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/${POLICY_ID}" + + echo -e "${GREEN}✓ Claims Mapping Policy deleted${NC}" + echo "" +fi + +# Delete rhtpa-api +if [ "$RHTPA_API_FOUND" = true ]; then + echo -e "${YELLOW}Deleting ${RHTPA_API_APP_NAME}...${NC}" + + # Delete service principal first (if exists) + if [ -n "$RHTPA_API_SP_ID" ]; then + echo " Deleting service principal..." + $az_cmd ad sp delete --id "${RHTPA_API_SP_ID}" 2>/dev/null || echo " Warning: Service principal already deleted" + fi + + # Delete app registration + echo " Deleting app registration..." + $az_cmd ad app delete --id "${RHTPA_API_APP_ID}" + + echo -e "${GREEN}✓ ${RHTPA_API_APP_NAME} deleted${NC}" + echo "" +fi + +# Delete rhtpa-frontend +if [ "$RHTPA_FRONTEND_FOUND" = true ]; then + echo -e "${YELLOW}Deleting ${RHTPA_FRONTEND_APP_NAME}...${NC}" + + # Delete service principal first (if exists) + if [ -n "$RHTPA_FRONTEND_SP_ID" ]; then + echo " Deleting service principal..." + $az_cmd ad sp delete --id "${RHTPA_FRONTEND_SP_ID}" 2>/dev/null || echo " Warning: Service principal already deleted" + fi + + # Delete app registration + echo " Deleting app registration..." + $az_cmd ad app delete --id "${RHTPA_FRONTEND_APP_ID}" + + echo -e "${GREEN}✓ ${RHTPA_FRONTEND_APP_NAME} deleted${NC}" + echo "" +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Cleanup Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}All ZTVP Azure Entra ID resources have been removed.${NC}" +echo "" +echo -e "${YELLOW}Next steps:${NC}" +echo "1. Remove or comment out Entra ID configuration from values-hub.yaml" +echo "2. Remove saved secrets from local files:" +echo " rm -f ~/.azure/ztvp-qtodo-entraid-secret" +echo " rm -f ~/.azure/ztvp-rhtas-entraid-secret" +echo " rm -f ~/.azure/ztvp-rhtpa-entraid-secret" +echo " rm -f ~/.azure/ztvp-entraid-secret" +echo "3. Redeploy the pattern if needed" +echo "" +echo -e "${GREEN}Done!${NC}" diff --git a/scripts/entraid/setup-qtodo-entraid.sh b/scripts/entraid/setup-qtodo-entraid.sh new file mode 100755 index 00000000..a4cdd5f2 --- /dev/null +++ b/scripts/entraid/setup-qtodo-entraid.sh @@ -0,0 +1,233 @@ +#!/bin/bash +# Setup script for qtodo Azure Entra ID integration with Client Assertion +# This script creates and configures an App Registration for qtodo with SPIFFE federated credentials +# +# This configuration supports: +# - Web-based user authentication via OAuth 2.0 authorization code flow +# - Backend authentication using SPIFFE JWT (client assertion) instead of client secrets + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check for az command +if command -v az &> /dev/null; then + # Use native az cli + az_cmd="az" +elif podman ps &> /dev/null 2>&1 && podman image exists azcli &> /dev/null 2>&1; then + # Use containerized az cli + az_cmd="podman run --rm -h az --net host -w /root -v $HOME/.azure:/root/.azure:Z -v $HOME/.ssh:/root/.ssh:Z azcli az" +else + echo -e "${RED}ERROR: Azure CLI not found${NC}" + echo "Please install Azure CLI or create an 'azcli' container image" + echo " Native: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + echo " Container: podman pull mcr.microsoft.com/azure-cli && podman tag mcr.microsoft.com/azure-cli azcli" + exit 1 +fi + +# Configuration variables with environment variable support +# Example: export CLUSTER_DOMAIN="ztvp.example.com" +APP_NAME="${APP_NAME:-qtodo}" +CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-ztvp.example.com}" # Example: ztvp.example.com (without 'apps.' prefix) +APPS_DOMAIN="${APPS_DOMAIN:-apps.${CLUSTER_DOMAIN}}" # OpenShift applications domain +QTODO_REDIRECT_URI="${QTODO_REDIRECT_URI:-https://qtodo-qtodo.${APPS_DOMAIN}/}" + +# SPIRE configuration - auto-detected from cluster or set manually +SPIRE_ISSUER="${SPIRE_ISSUER:-https://spire-spiffe-oidc-discovery-provider.${APPS_DOMAIN}}" +SPIRE_TRUST_DOMAIN="${SPIRE_TRUST_DOMAIN:-${APPS_DOMAIN}}" + +# SPIFFE ID for qtodo workload +QTODO_NAMESPACE="${QTODO_NAMESPACE:-qtodo}" +QTODO_SA="${QTODO_SA:-qtodo}" +SPIFFE_SUBJECT="spiffe://${SPIRE_TRUST_DOMAIN}/ns/${QTODO_NAMESPACE}/sa/${QTODO_SA}" + +# Audience for SPIFFE JWT token validation +# This is a generic audience value that can be safely used with Entra ID +AUDIENCE="${AUDIENCE:-api://AzureADTokenExchange}" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}QTodo Azure Entra ID Setup${NC}" +echo -e "${GREEN}Client Assertion with SPIFFE${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Verify Azure CLI is installed and logged in +echo -e "${YELLOW}Checking Azure CLI...${NC}" + +# Check if logged in +if ! $az_cmd account show &> /dev/null; then + echo -e "${RED}ERROR: Not logged in to Azure${NC}" + echo "Run: az login" + exit 1 +fi + +TENANT_ID=$($az_cmd account show --query tenantId -o tsv | tr -d '\t\n\r') +echo -e "${GREEN}✓ Logged in to Azure${NC}" +echo -e " Tenant ID: ${TENANT_ID}" +echo "" + +# Display configuration +echo -e "${YELLOW}Configuration:${NC}" +echo " App Name: ${APP_NAME}" +echo " Redirect URI: ${QTODO_REDIRECT_URI}" +echo " Cluster Domain: ${CLUSTER_DOMAIN}" +echo " Apps Domain: ${APPS_DOMAIN}" +echo " SPIRE Issuer: ${SPIRE_ISSUER}" +echo " SPIRE Trust Domain: ${SPIRE_TRUST_DOMAIN}" +echo " SPIFFE Subject: ${SPIFFE_SUBJECT}" +echo " Audience: ${AUDIENCE}" +echo "" + +read -p "Continue with this configuration? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 +fi + +echo "" +echo -e "${YELLOW}Step 1: Creating App Registration...${NC}" + +# Create the app registration with web redirect URIs for OAuth 2.0 authorization code flow +# The redirect URI is where Entra ID sends users after authentication +CLIENT_ID=$($az_cmd ad app create \ + --display-name="${APP_NAME}" \ + --web-redirect-uris="${QTODO_REDIRECT_URI}" \ + --enable-id-token-issuance \ + --query appId \ + -o tsv \ + | tr -d '\t\n\r') + +if [ -z "$CLIENT_ID" ]; then + echo -e "${RED}ERROR: Failed to create app registration${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ App Registration created${NC}" +echo -e " Client ID: ${CLIENT_ID}" + +# Get the object ID +OBJECT_ID=$($az_cmd ad app show --id="${CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +echo -e " Object ID: ${OBJECT_ID}" + +echo "" +echo -e "${YELLOW}Step 2: Verifying identifierUris configuration...${NC}" + +# Check current identifierUris value +CURRENT_URIS=$($az_cmd ad app show --id="${CLIENT_ID}" --query identifierUris -o json) + +if [ "$CURRENT_URIS" = "[]" ] || [ "$CURRENT_URIS" = "null" ]; then + echo -e "${GREEN}✓ identifierUris is empty (correct for client assertion with generic audience)${NC}" +else + echo -e "${YELLOW} Warning: identifierUris is set to: ${CURRENT_URIS}${NC}" + echo -e "${YELLOW} Clearing identifierUris to avoid conflicts...${NC}" + $az_cmd ad app update --id="${CLIENT_ID}" --identifier-uris "[]" + echo -e "${GREEN}✓ identifierUris cleared${NC}" +fi + +echo "" +echo -e "${YELLOW}Step 3: Configuring Federated Credentials...${NC}" + +# Create federated credential for SPIFFE +# This enables qtodo to authenticate using SPIFFE JWT tokens instead of client secrets +# Users still log in via browser redirect - this is for backend authentication only +CREDENTIAL_NAME="${APP_NAME}-spiffe-federation" + +# Use the configured audience value (e.g., api://AzureADTokenExchange) +$az_cmd ad app federated-credential create \ + --id="${CLIENT_ID}" \ + --parameters "{ + \"name\": \"${CREDENTIAL_NAME}\", + \"issuer\": \"${SPIRE_ISSUER}\", + \"subject\": \"${SPIFFE_SUBJECT}\", + \"audiences\": [\"${AUDIENCE}\"], + \"description\": \"SPIFFE workload identity for ${APP_NAME} application\" + }" + +echo -e "${GREEN}✓ Federated credential created${NC}" +echo -e " Name: ${CREDENTIAL_NAME}" +echo -e " Issuer: ${SPIRE_ISSUER}" +echo -e " Subject: ${SPIFFE_SUBJECT}" +echo -e " Audience: ${AUDIENCE}" + +echo "" +echo -e "${YELLOW}Step 4: Configuring optional claims...${NC}" + +# Add optional claims for email and preferred_username +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{ + "optionalClaims": { + "idToken": [ + { + "name": "email", + "source": null, + "essential": false, + "additionalProperties": [] + }, + { + "name": "preferred_username", + "source": null, + "essential": false, + "additionalProperties": [] + } + ] + } + }' + +echo -e "${GREEN}✓ Optional claims configured${NC}" +echo -e " Added: email, preferred_username" + +echo "" +echo -e "${YELLOW}Step 5: Disabling user assignment requirement...${NC}" + +# Create service principal if it doesn't exist +SP_ID=$($az_cmd ad sp list --filter "appId eq '${CLIENT_ID}'" --query "[0].id" -o tsv | tr -d '\t\n\r') + +if [ -z "$SP_ID" ]; then + echo " Creating service principal..." + SP_ID=$($az_cmd ad sp create --id="${CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +fi + +# Disable assignment requirement to allow all users in the tenant to access the app +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${SP_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{ + "appRoleAssignmentRequired": false + }' + +echo -e "${GREEN}✓ User assignment requirement disabled${NC}" +echo -e " All tenant users can now access the application" + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Setup Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}Configuration Data:${NC}" +echo "" +echo "Tenant ID: ${TENANT_ID}" +echo "Client ID: ${CLIENT_ID}" +echo "Audience: ${AUDIENCE}" +echo "" +echo -e "${YELLOW}Verification Commands:${NC}" +echo "" +echo "# View the app registration:" +echo "az ad app show --id=${CLIENT_ID}" +echo "" +echo "# View federated credentials:" +echo "az ad app federated-credential list --id=${CLIENT_ID}" +echo "" +echo "# Verify identifierUris is empty:" +echo "az ad app show --id=${CLIENT_ID} --query identifierUris" +echo "" +echo "# Test SPIRE OIDC Discovery (requires public access):" +echo "curl ${SPIRE_ISSUER}/.well-known/openid-configuration" +echo "" +echo -e "${GREEN}Done!${NC}" diff --git a/scripts/entraid/setup-rhtas-entraid.sh b/scripts/entraid/setup-rhtas-entraid.sh new file mode 100755 index 00000000..a4eeea0e --- /dev/null +++ b/scripts/entraid/setup-rhtas-entraid.sh @@ -0,0 +1,212 @@ +#!/bin/bash +# Setup script for RHTAS Azure Entra ID integration +# This script creates and configures an App Registration for RHTAS with device code flow + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check for az command +if command -v az &> /dev/null; then + # Use native az cli + az_cmd="az" +elif podman ps &> /dev/null 2>&1 && podman image exists azcli &> /dev/null 2>&1; then + # Use containerized az cli + az_cmd="podman run --rm -h az --net host -w /root -v $HOME/.azure:/root/.azure:Z -v $HOME/.ssh:/root/.ssh:Z azcli az" +else + echo -e "${RED}ERROR: Azure CLI not found${NC}" + echo "Please install Azure CLI or create an 'azcli' container image" + echo " Native: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + echo " Container: podman pull mcr.microsoft.com/azure-cli && podman tag mcr.microsoft.com/azure-cli azcli" + exit 1 +fi + +# Configuration variables with environment variable support +# Example: export CLUSTER_DOMAIN="ztvp.example.com" +APP_NAME="${APP_NAME:-rhtas-ztvp}" +CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-ztvp.example.com}" # Example: ztvp.example.com (without 'apps.' prefix) +APPS_DOMAIN="${APPS_DOMAIN:-apps.${CLUSTER_DOMAIN}}" # OpenShift applications domain +RHTAS_REDIRECT_URI="${RHTAS_REDIRECT_URI:-https://qtodo-qtodo.${APPS_DOMAIN}/auth/callback}" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}RHTAS Azure Entra ID Setup${NC}" +echo -e "${GREEN}Device Code Flow Authentication${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Check if logged in +if ! $az_cmd account show &> /dev/null; then + echo -e "${RED}ERROR: Not logged in to Azure${NC}" + echo "Run: az login" + exit 1 +fi + +TENANT_ID=$($az_cmd account show --query tenantId -o tsv | tr -d '\t\n\r') +echo -e "${GREEN}✓ Logged in to Azure${NC}" +echo -e " Tenant ID: ${TENANT_ID}" +echo "" + +# Display configuration +echo -e "${YELLOW}Configuration:${NC}" +echo " App Name: ${APP_NAME}" +echo " Redirect URI: ${RHTAS_REDIRECT_URI}" +echo " Cluster Domain: ${CLUSTER_DOMAIN}" +echo " Apps Domain: ${APPS_DOMAIN}" +echo "" + +read -p "Continue with this configuration? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 +fi + +echo "" +echo -e "${YELLOW}Step 1: Creating App Registration...${NC}" + +# Create the app registration +CLIENT_ID=$($az_cmd ad app create \ + --display-name="${APP_NAME}" \ + --web-redirect-uris="${RHTAS_REDIRECT_URI}" \ + --enable-id-token-issuance \ + --query appId \ + -o tsv \ + | tr -d '\t\n\r') + +if [ -z "$CLIENT_ID" ]; then + echo -e "${RED}ERROR: Failed to create app registration${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ App Registration created${NC}" +echo -e " Client ID: ${CLIENT_ID}" + +# Get the object ID +OBJECT_ID=$($az_cmd ad app show --id="${CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +echo -e " Object ID: ${OBJECT_ID}" + +echo "" +echo -e "${YELLOW}Step 2: Creating Client Secret...${NC}" + +# Create client secret +CLIENT_SECRET=$($az_cmd ad app credential reset \ + --id="${CLIENT_ID}" \ + --display-name="RHTAS Client Secret" \ + --query 'password' \ + -o tsv | tr -d '\t\n\r') + +echo -e "${GREEN}✓ Client Secret created${NC}" +echo -e "${RED} IMPORTANT: Save this secret securely - it won't be shown again${NC}" + +echo "" +echo -e "${YELLOW}Step 3: Creating Claims Mapping Policy...${NC}" + +# Create claims mapping policy for email_verified +POLICY_RESULT=$($az_cmd 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-RHTAS" + }') + +POLICY_ID=$(echo "$POLICY_RESULT" | jq -r '.id') + +if [ -z "$POLICY_ID" ] || [ "$POLICY_ID" = "null" ]; then + # Policy might already exist, try to get it + POLICY_ID=$($az_cmd rest \ + --uri https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies \ + --query "value[?displayName=='EmailVerified-RHTAS'] | [0].id" \ + -o tsv) + + if [ -z "$POLICY_ID" ] || [ "$POLICY_ID" = "null" ]; then + echo -e "${RED}ERROR: Failed to create or find claims mapping policy${NC}" + exit 1 + fi + echo -e "${YELLOW} Using existing claims mapping policy${NC}" +fi + +echo -e "${GREEN}✓ Claims Mapping Policy configured${NC}" +echo -e " Policy ID: ${POLICY_ID}" + +echo "" +echo -e "${YELLOW}Step 4: Enabling Mapped Claims in manifest...${NC}" + +# Enable acceptMappedClaims in the app manifest +$az_cmd rest --method patch \ + --uri "https://graph.microsoft.com/v1.0/applications/${OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{"api":{"acceptMappedClaims":true}}' + +echo -e "${GREEN}✓ Mapped claims enabled${NC}" + +echo "" +echo -e "${YELLOW}Step 5: Creating Service Principal...${NC}" + +# Create service principal +SP_ID=$($az_cmd ad sp create --id="${CLIENT_ID}" -o tsv --query 'id' | tr -d '\t\n\r') + +if [ -z "$SP_ID" ]; then + echo -e "${RED}ERROR: Failed to create service principal${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Service Principal created${NC}" +echo -e " Service Principal ID: ${SP_ID}" + +echo "" +echo -e "${YELLOW}Step 6: Associating Claims Mapping Policy with Service Principal...${NC}" + +# Associate the claims mapping policy with the service principal +$az_cmd rest -m post \ + --headers Content-Type=application/json \ + --uri "https://graph.microsoft.com/v1.0/servicePrincipals/${SP_ID}/claimsMappingPolicies/\$ref" \ + --body "{\"@odata.id\": \"https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/${POLICY_ID}\"}" + +echo -e "${GREEN}✓ Claims Mapping Policy associated${NC}" + +echo "" +echo -e "${YELLOW}Step 7: Enabling Public Client Flow...${NC}" + +# Enable public client flow (isFallbackPublicClient) +$az_cmd ad app update --id="${OBJECT_ID}" --set isFallbackPublicClient=true + +echo -e "${GREEN}✓ Public client flow enabled${NC}" + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Setup Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}Configuration Data:${NC}" +echo "" +echo "Tenant ID: ${TENANT_ID}" +echo "Client ID: ${CLIENT_ID}" +echo "Client Secret: ${CLIENT_SECRET}" +echo "" +echo -e "${RED}IMPORTANT: Save the client secret to a file:${NC}" +echo "" +echo "mkdir -p ~/.azure" +echo "echo '${CLIENT_SECRET}' > ~/.azure/ztvp-rhtas-entraid-secret" +echo "chmod 600 ~/.azure/ztvp-rhtas-entraid-secret" +echo "" +echo -e "${YELLOW}Note: You'll need your Azure Entra ID email address for the supply-chain configuration${NC}" +echo "" +echo -e "${YELLOW}Verification Commands:${NC}" +echo "" +echo "# View the app registration:" +echo "az ad app show --id=${CLIENT_ID}" +echo "" +echo "# View service principal:" +echo "az ad sp show --id=${SP_ID}" +echo "" +echo "# View claims mapping policy:" +echo "az rest --uri https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/${POLICY_ID}" +echo "" +echo -e "${GREEN}Done!${NC}" diff --git a/scripts/entraid/setup-rhtpa-entraid.sh b/scripts/entraid/setup-rhtpa-entraid.sh new file mode 100755 index 00000000..78d3529b --- /dev/null +++ b/scripts/entraid/setup-rhtpa-entraid.sh @@ -0,0 +1,390 @@ +#!/bin/bash +# Setup script for RHTPA Azure Entra ID integration +# This script creates and configures two App Registrations for RHTPA: +# 1. API: Exposes the Trustify API with scopes and app roles +# 2. Frontend: Browser-based authentication for the React frontend + +set -e + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check for az command +if command -v az &> /dev/null; then + # Use native az cli + az_cmd="az" +elif podman ps &> /dev/null 2>&1 && podman image exists azcli &> /dev/null 2>&1; then + # Use containerized az cli + az_cmd="podman run --rm -h az --net host -w /root -v $HOME/.azure:/root/.azure:Z -v $HOME/.ssh:/root/.ssh:Z azcli az" +else + echo -e "${RED}ERROR: Azure CLI not found${NC}" + echo "Please install Azure CLI or create an 'azcli' container image" + echo " Native: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" + echo " Container: podman pull mcr.microsoft.com/azure-cli && podman tag mcr.microsoft.com/azure-cli azcli" + exit 1 +fi + +# Configuration variables with environment variable support +# Example: export CLUSTER_DOMAIN="ztvp.example.com" +API_APP_NAME="${API_APP_NAME:-rhtpa-api}" +FRONTEND_APP_NAME="${FRONTEND_APP_NAME:-rhtpa-frontend}" +CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-ztvp.example.com}" # Example: ztvp.example.com (without 'apps.' prefix) +APPS_DOMAIN="${APPS_DOMAIN:-apps.${CLUSTER_DOMAIN}}" # OpenShift applications domain +RHTPA_REDIRECT_URI="${RHTPA_REDIRECT_URI:-https://servertrustify.${APPS_DOMAIN}/}" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}RHTPA Azure Entra ID Setup${NC}" +echo -e "${GREEN}API + Frontend Configuration${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Check if logged in +if ! $az_cmd account show &> /dev/null; then + echo -e "${RED}ERROR: Not logged in to Azure${NC}" + echo "Run: az login" + exit 1 +fi + +TENANT_ID=$($az_cmd account show --query tenantId -o tsv | tr -d '\t\n\r') +echo -e "${GREEN}✓ Logged in to Azure${NC}" +echo -e " Tenant ID: ${TENANT_ID}" +echo "" + +# Display configuration +echo -e "${YELLOW}Configuration:${NC}" +echo " API App Name: ${API_APP_NAME}" +echo " Frontend App Name: ${FRONTEND_APP_NAME}" +echo " Redirect URI: ${RHTPA_REDIRECT_URI}" +echo " Cluster Domain: ${CLUSTER_DOMAIN}" +echo " Apps Domain: ${APPS_DOMAIN}" +echo "" + +read -p "Continue with this configuration? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 +fi + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Part 1: API App Registration${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +echo -e "${YELLOW}Step 1: Creating API App Registration...${NC}" + +# Create the API app registration (no redirect URI needed for API) +API_CLIENT_ID=$($az_cmd ad app create \ + --display-name="${API_APP_NAME}" \ + --query appId \ + -o tsv \ + | tr -d '\t\n\r') + +if [ -z "$API_CLIENT_ID" ]; then + echo -e "${RED}ERROR: Failed to create API app registration${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ API App Registration created${NC}" +echo -e " API Client ID: ${API_CLIENT_ID}" + +# Get the object ID +API_OBJECT_ID=$($az_cmd ad app show --id="${API_CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +echo -e " API Object ID: ${API_OBJECT_ID}" + +# Application ID URI +API_ID_URI="api://${API_CLIENT_ID}" + +echo "" +echo -e "${YELLOW}Step 2: Setting Application ID URI...${NC}" + +# Set the Application ID URI (Expose an API) +$az_cmd ad app update \ + --id="${API_CLIENT_ID}" \ + --identifier-uris="${API_ID_URI}" + +echo -e "${GREEN}✓ Application ID URI configured${NC}" +echo -e " URI: ${API_ID_URI}" + +echo "" +echo -e "${YELLOW}Step 3: Creating API Scopes...${NC}" + +# Generate UUIDs for scopes and store them +SCOPE_CREATE_ID=$(uuidgen) +SCOPE_READ_ID=$(uuidgen) +SCOPE_UPDATE_ID=$(uuidgen) +SCOPE_DELETE_ID=$(uuidgen) + +# Define scopes using Microsoft Graph API +# We need to add oauth2PermissionScopes to the api section +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${API_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body "{ + \"api\": { + \"oauth2PermissionScopes\": [ + { + \"adminConsentDescription\": \"Allows the application to create documents\", + \"adminConsentDisplayName\": \"Create documents in Trustify\", + \"id\": \"${SCOPE_CREATE_ID}\", + \"isEnabled\": true, + \"type\": \"User\", + \"userConsentDescription\": \"Allows the application to create documents\", + \"userConsentDisplayName\": \"Create documents in Trustify\", + \"value\": \"create:document\" + }, + { + \"adminConsentDescription\": \"Allows the application to read documents\", + \"adminConsentDisplayName\": \"Read documents in Trustify\", + \"id\": \"${SCOPE_READ_ID}\", + \"isEnabled\": true, + \"type\": \"User\", + \"userConsentDescription\": \"Allows the application to read documents\", + \"userConsentDisplayName\": \"Read documents in Trustify\", + \"value\": \"read:document\" + }, + { + \"adminConsentDescription\": \"Allows the application to update documents\", + \"adminConsentDisplayName\": \"Update documents in Trustify\", + \"id\": \"${SCOPE_UPDATE_ID}\", + \"isEnabled\": true, + \"type\": \"User\", + \"userConsentDescription\": \"Allows the application to update documents\", + \"userConsentDisplayName\": \"Update documents in Trustify\", + \"value\": \"update:document\" + }, + { + \"adminConsentDescription\": \"Allows the application to delete documents\", + \"adminConsentDisplayName\": \"Delete documents in Trustify\", + \"id\": \"${SCOPE_DELETE_ID}\", + \"isEnabled\": true, + \"type\": \"User\", + \"userConsentDescription\": \"Allows the application to delete documents\", + \"userConsentDisplayName\": \"Delete documents in Trustify\", + \"value\": \"delete:document\" + } + ] + } + }" + +echo -e "${GREEN}✓ API Scopes created${NC}" +echo -e " - create:document (${SCOPE_CREATE_ID})" +echo -e " - read:document (${SCOPE_READ_ID})" +echo -e " - update:document (${SCOPE_UPDATE_ID})" +echo -e " - delete:document (${SCOPE_DELETE_ID})" + +echo "" +echo -e "${YELLOW}Step 4: Creating API Client Secret...${NC}" + +# Create client secret +API_CLIENT_SECRET=$($az_cmd ad app credential reset \ + --id="${API_CLIENT_ID}" \ + --display-name="RHTPA API Secret" \ + --query 'password' \ + -o tsv | tr -d '\t\n\r') + +echo -e "${GREEN}✓ API Client Secret created${NC}" +echo -e "${RED} IMPORTANT: Save this secret securely - it won't be shown again${NC}" + +echo "" +echo -e "${YELLOW}Step 5: Configuring Token Version...${NC}" + +# Set requestedAccessTokenVersion to 2 +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${API_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{ + "api": { + "requestedAccessTokenVersion": 2 + } + }' + +echo -e "${GREEN}✓ Token version set to v2${NC}" + +echo "" +echo -e "${YELLOW}Step 6: Creating Application Roles...${NC}" + +# Create app roles for service-to-service authentication +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${API_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body "{ + \"appRoles\": [ + { + \"allowedMemberTypes\": [\"Application\"], + \"description\": \"Allows the application to read documents\", + \"displayName\": \"App.Read.Document\", + \"id\": \"$(uuidgen)\", + \"isEnabled\": true, + \"value\": \"App.Read.Document\" + }, + { + \"allowedMemberTypes\": [\"Application\"], + \"description\": \"Allows the application to create documents\", + \"displayName\": \"App.Create.Document\", + \"id\": \"$(uuidgen)\", + \"isEnabled\": true, + \"value\": \"App.Create.Document\" + }, + { + \"allowedMemberTypes\": [\"Application\"], + \"description\": \"Allows the application to update documents\", + \"displayName\": \"App.Update.Document\", + \"id\": \"$(uuidgen)\", + \"isEnabled\": true, + \"value\": \"App.Update.Document\" + }, + { + \"allowedMemberTypes\": [\"Application\"], + \"description\": \"Allows the application to delete documents\", + \"displayName\": \"App.Delete.Document\", + \"id\": \"$(uuidgen)\", + \"isEnabled\": true, + \"value\": \"App.Delete.Document\" + } + ] + }" + +echo -e "${GREEN}✓ Application Roles created${NC}" +echo -e " - App.Read.Document" +echo -e " - App.Create.Document" +echo -e " - App.Update.Document" +echo -e " - App.Delete.Document" + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Part 2: Frontend App Registration${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +echo -e "${YELLOW}Step 7: Creating Frontend App Registration...${NC}" + +# Create the frontend app registration (without redirect URI first) +FRONTEND_CLIENT_ID=$($az_cmd ad app create \ + --display-name="${FRONTEND_APP_NAME}" \ + --query appId \ + -o tsv \ + | tr -d '\t\n\r') + +if [ -z "$FRONTEND_CLIENT_ID" ]; then + echo -e "${RED}ERROR: Failed to create frontend app registration${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Frontend App Registration created${NC}" +echo -e " Frontend Client ID: ${FRONTEND_CLIENT_ID}" + +# Get the object ID +FRONTEND_OBJECT_ID=$($az_cmd ad app show --id="${FRONTEND_CLIENT_ID}" --query id -o tsv | tr -d '\t\n\r') +echo -e " Frontend Object ID: ${FRONTEND_OBJECT_ID}" + +# Configure SPA redirect URI via Graph API +echo -e "${YELLOW} Configuring SPA redirect URI...${NC}" + +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${FRONTEND_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body "{ + \"spa\": { + \"redirectUris\": [\"${RHTPA_REDIRECT_URI}\"] + } + }" + +echo -e "${GREEN} SPA redirect URI configured${NC}" + +echo "" +echo -e "${YELLOW}Step 8: Configuring Frontend Token Version...${NC}" + +# Set requestedAccessTokenVersion to 2 for frontend +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${FRONTEND_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body '{ + "api": { + "requestedAccessTokenVersion": 2 + } + }' + +echo -e "${GREEN}✓ Frontend token version set to v2${NC}" + +echo "" +echo -e "${YELLOW}Step 9: Granting Frontend Permissions to API...${NC}" + +# Add API permissions to frontend app using the stored scope IDs +# We need to add requiredResourceAccess for the API scopes +$az_cmd rest --method PATCH \ + --uri "https://graph.microsoft.com/v1.0/applications/${FRONTEND_OBJECT_ID}" \ + --headers 'Content-Type=application/json' \ + --body "{ + \"requiredResourceAccess\": [ + { + \"resourceAppId\": \"${API_CLIENT_ID}\", + \"resourceAccess\": [ + { + \"id\": \"${SCOPE_CREATE_ID}\", + \"type\": \"Scope\" + }, + { + \"id\": \"${SCOPE_READ_ID}\", + \"type\": \"Scope\" + }, + { + \"id\": \"${SCOPE_UPDATE_ID}\", + \"type\": \"Scope\" + }, + { + \"id\": \"${SCOPE_DELETE_ID}\", + \"type\": \"Scope\" + } + ] + } + ] + }" + +echo -e "${GREEN}✓ Frontend permissions configured${NC}" +echo -e "${YELLOW} Note: Admin consent is required but must be granted by an administrator${NC}" + +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Setup Complete!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" +echo -e "${YELLOW}Configuration Data:${NC}" +echo "" +echo "Tenant ID: ${TENANT_ID}" +echo "API Client ID: ${API_CLIENT_ID}" +echo "API Client Secret: ${API_CLIENT_SECRET}" +echo "Frontend Client ID: ${FRONTEND_CLIENT_ID}" +echo "" +echo -e "${RED}IMPORTANT: Save the API client secret to a file:${NC}" +echo "" +echo "mkdir -p ~/.azure" +echo "echo '${API_CLIENT_SECRET}' > ~/.azure/ztvp-rhtpa-entraid-secret" +echo "chmod 600 ~/.azure/ztvp-rhtpa-entraid-secret" +echo "" +echo -e "${YELLOW}IMPORTANT: Grant Admin Consent${NC}" +echo "" +echo "The API permissions require admin consent. Ask your Azure administrator to:" +echo "1. Go to Azure Portal > App registrations > ${FRONTEND_APP_NAME}" +echo "2. Navigate to API permissions" +echo "3. Click 'Grant admin consent' for the application roles" +echo "" +echo -e "${YELLOW}Verification Commands:${NC}" +echo "" +echo "# View API app registration:" +echo "az ad app show --id=${API_CLIENT_ID}" +echo "" +echo "# View frontend app registration:" +echo "az ad app show --id=${FRONTEND_CLIENT_ID}" +echo "" +echo "# View API scopes:" +echo "az ad app show --id=${API_CLIENT_ID} --query 'api.oauth2PermissionScopes[].value'" +echo "" +echo "# View API app roles:" +echo "az ad app show --id=${API_CLIENT_ID} --query 'appRoles[].value'" +echo "" +echo -e "${GREEN}Done!${NC}" diff --git a/scripts/features/entra-id.yaml b/scripts/features/entra-id.yaml index 07773509..545fbc12 100644 --- a/scripts/features/entra-id.yaml +++ b/scripts/features/entra-id.yaml @@ -1,23 +1,57 @@ # Azure Entra ID integration # Depends on: supply-chain +# +# Replace placeholders with your actual Azure Entra ID values: +# - : Directory (tenant) ID from Azure Portal +# - : Application (client) ID for qtodo +# - : Application (client) ID for RHTAS +# - : Application (client) ID for RHTPA frontend +# - : Application (client) ID for RHTPA API/CLI +# - : Your email address for RHTAS identity +# clusterGroup: merge_into_applications: qtodo: overrides: - - name: app.oidc.authServerUrl - value: "https://login.microsoftonline.com//v2.0" + # Enable OIDC authentication + - name: app.oidc.enabled + value: "true" + # Set Entra ID as the OIDC provider (enables client assertion) + - name: app.oidc.provider + value: "entraid" + # Entra ID tenant ID (from Azure portal) + - name: app.oidc.entraid.tenantId + value: "" + # Application (client) ID from Azure portal - name: app.oidc.clientId - value: "" - - name: app.oidc.clientSecret.enabled + value: "" + # API identifier (Application ID URI) - used as JWT audience for client assertion + - name: app.oidc.entraid.audience + value: "api://AzureADTokenExchange" + # Enable client assertion (SPIFFE JWT) - recommended for zero-trust + - name: app.oidc.clientAssertion.enabled value: "true" - - name: app.oidc.clientSecret.vaultPath - value: "secret/data/apps/qtodo/qtodo-oidc-entraid" + # Disable client secret when using client assertion + - name: app.oidc.clientSecret.enabled + value: "false" + # Alternative: Use client secret instead of client assertion (not recommended) + # Uncomment these and comment out the client assertion settings above + # - name: app.oidc.provider + # value: "entraid" + # - name: app.oidc.entraid.tenantId + # value: "" + # - name: app.oidc.clientSecret.enabled + # value: "true" + # - name: app.oidc.clientSecret.vaultPath + # value: "secret/data/apps/qtodo/qtodo-oidc-entraid" + # - name: app.oidc.clientAssertion.enabled + # value: "false" trusted-artifact-signer: overrides: - name: rhtas.zeroTrust.email.issuer value: "https://login.microsoftonline.com//v2.0" - name: rhtas.fulcio.oidcIssuers.email.clientID - value: "" + value: "" trusted-profile-analyzer: overrides: - name: rhtpa.zeroTrust.oidc.authServerUrl @@ -27,7 +61,7 @@ clusterGroup: - name: rhtpa.zeroTrust.oidc.clients.cli.clientId value: "" - name: rhtpa.zeroTrust.oidc.clients.cli.apiId - value: "" + value: "" # UUID only - Helm template prepends api:// supply-chain: overrides: - name: rhtas.spire.enabled @@ -37,7 +71,7 @@ clusterGroup: - name: rhtas.oidc.url value: "https://login.microsoftonline.com//v2.0" - name: rhtas.oidc.clientId - value: "" + value: "" - name: rhtas.oidc.clientSecretName value: "" - name: rhtas.oidc.issuer @@ -49,5 +83,5 @@ clusterGroup: - name: rhtpa.oidc.clientId value: "" - name: rhtpa.oidc.apiId - value: "" + value: "" # UUID only - script prepends api://