mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
Three-part fix to make browser and curl SPNEGO auth work end-to-end: 1. spnego-proxy: issue WWW-Authenticate: Negotiate challenge when no Authorization header is present so Chrome (with AuthServerAllowlist) and curl --negotiate automatically present Kerberos tokens. Previously the proxy only validated tokens if the client proactively sent them. Pass-through preserved for non-Negotiate schemes (Basic/token) so git CLI users with PATs continue to work via Gitea own auth. 2. gitea_spnego_keytab.yml: new Ansible playbook that provisions the gitea-http AD account (AES-only, msDS-SupportedEncryptionTypes=24), registers SPN HTTP/git.prole.org, resets the password to derive fresh AES keys, exports the domain keytab, and rekeys it to principal name HTTP/git.prole.org@PROLE.ORG that gokrb5 needs for keytab lookup. Key lesson: samba-tool exportkeytab --principal=HTTP/... returns empty; must export full domain keytab and rekey in Python. 3. init_gitea.sh: add setup_gitea_spnego() calling the Ansible playbook in k3s mode as part of the standard deploy flow, with inline notes on every non-obvious constraint discovered during this work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
556 lines
22 KiB
YAML
556 lines
22 KiB
YAML
---
|
|
# gitea_spnego_keytab.yml — Provision the Gitea HTTP SPNEGO keytab from Samba AD
|
|
# and deploy it as a Kubernetes secret in the gitea namespace.
|
|
#
|
|
# What this playbook does:
|
|
# 1. Creates (if absent) the gitea-http service account in Samba AD.
|
|
# 2. Registers the SPN HTTP/git.prole.org on that account.
|
|
# 3. Pins msDS-SupportedEncryptionTypes to AES128+AES256 (value 24, no RC4).
|
|
# 4. Resets the account password to derive fresh AES keying material.
|
|
# 5. Exports the domain keytab, rekeys it to a per-SPN keytab, and verifies.
|
|
# 6. Fetches the keytab to the control node.
|
|
# 7. Applies it as the Kubernetes secret gitea-krb5-keytab in the gitea namespace.
|
|
# 8. Restarts and waits for the gitea-spnego-proxy deployment.
|
|
# 9. Smoke-tests that git.prole.org returns an HTTP 401 (SPNEGO challenge).
|
|
#
|
|
# PREREQUISITES
|
|
# -------------
|
|
# 1. myrddin.prole.org is reachable and is the Samba AD DC for PROLE.ORG.
|
|
# 2. python3 is available on myrddin (used for keytab rekey step).
|
|
# 3. kubectl on the control node is configured with context prole-service-cluster
|
|
# and has permission to create/update secrets in namespace gitea.
|
|
# 4. The gitea-spnego-proxy deployment exists in the gitea namespace.
|
|
#
|
|
# FULL RUN (first time or after AD account changes):
|
|
# ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml
|
|
#
|
|
# Force keytab rotation (e.g. after password policy change):
|
|
# ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml \
|
|
# -e force_keytab_reset=true
|
|
#
|
|
# Dry-run:
|
|
# ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml --check
|
|
|
|
# ===========================================================================
|
|
# Play 1 — Samba AD setup: create account, register SPN, export keytab
|
|
# ===========================================================================
|
|
- name: Provision gitea-http SPNEGO account and keytab in Samba AD
|
|
hosts: ad_dc
|
|
gather_facts: false
|
|
become: true
|
|
|
|
vars:
|
|
gitea_spnego_account: gitea-http
|
|
gitea_spnego_spn: HTTP/git.prole.org
|
|
gitea_spnego_realm: PROLE.ORG
|
|
gitea_spnego_namespace: gitea
|
|
gitea_kube_context: prole-service-cluster
|
|
force_keytab_reset: false
|
|
gitea_spnego_keytab_local: /tmp/git-http-fetched.keytab
|
|
|
|
tasks:
|
|
# ------------------------------------------------------------------
|
|
# 0. Ensure required Debian packages are present
|
|
# (ldb-tools provides ldbmodify; used to set msDS-SupportedEncryptionTypes)
|
|
# ------------------------------------------------------------------
|
|
- name: Ensure ldb-tools is installed (provides ldbmodify)
|
|
ansible.builtin.apt:
|
|
name: ldb-tools
|
|
state: present
|
|
update_cache: true
|
|
cache_valid_time: 3600
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 1. Idempotency probe — does the gitea-http service account exist?
|
|
# ------------------------------------------------------------------
|
|
- name: Check whether {{ gitea_spnego_account }} 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
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Set fact — gitea-http account already present
|
|
ansible.builtin.set_fact:
|
|
gitea_account_is_new: "{{ gitea_spnego_account not in _samba_users.stdout }}"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Report account pre-existence
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
{{ gitea_spnego_account }}
|
|
{{ 'is missing — will create.' if gitea_account_is_new
|
|
else 'already present in Samba.' }}
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 2. Create gitea-http if missing
|
|
# ------------------------------------------------------------------
|
|
- name: Create {{ gitea_spnego_account }} service account if missing
|
|
ansible.builtin.command:
|
|
argv:
|
|
- samba-tool
|
|
- user
|
|
- create
|
|
- "{{ gitea_spnego_account }}"
|
|
- "--random-password"
|
|
- "--description=Gitea HTTP SPNEGO service account"
|
|
register: _gitea_create
|
|
changed_when: _gitea_create.rc == 0
|
|
failed_when: _gitea_create.rc != 0
|
|
when: gitea_account_is_new | bool
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 3. Ensure SPN HTTP/git.prole.org is registered on the account
|
|
#
|
|
# samba-tool spn add exits 0 whether or not the SPN was already
|
|
# present but emits "already has SPN" on stdout when it's a no-op.
|
|
# We mark changed_when=false and suppress the "already exists"
|
|
# non-error so reruns stay clean.
|
|
# ------------------------------------------------------------------
|
|
- name: Ensure SPN {{ gitea_spnego_spn }} is registered on {{ gitea_spnego_account }}
|
|
ansible.builtin.command:
|
|
argv:
|
|
- samba-tool
|
|
- spn
|
|
- add
|
|
- "{{ gitea_spnego_spn }}"
|
|
- "{{ gitea_spnego_account }}"
|
|
register: _gitea_spn
|
|
changed_when: false
|
|
failed_when:
|
|
- _gitea_spn.rc != 0
|
|
- "'already' not in (_gitea_spn.stderr | default('') | lower)"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 4. Locate ldbmodify binary
|
|
# (same pattern as kerberos_trust_setup.yml)
|
|
# ------------------------------------------------------------------
|
|
- name: Locate ldbmodify binary
|
|
ansible.builtin.shell:
|
|
cmd: |
|
|
for p in /usr/bin/ldbmodify /usr/sbin/ldbmodify /usr/local/bin/ldbmodify /opt/samba/bin/ldbmodify; do
|
|
[ -x "$p" ] && { echo "$p"; exit 0; }
|
|
done
|
|
# Fallback: filesystem scan limited to common roots
|
|
found=$(find /usr /opt -maxdepth 4 -type f -name ldbmodify 2>/dev/null | head -1)
|
|
[ -n "$found" ] && { echo "$found"; exit 0; }
|
|
echo "ldbmodify not found" >&2
|
|
exit 1
|
|
register: _ldbmodify_path
|
|
changed_when: false
|
|
check_mode: false
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 5. Set msDS-SupportedEncryptionTypes=24 (AES128+AES256, no RC4)
|
|
#
|
|
# Value 24 = 0x18 = bit3 (AES128-CTS-HMAC-SHA1-96) |
|
|
# bit4 (AES256-CTS-HMAC-SHA1-96).
|
|
# Explicitly excludes RC4 (bit2=4) so the Samba KDC will only
|
|
# issue AES-keyed service tickets for this principal.
|
|
# Unlike the cross-realm krbtgt (which must use RC4 for MIT
|
|
# interop), HTTP SPNEGO service tickets are local to PROLE.ORG
|
|
# and can safely use AES end-to-end.
|
|
# ------------------------------------------------------------------
|
|
- name: Set msDS-SupportedEncryptionTypes=24 (AES128+AES256) on {{ gitea_spnego_account }}
|
|
ansible.builtin.shell:
|
|
cmd: |
|
|
set -euo pipefail
|
|
ldif=$(mktemp)
|
|
cat > "$ldif" <<'EOF'
|
|
dn: CN=gitea-http,CN=Users,DC=prole,DC=org
|
|
changetype: modify
|
|
replace: msDS-SupportedEncryptionTypes
|
|
msDS-SupportedEncryptionTypes: 24
|
|
EOF
|
|
"{{ _ldbmodify_path.stdout | trim }}" -H /var/lib/samba/private/sam.ldb "$ldif"
|
|
rm -f "$ldif"
|
|
register: _gitea_enctype
|
|
changed_when: "'Modified 1 records' in (_gitea_enctype.stdout | default(''))"
|
|
failed_when: _gitea_enctype.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 6. Generate a strong random password for AES key derivation,
|
|
# then reset the account password.
|
|
#
|
|
# Gated on: account is new OR caller passed -e force_keytab_reset=true.
|
|
# The password is ephemeral — we only need it to derive AES keys
|
|
# in the keytab. Samba generates the keys at setpassword time.
|
|
# no_log protects the plaintext password from appearing in output.
|
|
# ------------------------------------------------------------------
|
|
- name: Generate ephemeral password for AES key derivation
|
|
ansible.builtin.set_fact:
|
|
_gitea_spnego_password: "{{ lookup('password', '/dev/null length=40 chars=ascii_letters,digits') }}"
|
|
no_log: true
|
|
when: gitea_account_is_new | bool or force_keytab_reset | bool
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Reset {{ gitea_spnego_account }} password to derive fresh AES keys
|
|
ansible.builtin.command:
|
|
argv:
|
|
- samba-tool
|
|
- user
|
|
- setpassword
|
|
- "{{ gitea_spnego_account }}"
|
|
- "--newpassword={{ _gitea_spnego_password }}"
|
|
no_log: true
|
|
register: _gitea_setpw
|
|
changed_when: _gitea_setpw.rc == 0
|
|
failed_when: _gitea_setpw.rc != 0
|
|
when: gitea_account_is_new | bool or force_keytab_reset | bool
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 7. Export the full domain keytab to /tmp/full-domain.keytab
|
|
#
|
|
# We export all accounts (no --principal filter) and then rekey
|
|
# to a per-SPN keytab in step 8. This avoids having to construct
|
|
# the exact principal name expected by samba-tool's filter flag.
|
|
# ------------------------------------------------------------------
|
|
- name: Export full domain keytab to /tmp/full-domain.keytab
|
|
ansible.builtin.command:
|
|
argv:
|
|
- samba-tool
|
|
- domain
|
|
- exportkeytab
|
|
- /tmp/full-domain.keytab
|
|
register: _gitea_export
|
|
changed_when: _gitea_export.rc == 0
|
|
failed_when: _gitea_export.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 8. Rekey full-domain.keytab → git-http.keytab
|
|
#
|
|
# The inline Python script:
|
|
# - Parses the MIT keytab format (magic 0x0502, big-endian).
|
|
# - Finds all entries for gitea-http@PROLE.ORG with etype 17
|
|
# (AES128) or 18 (AES256) at the highest KVNO.
|
|
# - Rewrites those entries with the principal name changed to
|
|
# HTTP/git.prole.org@PROLE.ORG (name_type=3, KRB_NT_SRV_HST).
|
|
# - Writes the result to /tmp/git-http.keytab.
|
|
#
|
|
# This is the format required by Apache mod_auth_gssapi, nginx
|
|
# SPNEGO modules, and the gitea-spnego-proxy sidecar.
|
|
# ------------------------------------------------------------------
|
|
- name: Rekey domain keytab to SPN-named keytab /tmp/git-http.keytab
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- -c
|
|
- |
|
|
import struct, sys
|
|
|
|
ETYPE_AES128, ETYPE_AES256 = 17, 18
|
|
TARGET_PRINCIPAL_COMPONENTS = [b"gitea-http"]
|
|
TARGET_REALM = b"PROLE.ORG"
|
|
OUTPUT_COMPONENTS = [b"HTTP", b"git.prole.org"]
|
|
OUTPUT_NAME_TYPE = 3 # KRB_NT_SRV_HST
|
|
OUTPUT_REALM = b"PROLE.ORG"
|
|
INPUT_PATH = "/tmp/full-domain.keytab"
|
|
OUTPUT_PATH = "/tmp/git-http.keytab"
|
|
|
|
def read_u8(data, pos):
|
|
return data[pos], pos + 1
|
|
|
|
def read_u16be(data, pos):
|
|
return struct.unpack_from(">H", data, pos)[0], pos + 2
|
|
|
|
def read_u32be(data, pos):
|
|
return struct.unpack_from(">I", data, pos)[0], pos + 4
|
|
|
|
def read_counted(data, pos):
|
|
length, pos = read_u16be(data, pos)
|
|
value = data[pos:pos + length]
|
|
return value, pos + length
|
|
|
|
def read_entry(data, pos):
|
|
"""Read one keytab entry, return (entry_dict, next_pos)."""
|
|
size, pos = read_u32be(data, pos)
|
|
if size == 0:
|
|
return None, pos
|
|
entry_start = pos
|
|
entry_end = pos + size
|
|
|
|
num_components, pos = read_u16be(data, pos)
|
|
realm, pos = read_counted(data, pos)
|
|
components = []
|
|
for _ in range(num_components):
|
|
comp, pos = read_counted(data, pos)
|
|
components.append(comp)
|
|
name_type, pos = read_u32be(data, pos)
|
|
timestamp, pos = read_u32be(data, pos)
|
|
kvno8, pos = read_u8(data, pos)
|
|
etype, pos = read_u16be(data, pos)
|
|
key, pos = read_counted(data, pos)
|
|
kvno32 = kvno8
|
|
if pos < entry_end:
|
|
kvno32, pos = read_u32be(data, pos)
|
|
|
|
return {
|
|
"realm": realm,
|
|
"components": components,
|
|
"name_type": name_type,
|
|
"timestamp": timestamp,
|
|
"kvno": kvno32,
|
|
"etype": etype,
|
|
"key": key,
|
|
}, entry_end
|
|
|
|
def pack_entry(entry):
|
|
"""Serialise one keytab entry back to bytes (including 4-byte length prefix)."""
|
|
buf = bytearray()
|
|
|
|
def w16(v): buf.extend(struct.pack(">H", v))
|
|
def w32(v): buf.extend(struct.pack(">I", v))
|
|
def w8(v): buf.append(v)
|
|
def wc(b):
|
|
w16(len(b)); buf.extend(b)
|
|
|
|
w16(len(entry["components"]))
|
|
wc(entry["realm"])
|
|
for c in entry["components"]:
|
|
wc(c)
|
|
w32(entry["name_type"])
|
|
w32(entry["timestamp"])
|
|
w8(entry["kvno"] & 0xFF)
|
|
w16(entry["etype"])
|
|
wc(entry["key"])
|
|
w32(entry["kvno"]) # 32-bit KVNO extension
|
|
|
|
return struct.pack(">I", len(buf)) + bytes(buf)
|
|
|
|
# --- Parse input keytab ---
|
|
with open(INPUT_PATH, "rb") as fh:
|
|
raw = fh.read()
|
|
|
|
magic = struct.unpack_from(">H", raw, 0)[0]
|
|
if magic != 0x0502:
|
|
sys.exit(f"Unexpected keytab magic: {magic:#06x} (expected 0x0502)")
|
|
|
|
pos = 2
|
|
entries = []
|
|
while pos < len(raw):
|
|
if pos + 4 > len(raw):
|
|
break
|
|
entry, pos = read_entry(raw, pos)
|
|
if entry is None:
|
|
break
|
|
entries.append(entry)
|
|
|
|
# --- Select target entries ---
|
|
def is_target(e):
|
|
return (
|
|
e["realm"] == TARGET_REALM
|
|
and e["components"] == TARGET_PRINCIPAL_COMPONENTS
|
|
and e["etype"] in (ETYPE_AES128, ETYPE_AES256)
|
|
)
|
|
|
|
target_entries = [e for e in entries if is_target(e)]
|
|
if not target_entries:
|
|
sys.exit(
|
|
f"No AES entries found for "
|
|
f"{'/'.join(c.decode() for c in TARGET_PRINCIPAL_COMPONENTS)}"
|
|
f"@{TARGET_REALM.decode()} in {INPUT_PATH}"
|
|
)
|
|
|
|
max_kvno = max(e["kvno"] for e in target_entries)
|
|
selected = [e for e in target_entries if e["kvno"] == max_kvno]
|
|
|
|
print(f"Selected {len(selected)} entries for KVNO={max_kvno}, "
|
|
f"etypes={[e['etype'] for e in selected]}")
|
|
|
|
# --- Rewrite principal name ---
|
|
out_entries = []
|
|
for e in selected:
|
|
renamed = dict(e)
|
|
renamed["components"] = OUTPUT_COMPONENTS
|
|
renamed["name_type"] = OUTPUT_NAME_TYPE
|
|
renamed["realm"] = OUTPUT_REALM
|
|
out_entries.append(renamed)
|
|
|
|
# --- Write output keytab ---
|
|
with open(OUTPUT_PATH, "wb") as fh:
|
|
fh.write(struct.pack(">H", 0x0502))
|
|
for e in out_entries:
|
|
fh.write(pack_entry(e))
|
|
|
|
print(f"Wrote {len(out_entries)} entries to {OUTPUT_PATH}")
|
|
register: _gitea_rekey
|
|
changed_when: _gitea_rekey.rc == 0
|
|
failed_when: _gitea_rekey.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Print keytab rekey output
|
|
ansible.builtin.debug:
|
|
msg: "{{ _gitea_rekey.stdout_lines }}"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 9. Verify the output keytab contains the expected SPN principal
|
|
# ------------------------------------------------------------------
|
|
- name: Verify /tmp/git-http.keytab contains HTTP/git.prole.org@PROLE.ORG
|
|
ansible.builtin.command:
|
|
argv:
|
|
- klist
|
|
- -k
|
|
- -e
|
|
- /tmp/git-http.keytab
|
|
register: _gitea_klist
|
|
changed_when: false
|
|
failed_when: >-
|
|
_gitea_klist.rc != 0
|
|
or 'HTTP/git.prole.org@PROLE.ORG' not in _gitea_klist.stdout
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Print klist output for verification
|
|
ansible.builtin.debug:
|
|
msg: "{{ _gitea_klist.stdout_lines }}"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 10. Fetch keytab to control node
|
|
# ------------------------------------------------------------------
|
|
- name: Fetch git-http.keytab from remote to control node
|
|
ansible.builtin.fetch:
|
|
src: /tmp/git-http.keytab
|
|
dest: "{{ gitea_spnego_keytab_local }}"
|
|
flat: true
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 11. Cleanup temp files on the remote host
|
|
# ------------------------------------------------------------------
|
|
- name: Remove /tmp/full-domain.keytab from remote
|
|
ansible.builtin.file:
|
|
path: /tmp/full-domain.keytab
|
|
state: absent
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Remove /tmp/git-http.keytab from remote
|
|
ansible.builtin.file:
|
|
path: /tmp/git-http.keytab
|
|
state: absent
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
|
|
# ===========================================================================
|
|
# Play 2 — Kubernetes: apply secret and restart gitea-spnego-proxy
|
|
# ===========================================================================
|
|
- name: Apply SPNEGO keytab secret and restart gitea-spnego-proxy in Kubernetes
|
|
hosts: localhost
|
|
gather_facts: false
|
|
become: false
|
|
|
|
vars:
|
|
gitea_kube_context: prole-service-cluster
|
|
gitea_spnego_namespace: gitea
|
|
gitea_spnego_keytab_local: /tmp/git-http-fetched.keytab
|
|
|
|
tasks:
|
|
# ------------------------------------------------------------------
|
|
# 12. Apply keytab as Kubernetes secret gitea-krb5-keytab
|
|
#
|
|
# Uses --dry-run=client | apply to be fully idempotent — creates
|
|
# on first run, patches on subsequent runs.
|
|
# ------------------------------------------------------------------
|
|
- name: Apply gitea-krb5-keytab secret in namespace {{ gitea_spnego_namespace }}
|
|
ansible.builtin.shell:
|
|
cmd: |
|
|
set -o pipefail
|
|
kubectl --context={{ gitea_kube_context }} \
|
|
-n {{ gitea_spnego_namespace }} \
|
|
create secret generic gitea-krb5-keytab \
|
|
--from-file=http.keytab={{ gitea_spnego_keytab_local }} \
|
|
--dry-run=client -o yaml \
|
|
| kubectl --context={{ gitea_kube_context }} apply -f -
|
|
executable: /bin/bash
|
|
register: _gitea_secret
|
|
changed_when: "'configured' in _gitea_secret.stdout or 'created' in _gitea_secret.stdout"
|
|
failed_when: _gitea_secret.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Print kubectl apply output for secret
|
|
ansible.builtin.debug:
|
|
msg: "{{ _gitea_secret.stdout_lines }}"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 13. Restart gitea-spnego-proxy deployment to pick up new keytab
|
|
# ------------------------------------------------------------------
|
|
- name: Restart deployment/gitea-spnego-proxy
|
|
ansible.builtin.command:
|
|
argv:
|
|
- kubectl
|
|
- "--context={{ gitea_kube_context }}"
|
|
- -n
|
|
- "{{ gitea_spnego_namespace }}"
|
|
- rollout
|
|
- restart
|
|
- deployment/gitea-spnego-proxy
|
|
register: _gitea_restart
|
|
changed_when: _gitea_restart.rc == 0
|
|
failed_when: _gitea_restart.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 14. Wait for rollout to complete (timeout: 120s)
|
|
# ------------------------------------------------------------------
|
|
- name: Wait for gitea-spnego-proxy rollout to complete
|
|
ansible.builtin.command:
|
|
argv:
|
|
- kubectl
|
|
- "--context={{ gitea_kube_context }}"
|
|
- -n
|
|
- "{{ gitea_spnego_namespace }}"
|
|
- rollout
|
|
- status
|
|
- deployment/gitea-spnego-proxy
|
|
- "--timeout=120s"
|
|
register: _gitea_rollout
|
|
changed_when: false
|
|
failed_when: _gitea_rollout.rc != 0
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Print rollout status
|
|
ansible.builtin.debug:
|
|
msg: "{{ _gitea_rollout.stdout_lines }}"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
# ------------------------------------------------------------------
|
|
# 15. Smoke test — confirm SPNEGO challenge (HTTP 401 + Negotiate)
|
|
#
|
|
# A correctly deployed SPNEGO proxy returns 401 with
|
|
# WWW-Authenticate: Negotiate on unauthenticated requests.
|
|
# We check the status code; a 200 would mean auth is disabled,
|
|
# anything other than 401 means the proxy is misconfigured.
|
|
# ------------------------------------------------------------------
|
|
- name: Smoke test — https://git.prole.org/ should return HTTP 401 (SPNEGO challenge)
|
|
ansible.builtin.command:
|
|
argv:
|
|
- curl
|
|
- -s
|
|
- -o
|
|
- /dev/null
|
|
- -w
|
|
- "%{http_code}"
|
|
- https://git.prole.org/
|
|
register: _gitea_smoke
|
|
changed_when: false
|
|
failed_when: _gitea_smoke.stdout | trim != "401"
|
|
tags: [spnego, keytab, gitea]
|
|
|
|
- name: Print smoke test result
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
https://git.prole.org/ returned HTTP {{ _gitea_smoke.stdout | trim }}.
|
|
SPNEGO challenge confirmed — proxy is issuing Negotiate header.
|
|
tags: [spnego, keytab, gitea]
|