Compare commits
19 Commits
main
...
hotfix/bac
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d33b6a9c3 | |||
| 0036d986a8 | |||
| 70dab12b05 | |||
| 47e9619ae2 | |||
| 2e09a2d404 | |||
| 9e9254a466 | |||
| 539217c3f2 | |||
| 80cf435486 | |||
| 0d7980d105 | |||
| f280596ddb | |||
| 65dc795cd6 | |||
| 237dc0ff90 | |||
| 788cc8f4f4 | |||
| 4def4d2ed7 | |||
| 7d1e2d4665 | |||
| 417185d567 | |||
| 03e60a3512 | |||
| 2135580210 | |||
| 37a38a1179 |
53
apps/overlays/upc-dev/feedback/feedback.yaml
Normal file
53
apps/overlays/upc-dev/feedback/feedback.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: feedback
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "12"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: feedback
|
||||||
|
app.kubernetes.io/part-of: apps
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
finalizers:
|
||||||
|
- resources-finalizer.argocd.argoproj.io
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
|
||||||
|
sources:
|
||||||
|
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||||
|
path: forteapp
|
||||||
|
targetRevision: HEAD
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- $values/feedback/values.yaml
|
||||||
|
|
||||||
|
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
ref: values
|
||||||
|
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: feedback
|
||||||
|
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
allowEmpty: false
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- Validate=true
|
||||||
|
- ServerSideApply=true
|
||||||
|
retry:
|
||||||
|
limit: 5
|
||||||
|
backoff:
|
||||||
|
duration: 5s
|
||||||
|
factor: 2
|
||||||
|
maxDuration: 3m
|
||||||
|
|
||||||
|
ignoreDifferences:
|
||||||
|
- group: apps
|
||||||
|
kind: StatefulSet
|
||||||
|
jsonPointers:
|
||||||
|
- /spec/volumeClaimTemplates
|
||||||
4
apps/overlays/upc-dev/feedback/kustomization.yaml
Normal file
4
apps/overlays/upc-dev/feedback/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- feedback.yaml
|
||||||
@@ -3,6 +3,7 @@ kind: Kustomization
|
|||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../base
|
||||||
- dbunk-demo
|
- dbunk-demo
|
||||||
|
- feedback
|
||||||
|
|
||||||
# No patches needed — base already has "upc-dev" paths
|
# No patches needed — base already has "upc-dev" paths
|
||||||
# upc-dev is the default/base cluster
|
# upc-dev is the default/base cluster
|
||||||
|
|||||||
@@ -245,6 +245,12 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}"
|
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}"
|
||||||
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}"
|
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}"
|
||||||
|
- name: AUTH_OIDC_IDP_HINT
|
||||||
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-idp-hint\" || '' }}"
|
||||||
|
- name: AUTH_OIDC_BROKER_ALIAS
|
||||||
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-broker-alias\" || '' }}"
|
||||||
|
- name: AUTH_OIDC_BROKER_TOKEN_HEADER
|
||||||
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-broker-token-header\" || '' }}"
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 50m
|
cpu: 50m
|
||||||
@@ -324,6 +330,8 @@ spec:
|
|||||||
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
|
||||||
- name: AUTH_MCP_SCOPES_SUPPORTED
|
- name: AUTH_MCP_SCOPES_SUPPORTED
|
||||||
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}"
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}"
|
||||||
|
- name: AUTH_MCP_IDP_HINT
|
||||||
|
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-idp-hint\" || '' }}"
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 50m
|
cpu: 50m
|
||||||
|
|||||||
@@ -1384,6 +1384,46 @@ spec:
|
|||||||
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`)
|
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`)
|
||||||
- `synchronize: true` — changes to the source Secret are reflected in the clone
|
- `synchronize: true` — changes to the source Secret are reflected in the clone
|
||||||
|
|
||||||
|
### Keycloak Microsoft/Entra Identity Provider
|
||||||
|
|
||||||
|
**File**: `infra/values/upc-dev/keycloak-values.yaml`
|
||||||
|
**Namespace**: `keycloak`
|
||||||
|
|
||||||
|
**Purpose**: Configures Microsoft Entra (Azure AD) as an external identity provider for the Forte realm, enabling SSO via Microsoft accounts with token storage for downstream API access (e.g., Microsoft Graph).
|
||||||
|
|
||||||
|
**Configuration via keycloakConfigCli**:
|
||||||
|
- IdP alias: `forte-entra`, provider: `microsoft`
|
||||||
|
- Client secret injected from `microsoft-idp-credentials` Secret via `$(env:MS_IDP_CLIENT_SECRET)` syntax
|
||||||
|
- `extraEnvVarsSecret: microsoft-idp-credentials` makes the Secret available as env vars to config-cli
|
||||||
|
|
||||||
|
**Key Configuration Notes**:
|
||||||
|
|
||||||
|
| Field | Location | Notes |
|
||||||
|
|-------|----------|-------|
|
||||||
|
| `tenant` | `config.tenant` | **Must be `tenant`, NOT `tenantId`** — wrong key silently falls back to `common` (multi-tenant) |
|
||||||
|
| `storeToken` | Top-level IdP field | **NOT inside `config`** — enables broker token storage for KC broker API |
|
||||||
|
| `defaultScope` | `config.defaultScope` | Space-separated: `openid email profile User.Read Mail.Send` |
|
||||||
|
| `syncMode` | `config.syncMode` | `IMPORT` — imports user on first login |
|
||||||
|
|
||||||
|
**Token Storage & Broker Access**:
|
||||||
|
- `storeToken: true` persists the Entra access token in Keycloak
|
||||||
|
- Realm role `default-roles-forte` includes composite `broker.read-token` — grants all realm users access to broker token API
|
||||||
|
- Broker token retrievable via: `GET /realms/forte/broker/forte-entra/token`
|
||||||
|
|
||||||
|
**Identity Provider Mappers**:
|
||||||
|
- `forte-entra-email`: Hardcodes `emailVerified=true` for Entra-authenticated users (Entra guarantees email verification)
|
||||||
|
|
||||||
|
**Required Secret** (`microsoft-idp-credentials`):
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: microsoft-idp-credentials
|
||||||
|
namespace: keycloak
|
||||||
|
stringData:
|
||||||
|
MS_IDP_CLIENT_SECRET: "<entra-app-client-secret>"
|
||||||
|
```
|
||||||
|
|
||||||
### Default Namespace Blocker
|
### Default Namespace Blocker
|
||||||
|
|
||||||
**File**: `cluster-resources/policies/default-ns-blocker.yaml`
|
**File**: `cluster-resources/policies/default-ns-blocker.yaml`
|
||||||
|
|||||||
15
infra/overlays/upc-dev/entra-upc-dev-credentials-sealed.yaml
Normal file
15
infra/overlays/upc-dev/entra-upc-dev-credentials-sealed.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
apiVersion: bitnami.com/v1alpha1
|
||||||
|
kind: SealedSecret
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: microsoft-idp-credentials
|
||||||
|
namespace: keycloak
|
||||||
|
spec:
|
||||||
|
encryptedData:
|
||||||
|
MS_IDP_CLIENT_SECRET: AgBGeloOR8LVop8yluydjc7qj4JUkb85z7B3h07i3xLPup1ojgSqtx08huv+8gvSaFoPdxOY8g23iuKeAr2b2A70paHv0ILSVRsIpxzjuao4aDRWxMHD/SFzvitvqaXD1eQUcBNRDk/1NOG0o5b9o4ddMWkNmyCYuZcmOyx18DC1kFrcWGUgJkLTr+YRZcLJ2T80obZ+y4zztYc+vNQlu11KSIALz++c9XzK2XYSdOMdFHmzadWxoCjvkJqG4W5C6AQBlYa7NSydyU6K3TnwS4VHF8w1DRNneM/IuHLMNbcL8WZjHlSS8xgXFUMhG4rkejwM6joQLx57OosTQe1/xdCOPBXq4NO8Q76RXXkoI0EYMbzfK33vr4NVh8zmBUYxhaeQySh2jYdYp7t/79UzvP7jtGYUgqIZNEBqbDKkzvXsJ4dHJnss8U8lWVgLev31ZhaTjIr3A8VKDPbJKIRZPO71/4DiCF0WKLYNKfbYJ5tsyTxivFWDY5NPNkKkgXk4QoScZ3r4bYNZjDp1rC5zCRPGf0Lt1C5Zovx1HTUjpGpAFVi7DyNmEc0uXn2sTwPARAuAj8str+RnlRNzBkpPWKdlOoDVPTSA41dSTfUVZMlQlluu4fdDgGj5sEnhEN0iIxZNRwU/2DD2AVSqG+KtdIkfH6/j0jzdFn4D3Ha6vwAsBIURK4Ird/pLuNGGCDB+LrrNGXDTTKAPFydCuRtpyoM9kFOtSZb7T7q6Vfkqa7LfgP8mdG9JGXJx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: microsoft-idp-credentials
|
||||||
|
namespace: keycloak
|
||||||
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
- ../../base
|
- ../../base
|
||||||
|
- entra-upc-dev-credentials-sealed.yaml
|
||||||
|
|
||||||
# No patches needed — base already has "upc-dev" paths
|
# No patches needed — base already has "upc-dev" paths
|
||||||
# upc-dev is the default/base cluster
|
# upc-dev is the default/base cluster
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ extraDeploy:
|
|||||||
ADMIN_PASS=$(cat /secrets/admin-password)
|
ADMIN_PASS=$(cat /secrets/admin-password)
|
||||||
|
|
||||||
echo "Authenticating to Keycloak..."
|
echo "Authenticating to Keycloak..."
|
||||||
TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
|
TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
|
||||||
-d "client_id=admin-cli" \
|
-d "client_id=admin-cli" \
|
||||||
-d "username=${ADMIN_USER}" \
|
-d "username=${ADMIN_USER}" \
|
||||||
-d "password=${ADMIN_PASS}" \
|
-d "password=${ADMIN_PASS}" \
|
||||||
@@ -276,7 +276,7 @@ extraDeploy:
|
|||||||
upsert_secret() {
|
upsert_secret() {
|
||||||
local ns="$1" name="$2" manifest="$3"
|
local ns="$1" name="$2" manifest="$3"
|
||||||
local code
|
local code
|
||||||
code=$(curl -sf -o /dev/null -w "%{http_code}" \
|
code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -285,7 +285,7 @@ extraDeploy:
|
|||||||
if [ "$code" = "200" ]; then
|
if [ "$code" = "200" ]; then
|
||||||
echo " Updated secret '${ns}/${name}'"
|
echo " Updated secret '${ns}/${name}'"
|
||||||
elif [ "$code" = "404" ]; then
|
elif [ "$code" = "404" ]; then
|
||||||
code=$(curl -sf -o /dev/null -w "%{http_code}" \
|
code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@@ -332,7 +332,7 @@ extraDeploy:
|
|||||||
|
|
||||||
# Get the client secret from Keycloak
|
# Get the client secret from Keycloak
|
||||||
local secret_value
|
local secret_value
|
||||||
secret_value=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
|
secret_value=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
|
||||||
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${client_uuid}/client-secret" \
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${client_uuid}/client-secret" \
|
||||||
| jq -r '.value')
|
| jq -r '.value')
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ extraDeploy:
|
|||||||
|
|
||||||
# Write to target namespace (if it exists)
|
# Write to target namespace (if it exists)
|
||||||
local ns_status
|
local ns_status
|
||||||
ns_status=$(curl -sf -o /dev/null -w "%{http_code}" \
|
ns_status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
"${K8S_API}/api/v1/namespaces/${target_ns}")
|
"${K8S_API}/api/v1/namespaces/${target_ns}")
|
||||||
@@ -371,12 +371,12 @@ extraDeploy:
|
|||||||
local ns="$1" name="$2" key="$3" value="$4"
|
local ns="$1" name="$2" key="$3" value="$4"
|
||||||
local patch
|
local patch
|
||||||
patch=$(printf '{"metadata":{"annotations":{"%s":"%s"}}}' "$key" "$value")
|
patch=$(printf '{"metadata":{"annotations":{"%s":"%s"}}}' "$key" "$value")
|
||||||
curl -sf -o /dev/null \
|
curl -s -o /dev/null \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
-H "Content-Type: application/strategic-merge-patch+json" \
|
-H "Content-Type: application/strategic-merge-patch+json" \
|
||||||
-X PATCH -d "$patch" \
|
-X PATCH -d "$patch" \
|
||||||
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}"
|
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
@@ -384,7 +384,7 @@ extraDeploy:
|
|||||||
# =============================================
|
# =============================================
|
||||||
echo "=== Legacy sync: clients with k8s.secret.sync=true ==="
|
echo "=== Legacy sync: clients with k8s.secret.sync=true ==="
|
||||||
|
|
||||||
CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
|
CLIENTS=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
|
||||||
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
|
||||||
|
|
||||||
SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]')
|
SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]')
|
||||||
@@ -409,7 +409,7 @@ extraDeploy:
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== Self-service: config Secrets with label keycloak.forteapps.net/client-config=true ==="
|
echo "=== Self-service: config Secrets with label keycloak.forteapps.net/client-config=true ==="
|
||||||
|
|
||||||
CONFIG_SECRETS=$(curl -sf \
|
CONFIG_SECRETS=$(curl -s \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
"${K8S_API}/api/v1/namespaces/keycloak/secrets?labelSelector=keycloak.forteapps.net/client-config=true")
|
"${K8S_API}/api/v1/namespaces/keycloak/secrets?labelSelector=keycloak.forteapps.net/client-config=true")
|
||||||
@@ -430,6 +430,10 @@ extraDeploy:
|
|||||||
CLIENT_JSON=$(printf '%s' "$CLIENT_JSON_B64" | base64 -d)
|
CLIENT_JSON=$(printf '%s' "$CLIENT_JSON_B64" | base64 -d)
|
||||||
|
|
||||||
CLIENT_ID=$(echo "$CLIENT_JSON" | jq -r '.clientId')
|
CLIENT_ID=$(echo "$CLIENT_JSON" | jq -r '.clientId')
|
||||||
|
if [ -z "$CLIENT_ID" ] || [ "$CLIENT_ID" = "null" ]; then
|
||||||
|
echo "ERROR: Could not extract clientId from config '${CONFIG_NAME}', skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
echo "Processing self-service client '${CLIENT_ID}' from config '${CONFIG_NAME}'"
|
echo "Processing self-service client '${CLIENT_ID}' from config '${CONFIG_NAME}'"
|
||||||
|
|
||||||
# Compute config hash for change detection
|
# Compute config hash for change detection
|
||||||
@@ -443,7 +447,7 @@ extraDeploy:
|
|||||||
CRED_SECRET_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientSecret // "client-secret"')
|
CRED_SECRET_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientSecret // "client-secret"')
|
||||||
|
|
||||||
# Check if credential Secret already exists in target namespace
|
# Check if credential Secret already exists in target namespace
|
||||||
CRED_EXISTS=$(curl -sf -o /dev/null -w "%{http_code}" \
|
CRED_EXISTS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
--cacert "$CA_CERT" \
|
--cacert "$CA_CERT" \
|
||||||
-H "Authorization: Bearer ${SA_TOKEN}" \
|
-H "Authorization: Bearer ${SA_TOKEN}" \
|
||||||
"${K8S_API}/api/v1/namespaces/${CRED_NS}/secrets/${CRED_NAME}")
|
"${K8S_API}/api/v1/namespaces/${CRED_NS}/secrets/${CRED_NAME}")
|
||||||
@@ -466,18 +470,17 @@ extraDeploy:
|
|||||||
publicClient: false,
|
publicClient: false,
|
||||||
redirectUris: .redirectUris,
|
redirectUris: .redirectUris,
|
||||||
webOrigins: .webOrigins,
|
webOrigins: .webOrigins,
|
||||||
defaultClientScopes: .defaultClientScopes,
|
|
||||||
protocolMappers: (.protocolMappers // [])
|
protocolMappers: (.protocolMappers // [])
|
||||||
}')
|
} + if .defaultClientScopes then {defaultClientScopes: .defaultClientScopes} else {} end')
|
||||||
|
|
||||||
# Check if client already exists
|
# Check if client already exists
|
||||||
EXISTING=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
|
EXISTING=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
|
||||||
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
|
||||||
| jq -r '.[0].id // empty')
|
| jq -r '.[0].id // empty')
|
||||||
|
|
||||||
if [ -n "$EXISTING" ]; then
|
if [ -n "$EXISTING" ]; then
|
||||||
echo " Updating existing Keycloak client (uuid: ${EXISTING})"
|
echo " Updating existing Keycloak client (uuid: ${EXISTING})"
|
||||||
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
-H "Authorization: Bearer ${TOKEN}" \
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-X PUT -d "$KC_CLIENT" \
|
-X PUT -d "$KC_CLIENT" \
|
||||||
@@ -490,7 +493,7 @@ extraDeploy:
|
|||||||
CLIENT_UUID="$EXISTING"
|
CLIENT_UUID="$EXISTING"
|
||||||
else
|
else
|
||||||
echo " Creating new Keycloak client '${CLIENT_ID}'"
|
echo " Creating new Keycloak client '${CLIENT_ID}'"
|
||||||
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
-H "Authorization: Bearer ${TOKEN}" \
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-X POST -d "$KC_CLIENT" \
|
-X POST -d "$KC_CLIENT" \
|
||||||
@@ -501,11 +504,37 @@ extraDeploy:
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
# Fetch the newly created client's UUID
|
# Fetch the newly created client's UUID
|
||||||
CLIENT_UUID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
|
CLIENT_UUID=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
|
||||||
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
|
||||||
| jq -r '.[0].id')
|
| jq -r '.[0].id')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Assign default client scopes (KC REST API ignores defaultClientScopes in POST/PUT body)
|
||||||
|
REQUESTED_SCOPES=$(echo "$CLIENT_JSON" | jq -r '.defaultClientScopes // [] | .[]' 2>/dev/null)
|
||||||
|
if [ -n "$REQUESTED_SCOPES" ]; then
|
||||||
|
# Fetch all realm client scopes once
|
||||||
|
ALL_SCOPES=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/client-scopes")
|
||||||
|
|
||||||
|
echo "$REQUESTED_SCOPES" | while read -r SCOPE_NAME; do
|
||||||
|
[ -z "$SCOPE_NAME" ] && continue
|
||||||
|
SCOPE_ID=$(echo "$ALL_SCOPES" | jq -r --arg name "$SCOPE_NAME" '.[] | select(.name == $name) | .id // empty')
|
||||||
|
if [ -z "$SCOPE_ID" ]; then
|
||||||
|
echo " WARNING: Scope '${SCOPE_NAME}' not found in realm, skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
SC_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
-X PUT \
|
||||||
|
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/default-client-scopes/${SCOPE_ID}")
|
||||||
|
if [ "$SC_CODE" = "204" ] || [ "$SC_CODE" = "200" ]; then
|
||||||
|
echo " Assigned scope '${SCOPE_NAME}'"
|
||||||
|
else
|
||||||
|
echo " WARNING: Failed to assign scope '${SCOPE_NAME}' (HTTP ${SC_CODE})"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# Sync credentials to target namespace
|
# Sync credentials to target namespace
|
||||||
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY"
|
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY"
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,112 @@
|
|||||||
ingress:
|
ingress:
|
||||||
hostname: id.forteapps.net
|
hostname: id.forteapps.net
|
||||||
|
|
||||||
|
extraEnvVars:
|
||||||
|
- name: KC_FEATURES
|
||||||
|
value: "token-exchange:v1,admin-fine-grained-authz:v1"
|
||||||
|
|
||||||
|
keycloakConfigCli:
|
||||||
|
enabled: true
|
||||||
|
extraEnvVars:
|
||||||
|
- name: IMPORT_VAR_SUBSTITUTION_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: MS_IDP_CLIENT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: microsoft-idp-credentials
|
||||||
|
key: MS_IDP_CLIENT_SECRET
|
||||||
|
configuration:
|
||||||
|
microsoft-idp.json: |
|
||||||
|
{
|
||||||
|
"realm": "forte",
|
||||||
|
"authenticationFlows": [
|
||||||
|
{
|
||||||
|
"alias": "auto-link-first-broker-login",
|
||||||
|
"description": "Auto-link IdP accounts to existing users by email",
|
||||||
|
"providerId": "basic-flow",
|
||||||
|
"topLevel": true,
|
||||||
|
"builtIn": false,
|
||||||
|
"authenticationExecutions": [
|
||||||
|
{
|
||||||
|
"authenticator": "idp-create-user-if-unique",
|
||||||
|
"authenticatorFlow": false,
|
||||||
|
"requirement": "ALTERNATIVE",
|
||||||
|
"priority": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"authenticator": "idp-auto-link",
|
||||||
|
"authenticatorFlow": false,
|
||||||
|
"requirement": "ALTERNATIVE",
|
||||||
|
"priority": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identityProviders": [
|
||||||
|
{
|
||||||
|
"alias": "forte-entra",
|
||||||
|
"displayName": "Forte Entra",
|
||||||
|
"providerId": "microsoft",
|
||||||
|
"enabled": true,
|
||||||
|
"trustEmail": true,
|
||||||
|
"firstBrokerLoginFlowAlias": "auto-link-first-broker-login",
|
||||||
|
"config": {
|
||||||
|
"clientId": "7995d2b5-b798-4caf-8da6-b00b78bb34d7",
|
||||||
|
"clientSecret": "$(env:MS_IDP_CLIENT_SECRET)",
|
||||||
|
"defaultScope": "openid email profile",
|
||||||
|
"tenantId": "063afd9e-5fcb-48d2-a769-ca31b0f5b443",
|
||||||
|
"syncMode": "IMPORT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": "forte-entra-graph",
|
||||||
|
"displayName": "Forte Entra (Graph)",
|
||||||
|
"providerId": "microsoft",
|
||||||
|
"enabled": true,
|
||||||
|
"storeToken": true,
|
||||||
|
"trustEmail": true,
|
||||||
|
"firstBrokerLoginFlowAlias": "auto-link-first-broker-login",
|
||||||
|
"config": {
|
||||||
|
"clientId": "7995d2b5-b798-4caf-8da6-b00b78bb34d7",
|
||||||
|
"clientSecret": "$(env:MS_IDP_CLIENT_SECRET)",
|
||||||
|
"defaultScope": "openid email profile User.Read Mail.Send",
|
||||||
|
"tenantId": "063afd9e-5fcb-48d2-a769-ca31b0f5b443",
|
||||||
|
"syncMode": "IMPORT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"identityProviderMappers": [
|
||||||
|
{
|
||||||
|
"name": "forte-entra-email",
|
||||||
|
"identityProviderAlias": "forte-entra",
|
||||||
|
"identityProviderMapper": "hardcoded-attribute-idp-mapper",
|
||||||
|
"config": {
|
||||||
|
"syncMode": "INHERIT",
|
||||||
|
"attribute": "emailVerified",
|
||||||
|
"attribute.value": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forte-entra-graph-email",
|
||||||
|
"identityProviderAlias": "forte-entra-graph",
|
||||||
|
"identityProviderMapper": "hardcoded-attribute-idp-mapper",
|
||||||
|
"config": {
|
||||||
|
"syncMode": "INHERIT",
|
||||||
|
"attribute": "emailVerified",
|
||||||
|
"attribute.value": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"roles": {
|
||||||
|
"realm": [
|
||||||
|
{
|
||||||
|
"name": "default-roles-forte",
|
||||||
|
"composites": {
|
||||||
|
"client": {
|
||||||
|
"broker": ["read-token"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user