mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
Compare commits
2 Commits
b245593b0c
...
0e937eb9db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e937eb9db | ||
|
|
5077e1335d |
218
README.md
218
README.md
@ -297,33 +297,192 @@ Detailed setup docs, cluster procedures, and host-specific notes should live in
|
||||
|
||||
---
|
||||
|
||||
## prole.org deployment
|
||||
|
||||
This repository is the **prole.org fork** of the upstream knoe-db platform. It runs a production k3s cluster on a three-node ARM64 LAN (myrddin / gandalf / merlin) with Samba AD providing Kerberos identity for single sign-on across all platform services.
|
||||
|
||||
### Live services
|
||||
|
||||
| Service | URL | Auth |
|
||||
|---|---|---|
|
||||
| Supabase Studio | `https://db.prole.org` | SPNEGO → OIDC → Studio |
|
||||
| Gitea | `https://git.prole.org` | SPNEGO (auto) or Gitea login |
|
||||
| PostgreSQL | `pg.prole.org:5432` | mTLS + CNPG cert |
|
||||
| SSH (Gitea) | `git.prole.org:3022` | SSH key |
|
||||
|
||||
### Cluster nodes
|
||||
|
||||
| Host | Role | IP |
|
||||
|---|---|---|
|
||||
| `myrddin.prole.org` | k3s server, Samba AD DC, container registry | 10.0.0.3 |
|
||||
| `gandalf.prole.org` | k3s agent | 10.0.0.5 |
|
||||
| `merlin.prole.org` | k3s agent | 10.0.0.6 |
|
||||
|
||||
Kubectl context: `prole-service-cluster` (kubeconfig at `knoe-k3s.kubeconfig`).
|
||||
|
||||
### Key namespaces
|
||||
|
||||
| Namespace | Contents |
|
||||
|---|---|
|
||||
| `knoe-system` | knoe-auth, Kong API gateway, KDC, Redis, Traefik |
|
||||
| `gitea` | Gitea, gitea-spnego-proxy |
|
||||
| `knoe-db` | CloudNativePG cluster |
|
||||
| `supabase` | Supabase Studio, Kong (supabase), oauth2-proxy |
|
||||
|
||||
---
|
||||
|
||||
## Identity & SSO
|
||||
|
||||
All platform services use a unified Kerberos SSO stack. The flow from browser to service is:
|
||||
|
||||
```
|
||||
Browser (Chrome, Safari, curl --negotiate)
|
||||
│ kinit-obtained TGT from PROLE.ORG KDC (myrddin.prole.org)
|
||||
▼
|
||||
Traefik (TLS termination, *.prole.org wildcard cert via ACME)
|
||||
│
|
||||
▼
|
||||
Kong API gateway (knoe-system/knoe-svc-kong)
|
||||
│ routes by Host header
|
||||
├── db.prole.org ──► oauth2-proxy ──► knoe-auth (OIDC) ──► Supabase Studio
|
||||
└── git.prole.org ──► gitea-spnego-proxy (port 4000) ──► Gitea (port 3000)
|
||||
```
|
||||
|
||||
### Kerberos realm
|
||||
|
||||
- **Realm:** `PROLE.ORG`
|
||||
- **KDC / AD DC:** `myrddin.prole.org` (Samba 4, `10.0.0.3`)
|
||||
- **Cross-realm trust:** `PROLE.ORG ↔ KNOE.LOCAL` (in-cluster MIT KDC for knoe-auth internal use)
|
||||
- **Encryption:** AES256 + AES128 only (`msDS-SupportedEncryptionTypes=24`); RC4 disabled on all service accounts
|
||||
|
||||
### knoe-auth (OIDC provider — `api.prole.org/auth`)
|
||||
|
||||
knoe-auth is a Spring Boot 3 / JDK 21 OIDC authorization server that validates Kerberos SPNEGO tokens and issues OIDC tokens for downstream services.
|
||||
|
||||
**Key implementation details:**
|
||||
|
||||
| Detail | Value |
|
||||
|---|---|
|
||||
| Deployment | `knoe-system/knoe-auth` |
|
||||
| SPNEGO endpoint | `https://api.prole.org/auth/spnego` |
|
||||
| Keytab secret | `knoe-system/knoe-auth-keytab` (`HTTP/api.prole.org@PROLE.ORG`, AES-only) |
|
||||
| JDK Subject API | `Subject.callAs()` — **not** `Subject.doAs()` (removed in JDK 21) |
|
||||
| RC4 | Hard-removed in JDK 21 JGSS — keytab and AD account must be AES-only |
|
||||
| krb5.conf | Mounted via ConfigMap; `permitted_enctypes = aes256 aes128` (cannot re-enable RC4 here) |
|
||||
|
||||
Keytab provisioning (`etc/init_knoe_users.sh`):
|
||||
```bash
|
||||
# On myrddin — set AES-only, reset password, export and rekey keytab
|
||||
sudo ldbmodify -H /var/lib/samba/private/sam.ldb <<EOF
|
||||
dn: CN=knoe-auth,CN=Users,DC=prole,DC=org
|
||||
changetype: modify
|
||||
replace: msDS-SupportedEncryptionTypes
|
||||
msDS-SupportedEncryptionTypes: 24
|
||||
EOF
|
||||
sudo samba-tool user setpassword knoe-auth --newpassword="$(openssl rand -base64 32 | tr -d '=/+' | head -c 40)"
|
||||
# Export full domain keytab; rekey to HTTP/api.prole.org@PROLE.ORG in Python
|
||||
# (samba-tool exportkeytab --principal=HTTP/... returns empty — see note below)
|
||||
```
|
||||
|
||||
**Note:** `samba-tool domain exportkeytab --principal=HTTP/hostname` always produces an empty keytab on Samba 4 — the filter only matches UPN format, not SPN format. The correct procedure is to export the full domain keytab and rekey the AES entries to the SPN principal name using the Python script embedded in `infrastructure/playbooks/gitea_spnego_keytab.yml` (same logic applies for all HTTP service principals).
|
||||
|
||||
### Supabase Studio SSO (`db.prole.org`)
|
||||
|
||||
```
|
||||
Browser → Kong → oauth2-proxy → knoe-auth OIDC → oauth2-proxy (cookie set)
|
||||
→ Kong (upstream to studio)
|
||||
→ Supabase Studio
|
||||
```
|
||||
|
||||
- **oauth2-proxy** handles the OIDC callback and sets a session cookie (`_oauth2_proxy`)
|
||||
- **Supabase Kong** dashboard route: `cors` plugin only — `basic-auth` plugin **removed** (it blocked oauth2-proxy's proxied requests)
|
||||
- The `basic-auth` removal is applied directly to the live ConfigMap and must be re-applied after any `helm upgrade` of the supabase chart
|
||||
|
||||
Supabase Studio access:
|
||||
```bash
|
||||
# Via browser (automatic SPNEGO with Chrome policy)
|
||||
open https://db.prole.org
|
||||
|
||||
# Check oauth2-proxy is passing through correctly
|
||||
curl -I https://db.prole.org/oauth2/sign_in
|
||||
```
|
||||
|
||||
Chrome SPNEGO policy (`/Library/Managed Preferences/com.google.Chrome.plist`):
|
||||
```xml
|
||||
<key>AuthServerAllowlist</key>
|
||||
<string>*.prole.org</string>
|
||||
<key>AuthNegotiateDelegateAllowlist</key>
|
||||
<string>*.prole.org</string>
|
||||
```
|
||||
|
||||
Deploy to a Mac workstation:
|
||||
```bash
|
||||
make workstation
|
||||
# or: ansible-playbook infrastructure/playbooks/workstation_kerberos.yml --ask-become-pass
|
||||
```
|
||||
|
||||
### git.prole.org SPNEGO (Gitea)
|
||||
|
||||
```
|
||||
Browser/curl → Kong → gitea-spnego-proxy (:4000) → Gitea (:3000)
|
||||
│
|
||||
├─ No Authorization header → 401 + WWW-Authenticate: Negotiate
|
||||
├─ Authorization: Negotiate <token> → SPNEGO validate → X-WEBAUTH-USER → Gitea auto-login
|
||||
└─ Authorization: Basic/token → pass-through → Gitea auth
|
||||
```
|
||||
|
||||
**Component:** `gitea/spnego-proxy/` — Go binary using `gokrb5/v8`, built for `linux/arm64`.
|
||||
|
||||
| Detail | Value |
|
||||
|---|---|
|
||||
| Image | `myrddin.prole.org:5000/gitea-spnego-proxy:latest` |
|
||||
| Keytab secret | `gitea/gitea-krb5-keytab` (`HTTP/git.prole.org@PROLE.ORG`, AES-only, KVNO 4) |
|
||||
| AD account | `CN=gitea-http,CN=Users,DC=prole,DC=org` (`msDS-SupportedEncryptionTypes=24`) |
|
||||
| Rebuild | Build on myrddin (native arm64); `docker build` then `docker push localhost:5000/...` |
|
||||
|
||||
Keytab rotation:
|
||||
```bash
|
||||
ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml -e force_keytab_reset=true
|
||||
```
|
||||
|
||||
Manual rebuild (if source changed):
|
||||
```bash
|
||||
# Transfer source and build on myrddin (all nodes are arm64)
|
||||
tar -czf /tmp/src.tar.gz gitea/spnego-proxy/ && scp /tmp/src.tar.gz myrddin:/tmp/
|
||||
ssh myrddin "mkdir -p /tmp/spnego-build && tar -xzf /tmp/src.tar.gz -C /tmp/spnego-build && \
|
||||
sudo docker build -t localhost:5000/gitea-spnego-proxy:latest /tmp/spnego-build/gitea/spnego-proxy/ && \
|
||||
sudo docker push localhost:5000/gitea-spnego-proxy:latest"
|
||||
kubectl --context=prole-service-cluster -n gitea rollout restart deployment/gitea-spnego-proxy
|
||||
```
|
||||
|
||||
Smoke test:
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" https://git.prole.org/ # → 401 (challenge)
|
||||
curl -s -o /dev/null -w "%{http_code}\n" --negotiate -u : https://git.prole.org/ # → 200 (authed)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
Knoe is an actively evolving platform stack aimed at practical self-hosted, edge, and cloud operation.
|
||||
**As of 2026-05-28** the following work has shipped on the prole.org fork:
|
||||
|
||||
**As of 2026-05-23** the following work has shipped:
|
||||
- `fix(cfg)` — `_validate_cfg_values` prevents MagicMock reprs from leaking into `conf/*.cfg` (`ef20c8a`)
|
||||
- `feat(env)` — `gke_` kubecontext prefix auto-detected as `prod`; `status.py` context helpers (`da0fd2c`)
|
||||
- `refactor(mock_val)` — `prole_*` shell lib and cfg tooling renamed to `knoe_*` namespace (`11064cb`)
|
||||
- `feat(mock_val)` — init scripts rewritten; 10+ new service init scripts added (`3f96f66`)
|
||||
- `feat(mock_val)` — diagnostics, utilities, certs, and operational scripts added (`3943d1b`)
|
||||
- `feat(scripts)` — upstream knoe-db sync script + procedure doc (`decb9a5`)
|
||||
- `feat(infrastructure)` — Pi-hole DNS flush Ansible playbook (`3ada16c`)
|
||||
- `docs` — shipped briefs moved to `docs/completed/`; `conf/service/knoe.cfg` added (`73dce03`)
|
||||
- `feat(mock_val)` — `common_core_lib` mode-aware config path helper (`d806905`)
|
||||
- `fix(conf)` — MagicMock contamination removed from `conf/k3d.cfg` and `conf/k3s.cfg` (`568f03b`)
|
||||
- `docs(branches)` — upstream knoe-db/20260523 review doc, branch index, and Junie integration brief (`0e822ea`)
|
||||
- `feat(pg-knoe-auth)` — upstream PostgreSQL JWT auth extension imported; compiled in `knoe-db` image (`57886f9`)
|
||||
- `docs(branches)` — Task 1 marked complete in branch index (`e5cf9b1`)
|
||||
**Identity / SSO (May 2026)**
|
||||
- `feat(gitea)` — SPNEGO Kerberos SSO for `git.prole.org`; proxy issues `WWW-Authenticate: Negotiate` challenge; `gitea_spnego_keytab.yml` Ansible playbook for full provisioning lifecycle (`5077e13`)
|
||||
- `fix(ansible)` — workstation install script + `make workstation` target for Chrome SPNEGO policy on personal Macs (`b245593`)
|
||||
- `fix(spnego)` — `Subject.callAs()`, AES-only keytab, and `krb5.conf` sync for JDK 21 knoe-auth (`c1d2a91`)
|
||||
- `feat(oidc)` — knoe-auth routing through Kong; Flyway schema baseline for clean OIDC DB (`9523045`)
|
||||
- `feat(prole)` — knoe-auth bootstrap on k3s; tenant onboarding; cluster stabilisation (`cf33342`)
|
||||
|
||||
Working tree is clean. Upstream sync tooling is operational; `upstream/knoe-db/20260523` review is in progress (Task 1 ✅).
|
||||
**Infrastructure (earlier)**
|
||||
- `fix(cfg)` — `_validate_cfg_values` prevents MagicMock reprs from leaking into `conf/*.cfg`
|
||||
- `feat(env)` — `gke_` kubecontext prefix auto-detected as `prod`; `status.py` context helpers
|
||||
- `refactor(mock_val)` — `prole_*` shell lib and cfg tooling renamed to `knoe_*` namespace
|
||||
- `feat(mock_val)` — init scripts rewritten; 10+ new service init scripts added
|
||||
- `feat(scripts)` — upstream knoe-db sync script + procedure doc
|
||||
- `feat(pg-knoe-auth)` — upstream PostgreSQL JWT auth extension imported; compiled in `knoe-db` image
|
||||
|
||||
Expect the architecture to continue being refined toward:
|
||||
- cleaner bootstrapping
|
||||
- better shard isolation
|
||||
- smoother rejoin/reset behavior for cluster nodes
|
||||
- clearer service boundaries
|
||||
- improved onboarding and operations documentation
|
||||
Working tree is clean. Browser SPNEGO SSO is live on `db.prole.org` and `git.prole.org`.
|
||||
|
||||
---
|
||||
|
||||
@ -347,6 +506,21 @@ Upstream changes are pulled into a dated review branch (`upstream/knoe-db/YYYYMM
|
||||
|
||||
See [`docs/upstream-knoe-db-sync.md`](docs/upstream-knoe-db-sync.md) for the full review-and-merge procedure.
|
||||
|
||||
### Fork-specific files
|
||||
|
||||
Files added or substantially modified in the prole.org fork (not present or not relevant upstream):
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `infrastructure/` | Ansible roles, playbooks, and inventory for the prole.org cluster |
|
||||
| `infrastructure/playbooks/gitea_spnego_keytab.yml` | Gitea SPNEGO keytab provisioning |
|
||||
| `infrastructure/playbooks/workstation_kerberos.yml` | Chrome SPNEGO policy + krb5.conf for macOS workstations |
|
||||
| `infrastructure/playbooks/kerberos_trust_setup.yml` | PROLE.ORG ↔ KNOE.LOCAL cross-realm trust |
|
||||
| `infrastructure/bin/install_workstation.sh` | Wrapper for workstation Ansible (adds `--ask-become-pass`) |
|
||||
| `gitea/spnego-proxy/` | Go SPNEGO reverse proxy for `git.prole.org` |
|
||||
| `deploy/opentofu/k3s/manifests/knoe/gitea-spnego-proxy.yaml` | k8s deployment for the SPNEGO proxy |
|
||||
| `conf/k3s.cfg` | prole.org k3s cluster configuration |
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
@ -463,6 +463,73 @@ spec:
|
||||
EOF
|
||||
}
|
||||
|
||||
setup_gitea_spnego() {
|
||||
# Provision the SPNEGO keytab and deploy the gitea-spnego-proxy sidecar.
|
||||
#
|
||||
# In k3s mode: runs the Ansible playbook gitea_spnego_keytab.yml which:
|
||||
# - Creates/updates the gitea-http AD account with AES-only encryption
|
||||
# - Registers the SPN HTTP/git.prole.org on that account
|
||||
# - Exports and rekeys the keytab to HTTP/git.prole.org@PROLE.ORG
|
||||
# - Applies the k8s secret gitea-krb5-keytab in the gitea namespace
|
||||
# - Restarts the gitea-spnego-proxy deployment
|
||||
#
|
||||
# Key knowledge captured here:
|
||||
# - samba-tool domain exportkeytab with --principal=HTTP/git.prole.org
|
||||
# produces an empty keytab. The account principal (gitea-http@PROLE.ORG)
|
||||
# must be exported via the full domain keytab and rekeyed in Python.
|
||||
# - msDS-SupportedEncryptionTypes=24 must be set before the password reset
|
||||
# to ensure the KDC generates AES keys (16=AES256, 8=AES128, sum=24).
|
||||
# - gokrb5 v8 matches keytab entries by principal name components, so the
|
||||
# keytab MUST contain HTTP/git.prole.org@PROLE.ORG (not gitea-http@PROLE.ORG)
|
||||
# for the SPNEGO token decryption to succeed.
|
||||
# - The proxy must issue WWW-Authenticate: Negotiate on requests with no
|
||||
# Authorization header; without the challenge Chrome never sends a token.
|
||||
# - Nodes are arm64 — build the proxy image on myrddin, not cross-compiled.
|
||||
#
|
||||
# Keytab rotation (e.g. after AD password policy change):
|
||||
# ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml \
|
||||
# -e force_keytab_reset=true
|
||||
|
||||
# Apply the spnego-proxy manifest (idempotent — already defined in the k3s
|
||||
# manifests directory; this ensures it's present after a fresh cluster build).
|
||||
local spnego_manifest="${SCRIPT_DIR}/../deploy/opentofu/k3s/manifests/knoe/gitea-spnego-proxy.yaml"
|
||||
if [[ -f "$spnego_manifest" ]]; then
|
||||
log "Applying gitea-spnego-proxy manifest..."
|
||||
kubectl apply -f "$spnego_manifest" >/dev/null 2>&1 || warn "gitea-spnego-proxy manifest apply failed (may already be up-to-date)"
|
||||
else
|
||||
warn "gitea-spnego-proxy manifest not found at $spnego_manifest — skipping"
|
||||
fi
|
||||
|
||||
if [[ "$MODE" != "k3s" ]]; then
|
||||
warn "SPNEGO keytab provisioning via Ansible only runs in k3s mode (current: $MODE)."
|
||||
warn "For other modes, manually run:"
|
||||
warn " ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v ansible-playbook >/dev/null 2>&1; then
|
||||
warn "ansible-playbook not found — skipping SPNEGO keytab provisioning."
|
||||
warn "Run manually: ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local playbook="${SCRIPT_DIR}/../infrastructure/playbooks/gitea_spnego_keytab.yml"
|
||||
if [[ ! -f "$playbook" ]]; then
|
||||
warn "Playbook not found at $playbook — skipping SPNEGO keytab provisioning"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Running SPNEGO keytab provisioning via Ansible..."
|
||||
if ansible-playbook "$playbook" \
|
||||
-e "gitea_kube_context=${KUBECONTEXT:-prole-service-cluster}" \
|
||||
-e "gitea_spnego_namespace=${NAMESPACE}"; then
|
||||
log "✓ Gitea SPNEGO keytab provisioned — SPNEGO challenge active on git.prole.org"
|
||||
else
|
||||
warn "SPNEGO keytab provisioning failed. Gitea will still work via its own login page."
|
||||
warn "Retry: ansible-playbook infrastructure/playbooks/gitea_spnego_keytab.yml -e force_keytab_reset=true"
|
||||
fi
|
||||
}
|
||||
|
||||
ACTION="${1:-deploy}"
|
||||
case "$ACTION" in
|
||||
deploy|"") ;;
|
||||
@ -484,5 +551,9 @@ fi
|
||||
log "Waiting for deployment rollout..."
|
||||
kubectl -n "$NAMESPACE" rollout status deploy/gitea --timeout=10m
|
||||
|
||||
# Provision the SPNEGO proxy keytab and verify the Negotiate challenge is live.
|
||||
# This step is idempotent — safe to run on upgrades/re-deploys.
|
||||
setup_gitea_spnego
|
||||
|
||||
log "Done. Service endpoints:"
|
||||
kubectl -n "$NAMESPACE" get svc gitea-http gitea-ssh
|
||||
|
||||
@ -75,7 +75,8 @@ func main() {
|
||||
})
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.Header.Get("Authorization"), "Negotiate ") {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(auth, "Negotiate ") {
|
||||
// Client is presenting a Kerberos ticket — run full SPNEGO negotiation.
|
||||
// On success: X-WEBAUTH-USER is set by authedInner, Gitea auto-logs in.
|
||||
// On failure: gokrb5 returns 401 (invalid ticket — correct to reject).
|
||||
@ -84,12 +85,25 @@ func main() {
|
||||
if rw.status == http.StatusUnauthorized {
|
||||
log.Printf("SPNEGO auth FAILED: from=%s host=%s path=%s token-prefix=%.20s",
|
||||
r.RemoteAddr, r.Host, r.URL.Path,
|
||||
r.Header.Get("Authorization")[len("Negotiate "):])
|
||||
auth[len("Negotiate "):])
|
||||
}
|
||||
return
|
||||
}
|
||||
// No Kerberos ticket — pass through so Gitea can show its own login page.
|
||||
proxy.ServeHTTP(w, r)
|
||||
if auth != "" {
|
||||
// Other auth scheme (Basic, token) — pass through to Gitea.
|
||||
// This allows git CLI users with personal access tokens or passwords to
|
||||
// authenticate directly via Gitea's own credential validation.
|
||||
log.Printf("pass-through auth: from=%s scheme=%.6s...", r.RemoteAddr, auth)
|
||||
proxy.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// No auth header — issue a SPNEGO challenge.
|
||||
// Browsers with Chrome SPNEGO policy (AuthServerAllowlist=*.prole.org) will
|
||||
// automatically respond with Authorization: Negotiate <kerberos-token>.
|
||||
// git CLI users with PATs will retry with Basic auth (handled above).
|
||||
w.Header().Set("WWW-Authenticate", "Negotiate")
|
||||
http.Error(w, "Authentication required (SPNEGO/Kerberos)", http.StatusUnauthorized)
|
||||
log.Printf("SPNEGO challenge issued: from=%s host=%s path=%s", r.RemoteAddr, r.Host, r.URL.Path)
|
||||
})
|
||||
|
||||
log.Printf("spnego-proxy listening on %s, upstream %s", listenAddr, upstreamURL)
|
||||
|
||||
555
infrastructure/playbooks/gitea_spnego_keytab.yml
Normal file
555
infrastructure/playbooks/gitea_spnego_keytab.yml
Normal file
@ -0,0 +1,555 @@
|
||||
---
|
||||
# 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]
|
||||
Loading…
Reference in New Issue
Block a user