Commit Graph

708 Commits

Author SHA1 Message Date
chrisfu
0e937eb9db docs(readme): document prole.org fork — identity stack, live services, SSO
Add dedicated sections covering:
- prole.org cluster topology (myrddin/gandalf/merlin, namespaces, kubectl context)
- Live services table (db.prole.org, git.prole.org, pg.prole.org)
- Full identity/SSO stack: PROLE.ORG Kerberos → knoe-auth OIDC → Supabase Studio
- knoe-auth operational notes: Subject.callAs vs doAs, JDK 21 RC4 removal,
  AES-only keytab requirement, samba-tool exportkeytab --principal empty-keytab gotcha
- git.prole.org SPNEGO: proxy challenge logic, keytab rotation, arm64 build procedure
- Supabase Studio SSO: oauth2-proxy flow, basic-auth removal from Kong dashboard route
- Chrome SPNEGO policy deployment via make workstation
- Fork-specific files table

Update status section to 2026-05-28 with Identity/SSO commits clearly grouped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 06:14:53 -07:00
chrisfu
5077e1335d feat(gitea): SPNEGO Kerberos SSO for git.prole.org
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>
2026-05-28 05:19:59 -07:00
chrisfu
b245593b0c fix(ansible): workstation install script + Makefile target for local Macs
ansible_become_ask_pass in group_vars does not reliably trigger an
interactive sudo prompt for ansible_connection=local — Ansible cannot
prompt per-host mid-play. The fix is a wrapper script that passes
-K / --ask-become-pass before any play execution.

- infrastructure/bin/install_workstation.sh: auto-detects FQDN, adds
  --ask-become-pass, passes extra args through. Run from repo root:
    ./infrastructure/bin/install_workstation.sh
    make workstation
- Makefile: add 'workstation' target pointing to the script
- group_vars/workstations.yml: drop ansible_become_ask_pass (does not
  work), keep ansible_pipelining=false (needed for SSH workstations
  with sudo passwords — pipelining replaces stdin, breaking sudo -S)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:15:49 -07:00
chrisfu
d945d88b5d fix(ansible): workstations group vars — become_ask_pass + no pipelining
Personal Macs require a sudo password; the global become_ask_pass=False
and pipelining=True combination silently swallows the prompt and fails.

- ansible_become_ask_pass: true  → Ansible prompts for sudo password
- ansible_pipelining: false      → sudo can read stdin for the password;
                                   pipelining replaces stdin with a pipe
                                   which sudo treats as non-interactive and
                                   refuses to ask for a password at all.

Applies to [workstations] group (mac + linux). Has no effect on
[workstations_windows] (WinRM auth is separate from become).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:10:26 -07:00
chrisfu
9571f42568 fix(ansible): wire vault_password_file to repo-root .vault_pass
Both ansible.cfg files lacked vault_password_file, causing
'Attempting to decrypt but no vault secrets found' when running any
playbook that auto-loads the encrypted group_vars/all/ files.

- ansible.cfg (root): vault_password_file = .vault_pass
- infrastructure/ansible.cfg: vault_password_file = ../.vault_pass

.vault_pass is gitignored. Each machine needs its own copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:05:49 -07:00
chrisfu
c1d2a91991 fix(spnego): Subject.callAs, AES keytab, and krb5.conf sync for JDK 21
- KerberosSpnegoService: replace Subject.doAs with Subject.callAs (JDK 18+)
  so Subject.current() is set correctly for JGSS credential lookup in JDK 21.
  Pass null GSSCredential to createContext so the mechanism auto-selects
  rather than failing with "No credential found for 1.3.6.1.5.5.2".
- knoe-auth-deployment: fix PROLE_KERBEROS_KEYTAB_PATH to /etc/knoe-auth/http.keytab
  (matches the knoe-auth-http-keytab secret volume mount).
- knoe-kdc-configmap: full rewrite from stale KNOE.DEV to KNOE.LOCAL;
  add PROLE.ORG realm (Samba AD at 10.0.0.3) and [capaths] for future
  cross-realm trust. Drop arcfour-hmac/des3 from permitted_enctypes —
  RC4 hard-removed in JDK 21 and service account now AES-only
  (msDS-SupportedEncryptionTypes=24). Fix hardcoded secret values in
  entrypoint.sh to use ${PROLE_KDC_MASTER_PASSWORD} variable references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:06:36 -07:00
