client cloner (#3)
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
Reviewed-on: #3 Reviewed-by: gitea_admin <admin@forteapps.net> Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com> Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
This commit was merged in pull request #3.
This commit is contained in:
@@ -123,6 +123,7 @@ launchpad/
|
||||
│ ├── replicaset-cleaner.yaml
|
||||
│ ├── default-ns-blocker.yaml
|
||||
│ ├── secret-cloner.yaml
|
||||
│ ├── keycloak-client-cloner.yaml
|
||||
│ └── auth-sidecar-injector.yaml
|
||||
│
|
||||
├── secrets/ # Application secrets (sealed)
|
||||
@@ -869,29 +870,44 @@ dind:
|
||||
- Gitea admin panel (`/admin/runners`) — runners show as Online
|
||||
- Create test workflow in `.gitea/workflows/test.yml` — job executes
|
||||
|
||||
### Keycloak Secret Syncer
|
||||
### Keycloak Client Registrar
|
||||
|
||||
**Type**: ArgoCD PostSync Job (deployed via Keycloak Helm chart `extraDeploy`)
|
||||
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
|
||||
**Namespace**: `keycloak`
|
||||
**Schedule**: `*/2 * * * *` (every 2 minutes)
|
||||
|
||||
**Purpose**: Automatically extracts Keycloak-generated client secrets and syncs them into Kubernetes Secrets in target namespaces. Eliminates the need to manually manage OIDC client secrets.
|
||||
**Purpose**: Handles two responsibilities:
|
||||
1. **Legacy sync** — extracts secrets from Keycloak clients with `k8s.secret.sync: "true"` attribute (same as former PostSync syncer)
|
||||
2. **Self-service registration** — processes config Secrets (cloned by Kyverno) to register new OIDC clients and sync their credentials
|
||||
|
||||
**How It Works**:
|
||||
1. Runs as an ArgoCD PostSync hook after Keycloak resources are healthy
|
||||
2. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret
|
||||
3. Queries all clients in the `forte` realm
|
||||
4. Filters clients with `k8s.secret.sync: "true"` attribute
|
||||
5. For each matching client, retrieves the auto-generated secret via Keycloak Admin API
|
||||
6. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute)
|
||||
7. Always writes a central copy to the `secrets` namespace (for external deployment retrieval)
|
||||
|
||||
*Legacy path (existing clients like Gitea):*
|
||||
1. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret
|
||||
2. Queries all clients in the `forte` realm
|
||||
3. Filters clients with `k8s.secret.sync: "true"` attribute
|
||||
4. For each matching client, retrieves the auto-generated secret via Keycloak Admin API
|
||||
5. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute)
|
||||
6. Always writes a central copy to the `secrets` namespace
|
||||
|
||||
*Self-service path (new clients):*
|
||||
1. Lists Secrets in `keycloak` namespace with label `keycloak.forteapps.net/client-config=true`
|
||||
2. For each config Secret, parses `client.json` and computes a config hash
|
||||
3. Skips if hash matches annotation and credential Secret already exists
|
||||
4. Creates or updates the Keycloak client via Admin API
|
||||
5. Fetches the generated client secret
|
||||
6. Upserts credential Secret in target namespace + central `secrets` namespace
|
||||
7. Annotates config Secret with sync status, config hash, and timestamp
|
||||
|
||||
**Resources**:
|
||||
- `ServiceAccount`: `keycloak-secret-syncer` (namespace: `keycloak`)
|
||||
- `ClusterRole`: `keycloak-secret-syncer` (secrets: get/create/update/patch; namespaces: get/list)
|
||||
- `ClusterRoleBinding`: `keycloak-secret-syncer`
|
||||
- `Job`: `keycloak-secret-syncer` (PostSync hook)
|
||||
- `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`)
|
||||
- `ClusterRole`: `keycloak-client-registrar` (secrets: get/list/create/update/patch; namespaces: get/list)
|
||||
- `ClusterRoleBinding`: `keycloak-client-registrar`
|
||||
- `CronJob`: `keycloak-client-registrar`
|
||||
|
||||
**Client Attributes** (set in `forte-realm.json`):
|
||||
**Kyverno Policy**: `keycloak-client-config-cloner` — clones labeled Secrets from app namespaces to `keycloak` namespace (see [Kyverno Policies](#kyverno-policies))
|
||||
|
||||
**Legacy Client Attributes** (set in `forte-realm.json`):
|
||||
|
||||
| Attribute | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
@@ -901,31 +917,68 @@ dind:
|
||||
| `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret |
|
||||
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret |
|
||||
|
||||
**Created Secret Format** (key names configurable via attributes):
|
||||
**Self-Service Config Secret Schema**:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: <k8s.secret.name>
|
||||
namespace: <k8s.secret.namespace>
|
||||
name: keycloak-client-<app>
|
||||
namespace: <app-namespace>
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: keycloak-secret-syncer
|
||||
keycloak.forteapps.net/client-config: "true"
|
||||
stringData:
|
||||
client.json: |
|
||||
{
|
||||
"clientId": "<app>",
|
||||
"name": "<App Name>",
|
||||
"redirectUris": ["https://<app>.forteapps.net/*"],
|
||||
"webOrigins": ["https://<app>.forteapps.net"],
|
||||
"defaultClientScopes": ["openid", "email", "profile"],
|
||||
"protocolMappers": [],
|
||||
"secret": {
|
||||
"namespace": "<app-namespace>",
|
||||
"name": "<app>-oidc-credentials",
|
||||
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Created Credential Secret Format**:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: <target-name>
|
||||
namespace: <target-namespace>
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: keycloak-client-registrar
|
||||
type: Opaque
|
||||
data:
|
||||
<client-id-key>: <base64-encoded client ID>
|
||||
<client-secret-key>: <base64-encoded client secret>
|
||||
```
|
||||
|
||||
**Config Secret Annotations** (set by registrar):
|
||||
|
||||
| Annotation | Description |
|
||||
|-----------|-------------|
|
||||
| `keycloak.forteapps.net/config-hash` | SHA-256 hash of client.json for change detection |
|
||||
| `keycloak.forteapps.net/sync-status` | `synced` or `error` |
|
||||
| `keycloak.forteapps.net/last-sync` | ISO 8601 timestamp of last successful sync |
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
# Check job status
|
||||
kubectl get jobs -n keycloak
|
||||
# Check CronJob status
|
||||
kubectl get cronjobs -n keycloak
|
||||
|
||||
# View syncer logs
|
||||
kubectl logs -n keycloak job/keycloak-secret-syncer
|
||||
# View latest registrar logs
|
||||
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
|
||||
|
||||
# Verify created secret
|
||||
kubectl get secret <name> -n <namespace> -o yaml
|
||||
|
||||
# Check config Secret annotations (self-service)
|
||||
kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.annotations}'
|
||||
```
|
||||
|
||||
**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client)
|
||||
@@ -1020,6 +1073,19 @@ spec:
|
||||
|
||||
**Label Requirement**: Secrets must have `allowedToBeCloned: "true"`
|
||||
|
||||
### Keycloak Client Config Cloner
|
||||
|
||||
**File**: `cluster-resources/policies/keycloak-client-cloner.yaml`
|
||||
|
||||
**Purpose**: Clones Secrets labeled `keycloak.forteapps.net/client-config: "true"` from app namespaces to the `keycloak` namespace. This allows apps to declare their OIDC client configuration in their own namespace, which the [Keycloak Client Registrar](#keycloak-client-registrar) then processes.
|
||||
|
||||
**Trigger**: Any Secret with label `keycloak.forteapps.net/client-config: "true"` created outside the `keycloak` namespace.
|
||||
|
||||
**Behavior**:
|
||||
- Generates a copy of the Secret in the `keycloak` namespace with the same 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
|
||||
|
||||
### Default Namespace Blocker
|
||||
|
||||
**File**: `cluster-resources/policies/default-ns-blocker.yaml`
|
||||
|
||||
Reference in New Issue
Block a user