Skip to content

Commit d15fa8d

Browse files
committed
feat(delve): Implement Phase 3 - Keycloak OIDC SSO and internal TLS re-encrypt
- Added OIDC configuration options in group_vars and defaults. - Created tasks for provisioning internal TLS certificates and configuring Keycloak OIDC client. - Updated deployment and Helm chart templates to support OIDC and TLS. - Enhanced README with details on OIDC integration and its phases. - Modified OpenBao ACL policies to include OIDC paths. - Introduced new templates for VaultStaticSecret to sync OIDC credentials. - Updated integration plan documentation to reflect Phase 3 implementation.
1 parent 419e765 commit d15fa8d

13 files changed

Lines changed: 718 additions & 30 deletions

File tree

ansible/inventories/development/group_vars/all.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ delve_vso_sa_name: delve-vso
5858
delve_openbao_policy_name: delve-vso
5959
delve_openbao_k8s_role: delve-vso
6060
delve_openbao_db_path: delve/db
61+
delve_openbao_oidc_path: delve/oidc
6162

6263
# Toggle for the delve role (mirrors keycloak_enabled). Default on for dev.
6364
delve_enabled: true

ansible/roles/delve/README.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# delve
22

33
Deploys the [Delve](https://github.com/McIndi/delve) audit-search platform
4-
(Django 5 + DRF) into the armory cluster. This is **Phase 1** of the
5-
[Delve audit-search integration plan](../../../doc/handoffs/delve-audit-integration-plan.md):
6-
Delve running on Postgres at `https://delve.armory.local`, authenticating with a
7-
local Django superuser. Keycloak SSO and machine ingestion land in later phases.
4+
(Django 5 + DRF) into the armory cluster, per the
5+
[Delve audit-search integration plan](../../../doc/handoffs/delve-audit-integration-plan.md).
6+
**Phase 1** stood Delve up on Postgres at `https://delve.armory.local` with
7+
local Django auth; **Phase 3** (this role's `pki.yml` + `oidc_client.yml`) adds
8+
Keycloak human SSO and internal-TLS ingress re-encrypt. Machine ingestion lands
9+
in Phase 2.
810

911
## What it does
1012

@@ -13,10 +15,23 @@ local Django superuser. Keycloak SSO and machine ingestion land in later phases.
1315
`delve` namespace as `delve-db-secret`, then provisions a single-replica
1416
Postgres StatefulSet (role-owned, **not** in the chart) and waits for it to
1517
be Ready.
16-
2. `deploy.yml` — provisions the external ingress TLS certificate, maps the
17-
ingress host locally, renders the chart values, and releases the vendored
18-
`charts/delve` Helm chart (`delve-web`, `delve-worker`, and a pre-install
19-
`migrate` hook).
18+
2. `pki.yml` *(Phase 3, gated on `delve_oidc_enabled`)* — issues the internal
19+
TLS cert (`delve-internal-tls`, from `openbao-pki-internal`) the web pod
20+
serves HTTPS from for ingress re-encrypt.
21+
3. `oidc_client.yml` *(Phase 3, gated on `delve_oidc_enabled`)* — creates/updates
22+
the confidential `delve` Keycloak client (redirect URI
23+
`https://delve.armory.local/oidc/callback/`), ensures a `groups`
24+
group-membership mapper, persists the credentials to `secret/delve/oidc`, and
25+
VSO-syncs them into the `delve` namespace as `delve-oidc` **before** the Helm
26+
release. Logins are the shared `armory` realm users; the Delve-side
27+
`DelveOIDCBackend` maps `armory-admins`→superuser, `armory-operators`→staff,
28+
`armory-viewers`→read.
29+
4. `deploy.yml` — provisions the external ingress TLS certificate, maps the
30+
ingress host locally, resolves the issuer host to the ingress IP (pod
31+
`hostAlias`), renders the chart values (flipping `oidc.enabled`/`tls.enabled`
32+
and the `backend-protocol: HTTPS` annotation when SSO is on), and releases the
33+
vendored `charts/delve` Helm chart (`delve-web`, `delve-worker`, and a
34+
pre-install `migrate` hook).
2035

2136
## Cross-role wiring (not in this role)
2237

@@ -25,12 +40,12 @@ These live elsewhere because role defaults are invisible across roles
2540

2641
- **`inventories/development/group_vars/all.yml`**`delve_namespace`,
2742
`delve_vso_sa_name`, `delve_openbao_policy_name`, `delve_openbao_k8s_role`,
28-
`delve_openbao_db_path`, `delve_enabled`, and `delve` in
29-
`trust_manager_internal_ca_target_namespaces`.
43+
`delve_openbao_db_path`, `delve_openbao_oidc_path`, `delve_enabled`, and
44+
`delve` in `trust_manager_internal_ca_target_namespaces`.
3045
- **`roles/openbao`**`delve` is in `openbao_provisioner_kv_prefixes`
3146
(defaults), and `consumer_wiring.yml` writes the `delve-vso` ACL policy and
3247
Kubernetes auth role that let VSO in the `delve` namespace read
33-
`secret/delve/db`.
48+
`secret/delve/db` and `secret/delve/oidc`.
3449

3550
## Deviation from the plan
3651

ansible/roles/delve/defaults/main.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,52 @@ delve_vault_auth_name: delve-vaultauth
8080
# SA and delve-vso).
8181
delve_pod_sa_name: delve
8282

83+
# --- Phase 3: human SSO (Keycloak OIDC) + internal TLS re-encrypt ------------
84+
# Mirrors the headlamp role's oidc_client.yml / pki.yml. The KV path for the
85+
# OIDC credential (delve_openbao_oidc_path) and the VSO policy/role live in
86+
# group_vars/all.yml so the openbao role can grant read on them.
87+
88+
# Keycloak admin API coordinates (used to create/update the delve client).
89+
delve_keycloak_enabled: true
90+
delve_keycloak_realm: armory
91+
delve_keycloak_namespace: keycloak
92+
delve_keycloak_service_name: keycloak-service
93+
delve_keycloak_service_https_port: 8443
94+
delve_keycloak_internal_fqdn: "{{ delve_keycloak_service_name }}.{{ delve_keycloak_namespace }}.svc.cluster.local"
95+
delve_keycloak_internal_api_url: "https://{{ delve_keycloak_internal_fqdn }}:{{ delve_keycloak_service_https_port }}"
96+
delve_keycloak_admin_secret_name: keycloak-bootstrap-admin
97+
delve_keycloak_admin_password_key: password
98+
delve_keycloak_admin_username_from_secret: true
99+
delve_keycloak_admin_username: admin
100+
101+
# OIDC relying-party (confidential, standard flow) for browser SSO.
102+
delve_oidc_client_id: delve
103+
# If empty, a strong secret is generated once and persisted to OpenBao.
104+
delve_oidc_client_secret: ""
105+
delve_oidc_issuer_url: "{{ (keycloak_public_base_url | default(lookup('ansible.builtin.env', 'ARMORY_PUBLIC_BASE_URL') | default('https://armory.local', true))) + '/realms/' + delve_keycloak_realm }}"
106+
delve_oidc_scopes: "openid profile email"
107+
delve_oidc_ca_pem_url: "{{ delve_openbao_api_addr }}/v1/{{ openbao_pki_external_mount | default('pki-ext') }}/ca/pem"
108+
delve_oidc_secret_name: delve-oidc
109+
# mozilla-django-oidc callback registered with Keycloak.
110+
delve_oidc_redirect_path: /oidc/callback/
111+
112+
# Pod-level OIDC issuer hostname resolution (hostAlias). The web pod must reach
113+
# the public issuer host in-cluster; resolve it to the ingress controller IP.
114+
delve_oidc_resolver_host: "{{ delve_oidc_issuer_url | urlsplit('hostname') }}"
115+
delve_oidc_resolver_service_namespace: ingress-nginx
116+
delve_oidc_resolver_service_name: ingress-nginx-controller
117+
118+
# Internal TLS (cert-manager + OpenBao internal ClusterIssuer) for ingress
119+
# re-encrypt. The web pod serves HTTPS (cherrypy DELVE_SSL_*) from this cert and
120+
# the ingress backend-protocol becomes HTTPS.
121+
delve_oidc_enabled: "{{ delve_keycloak_enabled | bool }}"
122+
delve_internal_tls_secret_name: delve-internal-tls
123+
delve_internal_tls_issuer_name: openbao-pki-internal
124+
delve_internal_fqdn: "{{ delve_release_name }}-web.{{ delve_namespace }}.svc.cluster.local"
125+
delve_internal_tls_mount_path: /etc/delve-tls
126+
delve_oidc_ca_mount_path: /etc/delve-oidc
127+
delve_oidc_ca_file_name: oidc-ca.pem
128+
83129
# Optional chart value overrides merged on top of role-managed values.
84130
delve_chart_values_overrides: {}
85131

ansible/roles/delve/tasks/deploy.yml

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,45 @@
5555
mode: "0644"
5656
become: true
5757

58+
- name: Resolve in-cluster ingress IP for Delve OIDC issuer hostname
59+
ansible.builtin.command:
60+
cmd: >-
61+
k3s kubectl get svc {{ delve_oidc_resolver_service_name }}
62+
--namespace {{ delve_oidc_resolver_service_namespace }}
63+
-o jsonpath={.spec.clusterIP}
64+
environment:
65+
KUBECONFIG: "{{ delve_kubeconfig_path }}"
66+
register: _delve_oidc_resolver_ip_result
67+
changed_when: false
68+
failed_when: false
69+
when:
70+
- not ansible_check_mode
71+
- delve_oidc_enabled | bool
72+
73+
- name: Build Delve OIDC issuer hostAliases
74+
ansible.builtin.set_fact:
75+
_delve_oidc_host_aliases: >-
76+
{{
77+
[{'ip': (_delve_oidc_resolver_ip_result.stdout | default('') | trim),
78+
'hostnames': [delve_oidc_resolver_host]}]
79+
if ((delve_oidc_enabled | bool)
80+
and (_delve_oidc_resolver_ip_result.stdout | default('') | trim | length) > 0)
81+
else []
82+
}}
83+
changed_when: false
84+
85+
- name: Build Delve ingress annotations
86+
ansible.builtin.set_fact:
87+
_delve_ingress_annotations: >-
88+
{{
89+
{'nginx.ingress.kubernetes.io/ssl-redirect': 'true'}
90+
| combine(
91+
{'nginx.ingress.kubernetes.io/backend-protocol': 'HTTPS'}
92+
if (delve_oidc_enabled | bool) else {}
93+
)
94+
}}
95+
changed_when: false
96+
5897
- name: Build Delve chart values payload
5998
ansible.builtin.set_fact:
6099
_delve_chart_values: >-
@@ -82,7 +121,17 @@
82121
'port': 8000
83122
},
84123
'oidc': {
85-
'enabled': False
124+
'enabled': (delve_oidc_enabled | bool),
125+
'secretName': delve_oidc_secret_name,
126+
'scopes': delve_oidc_scopes,
127+
'caMountPath': delve_oidc_ca_mount_path,
128+
'caFileName': delve_oidc_ca_file_name,
129+
'issuerHostAliases': (_delve_oidc_host_aliases | default([]))
130+
},
131+
'tls': {
132+
'enabled': (delve_oidc_enabled | bool),
133+
'secretName': delve_internal_tls_secret_name,
134+
'mountPath': delve_internal_tls_mount_path
86135
},
87136
'ingress': {
88137
'enabled': True,
@@ -91,9 +140,7 @@
91140
'path': delve_ingress_path,
92141
'pathType': delve_ingress_path_type,
93142
'tlsSecretName': delve_tls_secret_name,
94-
'annotations': {
95-
'nginx.ingress.kubernetes.io/ssl-redirect': 'true'
96-
}
143+
'annotations': _delve_ingress_annotations
97144
}
98145
} | combine(delve_chart_values_overrides | default({}), recursive=True)
99146
}}

ansible/roles/delve/tasks/main.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@
1111
ansible.builtin.import_tasks: db.yml
1212
tags: [delve, delve_db, openbao, vso]
1313

14+
- name: Provision Delve internal TLS certificate (re-encrypt)
15+
ansible.builtin.import_tasks: pki.yml
16+
tags: [delve, delve_pki, cert_manager]
17+
when: delve_oidc_enabled | bool
18+
19+
- name: Configure Delve Keycloak OIDC client and sync credentials
20+
ansible.builtin.import_tasks: oidc_client.yml
21+
tags: [delve, delve_oidc, keycloak, openbao, vso]
22+
when: delve_oidc_enabled | bool
23+
1424
- name: Deploy Delve via Helm
1525
ansible.builtin.import_tasks: deploy.yml
1626
tags: [delve, delve_deploy, openbao, vso]

0 commit comments

Comments
 (0)