From 9a31efc38e549c1954261ca1cebcfd4a3576a5bf Mon Sep 17 00:00:00 2001 From: Eric Meehan Date: Mon, 26 Oct 2026 18:54:58 -0400 Subject: [PATCH] Deploy --- README.md | 31 ++++++++++++++++--------------- defaults/main.yml | 2 +- playbooks/deploy-eom.yml | 31 +++---------------------------- templates/values.yml.j2 | 13 ++++--------- tests/example-vars.yml | 32 ++++++-------------------------- tests/validate.py | 4 +++- 6 files changed, 33 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index b00918b..9f64550 100755 --- a/README.md +++ b/README.md @@ -59,12 +59,13 @@ 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. +The eom.dev deployment uses one `ldap` connector for legacy POSIX accounts and +website `inetOrgPerson` accounts. It retains `idAttr: uid`, so existing OIDC +subjects remain stable. The website resolves the signed UID to LDAP and anchors +its account link to immutable `entryUUID`. Its combined user filter admits +`inetOrgPerson` entries throughout the configured search base; review that +authentication scope before deployment. Each relying application must still +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` @@ -145,8 +146,9 @@ passwords to network observers. Restrict it to the trusted internal network with your cluster's policies; no public LDAP exposure is created by this role. The generic connector uses `inetOrgPerson` and DN-based group matching. The site -playbook retains ID `ldap`, `idAttr: uid`, its POSIX/group filter, -and `userAttr: cn` group matching. For `uniqueMember` values containing full DNs, +playbook retains ID `ldap`, `idAttr: uid`, its POSIX/Dex Users filter branch plus +website `inetOrgPerson` accounts, and `userAttr: cn` group matching. For +`uniqueMember` values containing full DNs, set `dex_ldap_group_user_attribute: DN` deliberately after testing. Changing connector ID or idAttr changes OIDC subjects and can detach existing accounts. @@ -173,13 +175,12 @@ support `$ENV_NAME` references, such as `bindPW: $DEX_LDAP_BIND_PASSWORD`. The database password remains a protected Helm/Dex config value; it is not assumed to support the same connector substitution. Never commit actual secrets. -[tests/example-vars.yml](tests/example-vars.yml) shows a www client and separate -`www-ldap` connector using `entryUUID`, `inetOrgPerson` and DN group matching. -This avoids breaking legacy subjects while supporting new free website users. -The Secret's `WWW_DEX_CLIENT_SECRET` must match the website's `DEX_CLIENT_SECRET`; -set the website's `DEX_CONNECTOR_ID=www-ldap` and exact matching issuer. No new -client or connector is silently enabled by default. Verify that this connector's -search base includes the website account OU before enabling public registration. +[tests/example-vars.yml](tests/example-vars.yml) shows a www client using the +single existing `ldap` connector. The Secret's `WWW_DEX_CLIENT_SECRET` must match +the website's `DEX_CLIENT_SECRET`; set `DEX_CONNECTOR_ID=ldap` and the exact +matching issuer. No client or additional connector is silently enabled by +default. Verify the combined search filter and scope before enabling public +registration. ### Theme diff --git a/defaults/main.yml b/defaults/main.yml index 1df1d8a..16c07d2 100755 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -52,7 +52,7 @@ dex_matrix_client_enabled: false dex_matrix_redirect_uris: [] # Full objects (id, name, secret or secretEnv, redirectURIs). Use Vault for secrets. dex_static_clients: [] -# Additional connectors let www use entryUUID without changing legacy subjects. +# Optional extra connectors; the eom.dev site uses its existing LDAP connector. dex_additional_connectors: [] # Optional environment Secret for secretEnv and $ENV connector credentials. dex_env_secret_name: "" diff --git a/playbooks/deploy-eom.yml b/playbooks/deploy-eom.yml index 23a0747..ec06754 100755 --- a/playbooks/deploy-eom.yml +++ b/playbooks/deploy-eom.yml @@ -31,7 +31,8 @@ dex_postgres_password: "{{ dex_admin_password }}" dex_postgres_admin_password: "{{ postgresql_admin_password | default('') }}" - # Explicitly preserve internal plaintext LDAP and legacy OIDC subjects. + # Keep the existing connector ID and UID subject mapping. The filter also + # admits website inetOrgPerson accounts so there is one Dex login option. dex_ldap_host: openldap.openldap.svc.cluster.local dex_ldap_insecure_no_ssl: true dex_ldap_start_tls: false @@ -40,7 +41,7 @@ dex_ldap_connector_id: ldap dex_ldap_connector_name: eom.dev account dex_ldap_user_base_dn: dc=eom,dc=dev - dex_ldap_user_filter: "(&(objectClass=posixAccount)(memberOf=cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev))" + dex_ldap_user_filter: "(|(&(objectClass=posixAccount)(memberOf=cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev))(objectClass=inetOrgPerson))" dex_ldap_id_attribute: uid dex_ldap_name_attribute: givenName dex_ldap_group_base_dn: dc=eom,dc=dev @@ -93,31 +94,5 @@ # 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 - name: eom.dev Membership - config: - host: openldap.openldap.svc.cluster.local - insecureNoSSL: true - bindDN: cn=readonly,dc=eom,dc=dev - bindPW: "{{ openldap_readonly_password }}" - userSearch: - baseDN: ou=people,dc=eom,dc=dev - filter: "(objectClass=inetOrgPerson)" - username: uid - idAttr: entryUUID - emailAttr: mail - nameAttr: cn - preferredUsernameAttr: uid - groupSearch: - baseDN: ou=groups,dc=eom,dc=dev - filter: "(objectClass=groupOfUniqueNames)" - userMatchers: - - userAttr: DN - groupAttr: uniqueMember - nameAttr: cn roles: - ansible-role-dex diff --git a/templates/values.yml.j2 b/templates/values.yml.j2 index aad9b32..c08cba5 100755 --- a/templates/values.yml.j2 +++ b/templates/values.yml.j2 @@ -83,12 +83,8 @@ config: {% for connector in dex_additional_connectors %} - {{ connector | to_json }} {% endfor %} -volumes: - # The official entrypoint renders its config into /tmp before starting Dex. - - name: dex-tmp - emptyDir: - medium: Memory - sizeLimit: 16Mi +volumes:{% if not dex_theme_enabled and not dex_ca_secret_name and not dex_extra_volumes %} []{% endif %} + {% if dex_theme_enabled %} - name: theme configMap: @@ -102,9 +98,8 @@ volumes: {% for volume in dex_extra_volumes %} - {{ volume | to_json }} {% endfor %} -volumeMounts: - - name: dex-tmp - mountPath: /tmp +volumeMounts:{% if not dex_theme_enabled and not dex_ca_secret_name and not dex_extra_volume_mounts %} []{% endif %} + {% if dex_theme_enabled %} - name: theme mountPath: /srv/dex/web/themes/light/styles.css diff --git a/tests/example-vars.yml b/tests/example-vars.yml index b6eea6b..955e269 100755 --- a/tests/example-vars.yml +++ b/tests/example-vars.yml @@ -20,29 +20,9 @@ dex_static_clients: redirectURIs: - https://www.eom.dev/members/oidc/callback/ -# Additional connector: don't alter legacy ldap/idAttr uid, which changes sub -# identifiers for existing applications. This connector admits free web accounts. -dex_additional_connectors: - - type: ldap - id: www-ldap - name: eom.dev Membership - config: - host: openldap.openldap.svc.cluster.local - insecureNoSSL: true - bindDN: cn=readonly,dc=eom,dc=dev - bindPW: $DEX_LDAP_BIND_PASSWORD - userSearch: - baseDN: ou=people,dc=eom,dc=dev - filter: "(objectClass=inetOrgPerson)" - username: uid - idAttr: entryUUID - emailAttr: mail - nameAttr: cn - preferredUsernameAttr: uid - groupSearch: - baseDN: ou=groups,dc=eom,dc=dev - filter: "(objectClass=groupOfUniqueNames)" - userMatchers: - - userAttr: DN - groupAttr: uniqueMember - nameAttr: cn +# One LDAP connector admits both legacy POSIX accounts and website accounts. +# Keep its ID and idAttr unchanged so existing OIDC subjects remain stable. +dex_ldap_connector_id: ldap +dex_ldap_id_attribute: uid +dex_ldap_user_base_dn: dc=eom,dc=dev +dex_ldap_user_filter: "(|(&(objectClass=posixAccount)(memberOf=cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev))(objectClass=inetOrgPerson))" diff --git a/tests/validate.py b/tests/validate.py index c6c3825..448c903 100755 --- a/tests/validate.py +++ b/tests/validate.py @@ -31,6 +31,8 @@ for fixture_run in (1,): assert resolved['dex_ldap_group_user_attribute'] == 'cn' assert resolved['dex_theme_enabled'] is True assert resolved['dex_postgres_manage'] is False + assert resolved['dex_additional_connectors'] == [] + assert resolved['dex_ldap_user_filter'] == '(|(&(objectClass=posixAccount)(memberOf=cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev))(objectClass=inetOrgPerson))' 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 @@ -42,7 +44,6 @@ for fixture_run in (1,): 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'] @@ -53,6 +54,7 @@ ldap = config['connectors'][0] assert ldap['id'] == 'ldap' assert ldap['config']['insecureNoSSL'] is True assert ldap['config']['startTLS'] is False +assert len(config['connectors']) == 1 assert ldap['config']['userSearch']['idAttr'] == 'uid' assert ldap['config']['groupSearch']['userMatchers'][0]['userAttr'] == 'DN' assert config['staticClients'][0]['id'] == 'matrix-fixture'