Research-Stack/6-Documentation/wiki/Authentik-Agent-Management.md
2026-05-25 16:24:21 -05:00

12 KiB

Authentik Agent Management

Purpose: Enable autonomous LLM agents (OpenClaw, Hermes, ad-hoc models) to create, scope, and revoke identities in Authentik without human-in-the-loop. Last updated: 2026-05-20 Authentik version: 2026.2.3 OpenAPI: https://researchstack.info/api/v3/schema/


1. Architecture

┌─────────────────┐     Bearer Token      ┌──────────────────┐
│   LLM Agent     │ ────────────────────▶ │  Authentik API   │
│  (OpenClaw /    │   (llm-controller-  │  (nixos-laptop)  │
│   Hermes / etc) │      token)          │  :9000 internal  │
└─────────────────┘                        └────────┬─────────┘
                                                   │
                          ┌────────────────────────┘
                          │
              ┌───────────▼────────────┐
              │   PostgreSQL 16 (RDS)   │  ← user, group, token, app tables
              │   Redis (cache/sessions) │
              └─────────────────────────┘

Key principle

Authentik is not directly hooked to Postgres for LLM consumption. It exposes a REST API (OpenAPI 3.0.3, 568 paths). The Postgres database is Authentik's internal persistence layer. LLMs interact with Authentik exclusively through the REST API, not via SQL.


2. Service Account Setup

llm-agent-controller

Field Value
Username llm-agent-controller
Type service_account
Group AgentManager
Token identifier llm-controller-token
Token intent INTENT_API
Active Yes

Created via Django shell inside the Authentik server container:

ssh allaun@100.119.165.120
cd ~/authentik
podman cp create_agent.py authentik_server_1:/tmp/
podman exec authentik_server_1 python3 /manage.py shell < /tmp/create_agent.py

Token storage

The token lives in two places:

  1. SOPS-encrypted secrets (repo root):

    sops -d 4-Infrastructure/infra/secrets/credentials.json | jq '.authentik'
    
  2. AWS RDS credential database (pending — requires server-side AES key):

    • Stored in credential_store.credentials with provider = 'authentik'
    • Encrypted with the same AES-256-GCM key used by the credential server
    • The credential server on microvm-racknerd serves it via HTTP GET

3. Rust Shim (authentik_agent_manager)

A Rust CLI tool lives at 4-Infrastructure/shim/authentik_agent_manager/. It supports both individual commands and DAG execution.

Authentication required: You must be authenticated by Authentik to use this tool. Every operation requires a valid Authentik API token (--token or AUTHENTIK_TOKEN env var). The account associated with the token must have appropriate Authentik permissions. The llm-agent-controller service account (in the authentik Admins group) is the canonical identity for LLM-driven operations.

Individual commands (familiar CLI)

cd 4-Infrastructure/shim/authentik_agent_manager
cargo build --release

export AUTHENTIK_TOKEN=$(sops -d --extract '["authentik"]["api_token"]' \
  ../../infra/secrets/credentials.json)

./target/release/authentik_agent_manager --token "$AUTHENTIK_TOKEN" list-users
./target/release/authentik_agent_manager --token "$AUTHENTIK_TOKEN" create-agent openclaw-7 "OpenClaw Instance 7"
./target/release/authentik_agent_manager --token "$AUTHENTIK_TOKEN" create-token 42 openclaw-7-token
./target/release/authentik_agent_manager --token "$AUTHENTIK_TOKEN" suspend 42
./target/release/authentik_agent_manager --token "$AUTHENTIK_TOKEN" revoke 42

DAG mode (the primary feature)

Define a JSON DAG plan and execute it atomically:

./target/release/authentik_agent_manager --token "$TOKEN" execute plan.json

Example plan.json:

{
  "nodes": [
    {
      "id": "create_user",
      "op": "create_user",
      "params": {
        "username": "openclaw-7",
        "name": "OpenClaw Instance 7"
      }
    },
    {
      "id": "create_token",
      "op": "create_token",
      "depends_on": ["create_user"],
      "params": { "identifier": "openclaw-7-token" },
      "input_mapping": { "user_pk": "create_user.pk" }
    },
    {
      "id": "add_to_group",
      "op": "add_to_group",
      "depends_on": ["create_user"],
      "params": { "group_name": "AgentManager" },
      "input_mapping": { "user_pk": "create_user.pk" }
    }
  ]
}

How it works:

  1. The shim computes a topological order (Kahn's algorithm).
  2. Each node runs only after its depends_on prerequisites succeed.
  3. input_mapping pulls fields from upstream node outputs into downstream params.
  4. Every execution writes a receipt to ~/.cache/authentik-dag-receipts.jsonl.

Operations supported in DAG:

Operation Description
create_user Create a service-account user
create_token Generate an API token for a user
add_to_group Add a user to a named group
list_users List all users (read-only)
list_groups List all groups (read-only)
suspend_user Deactivate a user
revoke_user Hard-delete a user
rotate_token Regenerate a token key
create_application Create an application

4. API Patterns for LLM Agents

Authentication

Every request includes:

Authorization: Bearer <token>

4.1 Create a new agent (user)

TOKEN=$(sops -d --extract '["authentik"]["api_token"]' 4-Infrastructure/infra/secrets/credentials.json)

curl -s -X POST https://researchstack.info/api/v3/core/users/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "openclaw-instance-7",
    "name": "OpenClaw Instance 7",
    "type": "service_account",
    "is_active": true,
    "email": "openclaw-7@researchstack.info"
  }' | jq .

