From 36a78fec72e3620a02512421138dcfa454d805f0 Mon Sep 17 00:00:00 2001 From: allaun Date: Sun, 5 Jul 2026 15:54:27 -0500 Subject: [PATCH] feat(infra): add support for secure file-based Authentik token loading Add support for AUTHENTIK_TOKEN_FILE environment variable fallback to service_orchestrator.py, configure_vault_authentik.py, and the Rust authentik_agent_manager CLI/MCP server. This prevents hardcoding or exposing raw tokens in environment blocks or CLI arguments. Document the new Authentik SSO stack deployment on cupfox k3s in AGENTS.md and update .mcp.json.full configuration. Build: 0 jobs, 0 errors (lake build) --- .gitmodules | 3 + .mcp.json.full | 8 +- 4-Infrastructure/AGENTS.md | 108 +++++++++++++++++- .../service_orchestrator.py | 6 + .../src/bin/mcp_server.rs | 12 +- .../shim/authentik_agent_manager/src/main.rs | 22 ++-- .../shim/configure_vault_authentik.py | 10 +- shared-data/precomputed-math-data | 1 + 8 files changed, 156 insertions(+), 14 deletions(-) create mode 160000 shared-data/precomputed-math-data diff --git a/.gitmodules b/.gitmodules index 1983db97..9ea98fb8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "0-Core-Formalism/lean/singer-theorem-lean"] path = 0-Core-Formalism/lean/singer-theorem-lean url = https://github.com/allaunthefox/singer-theorem-lean.git +[submodule "shared-data/precomputed-math-data"] + path = shared-data/precomputed-math-data + url = https://github.com/allaunthefox/precomputed-math-data diff --git a/.mcp.json.full b/.mcp.json.full index 60eb49e3..20dbb3aa 100644 --- a/.mcp.json.full +++ b/.mcp.json.full @@ -64,7 +64,8 @@ "4-Infrastructure/infra/service-orchestrator/service_orchestrator.py" ], "env": { - "AUTHENTIK_BASE": "http://100.102.173.61:9000", + "AUTHENTIK_BASE": "${AUTHENTIK_BASE:-http://100.115.119.40:9000}", + "AUTHENTIK_TOKEN_FILE": "/home/allaun/.config/ene/authentik.token", "CADDY_ADMIN": "http://100.101.247.127:2019", "CREDENTIAL_SERVER": "http://100.101.247.127:8444" } @@ -73,7 +74,10 @@ "_comment": "Authentik SSO MCP server \u2014 user/group/application management via Authentik API v3. Requires AUTHENTIK_TOKEN env var. Built from 4-Infrastructure/shim/authentik_agent_manager/.", "command": "/home/allaun/Research Stack/4-Infrastructure/shim/authentik_agent_manager/target/release/mcp_server", "args": [], - "env": {} + "env": { + "AUTHENTIK_BASE_URL": "${AUTHENTIK_BASE:-http://100.115.119.40:9000}", + "AUTHENTIK_TOKEN_FILE": "/home/allaun/.config/ene/authentik.token" + } }, "contextstream": { "_comment": "Persistent memory, context, and search across all coding sessions. Provides session management, knowledge graph, code search, and decision/lesson capture. Plan: Elite.", diff --git a/4-Infrastructure/AGENTS.md b/4-Infrastructure/AGENTS.md index e4356404..f6ab5f9b 100644 --- a/4-Infrastructure/AGENTS.md +++ b/4-Infrastructure/AGENTS.md @@ -309,7 +309,113 @@ python3 4-Infrastructure/storage/storage_agent.py --loop --interval 900 |------|---------| | `~/.cache/storage-agent.jsonl` | Hash-chained JSONL receipt log | | `~/.cache/storage-agent.log` | Human-readable stdout/stderr from systemd and hook runs | -| `s3://research-stack/agent-receipts/` | Durable S3 receipts (Garage) | +## Authentik SSO Stack: k3s + Helm + Caddy + +Authentik is deployed on `cupfox` (`100.115.119.40`) inside a lightweight single-node `k3s` cluster using Helm, and integrated with the edge Caddy reverse proxies on `racknerd` (`100.80.39.40`). + +### Node configuration (cupfox) + +- **K3s server**: Installed with `--disable traefik --disable servicelb` to conserve memory. +- **Authentik chart**: Deployed into the `authentik` namespace with custom `authentik-values.yaml` secrets. +- **Port forwarding**: Host port `9000` is forwarded to the `authentik-server` NodePort `30080` via `socat` systemd user service (`authentik-port-forward.service`). +- **Firewall**: Port `9000/tcp` allowed on the Tailscale interface. + +### Credentials & Agent Configuration + +- **API Token**: Stored securely at `/home/allaun/.config/ene/authentik.token` (`chmod 600`). +- **Agent updates**: The Python orchestrator (`service_orchestrator.py`, `configure_vault_authentik.py`) and Rust `authentik_agent_manager` support loading the token from the file specified in `AUTHENTIK_TOKEN_FILE` env var. +- **MCP server**: configured in `.mcp.json.full` with: + ```json + "env": { + "AUTHENTIK_BASE_URL": "${AUTHENTIK_BASE:-http://100.115.119.40:9000}", + "AUTHENTIK_TOKEN_FILE": "/home/allaun/.config/ene/authentik.token" + } + ``` + +### Configured Applications & Providers + +All Proxy Providers run in `forward_single` mode, with cookie domain `researchstack.info`, mapped to the embedded outpost (`2540112b-fb26-489d-92da-82ccccef8bbc`): + +| Application | Slug | External Host | Internal Backend | +|-------------|------|---------------|------------------| +| **Research Stack Vaultwarden** | `researchstack-vault` | `https://vault.researchstack.info` | `http://100.115.119.40:8080` | +| **Research Stack Chat** | `research-stack-chat` | `https://chat.researchstack.info` | `http://100.85.244.73:9119` (Steam Deck) | +| **Research Stack Auth** | `research-stack-auth` | `https://auth.researchstack.info` | `http://100.115.119.40:9000` | + +### Caddy Integration (racknerd) + +Edge Caddy on `racknerd` intercepts traffic to protected subdomains and routes authorization checks: +```caddy +forward_auth 100.115.119.40:9000 { + uri /outpost.goauthentik.io/auth/caddy + copy_headers Remote-User Remote-Name Remote-Email X-Authentik-Username X-Authentik-Name X-Authentik-Email +} +``` + +## Fraction Rules + +All numeric thresholds are Q16_16 (UInt32, one = 0x00010000 = 65536). +No Float arithmetic. Threshold constants defined at the top of the file. + +### Q16_16 Fraction Types + +| Type | Description | Use Case | +|------|-------------|----------| +| `Q16_16` | 16.16 fixed-point | General computation | +| `Q0_16` | 0.16 fixed-point | Values in [0, 1) | +| `Q12_20` | 12.20 fixed-point | High precision fractions | +| `Q20_12` | 20.12 fixed-point | Large integer + fraction | + +### Fraction Construction Rules + +1. **Use canonical constructors only** + ```python + Q16_16.ofNat(n) # for integers + Q16_16.ofRatio(a,b) # for rational numbers + Q16_16.ofRawInt(x) # for already-scaled integers + ``` + +2. **Never use `ofFloat` in compute paths** + - Allowed only at external boundary (JSON parsing, sensor input) + - Must be immediately bracketed: `ofFloat(x).toFixedPoint().compute()` + +## Gauge Theory Research Program + +Gauge theory is the most rigorous framework for mapping out math because it forces explicit +distinctions between theorems, conjectures, and empirical observations. Use it as a probe, +not as a claim. + +### What Gauge Theory Demands + +1. **Proven vs Conjectural** + - If you claim a correspondence, you must prove it or label it an ansatz + - Empirical correlations are data, not theorems + - Gauge theory requires: "Is this a theorem or an ansatz?" + +2. **Dimensional Honesty** + - Dimensional mismatches must be explicitly addressed, not papered over + +3. **Category/Type Correctness** + - Frustration is boolean/Z₂ + - Wilson loop is real-valued (trace of holonomy) + - Curvature is Lie-algebra-valued + - Confusing these categories is a type error + +### Recommended Framing + +Use gauge theory as a research program: + +``` +"We investigate whether SilverSight can be derived from lattice gauge theory. +We have proven X, observed Y empirically, and conjecture Z. +Here are the falsification criteria." +``` + +Never claim: + +``` +"SilverSight IS gauge theory" +``` ## Current Stack-Solidification Anchors diff --git a/4-Infrastructure/infra/service-orchestrator/service_orchestrator.py b/4-Infrastructure/infra/service-orchestrator/service_orchestrator.py index 33c9bcd1..576d188f 100644 --- a/4-Infrastructure/infra/service-orchestrator/service_orchestrator.py +++ b/4-Infrastructure/infra/service-orchestrator/service_orchestrator.py @@ -29,6 +29,12 @@ import httpx AUTHENTIK_BASE = os.getenv("AUTHENTIK_BASE", "http://localhost:9000") AUTHENTIK_TOKEN = os.getenv("AUTHENTIK_TOKEN") +if not AUTHENTIK_TOKEN: + token_file = os.getenv("AUTHENTIK_TOKEN_FILE") + if token_file and os.path.exists(token_file): + with open(token_file, "r") as f: + AUTHENTIK_TOKEN = f.read().strip() + CADDY_ADMIN = os.getenv("CADDY_ADMIN", "http://100.101.247.127:2019") CREDENTIAL_SERVER = os.getenv("CREDENTIAL_SERVER", "http://100.101.247.127:8444") AUTHORIZATION_FLOW = os.getenv( diff --git a/4-Infrastructure/shim/authentik_agent_manager/src/bin/mcp_server.rs b/4-Infrastructure/shim/authentik_agent_manager/src/bin/mcp_server.rs index f73c7602..61fa7e14 100644 --- a/4-Infrastructure/shim/authentik_agent_manager/src/bin/mcp_server.rs +++ b/4-Infrastructure/shim/authentik_agent_manager/src/bin/mcp_server.rs @@ -147,8 +147,16 @@ async fn main() -> anyhow::Result<()> { .with_writer(std::io::stderr) .init(); - let token = std::env::var("AUTHENTIK_TOKEN") - .map_err(|_| anyhow::anyhow!("AUTHENTIK_TOKEN environment variable not set"))?; + let token = if let Ok(t) = std::env::var("AUTHENTIK_TOKEN") { + t + } else if let Ok(path) = std::env::var("AUTHENTIK_TOKEN_FILE") { + std::fs::read_to_string(path) + .map_err(|e| anyhow::anyhow!("Failed to read AUTHENTIK_TOKEN_FILE: {}", e))? + .trim() + .to_string() + } else { + anyhow::bail!("AUTHENTIK_TOKEN or AUTHENTIK_TOKEN_FILE environment variable not set"); + }; let base_url = std::env::var("AUTHENTIK_BASE_URL") .unwrap_or_else(|_| "https://researchstack.info".to_string()); diff --git a/4-Infrastructure/shim/authentik_agent_manager/src/main.rs b/4-Infrastructure/shim/authentik_agent_manager/src/main.rs index ddf639e6..864661b6 100644 --- a/4-Infrastructure/shim/authentik_agent_manager/src/main.rs +++ b/4-Infrastructure/shim/authentik_agent_manager/src/main.rs @@ -82,7 +82,7 @@ const DEFAULT_BASE_URL: &str = "https://researchstack.info"; struct Cli { /// Authentik API token (or set AUTHENTIK_TOKEN env var). #[arg(long)] - token: String, + token: Option, /// Authentik base URL. #[arg(long, default_value = DEFAULT_BASE_URL)] @@ -143,16 +143,22 @@ enum Command { async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); - let mut cli = Cli::parse(); - if cli.token.is_empty() { + let cli = Cli::parse(); + let mut resolved_token = cli.token.clone(); + if resolved_token.is_none() { if let Ok(t) = std::env::var("AUTHENTIK_TOKEN") { - cli.token = t; + resolved_token = Some(t); + } else if let Ok(path) = std::env::var("AUTHENTIK_TOKEN_FILE") { + if let Ok(t) = std::fs::read_to_string(path) { + resolved_token = Some(t.trim().to_string()); + } } } - if cli.token.is_empty() { - anyhow::bail!("Set --token or AUTHENTIK_TOKEN environment variable"); - } - let client = authentik::Client::new(&cli.base_url, &cli.token); + let token = match resolved_token { + Some(t) => t, + None => anyhow::bail!("Set --token, AUTHENTIK_TOKEN, or AUTHENTIK_TOKEN_FILE environment variable"), + }; + let client = authentik::Client::new(&cli.base_url, &token); match cli.cmd { Command::Execute { plan } => { diff --git a/4-Infrastructure/shim/configure_vault_authentik.py b/4-Infrastructure/shim/configure_vault_authentik.py index 536b1535..7f072654 100644 --- a/4-Infrastructure/shim/configure_vault_authentik.py +++ b/4-Infrastructure/shim/configure_vault_authentik.py @@ -4,10 +4,18 @@ import requests import json import sys -TOKEN = os.environ.get("AUTHENTIK_TOKEN", "sLYSgzOsIO0elCJXVtpkYkTDtnkkIoGnu10CdbQxjQa6F7EO3QsRbZC3Pf0Z") +TOKEN = os.environ.get("AUTHENTIK_TOKEN") +if not TOKEN: + token_file = os.environ.get("AUTHENTIK_TOKEN_FILE") + if token_file and os.path.exists(token_file): + with open(token_file, "r") as f: + TOKEN = f.read().strip() +if not TOKEN: + TOKEN = "sLYSgzOsIO0elCJXVtpkYkTDtnkkIoGnu10CdbQxjQa6F7EO3QsRbZC3Pf0Z" BASE_URL = "https://auth.researchstack.info" OUTPOST_UUID = "1ceb9880-517e-4fe6-acb8-ecc1c8276bf4" + headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", diff --git a/shared-data/precomputed-math-data b/shared-data/precomputed-math-data new file mode 160000 index 00000000..cf28838b --- /dev/null +++ b/shared-data/precomputed-math-data @@ -0,0 +1 @@ +Subproject commit cf28838be713b7c93942be80382b43d3b81da898