chrisfu
9523045add feat(oidc): fix knoe-auth routing for Kong strip_path=false; add Flyway baseline
Add @RequestMapping(/auth) to all OIDC and login controllers so paths
match the Kong route (strip_path: false keeps /auth in the forwarded path).

Changes:
- authority/web: add @RequestMapping(/auth) to LoginController,
  OidcAuthorizeController, OidcTokenController, OidcUserInfoController
- authority/resources: add V1__oidc_authorization_codes Flyway migration
- application-prole.yml: add OIDC clientId/clientSecret env bindings
- knoe-auth-deployment.yaml: add Flyway baseline env vars and
  KNOE_AUTH_OIDC_CLIENT_ID/SECRET from knoe-auth-oidc-client secret

Result: db.prole.org login flow redirects correctly through
https://api.prole.org/auth/login

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 02:13:33 -07:00
chrisfu
cf33342500 feat(prole): bootstrap knoe-auth on k3s; tenant onboarding; cluster stabilisation
knoe-auth (prole.org k3s):
- Fix CNPG manifest drift: remove spec.backup.pluginConfiguration (CNPG 1.28 only),
  switch spec.certificates from serverTLSSecret to serverAltDNSNames
- Apply knoe-auth Round 1 schema + GRANTs manually (postInitSQL had never run on live cluster)
- Fix OIDC signing key generator: base64(DER) not base64(PEM) — OidcTokenService
  does Base64.decode() → PKCS8EncodedKeySpec which requires raw DER bytes
- Add OIDC controllers: authorize, token, userinfo, jwks, discovery
- Add prole Spring profile: cookieDomain, emailDomain, Kerberos config
- Add secret example templates: knoe-db-user, knoe-auth-oidc-signing, knoe-auth-google-prole
- Kong configmap: scope knoe-auth route to /auth prefix only

Tenant onboarding:
- Add etc/onboard_tenant.sh: provision/apply/rotate/status workflow backed by 1Password
  vaults; types: 'enterprise' (own Kerberos + domain) and 'tenant' (hosted, initContainer KDC)
- Provision 'Knoe Tenant - prole.org' vault; apply all 7 k8s secrets to knoe-system
- init_knoe_auth.sh: add explicit GRANT + ALTER DEFAULT PRIVILEGES for knoe role

Cluster stabilisation:
- gitea: roll back 14-day stuck rollout (RWO PVC + maxSurge=100% deadlock);
  patch deployment strategy to Recreate
- supabase: create supabase_admin role, _supabase db, _analytics schema, _realtime schema
  in CNPG — analytics and realtime had never connected since Helm install day 1
- knoe-db barman ObjectStore: add GCS-backed objectstore manifest + scheduled backup

Infrastructure:
- gandalf host_vars: k3s registry config
- pi host_vars: clean up stale entries
- knoe-db schemas: ekosystem.sql, ekosystem_objects.sql
- init_prole_app.sql: prole app DB initialisation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 00:50:37 -07:00
chrisfu
03d1c3d9e8 docs: update README and branch/plan indexes for pg-knoe-auth import (Task 1 complete) 2026-05-23 21:54:35 -07:00
chrisfu
e5cf9b1bb6 docs(branches): mark upstream-knoe-db-20260523 Task 1 complete (pg-knoe-auth imported) 2026-05-23 21:52:15 -07:00
chrisfu
57886f9268 feat(pg-knoe-auth): import upstream PostgreSQL JWT auth extension; compile in knoe-db image
- Copy pg-knoe-auth/ wholesale from upstream/knoe-db/20260523 (Task 1 of
  docs/plans/junie/upstream-knoe-db-20260523-integration.md).
- Extension: PG18 OAUTHBEARER JWT validator using libcurl + OpenSSL RS256.
- knoe-db/Dockerfile: add libcurl4-openssl-dev to dev deps; COPY src/ and
  build with make USE_PGXS=1 install after tds_fdw.
- NOT enabled in the default database build (absent from 20_create_extensions.sh).
  To enable: CREATE EXTENSION pg_knoe_auth; (requires pg_hba.conf oauth_issuer).

Closes Task 1 of upstream-knoe-db-20260523-integration.md.
2026-05-23 21:52:06 -07:00
chrisfu
0e822ea976 docs(branches): upstream knoe-db/20260523 review — branch doc, index, and Junie integration brief
- docs/branches/README.md: index of upstream review branches
- docs/branches/upstream-knoe-db-20260523.md: full analysis of 403-file diff
  (no shared history; conflict risk by area; recommended actions)