Response:

{
  "pk": 42,
  "username": "openclaw-instance-7",
  "name": "OpenClaw Instance 7",
  "email": "openclaw-7@researchstack.info",
  "is_active": true,
  "type": "service_account",
  "uuid": "..."
}

4.2 Create an API token for the new agent

curl -s -X POST https://researchstack.info/api/v3/core/tokens/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "openclaw-7-token",
    "intent": "api",
    "user": 42,
    "description": "API token for OpenClaw instance 7"
  }' | jq '.key'

Important: Save the .key from the response — it is shown only once.

4.3 Add agent to a scoped group

Groups define what applications and resources an agent can access.

# Get the AgentManager group UUID
GROUP_UUID=$(curl -s https://researchstack.info/api/v3/core/groups/ \
  -H "Authorization: Bearer $TOKEN" | jq -r '.results[] | select(.name=="AgentManager") | .pk')

# Add user to group
curl -s -X POST "https://researchstack.info/api/v3/core/groups/${GROUP_UUID}/add_user/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pk": 42}'

4.4 Create an application (what the agent can access)

curl -s -X POST https://researchstack.info/api/v3/core/applications/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OpenClaw Data Sink",
    "slug": "openclaw-data-sink",
    "provider": null,
    "policy_engine_mode": "any"
  }' | jq .

4.5 List all agents

curl -s https://researchstack.info/api/v3/core/users/ \
  -H "Authorization: Bearer $TOKEN" | jq '.results[] | {pk, username, name, is_active}'

4.6 Revoke an agent (soft delete)

curl -s -X PATCH "https://researchstack.info/api/v3/core/users/42/" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

4.7 Hard delete an agent

curl -s -X DELETE "https://researchstack.info/api/v3/core/users/42/" \
  -H "Authorization: Bearer $TOKEN"

4. Agent Lifecycle

State machine

[Provisioning] ──▶ [Active] ──▶ [Suspended] ──▶ [Revoked]
      │                │            │
      │                ▼            │
      │            [Token          │
      │             Rotation]       │
      │                │            │
      └────────────────┴────────────┘

4.1 Provisioning

  1. LLM decides a new agent is needed (e.g., a new model instance, a specialized worker)
  2. LLM calls POST /core/users/ with type: service_account
  3. LLM calls POST /core/tokens/ to generate an API token for the new agent
  4. LLM calls POST /core/groups/{uuid}/add_user/ to assign RBAC scope
  5. LLM stores the new token in the credential system (SOPS + RDS)

4.2 Active operation

  • Agent uses its own token to authenticate to Authentik-protected applications
  • Agent may query GET /core/users/me/ to verify its own identity
  • Agent may read application entitlements via GET /core/application_entitlements/

4.3 Token rotation

# Regenerate key for an existing token
curl -s -X POST "https://researchstack.info/api/v3/core/tokens/openclaw-7-token/set_key/" \
  -H "Authorization: Bearer $TOKEN" | jq '.key'

Policy: Rotate tokens every 90 days. The old key becomes invalid immediately.

4.4 Suspension (soft revoke)

Set is_active: false on the user. The user and all its tokens remain in the database but cannot authenticate. This is reversible.

4.5 Hard revocation

DELETE /core/users/{pk}/ — irreversible. All tokens, sessions, and group memberships for that user are cascade-deleted.


5. Security Boundaries

Boundary Rule
Token visibility The llm-controller-token is known only to the SOPS secrets file and the RDS credential database. Never log it.
Scope The AgentManager group currently has no Authentik-native RBAC restrictions. Future: bind AgentManager to a custom role that limits users CRUD to service_account types only.
Network Authentik admin API is exposed only via researchstack.info:443 (Caddy reverse proxy). Direct :9000 access is blocked by nixos-laptop firewall.
Audit All API calls are logged by Authentik. Future: wire access_log table in RDS to record credential fetches.
Rate limiting No explicit rate limit is configured. Future: add Caddy rate limiting on /api/v3/ paths.

6. Integration with Other Stack Components

Component Integration Pattern
Credential Server LLM controller token served from RDS at http://100.101.247.127:8444/credentials/authentik
SOPS Secrets Token + metadata encrypted in 4-Infrastructure/infra/secrets/credentials.json
Garage S3 Agent artifacts (receipts, outputs) stored in research-stack bucket with per-agent prefix
RDS Postgres credential_store schema holds the token; access_log table records fetches (not yet wired)
Tailscale All internal traffic (LLM → Authentik API, Authentik → Postgres, etc.) flows over Tailscale mesh

7. TODO / Next Steps

  • Wire access_log on every credential fetch from RDS
  • Add node_assignments enforcement (only qfox-1, nixos-laptop may request the authentik token)
  • Create custom role in Authentik to restrict AgentManager to service_account CRUD only
  • Add Caddy rate limit for /api/v3/ paths on microvm-racknerd
  • Generate typed client from OpenAPI spec (e.g., openapi-python-client)
  • Build MCP server wrapping the 7 key endpoints (create user, create token, add to group, list users, suspend, revoke, rotate)
  • Document per-agent application binding — how each LLM agent gets its own scoped application