This commit is contained in:
2026-10-27 16:04:33 -04:00
parent 6de8385829
commit 9a31efc38e
6 changed files with 33 additions and 80 deletions
+16 -15
View File
@@ -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 environment variables. Keep a tested administrator fallback and avoid automatic
email-only account linking when migrating existing users. email-only account linking when migrating existing users.
The website's `www-ldap` connector uses entryUUID while the legacy `ldap` The eom.dev deployment uses one `ldap` connector for legacy POSIX accounts and
connector uses uid. Those produce different subjects for the same directory website `inetOrgPerson` accounts. It retains `idAttr: uid`, so existing OIDC
entry: do not switch existing accounts between connectors without an explicit subjects remain stable. The website resolves the signed UID to LDAP and anchors
linking/migration plan. The legacy connector still filters for Dex Users; merely its account link to immutable `entryUUID`. Its combined user filter admits
registering an application does not bypass this restriction or grant paid room `inetOrgPerson` entries throughout the configured search base; review that
access. Each relying application must implement its own authorization policy. 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 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` 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. 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 The generic connector uses `inetOrgPerson` and DN-based group matching. The site
playbook retains ID `ldap`, `idAttr: uid`, its POSIX/group filter, playbook retains ID `ldap`, `idAttr: uid`, its POSIX/Dex Users filter branch plus
and `userAttr: cn` group matching. For `uniqueMember` values containing full DNs, 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 set `dex_ldap_group_user_attribute: DN` deliberately after testing. Changing
connector ID or idAttr changes OIDC subjects and can detach existing accounts. 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 database password remains a protected Helm/Dex config value; it is not assumed
to support the same connector substitution. Never commit actual secrets. to support the same connector substitution. Never commit actual secrets.
[tests/example-vars.yml](tests/example-vars.yml) shows a www client and separate [tests/example-vars.yml](tests/example-vars.yml) shows a www client using the
`www-ldap` connector using `entryUUID`, `inetOrgPerson` and DN group matching. single existing `ldap` connector. The Secret's `WWW_DEX_CLIENT_SECRET` must match
This avoids breaking legacy subjects while supporting new free website users. the website's `DEX_CLIENT_SECRET`; set `DEX_CONNECTOR_ID=ldap` and the exact
The Secret's `WWW_DEX_CLIENT_SECRET` must match the website's `DEX_CLIENT_SECRET`; matching issuer. No client or additional connector is silently enabled by
set the website's `DEX_CONNECTOR_ID=www-ldap` and exact matching issuer. No new default. Verify the combined search filter and scope before enabling public
client or connector is silently enabled by default. Verify that this connector's registration.
search base includes the website account OU before enabling public registration.
### Theme ### Theme
+1 -1
View File
@@ -52,7 +52,7 @@ dex_matrix_client_enabled: false
dex_matrix_redirect_uris: [] dex_matrix_redirect_uris: []
# Full objects (id, name, secret or secretEnv, redirectURIs). Use Vault for secrets. # Full objects (id, name, secret or secretEnv, redirectURIs). Use Vault for secrets.
dex_static_clients: [] 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: [] dex_additional_connectors: []
# Optional environment Secret for secretEnv and $ENV connector credentials. # Optional environment Secret for secretEnv and $ENV connector credentials.
dex_env_secret_name: "" dex_env_secret_name: ""
+3 -28
View File
@@ -31,7 +31,8 @@
dex_postgres_password: "{{ dex_admin_password }}" dex_postgres_password: "{{ dex_admin_password }}"
dex_postgres_admin_password: "{{ postgresql_admin_password | default('') }}" 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_host: openldap.openldap.svc.cluster.local
dex_ldap_insecure_no_ssl: true dex_ldap_insecure_no_ssl: true
dex_ldap_start_tls: false dex_ldap_start_tls: false
@@ -40,7 +41,7 @@
dex_ldap_connector_id: ldap dex_ldap_connector_id: ldap
dex_ldap_connector_name: eom.dev account dex_ldap_connector_name: eom.dev account
dex_ldap_user_base_dn: dc=eom,dc=dev 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_id_attribute: uid
dex_ldap_name_attribute: givenName dex_ldap_name_attribute: givenName
dex_ldap_group_base_dn: dc=eom,dc=dev dex_ldap_group_base_dn: dc=eom,dc=dev
@@ -93,31 +94,5 @@
# Override from its admin screen if your installation rewrites index.php. # Override from its admin screen if your installation rewrites index.php.
redirectURIs: redirectURIs:
- https://nextcloud.eom.dev/index.php/apps/user_oidc/code - 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: roles:
- ansible-role-dex - ansible-role-dex
+4 -9
View File
@@ -83,12 +83,8 @@ config:
{% for connector in dex_additional_connectors %} {% for connector in dex_additional_connectors %}
- {{ connector | to_json }} - {{ connector | to_json }}
{% endfor %} {% endfor %}
volumes: volumes:{% if not dex_theme_enabled and not dex_ca_secret_name and not dex_extra_volumes %} []{% endif %}
# The official entrypoint renders its config into /tmp before starting Dex.
- name: dex-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
{% if dex_theme_enabled %} {% if dex_theme_enabled %}
- name: theme - name: theme
configMap: configMap:
@@ -102,9 +98,8 @@ volumes:
{% for volume in dex_extra_volumes %} {% for volume in dex_extra_volumes %}
- {{ volume | to_json }} - {{ volume | to_json }}
{% endfor %} {% endfor %}
volumeMounts: volumeMounts:{% if not dex_theme_enabled and not dex_ca_secret_name and not dex_extra_volume_mounts %} []{% endif %}
- name: dex-tmp
mountPath: /tmp
{% if dex_theme_enabled %} {% if dex_theme_enabled %}
- name: theme - name: theme
mountPath: /srv/dex/web/themes/light/styles.css mountPath: /srv/dex/web/themes/light/styles.css
+6 -26
View File
@@ -20,29 +20,9 @@ dex_static_clients:
redirectURIs: redirectURIs:
- https://www.eom.dev/members/oidc/callback/ - https://www.eom.dev/members/oidc/callback/
# Additional connector: don't alter legacy ldap/idAttr uid, which changes sub # One LDAP connector admits both legacy POSIX accounts and website accounts.
# identifiers for existing applications. This connector admits free web accounts. # Keep its ID and idAttr unchanged so existing OIDC subjects remain stable.
dex_additional_connectors: dex_ldap_connector_id: ldap
- type: ldap dex_ldap_id_attribute: uid
id: www-ldap dex_ldap_user_base_dn: dc=eom,dc=dev
name: eom.dev Membership dex_ldap_user_filter: "(|(&(objectClass=posixAccount)(memberOf=cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev))(objectClass=inetOrgPerson))"
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
+3 -1
View File
@@ -31,6 +31,8 @@ for fixture_run in (1,):
assert resolved['dex_ldap_group_user_attribute'] == 'cn' assert resolved['dex_ldap_group_user_attribute'] == 'cn'
assert resolved['dex_theme_enabled'] is True assert resolved['dex_theme_enabled'] is True
assert resolved['dex_postgres_manage'] is False 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']} clients = {client['id']: client for client in resolved['dex_static_clients']}
assert len(clients) == len(resolved['dex_static_clients']) == 7 assert len(clients) == len(resolved['dex_static_clients']) == 7
assert resolved['dex_matrix_client_enabled'] is False 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['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 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 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()) values = yaml.safe_load(Path('/tmp/dex-values.yml').read_text())
config = values['config'] config = values['config']
@@ -53,6 +54,7 @@ ldap = config['connectors'][0]
assert ldap['id'] == 'ldap' assert ldap['id'] == 'ldap'
assert ldap['config']['insecureNoSSL'] is True assert ldap['config']['insecureNoSSL'] is True
assert ldap['config']['startTLS'] is False assert ldap['config']['startTLS'] is False
assert len(config['connectors']) == 1
assert ldap['config']['userSearch']['idAttr'] == 'uid' assert ldap['config']['userSearch']['idAttr'] == 'uid'
assert ldap['config']['groupSearch']['userMatchers'][0]['userAttr'] == 'DN' assert ldap['config']['groupSearch']['userMatchers'][0]['userAttr'] == 'DN'
assert config['staticClients'][0]['id'] == 'matrix-fixture' assert config['staticClients'][0]['id'] == 'matrix-fixture'