This commit is contained in:
2026-10-23 20:28:35 -04:00
parent 082fa31535
commit d19aef28eb
4 changed files with 130 additions and 24 deletions
+45 -6
View File
@@ -22,10 +22,49 @@ Ansible Vault-encrypted vars file:
- `openldap_readonly_password`: existing LDAP search-account password.
- `matrix_dex_oidc_client_id` and `matrix_dex_oidc_client_secret`: existing MAS client.
- `postgresql_admin_password`: needed only if deliberately enabling DB management.
- Optional `www_dex_client_secret`: enables the website client and its separate
`www-ldap`/`entryUUID` connector, without changing legacy identities. It must
match the website's `DEX_CLIENT_SECRET`. Leave absent to preserve Matrix-only
behavior. Review and declare any other deployed clients before rollout.
- `www_dex_client_secret`: matches the website's `DEX_CLIENT_SECRET`.
- `gitea_dex_client_secret`, `discourse_dex_client_secret`,
`peertube_dex_client_secret`, `mastodon_dex_client_secret`, and
`nextcloud_dex_client_secret`: one distinct confidential-client secret per app.
The site playbook now registers all seven clients explicitly in
`dex_static_clients` and disables the Matrix convenience client to avoid a
duplicate. None of these secrets is optional unless you deliberately remove its
client from your inventory/playbook. Existing secrets must be preserved, not
regenerated on each deploy. No real secrets are included in source control.
### Application configuration required after client registration
All applications use issuer **https://dex.eom.dev/** and discovery document
**https://dex.eom.dev/.well-known/openid-configuration**, with `openid profile
email` scopes (and `groups` only when used). Configure the matching ID/secret in
each service. Client creation in Dex **does not enable SSO in the application**.
| Application | Client ID | Exact callback / prerequisite |
| --- | --- | --- |
| Matrix Auth Service | Existing `matrix_dex_oidc_client_id` | Original `https://mas.eom.dev/upstream/callback/01K96AQEZKKABW34PY3R6BVNJ4` retained |
| www | `www-membership` | `https://www.eom.dev/members/oidc/callback/` |
| Gitea | `gitea` | `https://gitea.eom.dev/user/oauth2/dex/callback`; add OpenID Connect authentication source named **dex** |
| Discourse | `discourse` | `https://discourse.eom.dev/auth/oidc/callback`; enable discourse-openid-connect and confirm callback for your installed plugin version |
| PeerTube | `peertube` | `https://peertube.eom.dev/plugins/auth-openid-connect/router/code-cb`; install/configure official auth-openid-connect plugin |
| Mastodon | `mastodon` | `https://mastodon.eom.dev/auth/auth/openid_connect/callback`; retained from its configured OIDC_REDIRECT_URI; verify installed OIDC implementation supports that route |
| Nextcloud | `nextcloud` | `https://nextcloud.eom.dev/index.php/apps/user_oidc/code`; install **user_oidc**, not the OIDC provider app or oidc_login |
Callback paths are not interchangeable. Gitea's source name affects its URL;
Nextcloud's admin screen supplies the exact callback, which can omit index.php
when URL rewriting is enabled. If using **oidc_login** instead, explicitly replace
the callback with its `/apps/oidc_login/oidc` route rather than enabling both by
guesswork. The existing Mastodon deployment points to Google; changing it to Dex
is a separate app-side migration, not another set of automatically active OIDC
environment variables. Keep a tested administrator fallback and avoid automatic
email-only account linking when migrating existing users.
The website's `www-ldap` connector uses entryUUID while the legacy `ldap`
connector uses uid. Those produce different subjects for the same directory
entry: do not switch existing accounts between connectors without an explicit
linking/migration plan. The legacy connector still filters for Dex Users; merely
registering an application does not bypass this restriction or grant paid room
access. Each relying application must implement its own authorization policy.
Run the playbook on the controller with its kubeconfig and this role available
in `ANSIBLE_ROLES_PATH`. Load your encrypted variables with Ansible's `--extra-vars`
@@ -70,8 +109,8 @@ The site playbook maps legacy inventory variables explicitly:
| `openldap_readonly_password` | `dex_ldap_bind_password` |
| `matrix_dex_oidc_client_id` / `matrix_dex_oidc_client_secret` | Existing Matrix Auth Service client |
The site playbook enables the existing Matrix client. Its original
redirect URI is explicit in `dex_matrix_redirect_uris`; override to match your
The site playbook explicitly includes the existing Matrix client. Its original
redirect URI is explicit in `dex_static_clients`; override to match your
actual MAS configuration. Other clients previously added directly to Helm must
be declared in `dex_static_clients` before running this role. Helm values are
reset to this role's complete values: undeclared manual Helm customizations are
+48 -8
View File
@@ -46,14 +46,54 @@
dex_ldap_group_user_attribute: cn
dex_ldap_group_filter: "(objectClass=groupOfUniqueNames)"
dex_matrix_client_enabled: true
dex_matrix_redirect_uris:
- https://mas.eom.dev/upstream/callback/01K96AQEZKKABW34PY3R6BVNJ4
# Optional website integration. Set www_dex_client_secret in Vault to enable;
# it must equal the membership application's DEX_CLIENT_SECRET.
dex_static_clients: "{{ [{'id': 'www-membership', 'name': 'eom.dev Membership', 'secret': www_dex_client_secret, 'redirectURIs': ['https://www.eom.dev/members/oidc/callback/']}] if (www_dex_client_secret | default('') | length > 0) else [] }}"
dex_additional_connectors: "{{ [_dex_eom_www_connector] if (www_dex_client_secret | default('') | length > 0) else [] }}"
# All clients are declared once below; don't also emit the convenience client.
dex_matrix_client_enabled: false
# Credentials are separate Vault/inventory variables, shared only with the
# corresponding application. No secret is generated or rotated by this play.
dex_static_clients:
- id: "{{ matrix_dex_oidc_client_id }}"
name: Matrix Auth Service
secret: "{{ matrix_dex_oidc_client_secret }}"
redirectURIs:
- https://mas.eom.dev/upstream/callback/01K96AQEZKKABW34PY3R6BVNJ4
- id: www-membership
name: eom.dev Membership
secret: "{{ www_dex_client_secret }}"
redirectURIs:
- https://www.eom.dev/members/oidc/callback/
- id: gitea
name: Gitea
secret: "{{ gitea_dex_client_secret }}"
# Authentication source in Gitea must be named exactly "dex".
redirectURIs:
- https://gitea.eom.dev/user/oauth2/dex/callback
- id: discourse
name: Discourse
secret: "{{ discourse_dex_client_secret }}"
# discourse-openid-connect strategy name is oidc.
redirectURIs:
- https://discourse.eom.dev/auth/oidc/callback
- id: peertube
name: PeerTube
secret: "{{ peertube_dex_client_secret }}"
# Requires peertube-plugin-auth-openid-connect.
redirectURIs:
- https://peertube.eom.dev/plugins/auth-openid-connect/router/code-cb
- id: mastodon
name: Mastodon
secret: "{{ mastodon_dex_client_secret }}"
# Matches OIDC_REDIRECT_URI in the existing Mastodon deployment.
redirectURIs:
- https://mastodon.eom.dev/auth/auth/openid_connect/callback
- id: nextcloud
name: Nextcloud
secret: "{{ nextcloud_dex_client_secret }}"
# This chooses the user_oidc app, not oidc_login or the OIDC provider app.
# Override from its admin screen if your installation rewrites index.php.
redirectURIs:
- https://nextcloud.eom.dev/index.php/apps/user_oidc/code
dex_additional_connectors:
- "{{ _dex_eom_www_connector }}"
_dex_eom_www_connector:
type: ldap
id: www-ldap
+17
View File
@@ -13,6 +13,23 @@
fail_msg: Configure database/LDAP credentials, client secrets and explicit database chart version if managing PostgreSQL.
no_log: true
- name: Validate explicit OIDC clients
ansible.builtin.assert:
that:
- item.id | default('') | length > 0
- item.name | default('') | length > 0
- item.redirectURIs | default([]) | length > 0
- item.public | default(false) or ((item.secret | default('') | length > 0) != (item.secretEnv | default('') | length > 0))
fail_msg: Every confidential client needs an ID, name, exact callback URI and either a secret or secretEnv, but not both.
loop: "{{ dex_static_clients }}"
no_log: true
- name: Reject duplicate explicit client IDs
ansible.builtin.assert:
that:
- dex_static_clients | map(attribute='id') | unique | length == dex_static_clients | length
no_log: true
- name: Ensure Dex namespace
kubernetes.core.k8s:
state: present
+20 -10
View File
@@ -8,31 +8,41 @@ from kubernetes_validate import validate
from ansible.parsing.dataloader import DataLoader
from ansible.template import Templar
# Resolve the site's actual workflow variables with fixture credentials only;
# verify both Matrix-only migration and opt-in website integration.
# Resolve the site's actual workflow with fixture credentials only.
defaults = yaml.safe_load(Path('defaults/main.yml').read_text())
site = yaml.safe_load(Path('playbooks/deploy-eom.yml').read_text())[0]['vars']
for website_enabled in (False, True):
for fixture_run in (1,):
variables = {
**defaults, **site,
'dex_admin_password': 'fixture-db',
'openldap_readonly_password': 'fixture-ldap',
'matrix_dex_oidc_client_id': 'matrix-fixture',
'matrix_dex_oidc_client_secret': 'fixture-matrix',
'www_dex_client_secret': 'fixture-www',
'gitea_dex_client_secret': 'fixture-gitea',
'discourse_dex_client_secret': 'fixture-discourse',
'peertube_dex_client_secret': 'fixture-peertube',
'mastodon_dex_client_secret': 'fixture-mastodon',
'nextcloud_dex_client_secret': 'fixture-nextcloud',
}
if website_enabled:
variables['www_dex_client_secret'] = 'fixture-www'
resolved = Templar(loader=DataLoader(), variables=variables).template(site)
assert resolved['dex_issuer'] == 'https://dex.eom.dev/'
assert resolved['dex_postgres_password'] == 'fixture-db'
assert resolved['dex_ldap_group_user_attribute'] == 'cn'
assert resolved['dex_theme_enabled'] is True
assert resolved['dex_postgres_manage'] is False
assert bool(resolved['dex_static_clients']) == website_enabled
assert bool(resolved['dex_additional_connectors']) == website_enabled
if website_enabled:
assert resolved['dex_static_clients'][0]['secret'] == 'fixture-www'
assert resolved['dex_additional_connectors'][0]['config']['userSearch']['idAttr'] == 'entryUUID'
clients = {client['id']: client for client in resolved['dex_static_clients']}
assert len(clients) == len(resolved['dex_static_clients']) == 7
assert resolved['dex_matrix_client_enabled'] is False
assert clients['matrix-fixture']['redirectURIs'] == ['https://mas.eom.dev/upstream/callback/01K96AQEZKKABW34PY3R6BVNJ4']
assert clients['www-membership']['secret'] == 'fixture-www'
assert clients['gitea']['redirectURIs'] == ['https://gitea.eom.dev/user/oauth2/dex/callback']
assert clients['discourse']['redirectURIs'] == ['https://discourse.eom.dev/auth/oidc/callback']
assert clients['peertube']['redirectURIs'] == ['https://peertube.eom.dev/plugins/auth-openid-connect/router/code-cb']
assert clients['mastodon']['redirectURIs'] == ['https://mastodon.eom.dev/auth/auth/openid_connect/callback']
assert clients['nextcloud']['redirectURIs'] == ['https://nextcloud.eom.dev/index.php/apps/user_oidc/code']
assert all(client['secret'] and len(client['redirectURIs']) == 1 for client in clients.values())
assert resolved['dex_additional_connectors'][0]['config']['userSearch']['idAttr'] == 'entryUUID'
values = yaml.safe_load(Path('/tmp/dex-values.yml').read_text())
config = values['config']