diff --git a/infrastructure/playbooks/kerberos_trust_setup.yml b/infrastructure/playbooks/kerberos_trust_setup.yml index 2f144bd..5db5416 100644 --- a/infrastructure/playbooks/kerberos_trust_setup.yml +++ b/infrastructure/playbooks/kerberos_trust_setup.yml @@ -1,12 +1,26 @@ --- -# kerberos_trust_setup.yml — Register the reciprocal KNOE.LOCAL ↔ PROLE.ORG cross-realm trust -# in Samba AD on myrddin.prole.org. +# kerberos_trust_setup.yml — Register the KNOE.LOCAL ↔ PROLE.ORG cross-realm +# Kerberos trust in Samba AD on myrddin.prole.org. # # This unblocks SSO from PROLE.ORG (the on-prem Samba AD realm) into KNOE.LOCAL -# (the in-cluster MIT KDC realm), which is required for chrisfu@KNOE.LOCAL→ -# chrisfu@PROLE.ORG service ticket flows targeting db.prole.org and other +# (the in-cluster MIT KDC realm), which is required for chrisfu@PROLE.ORG → +# service@KNOE.LOCAL ticket flows targeting db.prole.org / pg_oauth and other # cluster-hosted services that authenticate against the KNOE.LOCAL KDC. # +# Why not `samba-tool domain trust create`? +# ----------------------------------------- +# That command requires the remote side to be another writeable AD-style DC, +# and tries to discover it via DNS-SRV lookup. Our peer is the in-cluster MIT +# KDC at svc/auth.knoe-system — a plain Kerberos KDC, not AD — so the +# command bails with "Failed to find a writeable DC for domain KNOE.LOCAL". +# +# The supported Samba-AD ↔ MIT-KDC approach is to create the inter-realm TGT +# principal as an ordinary user account in Samba whose sAMAccountName equals +# `krbtgt/KNOE.LOCAL`, and to share that account's password with the MIT side. +# The MIT side already has the matching principal (created by init_kdc.sh in +# the knoe-db repo), keyed to the same trust_shared_password we pull from the +# in-cluster Secret knoe-system/knoe-kdc-secrets. +# # PREREQUISITES # ------------- # 1. Run AFTER init_knoe_users.sh / init_kdc.sh has run on the k3s cluster. @@ -134,25 +148,34 @@ or set SAMBA_ADMIN_PASSWORD / -e samba_admin_password=... # ------------------------------------------------------------------ - # 1. Check if the trust already exists (idempotency guard) + # 1. Idempotency probe — does the inter-realm krbtgt user exist? # ------------------------------------------------------------------ - - name: Check whether {{ trust_realm }} trust already exists in Samba + # NOTE: `samba-tool domain trust create --type=external` only works + # against another AD-style writeable DC. For an MIT KDC peer + # (which KNOE.LOCAL is — it's the in-cluster Heimdal/MIT KDC, not AD), + # the documented approach is to create the inter-realm TGT + # principal as an ordinary user account in Samba and share its + # password with the MIT KDC. The MIT side already has + # krbtgt/KNOE.LOCAL@PROLE.ORG (created by init_kdc.sh) using the + # same trust_shared_password we just fetched from the cluster Secret. + - name: Check whether krbtgt/{{ trust_realm }} user already exists in Samba ansible.builtin.command: - cmd: samba-tool domain trust list - register: _trust_list + cmd: > + samba-tool user list + register: _samba_users changed_when: false failed_when: false check_mode: false - - name: Set fact — trust already present + - name: Set fact — inter-realm krbtgt user already present ansible.builtin.set_fact: - _trust_exists: "{{ trust_realm in _trust_list.stdout }}" + _trust_exists: "{{ ('krbtgt/' + trust_realm) in _samba_users.stdout }}" - name: Report trust pre-existence ansible.builtin.debug: msg: >- - {{ trust_realm }} trust - {{ 'already present in Samba AD — skipping create.' + krbtgt/{{ trust_realm }} user + {{ 'already present in Samba — will reset password to match cluster Secret.' if _trust_exists else 'is missing — will create.' }} # ------------------------------------------------------------------ @@ -179,60 +202,128 @@ knoe.local = {{ trust_realm }} # ------------------------------------------------------------------ - # 3. Register the trust in Samba + # 3. Create the inter-realm krbtgt user in Samba (if missing) # ------------------------------------------------------------------ - - name: Create Kerberos cross-realm trust for {{ trust_realm }} in Samba AD + # The account is named exactly `krbtgt/KNOE.LOCAL` — the slash is + # legal in a Samba sAMAccountName/userPrincipalName. Samba's KDC + # will then issue cross-realm TGTs whose source principal is + # `krbtgt/KNOE.LOCAL@PROLE.ORG` (PROLE.ORG is the local realm, + # added implicitly). + # + # The "no_log" wrapper protects the password but also hides the + # error message on failure; if a real run dies here re-run by hand + # to see stderr (see playbook header). + - name: Create inter-realm krbtgt user krbtgt/{{ trust_realm }} ansible.builtin.command: - cmd: > - samba-tool domain trust create {{ trust_realm }} - --type=external - --direction=both - --password={{ trust_shared_password }} - -U administrator%{{ samba_admin_password }} + argv: + - samba-tool + - user + - create + - "krbtgt/{{ trust_realm }}" + - "{{ trust_shared_password }}" + - "--description=Inter-realm TGT for {{ trust_realm }} (MIT KDC) cross-realm trust" + - "--use-username-as-cn" + - "-U" + - "administrator%{{ samba_admin_password }}" no_log: true - register: _trust_create - changed_when: "'Successfully created trust' in (_trust_create.stdout | default('')) or _trust_create.rc == 0" + register: _krbtgt_create + changed_when: _krbtgt_create.rc == 0 when: not _trust_exists # ------------------------------------------------------------------ - # 4. Validate the trust + # 4. Reset the password (idempotent: handles both fresh create and + # re-runs where the cluster Secret may have been rotated). + # Skipped on the fresh-create path because step 3 already set it. # ------------------------------------------------------------------ - - name: Validate the {{ trust_realm }} trust in Samba AD + - name: Re-sync krbtgt/{{ trust_realm }} password with cluster Secret ansible.builtin.command: - cmd: > - samba-tool domain trust validate {{ trust_realm }} - -U administrator%{{ samba_admin_password }} + argv: + - samba-tool + - user + - setpassword + - "krbtgt/{{ trust_realm }}" + - "--newpassword={{ trust_shared_password }}" + - "-U" + - "administrator%{{ samba_admin_password }}" no_log: true - register: _trust_validate + register: _krbtgt_setpw + changed_when: _krbtgt_setpw.rc == 0 + when: _trust_exists + + # ------------------------------------------------------------------ + # 5. Disable password expiry on the krbtgt account + # ------------------------------------------------------------------ + - name: Disable password expiry on krbtgt/{{ trust_realm }} + ansible.builtin.command: + argv: + - samba-tool + - user + - setexpiry + - "krbtgt/{{ trust_realm }}" + - "--noexpiry" + - "-U" + - "administrator%{{ samba_admin_password }}" + no_log: true + register: _krbtgt_noexpiry + changed_when: _krbtgt_noexpiry.rc == 0 + failed_when: _krbtgt_noexpiry.rc != 0 + + # ------------------------------------------------------------------ + # 6. Force supported encryption types to AES128+AES256+RC4 + # msDS-SupportedEncryptionTypes = 28 = 0x04 (RC4) | 0x08 (AES128) | 0x10 (AES256) + # This must match what the MIT KDC offers (init_kdc.sh defaults + # to AES256 keys for cross-realm krbtgt principals). + # ------------------------------------------------------------------ + - name: Set msDS-SupportedEncryptionTypes=28 on krbtgt/{{ trust_realm }} + ansible.builtin.shell: + cmd: | + set -euo pipefail + ldif=$(mktemp) + DN=$(samba-tool user show "krbtgt/{{ trust_realm }}" \ + -U "administrator%{{ samba_admin_password }}" \ + | awk -F': ' '/^dn:/ {print $2; exit}') + if [ -z "$DN" ]; then + echo "Could not resolve DN for krbtgt/{{ trust_realm }}" >&2 + exit 1 + fi + cat > "$ldif" < - samba-tool domain trust show {{ trust_realm }} - -U administrator%{{ samba_admin_password }} - no_log: true - register: _trust_show - changed_when: false - check_mode: false - failed_when: - - _trust_show.rc != 0 - - not (ansible_check_mode and not _trust_exists) - - - name: Print trust show output - ansible.builtin.debug: - msg: "{{ _trust_show.stdout_lines }}" + msg: + - "krbtgt/{{ trust_realm }} present in Samba (PROLE.ORG realm)." + - "Password is now in sync with knoe-system/knoe-kdc-secrets/trust_shared_password." + - "msDS-SupportedEncryptionTypes set to 28 (RC4 + AES128 + AES256)." + - "From a PROLE.ORG client try:" + - " kdestroy && kinit chrisfu@PROLE.ORG" + - " kvno HTTP/auth.knoe.dev@{{ trust_realm }}" + - " klist # expect krbtgt/{{ trust_realm }}@PROLE.ORG"