mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 12:03:59 +00:00
Three independent lines of work landing in one commit because they were all on disk together end-of-session and the cross-file edits (TODO, knoe-system, junie/README) interleave cleanly: 1. **Queue #3 — image rename `knoe-authority` → `knoe-auth`** (Junie). Closes drift R6. The Maven artifact has been `knoe-auth.jar` since commit b355855; the deploy manifests now match. authority/Dockerfile.app (NEW) deploy/gcp/gke/knoe-auth-deployment.yaml (3 image tags renamed) deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml (2 image tags renamed) Makefile (docker-build-auth + docker-push-auth + REGISTRY/KNOE_AUTH_VERSION defaults) docs/plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md (brief, kept as design record) 2. **Phase 2 OIDC provider — GKE deploy** (Junie). Source landed via the merge that brought claude/crazy-bose-fec256 back; the k3d sandbox shipped earlier today (commit93157b0). This commit completes the GKE path: Kong route `/auth` → knoe-auth, default values added, signing key wired via `knoe-auth-oidc` secret per the brief. supabase/helm/knoe-supabase/templates/kong/config.yaml (Kong route /auth, gated on .Values.knoeAuth.enabled) supabase/helm/knoe-supabase/values.yaml (knoeAuth.enabled=false default; studioIngress block — also fixes the helm lint issue noted in earlier commit03bb731) docs/plans/junie/phase2-oidc-gke-deploy.md (brief, kept as design record) 3. **k3d dev-user seed: `chrisfu` + `knoe_developer`** (Claude). Closes "I want auth as chrisfu@knoey.com to my local cnpg database" for the local dev loop. `etc/init_knoe_auth.sh --mode k3d` now runs `seed_dev_users_k3d()` after the schema bootstrap, creating: - `knoe_developer` group role with R/W on `knoe`+`public` (mirrors the GKE production layout from docs/db-access.md; was hand-rolled in production per the 2026-04-30 onboarding work, never baked into postInitTemplateSQL). - `chrisfu` LOGIN role with password `chrisfu-dev` (idempotent — resets on every `make k3d-knoe-up`, so the rebuild loop is deterministic). Granted into `knoe_developer`. From the host with port-forward up: PGPASSWORD=chrisfu-dev psql "postgresql://chrisfu@localhost:5432/knoe-db?sslmode=require" Cross-cutting doc updates: docs/TODO.md — Phase 2 GKE entry removed from "In progress" (now "(none — all items shipped or paused)"); queue #3 + reality table R6 removed; pg_oauth paused note flipped to "OIDC issuer now deployed to GKE — pg_oauth can resume"; Done section gets new entries for #3, Phase 2 GKE, and chrisfu seed. docs/knoe-system.md — Phase 2 GKE row → Shipped; pg_oauth → Ready; knoe-auth-deployment.yaml note "knoe-auth:latest" instead of "knoe-authority:latest"; "One-time cluster setup" mentions the chrisfu seed. docs/local-dev-knoe-auth.md — "Verify psql connectivity" rewritten to connect as chrisfu (was `knoe`); new "Reset / rebuild loop" section explaining idempotency. docs/plans/junie/README.md — two new rows under Shipped: brief 03 and phase2-oidc-gke-deploy. Verification (the bits I ran locally): - bash -n etc/init_knoe_auth.sh OK - The seed SQL is idempotent (DO blocks with EXISTS checks + ALTER ROLE on the password reset path) - The Kong /auth route is gated on .Values.knoeAuth.enabled — default false, no behavior change until someone flips it on per Junie's brief Out of scope for this commit: - Actually flipping knoeAuth.enabled=true on the live GKE chart and rolling out — Junie's brief covers the runbook; needs the knoe-auth-oidc K8s secret populated from 1Password first - Rebuilding + pushing the new knoe-auth:latest image — `make docker-push-auth` is wired, just hasn't been run yet - Round 1.5 OpenBao transit-key encryption (still queued) Co-authored-by: Junie <junie@jetbrains.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
574 lines
22 KiB
Bash
Executable File
574 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# init_knoe_auth.sh
|
|
# Provision knoe-auth (Kerberos KDC + Spring Boot enrollment service).
|
|
#
|
|
# Usage:
|
|
# ./etc/init_knoe_auth.sh [--context KUBECONTEXT] [--namespace NAMESPACE] [--project PROJECT_ID] [--mode MODE]
|
|
# ./etc/init_knoe_auth.sh initialize # full setup
|
|
# ./etc/init_knoe_auth.sh schema # schema only (idempotent)
|
|
# ./etc/init_knoe_auth.sh invite EMAIL # create first admin invite
|
|
# ./etc/init_knoe_auth.sh status # check pod + principal state
|
|
#
|
|
# Modes:
|
|
# (default / gke) GKE deploy — uses 1Password for secrets, GCP Workload Identity
|
|
# k3d Local k3d dev loop — uses hardcoded dev passwords, skips GCP steps
|
|
#
|
|
# Prerequisites:
|
|
# kubectl, op (1Password CLI — GKE mode only), psql (or kubectl exec fallback)
|
|
#
|
|
# Env vars (override args):
|
|
# APP_CLUSTER_KUBECONTEXT, KNOE_NAMESPACE, GCP_PROJECT_ID,
|
|
# KNOE_DB_HOST, KNOE_DB_PORT, KNOE_DB_NAME, KNOE_DB_SUPERUSER,
|
|
# KNOE_MODE (set to 'k3d' as alternative to --mode k3d)
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
GKE_DIR="$REPO_ROOT/deploy/gcp/gke"
|
|
|
|
# ── Defaults ────────────────────────────────────────────────────────────────
|
|
MODE="${KNOE_MODE:-gke}" # gke (default) or k3d
|
|
APP_CTX="${APP_CLUSTER_KUBECONTEXT:-gke_plenary-truck-485623-p7_us-west3_knoe-dev-0}"
|
|
NAMESPACE="${KNOE_NAMESPACE:-knoe-system}"
|
|
GCP_PROJECT="${GCP_PROJECT_ID:-plenary-truck-485623-p7}"
|
|
DB_HOST="${KNOE_DB_HOST:-}" # resolved from CNPG svc if blank
|
|
DB_PORT="${KNOE_DB_PORT:-5432}"
|
|
DB_NAME="${KNOE_DB_NAME:-knoe}"
|
|
DB_SUPERUSER="${KNOE_DB_SUPERUSER:-postgres}"
|
|
REALM="KNOE.DEV"
|
|
AUTH_HOST="${KNOE_AUTH_HOST:-https://auth.knoe.dev}"
|
|
|
|
# k3d-specific defaults (overridden when --mode k3d is active)
|
|
K3D_CLUSTER_NAME="${K3D_CLUSTER_NAME:-k3d-knoe}"
|
|
K3D_CTX="k3d-${K3D_CLUSTER_NAME}"
|
|
K3D_DB_NS="${K3D_DB_NS:-knoe-db-0}"
|
|
# Dev-only passwords — NOT used in GKE mode; safe to commit
|
|
K3D_KDC_MASTER_PASSWORD="${K3D_KDC_MASTER_PASSWORD:-knoe-local-master-dev}"
|
|
K3D_KDC_ADMIN_PASSWORD="${K3D_KDC_ADMIN_PASSWORD:-knoe-local-admin-dev}"
|
|
|
|
log() { echo "[init_knoe_auth] $*"; }
|
|
info() { log "INFO $*"; }
|
|
warn() { log "WARN $*" >&2; }
|
|
die() { log "ERROR $*" >&2; exit 1; }
|
|
|
|
kube() { kubectl --context="$APP_CTX" "$@"; }
|
|
|
|
# ── Argument parsing ─────────────────────────────────────────────────────────
|
|
COMMAND="${1:-initialize}"
|
|
shift || true
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--context) APP_CTX="$2"; shift 2 ;;
|
|
--namespace) NAMESPACE="$2"; shift 2 ;;
|
|
--project) GCP_PROJECT="$2"; shift 2 ;;
|
|
--db-host) DB_HOST="$2"; shift 2 ;;
|
|
--mode) MODE="$2"; shift 2 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
|
|
# Apply k3d mode overrides after argument parsing
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
APP_CTX="${APP_CLUSTER_KUBECONTEXT:-${K3D_CTX}}"
|
|
REALM="KNOE.LOCAL"
|
|
AUTH_HOST="${KNOE_AUTH_HOST:-http://localhost:8080}"
|
|
# DB is accessed via port-forward (localhost:5432) in k3d mode
|
|
DB_HOST="${KNOE_DB_HOST:-localhost}"
|
|
fi
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
require_tool() {
|
|
command -v "$1" >/dev/null 2>&1 || die "Required tool not found: $1 — install it and retry."
|
|
}
|
|
|
|
wait_for_pods() {
|
|
local label="$1"
|
|
local timeout="${2:-180}"
|
|
info "Waiting up to ${timeout}s for pods with label ${label} in ${NAMESPACE}..."
|
|
kube -n "$NAMESPACE" wait pod \
|
|
-l "$label" \
|
|
--for=condition=Ready \
|
|
--timeout="${timeout}s"
|
|
}
|
|
|
|
op_secret() {
|
|
# Retrieve a 1Password secret; fall back to prompting if op isn't authed.
|
|
local item="$1" field="${2:-password}"
|
|
if command -v op >/dev/null 2>&1; then
|
|
op item get "$item" --fields "$field" 2>/dev/null || {
|
|
warn "1Password: could not read $item/$field — prompting."
|
|
read -rsp "Enter value for $item/$field: " val; echo
|
|
printf '%s' "$val"
|
|
}
|
|
else
|
|
read -rsp "Enter value for $item/$field: " val; echo
|
|
printf '%s' "$val"
|
|
fi
|
|
}
|
|
|
|
resolve_db_host() {
|
|
if [[ -n "$DB_HOST" ]]; then return; fi
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
# In k3d mode the engineer runs make k3d-knoe-pf first; DB is at localhost:5432
|
|
DB_HOST="localhost"
|
|
info "k3d mode: using DB host localhost (port-forward expected on :5432)"
|
|
return
|
|
fi
|
|
# Try to resolve CNPG primary service from the DB cluster context
|
|
DB_CTX="${DB_CLUSTER_KUBECONTEXT:-gke_plenary-truck-485623-p7_us-west3_knoe-cnpg-0}"
|
|
DB_HOST=$(kubectl --context="$DB_CTX" -n knoe-db-0 \
|
|
get svc knoe-db-rw -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
|
|
[[ -z "$DB_HOST" ]] && die "Cannot resolve KNOE DB host. Set KNOE_DB_HOST or ensure knoe-db-rw svc exists."
|
|
info "Resolved DB host: $DB_HOST"
|
|
}
|
|
|
|
psql_file() {
|
|
local file="$1"
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
# In k3d mode: exec into the CNPG primary pod directly (no port-forward needed for schema)
|
|
local pod
|
|
pod=$(kubectl --context="$APP_CTX" -n "$K3D_DB_NS" \
|
|
get pod -l cnpg.io/cluster=knoe-db,role=primary \
|
|
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
[[ -z "$pod" ]] && die "Cannot find CNPG primary pod in k3d. Is 'make k3d-knoe-up' complete?"
|
|
kubectl --context="$APP_CTX" -n "$K3D_DB_NS" cp "$file" "${pod}:/tmp/knoe_auth_schema.sql"
|
|
kubectl --context="$APP_CTX" -n "$K3D_DB_NS" exec "$pod" -- \
|
|
psql -U "$DB_SUPERUSER" -d "$DB_NAME" -f /tmp/knoe_auth_schema.sql
|
|
return
|
|
fi
|
|
if command -v psql >/dev/null 2>&1 && [[ -n "${PGPASSWORD:-}" ]]; then
|
|
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_SUPERUSER" -d "$DB_NAME" -f "$file"
|
|
else
|
|
# Fallback: exec into CNPG primary pod
|
|
DB_CTX="${DB_CLUSTER_KUBECONTEXT:-gke_plenary-truck-485623-p7_us-west3_knoe-cnpg-0}"
|
|
local pod
|
|
pod=$(kubectl --context="$DB_CTX" -n knoe-db-0 \
|
|
get pod -l cnpg.io/cluster=knoe-db,role=primary \
|
|
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
[[ -z "$pod" ]] && die "Cannot find CNPG primary pod. Set PGPASSWORD and KNOE_DB_HOST for direct psql."
|
|
kubectl --context="$DB_CTX" -n knoe-db-0 cp "$file" "${pod}:/tmp/knoe_auth_schema.sql"
|
|
kubectl --context="$DB_CTX" -n knoe-db-0 exec "$pod" -- \
|
|
psql -U "$DB_SUPERUSER" -d "$DB_NAME" -f /tmp/knoe_auth_schema.sql
|
|
fi
|
|
}
|
|
|
|
# ── Schema ───────────────────────────────────────────────────────────────────
|
|
|
|
run_schema() {
|
|
info "Applying knoe-auth schema additions..."
|
|
resolve_db_host
|
|
|
|
local tmpfile
|
|
tmpfile=$(mktemp /tmp/knoe_auth_schema_XXXX.sql)
|
|
|
|
cat > "$tmpfile" <<'ENDSQL'
|
|
-- ── knoe-auth Round 1 schema additions ───────────────────────────────────────
|
|
-- Idempotent: all CREATE TABLE ... IF NOT EXISTS
|
|
|
|
-- Invite tokens (admin creates, single-use)
|
|
-- contact is the email/phone the invite was sent to — the OTP trust anchor.
|
|
-- knoe.dev starts with ZERO pre-knowledge of the developer's home org.
|
|
CREATE TABLE IF NOT EXISTS knoe.invitation (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
token TEXT NOT NULL UNIQUE,
|
|
contact TEXT NOT NULL,
|
|
contact_type TEXT NOT NULL DEFAULT 'email',
|
|
name_hint TEXT,
|
|
otp_hash TEXT NOT NULL,
|
|
otp_expires_at TIMESTAMPTZ NOT NULL,
|
|
otp_attempts INT NOT NULL DEFAULT 0,
|
|
otp_verified_at TIMESTAMPTZ,
|
|
created_by TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
used_by TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_invitation_token ON knoe.invitation(token);
|
|
CREATE INDEX IF NOT EXISTS idx_invitation_contact ON knoe.invitation(contact);
|
|
|
|
-- External identity corroborations (Google sub → knoe user)
|
|
-- provider_hd records the developer's home domain (prole.org, gmail.com, etc.)
|
|
-- for audit purposes only — it is NOT used for access control.
|
|
CREATE TABLE IF NOT EXISTS knoe.identity (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INT NOT NULL REFERENCES knoe.user(id) ON DELETE CASCADE,
|
|
provider TEXT NOT NULL,
|
|
provider_sub TEXT NOT NULL,
|
|
provider_email TEXT,
|
|
provider_hd TEXT,
|
|
verified_at TIMESTAMPTZ NOT NULL,
|
|
UNIQUE(provider, provider_sub)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_identity_user ON knoe.identity(user_id);
|
|
|
|
-- TOTP 2FA credentials (encrypted secret, backup codes)
|
|
CREATE TABLE IF NOT EXISTS knoe.totp_credential (
|
|
user_id INT PRIMARY KEY REFERENCES knoe.user(id) ON DELETE CASCADE,
|
|
secret TEXT NOT NULL,
|
|
verified_at TIMESTAMPTZ,
|
|
backup_codes TEXT[],
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Platform-managed resources (repos, db roles, policies, etc.)
|
|
CREATE TABLE IF NOT EXISTS knoe.knobject (
|
|
id SERIAL PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
platform_id TEXT,
|
|
metadata JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE(type, name)
|
|
);
|
|
|
|
-- Access grants (user → knobject with role)
|
|
CREATE TABLE IF NOT EXISTS knoe.access_grant (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INT NOT NULL REFERENCES knoe.user(id),
|
|
knobject_id INT NOT NULL REFERENCES knoe.knobject(id),
|
|
role TEXT NOT NULL,
|
|
granted_by TEXT NOT NULL,
|
|
granted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
revoked_at TIMESTAMPTZ,
|
|
UNIQUE(user_id, knobject_id)
|
|
);
|
|
|
|
-- Async provisioning job queue (GitLab user, Gitea user, CNPG role, etc.)
|
|
CREATE TABLE IF NOT EXISTS knoe.provisioning_job (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INT NOT NULL REFERENCES knoe.user(id),
|
|
job_type TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
payload JSONB,
|
|
result JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_provisioning_job_status
|
|
ON knoe.provisioning_job(status, created_at);
|
|
|
|
-- Seed well-known knobjects
|
|
INSERT INTO knoe.knobject (type, name, metadata) VALUES
|
|
('gitea_org', 'knoey.com', '{"description": "Knoey.com Gitea organisation"}'),
|
|
('gitlab_group','knoey.com', '{"description": "Knoey.com GitLab group"}')
|
|
ON CONFLICT (type, name) DO NOTHING;
|
|
|
|
SELECT 'knoe-auth schema v1 applied.' AS status;
|
|
ENDSQL
|
|
|
|
psql_file "$tmpfile"
|
|
rm -f "$tmpfile"
|
|
info "Schema applied."
|
|
}
|
|
|
|
# ── Dev-user seed (k3d only) ─────────────────────────────────────────────────
|
|
#
|
|
# Creates the `knoe_developer` group role (the production GKE deploy uses it
|
|
# too via pg_hba.conf `+knoe_developer` rules; on GKE it was hand-rolled per
|
|
# 2026-04-30 onboarding work, never baked into postInitTemplateSQL — see
|
|
# docs/db-access.md). Then creates a `chrisfu` LOGIN role with a dev
|
|
# password and grants `knoe_developer` to it, so the engineer can connect
|
|
# from the host as chrisfu@knoey.com via the port-forward.
|
|
#
|
|
# Idempotent: re-runs on every `make k3d-knoe-up` and either creates or
|
|
# updates the role's password. This makes the rebuild loop deterministic —
|
|
# after `down && up`, chrisfu's password is always `chrisfu-dev`.
|
|
seed_dev_users_k3d() {
|
|
info "Seeding dev users (knoe_developer + chrisfu) for k3d ..."
|
|
|
|
local tmpfile
|
|
tmpfile=$(mktemp /tmp/knoe_auth_seed_XXXX.sql)
|
|
|
|
cat > "$tmpfile" <<'ENDSQL'
|
|
-- knoe_developer group role: R/W on knoe + public, R/O on auth/storage/extensions.
|
|
-- Mirrors the GKE production layout (docs/db-access.md). NOLOGIN — group only.
|
|
DO $do$ BEGIN
|
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'knoe_developer') THEN
|
|
CREATE ROLE knoe_developer NOLOGIN;
|
|
END IF;
|
|
END $do$;
|
|
|
|
-- knoe + public — full R/W
|
|
GRANT USAGE ON SCHEMA knoe TO knoe_developer;
|
|
GRANT USAGE ON SCHEMA public TO knoe_developer;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA knoe TO knoe_developer;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO knoe_developer;
|
|
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA knoe TO knoe_developer;
|
|
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO knoe_developer;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA knoe GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO knoe_developer;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO knoe_developer;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA knoe GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO knoe_developer;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO knoe_developer;
|
|
|
|
-- Per-engineer LOGIN role for chrisfu (local-dev only; password is fixed dev value).
|
|
-- Creates if missing, otherwise resets the password — guarantees the rebuild loop
|
|
-- always produces the same credentials.
|
|
DO $do$ BEGIN
|
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'chrisfu') THEN
|
|
CREATE ROLE chrisfu LOGIN INHERIT PASSWORD 'chrisfu-dev';
|
|
ELSE
|
|
ALTER ROLE chrisfu WITH LOGIN INHERIT PASSWORD 'chrisfu-dev';
|
|
END IF;
|
|
END $do$;
|
|
|
|
GRANT knoe_developer TO chrisfu;
|
|
|
|
-- Sanity check (visible in psql_file output).
|
|
SELECT 'chrisfu' AS role,
|
|
pg_has_role('chrisfu', 'knoe_developer', 'MEMBER') AS is_developer;
|
|
ENDSQL
|
|
|
|
psql_file "$tmpfile"
|
|
rm -f "$tmpfile"
|
|
info "Dev-user seed applied (chrisfu / chrisfu-dev, member of knoe_developer)."
|
|
}
|
|
|
|
# ── KDC secrets ──────────────────────────────────────────────────────────────
|
|
|
|
create_kdc_secrets() {
|
|
if kube -n "$NAMESPACE" get secret knoe-kdc-secrets >/dev/null 2>&1; then
|
|
info "knoe-kdc-secrets already exists — skipping."
|
|
return
|
|
fi
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
info "k3d mode: creating knoe-kdc-secrets with dev passwords (no 1Password)..."
|
|
kube -n "$NAMESPACE" create secret generic knoe-kdc-secrets \
|
|
--from-literal=master_password="$K3D_KDC_MASTER_PASSWORD" \
|
|
--from-literal=admin_password="$K3D_KDC_ADMIN_PASSWORD"
|
|
info "knoe-kdc-secrets created (dev passwords)."
|
|
return
|
|
fi
|
|
info "Creating knoe-kdc-secrets from 1Password..."
|
|
local master admin
|
|
master=$(op_secret "knoe-kdc-master" "password")
|
|
admin=$(op_secret "knoe-kdc-admin" "password")
|
|
kube -n "$NAMESPACE" create secret generic knoe-kdc-secrets \
|
|
--from-literal=master_password="$master" \
|
|
--from-literal=admin_password="$admin"
|
|
info "knoe-kdc-secrets created."
|
|
}
|
|
|
|
create_google_oidc_secret() {
|
|
if kube -n "$NAMESPACE" get secret knoe-auth-google-oidc >/dev/null 2>&1; then
|
|
info "knoe-auth-google-oidc already exists — skipping."
|
|
return
|
|
fi
|
|
info "Creating knoe-auth-google-oidc secret..."
|
|
local client_id client_secret
|
|
client_id=$(op_secret "knoe-google-oidc" "client_id")
|
|
client_secret=$(op_secret "knoe-google-oidc" "client_secret")
|
|
kube -n "$NAMESPACE" create secret generic knoe-auth-google-oidc \
|
|
--from-literal=client_id="$client_id" \
|
|
--from-literal=client_secret="$client_secret"
|
|
info "knoe-auth-google-oidc created."
|
|
}
|
|
|
|
create_session_secret() {
|
|
if kube -n "$NAMESPACE" get secret knoe-auth-secrets >/dev/null 2>&1; then
|
|
info "knoe-auth-secrets already exists — skipping."
|
|
return
|
|
fi
|
|
info "Creating knoe-auth-secrets (session HMAC key)..."
|
|
local session_secret
|
|
session_secret=$(op_secret "knoe-auth-session" "password")
|
|
kube -n "$NAMESPACE" create secret generic knoe-auth-secrets \
|
|
--from-literal=sessionSecret="$session_secret"
|
|
info "knoe-auth-secrets created."
|
|
}
|
|
|
|
create_oidc_path_b_secret() {
|
|
if kube -n "$NAMESPACE" get secret knoe-auth-oidc >/dev/null 2>&1; then
|
|
info "knoe-auth-oidc already exists — skipping."
|
|
return
|
|
fi
|
|
|
|
info "Creating knoe-auth-oidc secret for Path B..."
|
|
local client_id client_secret signing_key
|
|
client_id=$(op_secret "knoe-auth-oidc-gitlab" "client_id")
|
|
client_secret=$(op_secret "knoe-auth-oidc-gitlab" "client_secret")
|
|
signing_key=$(op_secret "knoe-auth-oidc-signing" "private_key")
|
|
|
|
kube -n "$NAMESPACE" create secret generic knoe-auth-oidc \
|
|
--from-literal=client-id="$client_id" \
|
|
--from-literal=client-secret="$client_secret" \
|
|
--from-literal=signing-key="$signing_key"
|
|
info "knoe-auth-oidc created."
|
|
}
|
|
|
|
# ── Manifests ────────────────────────────────────────────────────────────────
|
|
|
|
apply_manifests() {
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
local k3d_dir="$REPO_ROOT/k8s/knoe"
|
|
info "k3d mode: applying KDC manifests from $k3d_dir..."
|
|
kube apply -f "$k3d_dir/knoe-kdc-configmap.yaml"
|
|
kube apply -f "$k3d_dir/knoe-kdc-pvc.yaml"
|
|
kube apply -f "$k3d_dir/knoe-kdc-deployment.yaml"
|
|
kube apply -f "$k3d_dir/knoe-kdc-service.yaml"
|
|
info "k3d mode: skipping knoe-auth Deployment (runs on host via mvn spring-boot:run)."
|
|
return
|
|
fi
|
|
info "Applying KDC ConfigMap..."
|
|
kube apply -f "$GKE_DIR/knoe-kdc-configmap.yaml"
|
|
|
|
info "Applying knoe-auth Deployment..."
|
|
kube apply -f "$GKE_DIR/knoe-auth-deployment.yaml"
|
|
}
|
|
|
|
# ── Invite helper ─────────────────────────────────────────────────────────────
|
|
|
|
create_first_invite() {
|
|
local contact="${1:-}"
|
|
[[ -z "$contact" ]] && { read -rp "Invite contact (email or phone): " contact; }
|
|
local name_hint=""
|
|
read -rp "Display name hint (optional, press Enter to skip): " name_hint || true
|
|
|
|
info "Creating invite for: $contact"
|
|
local admin_token
|
|
admin_token=$(op_secret "knoe-admin-token" "credential" 2>/dev/null || \
|
|
{ read -rsp "Admin token: " t; echo; printf '%s' "$t"; })
|
|
|
|
local response
|
|
response=$(curl -sf -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $admin_token" \
|
|
-d "{\"contact\":\"$contact\",\"contactType\":\"email\",\"nameHint\":\"$name_hint\"}" \
|
|
"${AUTH_HOST}/auth/admin/invites") || {
|
|
warn "Admin API call failed. knoe-auth may not be ready yet."
|
|
warn "Retry: POST ${AUTH_HOST}/auth/admin/invites"
|
|
return 1
|
|
}
|
|
|
|
local invite_url
|
|
invite_url=$(printf '%s' "$response" | grep -o '"enrollUrl":"[^"]*"' | sed 's/"enrollUrl":"//;s/"//')
|
|
printf '\n\033[1;32mInvite URL:\033[0m %s\n\n' "$invite_url"
|
|
info "Send the above URL to: $contact"
|
|
}
|
|
|
|
# ── Status ────────────────────────────────────────────────────────────────────
|
|
|
|
show_status() {
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
info "=== KDC pod status ==="
|
|
kube -n "$NAMESPACE" get pods -l app=knoe-kdc 2>/dev/null || true
|
|
info "=== KDC secret ==="
|
|
kube -n "$NAMESPACE" get secret knoe-kdc-secrets 2>/dev/null || true
|
|
info "=== KDC service ==="
|
|
kube -n "$NAMESPACE" get svc knoe-kdc 2>/dev/null || true
|
|
info "=== knoe-auth runs on host ==="
|
|
info " export KRB5_CONFIG=\$PWD/etc/krb5.local.conf"
|
|
info " mvn -pl authority spring-boot:run"
|
|
return
|
|
fi
|
|
info "=== Pod status ==="
|
|
kube -n "$NAMESPACE" get pods -l app=knoe-auth 2>/dev/null || true
|
|
info "=== Secrets ==="
|
|
kube -n "$NAMESPACE" get secret \
|
|
knoe-kdc-secrets knoe-auth-google-oidc knoe-auth-secrets 2>/dev/null || true
|
|
info "=== Services ==="
|
|
kube -n "$NAMESPACE" get svc knoe-auth 2>/dev/null || true
|
|
info "=== Enrollment endpoint ==="
|
|
info "${AUTH_HOST}/auth/enroll"
|
|
}
|
|
|
|
# ── Full initialization ───────────────────────────────────────────────────────
|
|
|
|
cmd_initialize() {
|
|
require_tool kubectl
|
|
|
|
info "=== knoe-auth initialization ==="
|
|
info " Mode: $MODE"
|
|
info " Realm: $REALM"
|
|
info " Cluster: $APP_CTX"
|
|
info " NS: $NAMESPACE"
|
|
info ""
|
|
|
|
if [[ "$MODE" == "k3d" ]]; then
|
|
cmd_initialize_k3d
|
|
return
|
|
fi
|
|
|
|
# 1. Ensure namespace exists
|
|
kube get namespace "$NAMESPACE" >/dev/null 2>&1 || \
|
|
kube create namespace "$NAMESPACE"
|
|
|
|
# 2. Secrets
|
|
create_kdc_secrets
|
|
create_google_oidc_secret
|
|
create_session_secret
|
|
create_oidc_path_b_secret
|
|
|
|
# 3. Apply ConfigMap + Deployment
|
|
apply_manifests
|
|
|
|
# 4. Wait for pods
|
|
wait_for_pods "app=knoe-auth" 240
|
|
|
|
# 5. Schema
|
|
run_schema
|
|
|
|
# 6. Done
|
|
info ""
|
|
info "=== knoe-auth is ready ==="
|
|
info "Enrollment URL: ${AUTH_HOST}/auth/enroll?token=<invite_token>"
|
|
info ""
|
|
info "Next: create first admin invite:"
|
|
info " $0 invite chrisfu@knoey.com"
|
|
info ""
|
|
show_status
|
|
}
|
|
|
|
cmd_initialize_k3d() {
|
|
info "=== k3d mode: provisioning KDC + schema ==="
|
|
|
|
# 1. Ensure namespace exists
|
|
kube get namespace "$NAMESPACE" >/dev/null 2>&1 || \
|
|
kube create namespace "$NAMESPACE"
|
|
|
|
# 2. KDC secret (dev passwords, no 1Password)
|
|
create_kdc_secrets
|
|
|
|
# 3. Apply KDC manifests
|
|
apply_manifests
|
|
|
|
# 4. Wait for KDC pod
|
|
info "Waiting for KDC deployment to be ready..."
|
|
kube -n "$NAMESPACE" rollout status deployment/knoe-kdc --timeout=120s
|
|
|
|
# 5. Schema (via kubectl exec into CNPG primary)
|
|
run_schema
|
|
|
|
# 6. Dev-user seed: knoe_developer group + chrisfu role (k3d-only).
|
|
seed_dev_users_k3d
|
|
|
|
info ""
|
|
info "=== k3d knoe-auth stack is ready ==="
|
|
info "Run: make k3d-knoe-pf"
|
|
info "Then in another terminal:"
|
|
info " export KRB5_CONFIG=\$PWD/etc/krb5.local.conf"
|
|
info " export KNOE_AUTH_OIDC_SIGNING_KEY=\$(cat etc/secrets/knoe-auth-oidc-key.b64)"
|
|
info " mvn -pl authority spring-boot:run -Dspring-boot.run.profiles=k3d"
|
|
info ""
|
|
info "Dev DB access (from host, with port-forward up):"
|
|
info " PGPASSWORD=chrisfu-dev psql -h localhost -U chrisfu -d knoe-db"
|
|
info ""
|
|
show_status
|
|
}
|
|
|
|
# ── Dispatch ──────────────────────────────────────────────────────────────────
|
|
|
|
case "$COMMAND" in
|
|
initialize) cmd_initialize ;;
|
|
schema) run_schema ;;
|
|
invite) create_first_invite "${1:-}" ;;
|
|
status) show_status ;;
|
|
*)
|
|
echo "Usage: $0 {initialize|schema|invite EMAIL|status} [--context CTX] [--namespace NS]" >&2
|
|
exit 1
|
|
;;
|
|
esac
|