--- # 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@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. # The trust_shared_password used here must match the value stored in the # in-cluster Secret knoe-system/knoe-kdc-secrets (key: trust_shared_password). # The in-cluster MIT KDC must already have created the principal # krbtgt/PROLE.ORG@KNOE.LOCAL # and krbtgt/KNOE.LOCAL@PROLE.ORG # before this playbook runs. (init_kdc.sh handles this.) # # 2. trust_kdc_ip must be the ClusterIP of the 'auth' Service in the knoe-system # namespace. myrddin (the Samba DC) must be able to reach it — either because # it is the k3s server node (and is therefore on the pod/service CIDR network) # or because a static route has been added. # # 3. The Samba AD administrator password lives in the Ansible vault as # vault_samba_dns_admin_pass # (group_vars/ad_dc/vault.yml). Decrypt with --vault-password-file .vault_pass # or `ANSIBLE_VAULT_PASSWORD_FILE=$PWD/.vault_pass`. # # DRY-RUN # ------- # ansible-playbook infrastructure/playbooks/kerberos_trust_setup.yml \ # --check --diff \ # --vault-password-file .vault_pass \ # -e trust_shared_password=dummy # # FULL RUN (auto-resolves trust_shared_password from the cluster Secret) # --------------------------------------------------------------------- # ansible-playbook infrastructure/playbooks/kerberos_trust_setup.yml \ # --vault-password-file .vault_pass # # OVERRIDES # --------- # -e trust_kdc_ip=10.43.x.y # skip ClusterIP autoresolve # -e trust_shared_password=... # skip Secret autoresolve # -e samba_admin_password=... # override vault (e.g. CI without vault) - name: Register KNOE.LOCAL cross-realm trust in Samba AD on myrddin hosts: myrddin.prole.org gather_facts: false become: true vars: trust_realm: "KNOE.LOCAL" trust_kdc_ip: "" # auto-resolved below if empty # Pull the trust password from the cluster Secret unless caller overrides. trust_shared_password: "{{ lookup('env', 'PROLE_TRUST_SHARED_PASSWORD') | default('', true) }}" # Admin password resolution order: # 1. -e samba_admin_password=... (explicit override) # 2. SAMBA_ADMIN_PASSWORD env var (legacy / CI) # 3. vault_samba_dns_admin_pass (Ansible vault — preferred) samba_admin_password: >- {{ lookup('env', 'SAMBA_ADMIN_PASSWORD') | default(hostvars[inventory_hostname].vault_samba_dns_admin_pass | default(vault_samba_dns_admin_pass | default('')), true) }} tasks: # ------------------------------------------------------------------ # 0a. Resolve the ClusterIP of the in-cluster MIT KDC if not provided # ------------------------------------------------------------------ - name: Resolve trust_kdc_ip from cluster if not provided ansible.builtin.command: cmd: kubectl -n knoe-system get svc auth -o jsonpath='{.spec.clusterIP}' delegate_to: localhost become: false register: _kdc_clusterip when: trust_kdc_ip == "" changed_when: false check_mode: false - name: Set trust_kdc_ip fact from cluster lookup ansible.builtin.set_fact: trust_kdc_ip: "{{ _kdc_clusterip.stdout | trim }}" when: trust_kdc_ip == "" and _kdc_clusterip is defined and _kdc_clusterip.stdout is defined - name: Assert trust_kdc_ip is set ansible.builtin.assert: that: - trust_kdc_ip != "" fail_msg: > trust_kdc_ip is empty. Provide it via --extra-vars trust_kdc_ip=... or ensure 'kubectl -n knoe-system get svc auth' succeeds from the control node. # ------------------------------------------------------------------ # 0b. Resolve trust_shared_password from the cluster Secret if not provided # ------------------------------------------------------------------ - name: Resolve trust_shared_password from knoe-system/knoe-kdc-secrets ansible.builtin.shell: cmd: > set -o pipefail; kubectl -n knoe-system get secret knoe-kdc-secrets -o jsonpath='{.data.trust_shared_password}' | base64 -d executable: /bin/bash delegate_to: localhost become: false register: _trust_pw when: trust_shared_password == "" changed_when: false check_mode: false no_log: true - name: Set trust_shared_password fact from cluster Secret ansible.builtin.set_fact: trust_shared_password: "{{ _trust_pw.stdout | trim }}" when: trust_shared_password == "" and _trust_pw is defined and _trust_pw.stdout is defined no_log: true - name: Assert trust_shared_password is set ansible.builtin.assert: that: - trust_shared_password | length > 0 fail_msg: > trust_shared_password is empty. Provide it via -e trust_shared_password=... or ensure the Secret knoe-system/knoe-kdc-secrets has key 'trust_shared_password' populated by init_kdc.sh. - name: Assert samba_admin_password is set ansible.builtin.assert: that: - samba_admin_password | length > 0 fail_msg: > samba_admin_password is empty. Decrypt with --vault-password-file .vault_pass or set SAMBA_ADMIN_PASSWORD / -e samba_admin_password=... # ------------------------------------------------------------------ # 1. Idempotency probe — does the inter-realm krbtgt user exist? # ------------------------------------------------------------------ # 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 user list register: _samba_users changed_when: false failed_when: false check_mode: false - name: Set fact — inter-realm krbtgt user already present ansible.builtin.set_fact: _trust_exists: "{{ ('krbtgt/' + trust_realm) in _samba_users.stdout }}" - name: Report trust pre-existence ansible.builtin.debug: msg: >- krbtgt/{{ trust_realm }} user {{ 'already present in Samba — will reset password to match cluster Secret.' if _trust_exists else 'is missing — will create.' }} # ------------------------------------------------------------------ # 2. Add KNOE.LOCAL realm block to /etc/krb5.conf on myrddin # ------------------------------------------------------------------ - name: Ensure {{ trust_realm }} realm block is present in /etc/krb5.conf ansible.builtin.blockinfile: path: /etc/krb5.conf marker: "# {mark} ANSIBLE MANAGED BLOCK — {{ trust_realm }}" insertafter: '^\[realms\]' block: | {{ trust_realm }} = { kdc = {{ trust_kdc_ip }} admin_server = {{ trust_kdc_ip }} } - name: Ensure [domain_realm] mapping prole-cluster → {{ trust_realm }} ansible.builtin.blockinfile: path: /etc/krb5.conf marker: "# {mark} ANSIBLE MANAGED BLOCK — domain_realm {{ trust_realm }}" insertafter: '^\[domain_realm\]' block: | .knoe.local = {{ trust_realm }} knoe.local = {{ trust_realm }} # ------------------------------------------------------------------ # 3. Create the inter-realm krbtgt user in Samba (if missing) # ------------------------------------------------------------------ # 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: 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: _krbtgt_create changed_when: _krbtgt_create.rc == 0 when: not _trust_exists # ------------------------------------------------------------------ # 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: Re-sync krbtgt/{{ trust_realm }} password with cluster Secret ansible.builtin.command: argv: - samba-tool - user - setpassword - "krbtgt/{{ trust_realm }}" - "--newpassword={{ trust_shared_password }}" - "-U" - "administrator%{{ samba_admin_password }}" no_log: true 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" <