# 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 ```sql 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 ```bash 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 ```bash # 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: ```bash curl -H "Authorization: Bearer rdev_sk_..." http://localhost:8080/projects ``` ### Create API Key ```bash 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 ```bash curl -H "Authorization: Bearer $ADMIN_KEY" http://localhost:8080/keys ``` ### Revoke Key ```bash 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: ```bash 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 ` 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