diff --git a/README.md b/README.md index 9cbe6f9..19a0f19 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,136 @@ [![Go Reference](https://pkg.go.dev/badge/github.com/m-lab/token-exchange.svg)](https://pkg.go.dev/github.com/m-lab/token-exchange) A Cloud Run service that exchanges API keys for signed JWTs for M-Lab services. + +## API Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/v0/token/autojoin` | Exchange autojoin API key for JWT token | +| `POST` | `/v0/token/integration` | Exchange client integration API key for JWT token | +| `GET` | `/.well-known/jwks.json` | Serve JSON Web Key Set for token verification | +| `GET` | `/health` | Health check endpoint | + +## Development + +### Build and Test + +```bash +# Build all packages +go build -v ./... + +# Run all tests with race detection +go test -race -v ./... + +# Run tests with coverage +go test -coverprofile=coverage.out -coverpkg=./... ./... +``` + +### Docker + +```bash +# Build Docker image +docker build -t token-exchange . +``` + +### Deploy to Cloud Run + +```bash +gcloud builds submit --config cloudbuild.yaml +``` + +## Configuration + +Configuration is via command-line flags. All flags can also be set via environment variables (e.g., `-port` becomes `PORT`, `-platform-ns` becomes `PLATFORM_NS`). + +| Flag | Default | Description | +|------|---------|-------------| +| `-port` | `8080` | Port to listen on | +| `-private-key-path` | `/secrets/jwk-priv.json` | Path to JWK private key file | +| `-platform-ns` | `platform-credentials` | Datastore namespace for autojoin credentials | +| `-client-integration-ns` | `client-integration` | Datastore namespace for client integration credentials | +| `-project-id` | `mlab-sandbox` | Google Cloud project ID | + +## CLI Tools + +### create-integration + +Creates a new client integration and API key in Datastore. + +```bash +go run ./cmd/create-integration -project=mlab-sandbox -integration-id=my-integration +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `-project` | (required) | Google Cloud project ID | +| `-integration-id` | (required) | Integration ID | +| `-namespace` | `client-integration` | Datastore namespace | +| `-key-id` | (auto-generated) | Key ID | +| `-integration-description` | | Human-readable description for the integration | +| `-key-description` | | Human-readable description for the API key | +| `-key-tier` | `0` | Service tier for the API key | + +## Usage + +### Integration Token Exchange + +Exchange an API key for a short-lived JWT token. + +**Request:** + +```bash +curl -X POST https://your-service-url/v0/token/integration \ + -H "Content-Type: application/json" \ + -d '{"api_key": "your-api-key"}' +``` + +**Response:** + +```json +{ + "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ii..." +} +``` + +**JWT Claims:** + +| Claim | Description | +|-------|-------------| +| `int_id` | Integration ID | +| `key_id` | API Key ID | +| `tier` | Service tier | +| `aud` | Audience (`integration`) | +| `exp` | Expiration time (20 seconds from issue) | +| `iat` | Issued at time | +| `iss` | Issuer | +| `jti` | Unique token ID | +| `nbf` | Not valid before time | + +**Error Responses:** + +| Status | Description | +|--------|-------------| +| `400 Bad Request` | Invalid request body | +| `401 Unauthorized` | Invalid API key | +| `405 Method Not Allowed` | Request method is not POST | +| `500 Internal Server Error` | Failed to generate token | + +## Architecture + +The service validates API keys stored in Google Cloud Datastore and returns signed JWT tokens. Tokens are signed using RS256 with keys loaded from a JWK file. + +### Token Flow + +```mermaid +sequenceDiagram + participant Client + participant TokenExchange + participant Datastore + + Client->>TokenExchange: POST /v0/token/integration (API key) + TokenExchange->>Datastore: Validate API key + Datastore-->>TokenExchange: Integration info + TokenExchange->>TokenExchange: Generate JWT (20-second expiry) + TokenExchange-->>Client: Signed JWT token +```