- docs/plans/junie/upstream-knoe-db-20260523-integration.md: 6-task Junie brief
  ordered by conflict risk (pg-knoe-auth → k3d manifests → briefs → authority
  OIDC fixes → knoe/core installer → etc/ init scripts)
- docs/plans/junie/README.md: integration brief added to active table
2026-05-23 21:44:13 -07:00
chrisfu
5babe04516 docs: update status — cfg files clean, no pending working-tree changes 2026-05-23 21:38:25 -07:00
chrisfu
568f03ba42 fix(conf): remove MagicMock contamination from k3d.cfg and k3s.cfg
Replace all <MagicMock ...> values with empty strings or sensible defaults:
- BooleanVar mocks → false
- StringVar mocks for known fields (cluster_name, db_host_port, db_namespace,
  db_username, cluster_env) → correct defaults
- All other StringVar mocks → empty string

Files are now safe to parse and commit. Protected going forward by
_validate_cfg_values() added in ef20c8a.
2026-05-23 21:38:08 -07:00
chrisfu
d806905b2e feat(mock_val): common_core_lib — mode-aware default config path helper
Add common_core_default_config_path() which resolves the correct knoe.cfg
path (k3d/k3s/gke) from KNOE_MODE/DEPLOYMENT_MODE/CLUSTER_ENV env vars,
falling back to the first cfg file found under conf/. Replaces the
hardcoded knoe.cfg reference in the config-loading comment.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:32:23 -07:00
chrisfu
73dce037f3 docs: reorganise briefs into docs/completed/; add conf/service/knoe.cfg
docs/completed/ — new directory; 9 shipped Junie briefs moved from
docs/plans/junie/ (02-k3s-prole-rename, 03-image-rename, 06-patch-garage,
07-init-cnpg-gke-sa-wiring, 13-podmonitor, 15-remove-dead-dashboard,
k3d-knoe-auth-dev-loop, phase2-oidc-gke-deploy, todo-1-cfg-save-path-bug).
docs/completed/README.md summarises all shipped work with dates/refs.

docs/plans/junie/README.md — updated to 2026-05-23; active/pending tables
reflect current state; shipped section now points to docs/completed/.

conf/service/knoe.cfg — new unattended deploy config for the service/k3s
environment (used by ./deploy.sh).

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:32:01 -07:00
chrisfu
3ada16c875 feat(infrastructure): add Pi-hole DNS flush playbook and logs dir
infrastructure/playbooks/pihole_flush_dns.yml — Ansible playbook to
flush Pi-hole DNS cache across inventory hosts.

infrastructure/logs/ — log directory for Ansible run output.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:52 -07:00
chrisfu
decb9a5ad0 feat(scripts): upstream knoe-db sync tooling and docs
Add scripts/sync_upstream_knoe_db.sh — idempotent script that registers
the upstream-knoe-db remote (git@git.knoe.dev:knoe-dev/knoe-db.git),
fetches upstream main, and creates a dated review branch
upstream/knoe-db/YYYYMMDD. Supports --dry-run; returns to previous
branch automatically.

Add docs/upstream-knoe-db-sync.md — step-by-step procedure covering
quick start, clean-tree requirement, diff/review, merge-to-main, PR
workflow, branch cleanup, and how to change the upstream branch name.

Update README.md: add 'Upstream fork — knoe-db' section with quick-start
commands and link to the sync procedure doc.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:48 -07:00
chrisfu
3943d1b298 feat(mock_val): add diagnostics, utilities, and misc operational scripts
Diagnostics:
  diag_gitlab_boot.sh, diag_gitlab_webservice_oom.sh, diag_gke_storage.sh

Utilities:
  ensure_default_storage_class.sh — set/verify default StorageClass
  preflight_kubecontext.sh        — validate kubecontext before ops
  onboard_engineer.sh             — new engineer onboarding script
  gen_oidc_signing_key.sh         — generate OIDC signing key
  fetch_prole_secrets.sh          — pull secrets from vault
  set-k3s-token-1password.sh      — store k3s token in 1Password
  sync_cnpg_grafana_dashboard.py  — sync CNPG dashboard to Grafana

Config/certs:
  krb5.local.conf, knoe-db-ca.crt

