Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Loading