mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
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)
This commit is contained in:
parent
5f2615eb0f
commit
36a78fec72
8 changed files with 156 additions and 14 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
|
||||
/// 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 } => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
1
shared-data/precomputed-math-data
Submodule
1
shared-data/precomputed-math-data
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit cf28838be713b7c93942be80382b43d3b81da898
|
||||
Loading…
Add table
Reference in a new issue