Updated: build-a-bao.sh, hostprobe-*.yaml, hosts.txt, knoe-db-passwwd.sh,
         repair_pipeline.sh, status.sh, status_common_services.sh
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:40 -07:00
chrisfu
3f96f66a78 feat(mock_val): rewrite init scripts; add new service init scripts
Rewrites (updated for knoe namespace, GKE support, and current service configs):
  init_gitlab.sh, init_kong.sh, init_cnpg_backup.sh, init_monitoring.sh,
  init_garage_store.sh, init_gitea.sh, init_kdc.sh, init_openbao.sh,
  init_argocd.sh, init_certmgr.sh, init_common_services.sh, init_db_manager.sh,
  init_forgejo.sh, init_k3s_registry.sh, init_kerberos.sh, init_nginx_ingress.sh,
  init_port_forwards.sh, init_registry.sh, init_service_layer.sh

Deleted: init_cloudnative_pg.sh (superseded by init_cnpg_gke.sh)

New scripts:
  init_cnpg_gke.sh      — CNPG setup for GKE with Workload Identity
  init_knoe_auth.sh     — knoe-auth OIDC service init
  init_knoe_users.sh    — user provisioning
  init_redis.sh         — Redis init
  init_oauth2_proxy.sh / init_oauth2_proxy_prole.sh — OAuth2 proxy setup
  init_grafana_oauth.sh / init_grafana_oauth_prole.sh — Grafana OAuth wiring
  init_1password.sh     — 1Password Connect init
  init_min.sh           — minimal bootstrap

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:27 -07:00
chrisfu
11064cbd5b refactor(mock_val): rename prole_* shell lib and cfg tooling to knoe_* namespace
Shell library (mock_val/lib/shell/):
  - Delete prole_cmd.sh, prole_env.sh, prole_guardrails.sh,
    prole_secrets.sh, prole_string.sh, prole_yaml.sh
  - Add knoe_* equivalents with same functionality

Config tooling:
  - Delete mock_val/prole_cfg.sh, mock_val/sync-prole-cfg.py
  - Add mock_val/knoe_cfg.sh, mock_val/sync-knoe-cfg.py

Mirrors the broader prole → knoe project rename.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:14 -07:00
chrisfu
da0fd2c545 feat(env): auto-detect gke_ kubecontext prefix as prod; status.py context helpers
knoe/knoe_conf.py: normalize_environment() now recognises the gke_ prefix
(e.g. gke_plenary-truck-485623-p7_us-west3_knoe-dev-0) as the 'prod'
environment, matching real GKE kubecontext naming.

status.py: add _current_kubecontext() and _cfg_path_from_kubecontext()
so status.py auto-selects the correct cfg file based on the active
kubectl context without manual KNOE_CONF overrides.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:07 -07:00
chrisfu
ef20c8a598 fix(cfg): refuse to serialize non-string widget values into knoe.cfg
Add _validate_cfg_values() to knoe/ui/screens/cfg.py — called before
_filter_cfg_values_for_persistence so MagicMock (or any non-str) widget
values raise TypeError instead of being silently str()-coerced into
conf/<mode>.cfg.

Also re-raise TypeError/ValueError from _save_knoe_cfg so the error
escapes the outer broad except-Exception handler.

New test: tests/installer/test_cfg_save_refuses_mock_values.py
  - test_save_knoe_cfg_refuses_non_string_widget_values: MagicMock vars → TypeError
  - test_save_knoe_cfg_real_strings_produce_clean_cfg: real _Var stubs → clean cfg

Fixes TODO-1 / tracked in docs/completed/todo-1-cfg-save-path-bug.md.
conf/k3d.cfg and conf/k3s.cfg still contain stale MagicMock values from
before this fix and must be regenerated before committing.

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-23 21:31:02 -07:00
chrisfu
b5f17ffa72 fix(samba_dns): update vault with correct AD Administrator password
vault_samba_dns_admin_pass was stale — samba-tool commands were failing
with NT_STATUS_LOGON_FAILURE. Updated to match current AD Administrator
credential so the samba_dns role can manage records end-to-end.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 23:58:09 -07:00
chrisfu
a2a82bc495 fix(samba_dns): use admin credentials instead of machine Kerberos (-P)
All samba-tool dns commands were using -P (machine account Kerberos) which
silently failed with no error — tasks reported changed=0 but records were
never written. Switch to -U Administrator --password={{ samba_dns_admin_pass }}
which uses the vault-protected admin credentials that were already defined
but never wired up.

Also fix regex patterns in record parsers: samba-tool output uses `A: IP`
and `PTR: fqdn` format, not `A IP` / `PTR fqdn` (space-separated), so
updated regex_findall patterns to match `TYPE:\s+value`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 23:15:13 -07:00
chrisfu
e014fd5b71 feat(k3s): add pg.prole.org as CNPG postgres endpoint with split-horizon DNS
- knoe-db.yaml: switch to CNPG-managed TLS cert with serverAltDNSNames
  (pg.prole.org + knoe-db-rw cluster service) — removes static serverTLSSecret/serverCASecret
