ansible: use krbtgt_KNOE.LOCAL sAMAccountName + UPN for trust account

samba-tool user create rejected the sAMAccountName "krbtgt/KNOE.LOCAL"
with "samldb: sAMAccountName contains invalid '/' character". AD/Samba
disallow "/" in sAMAccountName even though Kerberos principal names
require it (krbtgt/REMOTE@LOCAL).

Switch storage name to "krbtgt_KNOE.LOCAL" and add an ldbmodify pass
that sets the canonical Kerberos identity on the same object:

  userPrincipalName     = krbtgt/KNOE.LOCAL@PROLE.ORG
  servicePrincipalName  = krbtgt/KNOE.LOCAL
  msDS-SupportedEncryptionTypes = 28  (RC4+AES128+AES256)

Samba KDC resolves principals by UPN/SPN, so a TGS-REQ for
krbtgt/KNOE.LOCAL@PROLE.ORG will hit this account.

Note: key-salt parity with the MIT side is NOT guaranteed yet. Samba's
default salt for AES keys is REALM+UPN; MIT's default for cross-realm
krbtgt is REALM+"krbtgt"+REMOTE. If kvno fails with "decrypt integrity
check failed" we'll add a keytab-export/import step in a follow-up
rather than try to coerce Samba's salt at creation time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-10 22:02:55 -07:00
parent 6740e3dcc5
commit aa541af3f5

View File

@ -66,6 +66,12 @@
vars:
trust_realm: "KNOE.LOCAL"
samba_local_realm: "PROLE.ORG"
# sAMAccountName cannot contain a slash, so we store the inter-realm TGT
# account under an underscore-form name and explicitly set its
# userPrincipalName to the canonical Kerberos form below.
samba_account_name: "krbtgt_KNOE.LOCAL"
trust_principal: "krbtgt/KNOE.LOCAL@PROLE.ORG"
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) }}"
@ -158,7 +164,7 @@
# 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
- name: Check whether {{ samba_account_name }} user already exists in Samba
ansible.builtin.command:
cmd: >
samba-tool user list
@ -174,7 +180,7 @@
- name: Report trust pre-existence
ansible.builtin.debug:
msg: >-
krbtgt/{{ trust_realm }} user
{{ samba_account_name }} user
{{ 'already present in Samba — will reset password to match cluster Secret.'
if _trust_exists else 'is missing — will create.' }}
@ -213,13 +219,13 @@
# 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 }}
- name: Create inter-realm krbtgt user {{ samba_account_name }}
ansible.builtin.command:
argv:
- samba-tool
- user
- create
- "krbtgt/{{ trust_realm }}"
- "{{ samba_account_name }}"
- "{{ trust_shared_password }}"
- "--description=Inter-realm TGT for {{ trust_realm }} (MIT KDC) cross-realm trust"
- "--use-username-as-cn"
@ -235,13 +241,13 @@
# 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
- name: Re-sync {{ samba_account_name }} password with cluster Secret
ansible.builtin.command:
argv:
- samba-tool
- user
- setpassword
- "krbtgt/{{ trust_realm }}"
- "{{ samba_account_name }}"
- "--newpassword={{ trust_shared_password }}"
- "-U"
- "administrator%{{ samba_admin_password }}"
@ -253,13 +259,13 @@
# ------------------------------------------------------------------
# 5. Disable password expiry on the krbtgt account
# ------------------------------------------------------------------
- name: Disable password expiry on krbtgt/{{ trust_realm }}
- name: Disable password expiry on {{ samba_account_name }}
ansible.builtin.command:
argv:
- samba-tool
- user
- setexpiry
- "krbtgt/{{ trust_realm }}"
- "{{ samba_account_name }}"
- "--noexpiry"
- "-U"
- "administrator%{{ samba_admin_password }}"
@ -269,26 +275,42 @@
failed_when: _krbtgt_noexpiry.rc != 0
# ------------------------------------------------------------------
# 6. Force supported encryption types to AES128+AES256+RC4
# 6. Set the Kerberos principal name (UPN + SPN) and force supported
# encryption types to AES128+AES256+RC4.
#
# sAMAccountName cannot contain "/" so we stored the account
# under {{ samba_account_name }} (krbtgt_KNOE.LOCAL). For the
# Samba KDC to issue tickets for the canonical Kerberos name
# krbtgt/KNOE.LOCAL@PROLE.ORG, we set both:
#
# userPrincipalName = krbtgt/KNOE.LOCAL@PROLE.ORG
# servicePrincipalName = krbtgt/KNOE.LOCAL
#
# 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).
# must match what the MIT KDC offers (init_kdc.sh keys the
# cross-realm krbtgt with AES256).
# ------------------------------------------------------------------
- name: Set msDS-SupportedEncryptionTypes=28 on krbtgt/{{ trust_realm }}
- name: Set UPN, SPN, and supported encryption types on {{ samba_account_name }}
ansible.builtin.shell:
cmd: |
set -euo pipefail
ldif=$(mktemp)
DN=$(samba-tool user show "krbtgt/{{ trust_realm }}" \
DN=$(samba-tool user show "{{ samba_account_name }}" \
-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
echo "Could not resolve DN for {{ samba_account_name }}" >&2
exit 1
fi
cat > "$ldif" <<EOF
dn: $DN
changetype: modify
replace: userPrincipalName
userPrincipalName: {{ trust_principal }}
-
replace: servicePrincipalName
servicePrincipalName: krbtgt/{{ trust_realm }}
-
replace: msDS-SupportedEncryptionTypes
msDS-SupportedEncryptionTypes: 28
EOF
@ -302,13 +324,13 @@
# ------------------------------------------------------------------
# 7. Smoke probe — confirm the user is visible to samba-tool
# ------------------------------------------------------------------
- name: Smoke probe — samba-tool user show krbtgt/{{ trust_realm }}
- name: Smoke probe — samba-tool user show {{ samba_account_name }}
ansible.builtin.command:
argv:
- samba-tool
- user
- show
- "krbtgt/{{ trust_realm }}"
- "{{ samba_account_name }}"
- "-U"
- "administrator%{{ samba_admin_password }}"
no_log: true
@ -325,10 +347,10 @@
- name: Print summary
ansible.builtin.debug:
msg:
- "krbtgt/{{ trust_realm }} present in Samba (PROLE.ORG realm)."
- "{{ samba_account_name }} 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"
- " klist # expect {{ samba_account_name }}@PROLE.ORG"