rdev/history/v0.5.0.md
jordan 48f7dc9f74 docs: add v0.5.0 history - API key authentication
Documents the complete API key authentication system:
- Key format, hashing, and scopes
- Database schema and migrations
- Auth middleware and endpoints
- Build/deploy instructions
- Fixes for chi middleware ordering and Colima cross-compilation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 23:48:43 -07:00

6.8 KiB

rdev v0.5.0 - API Key Authentication

Date: 2026-01-25 Status: Deployed

Summary

API key authentication system for rdev-api. External clients must authenticate with API keys that have scoped permissions. Includes admin key bootstrapping, auto-running migrations, and a full key management API.

What Was Built

Authentication System

  • API Key Format: rdev_sk_<8-char-prefix>_<32-char-hex>
  • SHA-256 Hashing: Keys stored as hashes, secrets never persisted
  • Scoped Permissions: Fine-grained access control
  • Expiration Options: 30d, 60d, 90d, 1y, never
  • Admin Bootstrap: RDEV_ADMIN_KEY env var for initial access

Scopes

Scope Description
projects:read List and get project info
projects:execute Run commands (claude, shell, git)
keys:read List API keys
keys:write Create/revoke API keys
admin Full access (includes all scopes)

New API Endpoints

Method Path Description Required Scope
GET /keys List all API keys keys:read
POST /keys Create new API key keys:write
GET /keys/{id} Get key details keys:read
DELETE /keys/{id} Revoke API key keys:write

Packages Created

internal/
├── db/
│   ├── postgres.go              # Auto-migrating database connection
│   └── migrations/
│       └── 001_create_api_keys.sql
├── auth/
│   ├── keys.go                  # Key generation and hashing
│   ├── scopes.go                # Scope validation
│   ├── service.go               # CRUD operations
│   └── middleware.go            # HTTP auth middleware
└── handlers/
    └── keys.go                  # Key management handlers

Database Schema

CREATE TABLE api_keys (
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name         VARCHAR(255) NOT NULL,
    key_hash     VARCHAR(64) NOT NULL UNIQUE,
    key_prefix   VARCHAR(8) NOT NULL,
    scopes       TEXT[] NOT NULL DEFAULT '{}',
    project_ids  TEXT[] DEFAULT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at   TIMESTAMPTZ,
    last_used_at TIMESTAMPTZ,
    revoked_at   TIMESTAMPTZ,
    created_by   VARCHAR(255)
);

Files Created/Modified

# New files
internal/db/postgres.go
internal/db/migrations/001_create_api_keys.sql
internal/auth/keys.go
internal/auth/scopes.go
internal/auth/service.go
internal/auth/middleware.go
internal/handlers/keys.go
Dockerfile.api.simple              # Pre-built binary deployment

# Modified files
cmd/rdev-api/main.go               # Added auth middleware, key endpoints
pkg/api/app.go                     # Fixed middleware ordering
deployments/k8s/base/rdev-api.yaml # Added DB env vars, secrets

Dependencies Added

github.com/lib/pq                  # PostgreSQL driver

Deployment

Prerequisites

  1. PostgreSQL running in databases namespace
  2. rdev database created with appuser access
  3. K8s secret rdev-credentials with:
    • DB_PASSWORD: PostgreSQL password
    • RDEV_ADMIN_KEY: Bootstrap admin key

Build and Deploy

cd /path/to/rdev

# Build binary natively (avoids Colima cross-compile issues)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o rdev-api ./cmd/rdev-api

# Build and push image
docker build -f Dockerfile.api.simple -t ghcr.io/orchard9/rdev-api:v0.5.0 .
docker push ghcr.io/orchard9/rdev-api:v0.5.0

# Deploy
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
kubectl apply -k deployments/k8s/base
kubectl rollout restart deployment/rdev-api -n rdev

Verify

# Check API is running
kubectl get pods -n rdev -l app=rdev-api
kubectl logs -n rdev deployment/rdev-api

# Port forward for testing
kubectl port-forward -n rdev svc/rdev-api 8080:8080

# Test with admin key
export ADMIN_KEY="rdev_sk_6d49dcad_..."
curl -H "Authorization: Bearer $ADMIN_KEY" http://localhost:8080/projects

# Create a new key
curl -X POST -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  http://localhost:8080/keys \
  -d '{"name": "discord-bot", "scopes": ["projects:read", "projects:execute"], "expires_in": "90d"}'

Usage Examples

Authentication

All requests (except /health, /ready, /docs, /openapi.json) require authentication:

curl -H "Authorization: Bearer rdev_sk_..." http://localhost:8080/projects

Create API Key

curl -X POST http://localhost:8080/keys \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "discord-bot",
    "scopes": ["projects:read", "projects:execute"],
    "expires_in": "90d"
  }'

# Response includes the secret (only shown once)
{
  "data": {
    "key": {
      "id": "bceb2ff6-807d-48fa-8e99-cd3468cece42",
      "name": "discord-bot",
      "key_prefix": "3e7afcc3",
      "scopes": ["projects:read", "projects:execute"],
      "expires_at": "2026-04-25T06:27:33Z"
    },
    "secret": "rdev_sk_3e7afcc3_8b296dbc18924b4cf41cdcd99f9255b5"
  }
}

List Keys

curl -H "Authorization: Bearer $ADMIN_KEY" http://localhost:8080/keys

Revoke Key

curl -X DELETE -H "Authorization: Bearer $ADMIN_KEY" \
  http://localhost:8080/keys/bceb2ff6-807d-48fa-8e99-cd3468cece42

Fixes Applied

Chi Middleware Ordering

Problem: panic: chi: all middlewares must be defined before routes on a mux

Cause: Health endpoints were registered in New() before auth middleware was added via Use().

Fix: Moved setupHealthEndpoints() from New() to Run() in pkg/api/app.go.

Colima Cross-Compilation

Problem: Building inside Docker on Mac/Colima caused segfaults in crypto packages.

Fix: Build binary natively on Mac, then copy into minimal Alpine container:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o rdev-api ./cmd/rdev-api

Keys Created

Name Prefix Scopes Expires
admin (env) 6d49dcad admin never
discord-bot 3e7afcc3 projects:read, projects:execute 2026-04-25

What's Next (v0.6)

  • DNS setup: rdev.orchard9.ai
  • End-to-end testing
  • Discord bot integration
  • Rate limiting
  • Key rotation

Troubleshooting

401 Unauthorized

  • Check Authorization: Bearer <key> header is present
  • Verify key hasn't expired or been revoked
  • Ensure key has required scopes for the endpoint

403 Forbidden

  • Key is valid but lacks required scope
  • Check key's scopes match the endpoint requirements

Database connection failed

  • Verify DB_* env vars are set correctly
  • Check postgres pod is running: kubectl get pods -n databases
  • Ensure rdev database exists with appuser access

Migration failed

  • Check postgres logs for errors
  • Verify appuser has CREATE TABLE permissions on rdev database