- dns.yml: add pg.prole.org A record to prole_k3s_dns_records (10.0.0.3, 10.0.0.6)
  for Ansible-managed split-horizon DNS via Samba AD DC
- k3s.cfg: align KNOE_HOME paths to ~/dev/prole, add PROLE_KDC_* vars, remove
  hardcoded KUBECTL_CONTEXT (kubeconfig current-context is authoritative)
- prod.cfg: add PROLE_KDC_STORAGE_CLASS = prole-iscsi
- onepassword.py: skip vault check gracefully when no 1Password session active (non-TTY)
- CLAUDE.md: document production postgres connection string and DNS/CA cert ops

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 22:46:46 -07:00
chrisfu
f34d10908a docs(knoe-auth): use [placeholder] instead of <placeholder> in mermaid
Mermaid 10.7.0 chokes on bare <placeholder> in message text, parsing
it as HTML. Swap to square brackets in the cross-realm §4.3 sequence
diagram and the Gitea SPNEGO §5 diagram:

  <svc-host>, <SPNEGO>          (§4.3)
  <blob>                        (§5)

Matches the same fix landed in knoe-db's docs/knoe-auth.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 16:31:22 -07:00
chrisfu
f1450605c2 docs: knoe-auth — prole.org deployment overlay
Companion to the canonical architecture doc at
~/dev/knoe-db/docs/knoe-auth.md. Documents what's specific to the
prole.org homelab deployment:

  - Two upstream identity layers (Samba AD on myrddin for PROLE.ORG
    Kerberos, Google Workspace knoey.com for OIDC)
  - Three deploy targets (k3s on myrddin/merlin/gandalf, k3d for
    engineer dev loop, GKE for production API endpoint)
  - Hostname map (myrddin/morgana/zinfandel/db.prole.org/
    git.prole.org/api.knoe.dev/db.knoe.dev/etc.)
  - Cross-realm Kerberos trust setup (KNOE.LOCAL ↔ PROLE.ORG) via
    infrastructure/playbooks/kerberos_trust_setup.yml — RC4 pin
    explanation, kvno sync notes
  - Gitea SPNEGO at git.prole.org with Mermaid flow diagram
  - Supabase Studio status (db.prole.org Google OAuth working;
    db.knoe.dev knoe-auth OIDC blocked on pg_oauth image)
  - Engineer onboarding checklist (prole-specific steps)
  - Known fragility / current incidents table

Mermaid sequence diagram covers the cross-realm flow end-to-end
(workstation → Samba KDC → MIT KDC → service ticket), distinct from
the canonical doc's generic version.

References the matching Junie briefs in knoe-db for the open
fragility items (pg_oauth-image-install, init-user-schemas-heredoc-fix,
deploy-pipeline-restore).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 16:19:14 -07:00
chrisfu
3f34fa8b32 fix(installer): k3s --reset path hardening (kdc deploy, no-TTY 1password, context overrides)
Five fixes Junie surfaced while running the kdc-trust-reset-repeatable
Junie brief end-to-end (companion to commit 6f99f95). All hit during
the unattended `install.sh --mode k3s --reset` pipeline.

  - knoe/core/milestones.py (KerberosMilestone):
    For k3s and k3d modes, deploy the KDC pod via `init_kdc.sh start`
    before running init_kerberos.sh. init_kerberos.sh only chains into
    init_kdc.sh when PROLE_KDC_STANDALONE=1; without this hook the
    cluster came up with no KDC pod and the cross-realm trust principals
    had nowhere to land.

  - knoe/milestone.py (Milestone._get_script_env):
    Clear KUBECTL_CONTEXT in addition to KUBECONTEXT so stale entries
    from a different machine's cfg don't override the kubeconfig's
    own current-context.

  - etc/knoe_cfg.sh (_knoe_read_cfg):
    Skip KUBECTL_CONTEXT / KUBE_CONTEXT_NAME / KUBECONTEXT entries when
    reading cfg in k3s mode. Same theme: kubeconfig current-context is
    authoritative.

  - etc/init_1password.sh + knoe/core/onepassword.py:
    When running non-interactively (no TTY on stdin) and no `op`
    session exists, skip rather than hang on `op signin`. Lets the
    unattended pipeline proceed for k3s/k3d where in-cluster secrets
    are managed separately from 1Password.

Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 13:34:10 -07:00
chrisfu
6f99f95f84 kdc: verify install.sh --reset converges to a working cross-realm trust
Fix _deployment_mode_hint() to correctly map cluster_env=service → k3s
mode. Previously 'service' was not handled in the normalized_env checks,
causing fallthrough to build.deploy_env='Dev' → mode='dev' → k3d
dependency required. On a k3s node (myrddin/merlin/gandalf) k3d is not
installed and the DependenciesMilestone fatally aborted the install.

