ansible: rewrite kerberos_trust_setup for MIT KDC trust

samba-tool domain trust create --type=external only works against
another writeable AD-style DC; against an MIT KDC it fails with
"Failed to find a writeable DC for domain 'KNOE.LOCAL'". Our peer at
svc/auth in knoe-system is a plain Kerberos KDC, so we cannot use that
path.

Replace the trust create/validate/show steps with the supported
Samba-AD <-> MIT-KDC approach: create the inter-realm TGT principal as
an ordinary user account in Samba whose sAMAccountName is
"krbtgt/KNOE.LOCAL", with its password synced to the cluster Secret
knoe-system/knoe-kdc-secrets/trust_shared_password (which the MIT side
already keys against). Steps:

  1. samba-tool user list                            (idempotency probe)
  2. samba-tool user create krbtgt/KNOE.LOCAL ...    (when missing)
  3. samba-tool user setpassword krbtgt/KNOE.LOCAL   (when present, to
     re-sync after a cluster Secret rotation)
  4. samba-tool user setexpiry --noexpiry
  5. ldbmodify msDS-SupportedEncryptionTypes=28      (RC4+AES128+AES256
     to match what init_kdc.sh sets on the MIT side)
  6. samba-tool user show                            (smoke probe)
  7. debug task prints the manual kvno smoke-test command

Cluster-secret lookups and krb5.conf [realms]/[domain_realm] edits are
unchanged - those parts were correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-10 15:54:24 -07:00
parent 227f49042c
commit 555da4acfc

View File

@ -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" <<EOF
dn: $DN
changetype: modify
replace: msDS-SupportedEncryptionTypes
msDS-SupportedEncryptionTypes: 28
EOF
ldbmodify -H /var/lib/samba/private/sam.ldb "$ldif"
rm -f "$ldif"
no_log: true
register: _krbtgt_enctype
changed_when: "'Modified 1 records' in (_krbtgt_enctype.stdout | default(''))"
failed_when: _krbtgt_enctype.rc != 0
# ------------------------------------------------------------------
# 7. Smoke probe — confirm the user is visible to samba-tool
# ------------------------------------------------------------------
- name: Smoke probe — samba-tool user show krbtgt/{{ trust_realm }}
ansible.builtin.command:
argv:
- samba-tool
- user
- show
- "krbtgt/{{ trust_realm }}"
- "-U"
- "administrator%{{ samba_admin_password }}"
no_log: true
register: _krbtgt_show
changed_when: false
check_mode: false
# In --check mode the create step is skipped, so a missing trust
# is expected and shouldn't fail the dry run. In a real run we
# always want a non-zero rc to fail.
failed_when:
- _trust_validate.rc != 0
- not (ansible_check_mode and not _trust_exists)
failed_when: _krbtgt_show.rc != 0
- name: Print trust validation result
- name: Print summary
ansible.builtin.debug:
msg: "{{ _trust_validate.stdout_lines }}"
# ------------------------------------------------------------------
# 5. Smoke test — fetch a cross-realm TGT
# ------------------------------------------------------------------
- name: Smoke test — list krbtgt principals seen by Samba KDC
ansible.builtin.command:
cmd: >
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"