prole/infrastructure/playbooks/workstation_kerberos.yml
chrisfu 9020eae91e fix(ansible): remove become from Windows play — connect as admin directly
win_regedit writes HKLM keys without privilege escalation when
ansible_user is already a local/domain Administrator. become:true
with --ask-become-pass was overriding the playbook's runas method.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:36:48 -07:00

285 lines
11 KiB
YAML

---
# workstation_kerberos.yml — Configure Kerberos client + browser SPNEGO on managed endpoints.
#
# Run this playbook once during laptop provisioning (or re-run idempotently).
# It replaces the per-user "defaults write" workaround with a managed, auditable
# policy that is identical across every machine.
#
# WHAT IT DOES
# ------------
# 1. Writes /etc/krb5.conf (or /Library/Preferences/edu.mit.Kerberos on macOS)
# so that kinit resolves PROLE.ORG principals against myrddin.prole.org.
# 2. Deploys Chrome managed preferences (AuthServerAllowlist) so Chrome/Edge
# automatically negotiates Kerberos for *.prole.org without any per-user
# browser configuration.
# 3. On Windows: writes the equivalent HKLM registry keys (no GPO required).
#
# USAGE
# -----
# # All managed workstations (macOS + Windows):
# ansible-playbook infrastructure/playbooks/workstation_kerberos.yml
#
# # macOS / Linux only:
# ansible-playbook infrastructure/playbooks/workstation_kerberos.yml \
# --limit workstations
#
# # Windows only:
# ansible-playbook infrastructure/playbooks/workstation_kerberos.yml \
# --limit workstations_windows
#
# # Single machine:
# ansible-playbook infrastructure/playbooks/workstation_kerberos.yml \
# --limit <hostname>
#
# # Dry-run:
# ansible-playbook infrastructure/playbooks/workstation_kerberos.yml \
# --check --diff
#
# HOSTS
# -----
# [workstations] — macOS / Linux endpoints (become: sudo)
# [workstations_windows] — Windows endpoints (become: runas)
# Add new machines to the appropriate group; no playbook changes required.
#
# ADDING A NEW REALM
# ------------------
# Extend the krb5_realms list in vars below. The template generates the
# [realms] and [domain_realm] sections automatically.
# ============================================================================
# Play 1 — macOS / Linux workstations
# Uses sudo (the default become plugin for Unix targets).
# morgana runs ansible_connection=local so DNS self-lookup is not required.
# ============================================================================
- name: Configure Kerberos client and browser SPNEGO on macOS/Linux workstations
hosts: "workstations,!workstations_windows"
gather_facts: true
become: true
vars:
krb5_default_realm: "PROLE.ORG"
krb5_realms:
- realm: "PROLE.ORG"
kdc: "myrddin.prole.org"
admin_server: "myrddin.prole.org"
domain_suffixes:
- ".prole.org"
- "prole.org"
# Chrome / Chromium / Edge — negotiate Kerberos for these domains.
# *.prole.org covers git.prole.org, svc.prole.org, db.prole.org, etc.
chrome_negotiate_domains: "*.prole.org"
tasks:
# -----------------------------------------------------------------------
# 1. krb5.conf — shared by kinit, curl --negotiate, Python krb5, etc.
# -----------------------------------------------------------------------
- name: Write /etc/krb5.conf
ansible.builtin.copy:
dest: /etc/krb5.conf
owner: root
group: "{{ 'wheel' if ansible_os_family == 'Darwin' else 'root' }}"
mode: '0644'
content: |
[libdefaults]
default_realm = {{ krb5_default_realm }}
dns_canonicalize_hostname = false
rdns = false
# Ticket forwarding is disabled by default; enable per-service via
# [appdefaults] if needed (e.g. SSH GSSAPIDelegateCredentials).
forwardable = false
[realms]
{% for r in krb5_realms %}
{{ r.realm }} = {
kdc = {{ r.kdc }}
admin_server = {{ r.admin_server }}
}
{% endfor %}
[domain_realm]
{% for r in krb5_realms %}
{% for d in r.domain_suffixes %}
{{ d }} = {{ r.realm }}
{% endfor %}
{% endfor %}
# macOS also checks this legacy path; symlink for compatibility.
- name: Symlink macOS legacy Kerberos config
ansible.builtin.file:
src: /etc/krb5.conf
dest: /Library/Preferences/edu.mit.Kerberos
state: link
force: true
when: ansible_os_family == "Darwin"
# -----------------------------------------------------------------------
# 2. Chrome managed policy — macOS
# Placed in /Library/Managed Preferences/ so it applies to all users on
# the machine. No per-user Chrome configuration required.
# -----------------------------------------------------------------------
- name: Ensure Chrome managed preferences directory exists (macOS)
ansible.builtin.file:
path: /Library/Managed Preferences
state: directory
owner: root
group: wheel
mode: '0755'
when: ansible_os_family == "Darwin"
- name: Deploy Chrome SPNEGO managed policy (macOS)
ansible.builtin.copy:
dest: /Library/Managed Preferences/com.google.Chrome.plist
owner: root
group: wheel
mode: '0644'
content: |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Domains for which Chrome will negotiate Kerberos automatically. -->
<key>AuthServerAllowlist</key>
<string>{{ chrome_negotiate_domains }}</string>
<!-- Domains to which Chrome may delegate (forward) the Kerberos ticket. -->
<key>AuthNegotiateDelegateAllowlist</key>
<string>{{ chrome_negotiate_domains }}</string>
</dict>
</plist>
when: ansible_os_family == "Darwin"
notify: Restart Chrome (macOS)
# Microsoft Edge on macOS uses the same managed-preferences directory with
# a different bundle ID.
- name: Deploy Edge SPNEGO managed policy (macOS)
ansible.builtin.copy:
dest: /Library/Managed Preferences/com.microsoft.Edge.plist
owner: root
group: wheel
mode: '0644'
content: |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AuthServerAllowlist</key>
<string>{{ chrome_negotiate_domains }}</string>
<key>AuthNegotiateDelegateAllowlist</key>
<string>{{ chrome_negotiate_domains }}</string>
</dict>
</plist>
when: ansible_os_family == "Darwin"
# -----------------------------------------------------------------------
# 2b. Chrome / Chromium managed policy — Linux
# Google Chrome looks in /etc/opt/chrome/policies/managed/
# Chromium looks in /etc/chromium/policies/managed/
# -----------------------------------------------------------------------
- name: Ensure Chrome policy directory exists (Linux)
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
loop:
- /etc/opt/chrome/policies/managed
- /etc/chromium/policies/managed
when: ansible_os_family != "Darwin"
- name: Deploy Chrome/Chromium SPNEGO managed policy (Linux)
ansible.builtin.copy:
dest: "{{ item }}/kerberos.json"
owner: root
group: root
mode: '0644'
content: |
{
"AuthServerAllowlist": "{{ chrome_negotiate_domains }}",
"AuthNegotiateDelegateAllowlist": "{{ chrome_negotiate_domains }}"
}
loop:
- /etc/opt/chrome/policies/managed
- /etc/chromium/policies/managed
when: ansible_os_family != "Darwin"
# -----------------------------------------------------------------------
# 3. Verify — print where to check policy was applied
# -----------------------------------------------------------------------
- name: Show policy verification URL
ansible.builtin.debug:
msg: >
Chrome/Edge policy applied on {{ inventory_hostname }}.
Verify at chrome://policy (or edge://policy) in a browser.
Expected: AuthServerAllowlist = {{ chrome_negotiate_domains }}
handlers:
# Chrome must be fully quit and restarted (not just reloaded) for managed
# preferences to take effect on macOS.
- name: Restart Chrome (macOS)
ansible.builtin.debug:
msg: >
Chrome policy updated on {{ inventory_hostname }}.
Ask the user to quit Chrome completely (Cmd-Q) and relaunch.
Managed preferences take effect on next launch — no reinstall needed.
# ============================================================================
# Play 2 — Windows workstations
# Uses runas (the correct become plugin for Windows targets).
# No krb5.conf needed — Windows uses built-in SSPI/Kerberos via AD domain join.
# Browser policy is written to HKLM registry keys directly (no GPO required).
# If the machine later joins the PROLE.ORG Samba AD domain, these keys can be
# replaced or supplemented by a proper GPO; win_regedit is idempotent.
# ============================================================================
- name: Configure browser SPNEGO policy on Windows workstations
hosts: workstations_windows
gather_facts: true
# No become needed — connect directly as an Administrator account.
# win_regedit writes to HKLM without privilege escalation when the
# ansible_user is already a local or domain Administrator.
vars:
chrome_negotiate_domains: "*.prole.org"
tasks:
# -----------------------------------------------------------------------
# Chrome + Edge managed policy via registry
# HKLM\SOFTWARE\Policies\Google\Chrome\AuthServerAllowlist
# HKLM\SOFTWARE\Policies\Microsoft\Edge\AuthServerAllowlist
# -----------------------------------------------------------------------
- name: Set Chrome SPNEGO policy keys
ansible.windows.win_regedit:
path: "{{ item.path }}"
name: "{{ item.name }}"
data: "{{ chrome_negotiate_domains }}"
type: String
state: present
loop:
- path: HKLM:\SOFTWARE\Policies\Google\Chrome
name: AuthServerAllowlist
- path: HKLM:\SOFTWARE\Policies\Google\Chrome
name: AuthNegotiateDelegateAllowlist
- path: HKLM:\SOFTWARE\Policies\Microsoft\Edge
name: AuthServerAllowlist
- path: HKLM:\SOFTWARE\Policies\Microsoft\Edge
name: AuthNegotiateDelegateAllowlist
notify: Restart browsers (Windows)
- name: Show policy verification URL
ansible.builtin.debug:
msg: >
Chrome/Edge registry policy applied on {{ inventory_hostname }}.
Verify at chrome://policy or edge://policy.
Expected: AuthServerAllowlist = {{ chrome_negotiate_domains }}
handlers:
- name: Restart browsers (Windows)
ansible.builtin.debug:
msg: >
Chrome/Edge policy updated on {{ inventory_hostname }}.
Ask the user to close all Chrome and Edge windows and relaunch.
Registry policy takes effect on next browser start.