Also fix get_required_dependency_ids(): k3s mode does not require k3d
(k3s is provisioned on the cluster nodes by Ansible, not by the
installer binary).

Fixes: install.sh --mode k3s --reset failing with
  'Dependencies unresolved after install attempts. Required: k3d'

Co-authored-by: Junie <junie@jetbrains.com>
2026-05-11 02:33:38 -07:00
chrisfu
d6586cf1d9 kdc: backport init_kdc.sh trust fixes + add reset-repeatable Junie brief
Mirrors the knoe-db commit `ff7546d` patches into the prole copy of
`etc/init_kdc.sh` so a re-run of `install.sh --mode k3s --reset` from
this repo produces a working cross-realm trust without manual cluster
surgery. The k3s cluster is provisioned from this repo, so the source
fix must live here (knoe-db remains canonical for GKE).

Changes to etc/init_kdc.sh:

1. Create BOTH cross-realm krbtgts in MIT, not just the outbound one.
   The inbound `krbtgt/<REALM>@<TRUST_REALM>` (issued by Samba,
   decrypted here) was missing entirely; without it, MIT cannot
   decrypt inbound TGTs and the trust never carries traffic.

2. Pin both cross-realm krbtgts to RC4 (`arcfour-hmac:normal`). AES
   keys depend on salt, and Samba's `<remote_realm>+UPN` salt does
   not match MIT's `<local_realm>+<principal-no-realm>`; RC4 has no
   salt so both sides converge from the password alone. Matches the
   already-pinned Samba side (commit `ad1eced`).

3. Replace the broken "remote kadmin to Samba" reciprocal-trust block
   with a documented no-op pointing at
   `infrastructure/playbooks/kerberos_trust_setup.yml`. Samba AD
   does not accept additions over MIT's kadmin protocol; the block
   always failed with "Missing parameters in krb5.conf required for
   kadmin client".

4. Switch the KDC data volume from emptyDir to a PVC
   (claimName `knoe-kdc-data`, parameterized by
   `$PROLE_KDC_STORAGE_SIZE` and `$PROLE_KDC_STORAGE_CLASS`).
   State now survives pod restarts.

Adds Junie brief `docs/plans/junie/kdc-trust-reset-repeatable.md`
with four TDD acceptance criteria for an end-to-end --reset run on
the prole k3s cluster.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 01:24:34 -07:00
chrisfu
ad1ecedb5e kerberos_trust_setup: pin trust account to RC4 only (msDS-Supported... 4)
The matching knoe-db change (commit `ff7546d`) re-keys the MIT-side
cross-realm krbtgts with `-e arcfour-hmac:normal` to dodge the
AES-salt mismatch between Samba and MIT. For the trust to actually
carry traffic, the Samba side must also offer only RC4 on the
trust account — otherwise the client picks AES, the two sides
derive different keys from the same password (different salt
conventions), and TGT decryption fails.

Change `msDS-SupportedEncryptionTypes` from 28 (RC4+AES128+AES256)
to 4 (RC4 only). RC4 has no salt, so keys derive from the password
alone and both sides converge.

Updated docstring + summary debug print to reflect the new value
and the why.

Tracked alongside the Junie brief at
~/dev/knoe-db/docs/plans/junie/kdc-trust-reset-repeatable.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 00:29:01 -07:00
chrisfu
4fe8647953 init_kdc.sh: fix realm default from PROLE.LOCAL to KNOE.LOCAL
The post-rebrand realm is KNOE.LOCAL (matches knoe-db/etc/init_kdc.sh
already). The prole copy of init_kdc.sh was never updated, so the k3s
KDC pods on myrddin/merlin/gandalf got initialized with PROLE.LOCAL
realm baked into /etc/krb5.conf and /etc/krb5kdc/kdc.conf.

