Initial commit

This commit is contained in:
2026-10-23 18:15:49 -04:00
commit 935b3e9f4c
18 changed files with 909 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
FROM python:3.13-slim-trixie
RUN pip install --no-cache-dir 'ansible-core>=2.18,<2.19' 'kubernetes>=28,<34' PyYAML 'kubernetes-validate>=1.31,<1.35'
ADD https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz /tmp/helm.tar.gz
RUN echo 'ee88b3c851ae6466a3de507f7be73fe94d54cbf2987cbaa3d1a3832ea331f2cd /tmp/helm.tar.gz' | sha256sum -c - && tar -xzf /tmp/helm.tar.gz -C /tmp && mv /tmp/linux-amd64/helm /usr/local/bin/helm && rm -rf /tmp/linux-amd64 /tmp/helm.tar.gz
COPY requirements.yml /tmp/requirements.yml
RUN ansible-galaxy collection install -r /tmp/requirements.yml
WORKDIR /roles/ansible-role-dex
COPY . ./
ENV ANSIBLE_ROLES_PATH=/roles
CMD ["sh", "-ec", "ansible-playbook -i localhost, tests/test.yml --syntax-check; ansible-playbook -i localhost, playbooks/deploy-eom.yml --syntax-check; ansible-playbook -i localhost, tests/render.yml; helm repo add dex https://charts.dexidp.io; helm template dex dex/dex --version 0.26.0 --namespace dex -f /tmp/dex-values.yml > /tmp/dex-manifests.yml; python tests/validate.py"]
+48
View File
@@ -0,0 +1,48 @@
---
# Passwords supplied in a Vault-encrypted inventory, never in this example.
dex_issuer: https://dex.eom.dev/
dex_hostname: dex.eom.dev
dex_postgres_manage: false
dex_postgres_host: postgresql
dex_postgres_database: dex
dex_postgres_user: dex
# Legacy credential names are still supported by defaults:
# dex_admin_password, openldap_readonly_password, matrix_dex_oidc_client_secret.
# Existing Secret in the Dex namespace. WWW_DEX_CLIENT_SECRET must match the
# www membership app's DEX_CLIENT_SECRET. DEX_LDAP_BIND_PASSWORD is the search
# account password; no real values go in this public file.
dex_env_secret_name: dex-client-secrets
dex_static_clients:
- id: www-membership
name: eom.dev Membership
secretEnv: WWW_DEX_CLIENT_SECRET
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
+52
View File
@@ -0,0 +1,52 @@
---
- name: Render Dex values without a cluster or real secrets
hosts: localhost
connection: local
gather_facts: false
vars:
role_path: /roles/ansible-role-dex
tasks:
- name: Load defaults
ansible.builtin.include_vars: ../defaults/main.yml
- name: Set fake isolated credentials
ansible.builtin.set_fact:
dex_postgres_password: fixture-not-a-real-password
dex_ldap_bind_password: fixture-not-a-real-password
dex_matrix_client_enabled: true
matrix_dex_oidc_client_id: matrix-fixture
matrix_dex_oidc_client_secret: fixture-not-a-real-client-secret
dex_theme_enabled: true
- name: Render default values
ansible.builtin.template:
src: ../templates/values.yml.j2
dest: /tmp/dex-values.yml
mode: "0600"
- name: Set optional settings
ansible.builtin.set_fact:
dex_theme_enabled: false
dex_matrix_client_enabled: false
dex_ldap_insecure_no_ssl: false
dex_ldap_start_tls: true
dex_ldap_root_ca: /etc/dex-ca/ldap.pem
dex_ca_secret_name: ldap-ca
dex_env_secret_name: dex-client-secrets
dex_static_clients:
- id: www-membership
name: Example Membership
secretEnv: WWW_DEX_CLIENT_SECRET
redirectURIs: [https://www.example.com/members/oidc/callback/]
- name: Render optional values
ansible.builtin.template:
src: ../templates/values.yml.j2
dest: /tmp/dex-values-optional.yml
mode: "0600"
- name: Set empty optional lists
ansible.builtin.set_fact:
dex_ca_secret_name: ""
dex_env_secret_name: ""
dex_static_clients: []
- name: Render minimal values
ansible.builtin.template:
src: ../templates/values.yml.j2
dest: /tmp/dex-values-minimal.yml
mode: "0600"
Executable
+6
View File
@@ -0,0 +1,6 @@
---
- name: Check dedicated Dex role syntax
hosts: localhost
gather_facts: false
roles:
- ansible-role-dex
+72
View File
@@ -0,0 +1,72 @@
"""Check real Ansible-rendered values and Helm manifests without a cluster."""
import base64
from pathlib import Path
import yaml
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.
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):
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',
}
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'
values = yaml.safe_load(Path('/tmp/dex-values.yml').read_text())
config = values['config']
assert config['issuer'] == 'https://dex.example.com/'
assert config['storage']['config']['database'] == 'dex'
assert 'databbase' not in config['storage']['config']
ldap = config['connectors'][0]
assert ldap['id'] == 'ldap'
assert ldap['config']['insecureNoSSL'] is True
assert ldap['config']['startTLS'] is False
assert ldap['config']['userSearch']['idAttr'] == 'uid'
assert ldap['config']['groupSearch']['userMatchers'][0]['userAttr'] == 'DN'
assert config['staticClients'][0]['id'] == 'matrix-fixture'
assert config['frontend']['logoURL'] == 'theme/logo.svg'
assert config['frontend']['dir'] == '/srv/dex/web'
assert len(values['volumeMounts']) == 2
assert all(mount['readOnly'] for mount in values['volumeMounts'])
optional = yaml.safe_load(Path('/tmp/dex-values-optional.yml').read_text())
assert 'frontend' not in optional['config']
assert optional['config']['connectors'][0]['config']['startTLS'] is True
assert optional['config']['staticClients'][0]['secretEnv'] == 'WWW_DEX_CLIENT_SECRET'
assert optional['envFrom'][0]['secretRef']['name'] == 'dex-client-secrets'
minimal = yaml.safe_load(Path('/tmp/dex-values-minimal.yml').read_text())
assert minimal['config']['staticClients'] == []
assert minimal['volumes'] == [] and minimal['volumeMounts'] == []
objects = [obj for obj in yaml.safe_load_all(Path('/tmp/dex-manifests.yml').read_text()) if obj]
for obj in objects:
validate(obj, desired_version='1.31.0', strict=True)
deployment = next(obj for obj in objects if obj['kind'] == 'Deployment')
assert deployment['metadata']['name'] == 'dex'
mounts = deployment['spec']['template']['spec']['containers'][0]['volumeMounts']
assert any(mount['mountPath'].endswith('/light/styles.css') for mount in mounts)
secret = next(obj for obj in objects if obj['kind'] == 'Secret')
stored = yaml.safe_load(base64.b64decode(secret['data']['config.yaml']))
assert stored['storage']['config']['password'] == 'fixture-not-a-real-password'
assert not any(obj['kind'] in ['PersistentVolumeClaim', 'StatefulSet'] for obj in objects)
print(f'PASS: {len(objects)} real Helm resources; legacy identity/plain LDAP preserved, optional TLS, clients, theme mounts and Secret config validated')