When chrisfu later wired up the Samba <-> in-cluster KDC cross-realm
trust playbook (kerberos_trust_setup.yml, targeting KNOE.LOCAL), the
trust was bidirectionally correct on the Samba side but the in-cluster
KDC could not decrypt the inbound krbtgt/KNOE.LOCAL@PROLE.ORG TGT
because its own realm was PROLE.LOCAL — different krbtgt key, every
TGS_REQ failed with "Decrypt integrity check failed" (manifested as
"Server not found in Kerberos database" in the client trace).

This commit fixes the source. To pick it up in-cluster, the active
KDC pod must be redeployed (its DB sits on an emptyDir volume so a
restart re-initializes against this default).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 23:37:31 -07:00
chrisfu
19d1f136de ansible: fix klist principal form in trust-setup summary print
klist shows the Kerberos principal (slash form
krbtgt/KNOE.LOCAL@PROLE.ORG), not the sAMAccountName underscore form
(krbtgt_KNOE.LOCAL). Update the summary debug task accordingly and
split the kvno hint into two stages (the cross-realm TGT first, then
a service ticket) so the user can isolate which step fails when
chasing salt/enctype issues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:37:54 -07:00
chrisfu
78eef6fbd9 ansible: hoist ldb-tools install to top of play
The apt install task was positioned after the user-create step, so a
failure in create (or anywhere upstream) meant ldb-tools never got
installed. Move the apt task to the very top of the play, before the
cluster-secret lookups, so it always runs first on every invocation.

Drop the duplicate apt task that previously sat just before the
ldbmodify locator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:35:18 -07:00
chrisfu
4be9fbcafe ansible: idempotency check now uses underscore account name
The _trust_exists probe was still grepping samba-tool user list for
"krbtgt/KNOE.LOCAL" (slash form), but the actual sAMAccountName is
"krbtgt_KNOE.LOCAL" (underscore form, since slash is invalid in
sAMAccountName). _trust_exists was therefore always false, the create
step retried against the existing account on every re-run, and Samba
rejected it as a name collision.

Match against samba_account_name directly so re-runs route through
the setpassword branch instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:32:49 -07:00
chrisfu
52ab023ae2 ansible: install ldb-tools package (provides ldbmodify)
myrddin doesn't have ldbmodify installed — it's in the ldb-tools
Debian package, separate from samba-tool. Add an apt task ahead of
the locator so the package is in place before we try to use it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:20:26 -07:00
chrisfu
c45671283b ansible: locate ldbmodify binary explicitly (not on sudo PATH)
ldbmodify is part of the ldb-tools Debian package but is not in
root's default PATH under sudo on the Samba DC, so the user-attribute
modify step failed with "ldbmodify: command not found".

Add a locator task that probes the conventional install paths
(/usr/bin, /usr/sbin, /usr/local/bin, /opt/samba/bin) and falls back
to a depth-limited find under /usr and /opt. The downstream
ldbmodify invocation now uses the absolute path resolved here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:19:38 -07:00
chrisfu
3485046dbd ansible: temp-disable Samba password complexity for trust account write
samba-tool user create rejected the cluster-generated trust_shared_password
with "0000052D: Constraint violation - check_password_restrictions: the
password does not meet the complexity criteria". The trust password is
high-entropy random bytes from init_kdc.sh and may not satisfy AD's
"3 of 4 character categories" rule.

Wrap the create + setpassword steps in a block that:
  1. reads the current `samba-tool domain passwordsettings show` state
  2. flips complexity off if it was on
  3. does the user write
  4. always: restores complexity to its prior on/off state

The restore is in an `always:` clause so a failure inside the write
block does not leave the domain policy weakened.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:09:41 -07:00
chrisfu
aa541af3f5 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>
2026-05-10 22:02:55 -07:00
chrisfu
6740e3dcc5 ansible: guard krbtgt smoke probe against --check mode failure
In --check mode the user-create step is skipped (it's mutating), so
the subsequent `samba-tool user show krbtgt/KNOE.LOCAL` probe fails
with rc=1 on a first dry-run because the user doesn't yet exist. Add
the same guarded failed_when used on the trust validate/show tasks
earlier: only fail when NOT (check_mode AND trust didn't pre-exist).

Real runs still fail hard on rc!=0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:57:51 -07:00
chrisfu
555da4acfc 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>
2026-05-10 15:54:24 -07:00
chrisfu
227f49042c ansible: opt read-only kubectl/samba-tool tasks into --check mode
Ansible --check mode auto-skips command/shell tasks unless they
explicitly opt in via check_mode:false. The two cluster-state lookups
(svc/auth ClusterIP and knoe-kdc-secrets/trust_shared_password) were
silently skipped during dry-run, leaving trust_kdc_ip empty and tripping
the assertion immediately.

Add check_mode:false to the five read-only tasks so a --check run can
still resolve cluster state and report what would change:
  - Resolve trust_kdc_ip from cluster
  - Resolve trust_shared_password from knoe-kdc-secrets
  - samba-tool domain trust list  (idempotency probe)
  - samba-tool domain trust validate
  - samba-tool domain trust show  (smoke print)

The mutating create step (samba-tool domain trust create) keeps the
default behavior - skipped in check mode. validate/show gain a guarded
failed_when so a check-mode dry-run on a host where the trust does not
yet exist does not fail (the create was skipped, so a non-zero rc is
expected). Real runs still fail hard on rc!=0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 01:51:30 -07:00
chrisfu
5cece408bd ansible: switch kerberos_trust_setup to KNOE.LOCAL + vault wiring
The cross-realm trust playbook was written for the pre-rebrand realm name
PROLE.LOCAL, which no longer exists in the in-cluster KDC — the canonical
realm is now KNOE.LOCAL. The Samba AD trust was therefore never actually
established, leaving chrisfu@KNOE.LOCAL→chrisfu@PROLE.ORG service ticket
flows blocked (and blocking pg_oauth / db.prole.org Kerberos work).

Changes:
- Realm: PROLE.LOCAL → KNOE.LOCAL across all task names, vars, and the
  krb5.conf [realms] / [domain_realm] blocks added on myrddin.
- samba_admin_password now resolves from the Ansible vault var
  vault_samba_dns_admin_pass (group_vars/ad_dc/vault.yml) by default,
  with SAMBA_ADMIN_PASSWORD env and -e overrides preserved for CI.
- trust_shared_password auto-resolves from the in-cluster Secret
  knoe-system/knoe-kdc-secrets (key trust_shared_password) when not
  passed explicitly — same Secret init_kdc.sh writes.
- Added [domain_realm] mapping for *.knoe.local → KNOE.LOCAL so Samba
  can resolve in-cluster service principals.
- Added a final `samba-tool domain trust show` smoke step so a successful
  run prints the visible trust state for log review.

Run:
  ANSIBLE_VAULT_PASSWORD_FILE=$PWD/.vault_pass   ansible-playbook infrastructure/playbooks/kerberos_trust_setup.yml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 00:26:31 -07:00
chrisfu
48def19637 fix(inventory): specify ssh key for zinfandel — non-default key name 2026-05-09 22:42:26 -07:00
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
chrisfu
67b483bcf2 fix(ansible): split workstation playbook into two plays for Unix vs Windows
A single play with become:true uses sudo by default, which Ansible's
Windows exec wrapper rejects immediately at gather_facts. Split into:
- Play 1: hosts workstations,!workstations_windows — macOS/Linux, sudo
- Play 2: hosts workstations_windows — Windows, become_method:runas

Also set morgana to ansible_connection=local (running Ansible from
morgana itself, so SSH self-lookup fails DNS resolution).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:33:51 -07:00
chrisfu
5cd22c53d9 feat(inventory): add all 4 workstations; add Windows SPNEGO policy support
Add morgana and zinfandel (macOS) and morgoth + fairyland (Windows)
to the workstations inventory.

Extend workstation_kerberos.yml to handle Windows targets:
- Chrome and Edge AuthServerAllowlist set via win_regedit under
  HKLM\SOFTWARE\Policies\{Google\Chrome,Microsoft\Edge}
- krb5.conf and plist tasks skip on Windows (uses built-in SSPI/AD)
- Windows hosts in [workstations_windows] group with WinRM/NTLM
  transport; switch to kerberos transport after domain join

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:21:21 -07:00
chrisfu
d5f6e8f6a9 fix(gitea): set recovery password after admin promotion, not before
Gitea's AdminEditUser PATCH with source_id:0 resets the password field
as a side effect when applied to a SPNEGO-registered user (the auth
record re-initialisation clears the local password). Setting the
password before the promotion PATCH meant it was immediately wiped.

Move the 1Password recovery password step to run after the admin
promotion PATCH so the final Gitea state matches 1Password.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:15:50 -07:00
chrisfu
5d545d238c fix(gitea): include email in admin API set-password PATCH call
Gitea's AdminEditUser endpoint requires email alongside login_name
and source_id. Without it the server returns 200 but silently
ignores the password field — causing login failures despite the
script reporting success.

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