e316c1b639
* refactor(server): move daemon entrypoint into the server package Mirror the TS layout: the daemon process is now a server-package module (mirage/server/daemon.py, like server/bin/daemon.ts) that reads the daemon env vars and builds the app, replacing cli/server_factory.py. Because the server package now reads MIRAGE_IDLE_GRACE_SECONDS and MIRAGE_PERSIST_DIR, their name constants move from cli/env.py to server/env.py; cli/env.py re-exports them, matching cli/src/env.ts. Spawners updated to 'uvicorn mirage.server.daemon:app': cli client, cli test conftest, CLI CI workflow, and the auth docs. * docs(cli): add Python CLI README (basic usage + env vars)
102 lines
3.8 KiB
Plaintext
102 lines
3.8 KiB
Plaintext
---
|
|
title: Daemon Auth
|
|
description: How the Mirage daemon authenticates HTTP clients in local, token, and JWT modes.
|
|
icon: shield-halved
|
|
---
|
|
|
|
## What It Does
|
|
|
|
The daemon serves a local HTTP API on `127.0.0.1:8765`. Every request except `/v1/health` must present a bearer token. Which token is accepted is decided by `MIRAGE_AUTH_MODE`:
|
|
|
|
| Mode | When to use | What the daemon accepts |
|
|
| --- | --- | --- |
|
|
| `local` (default) | One user, one machine. Zero config. | A random token the CLI mints into `~/.mirage/auth_token` (mode 0o600). |
|
|
| `token` | Shared daemon, operator-issued PAT. | The exact string in `MIRAGE_AUTH_TOKEN`. |
|
|
| `jwt` | Multi-tenant, external issuer (Clerk, Auth0, your own). | Any RS256-signed JWT that verifies against `MIRAGE_JWT_PUBKEY` / `_FILE`. |
|
|
|
|
`/v1/health` is always reachable without a token so load balancers and process supervisors can probe it.
|
|
|
|
## Local Mode (Default)
|
|
|
|
You usually do nothing. The first time the CLI spawns the daemon it writes a random 32-byte token to `~/.mirage/auth_token` at mode 0o600 and uses it on every subsequent request.
|
|
|
|
```bash
|
|
mirage workspace create workspace.yaml --id demo
|
|
cat ~/.mirage/auth_token # the token the daemon expects
|
|
```
|
|
|
|
Probe it directly:
|
|
|
|
```bash
|
|
TOKEN=$(cat ~/.mirage/auth_token)
|
|
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8765/v1/workspaces # 200
|
|
curl http://127.0.0.1:8765/v1/workspaces # 401
|
|
curl http://127.0.0.1:8765/v1/health # 200
|
|
```
|
|
|
|
## Token Mode (Operator PAT)
|
|
|
|
For a daemon you run yourself (Docker, systemd, a shared dev box), pin one token across all clients.
|
|
|
|
```bash
|
|
export MIRAGE_AUTH_MODE=token
|
|
export MIRAGE_AUTH_TOKEN=<your-pat>
|
|
|
|
# Python
|
|
python -m uvicorn mirage.server.daemon:app --host 127.0.0.1 --port 8765
|
|
|
|
# TypeScript
|
|
node typescript/packages/cli/dist/bin/daemon.js
|
|
```
|
|
|
|
Clients pass the same value:
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer $MIRAGE_AUTH_TOKEN" http://127.0.0.1:8765/v1/workspaces
|
|
```
|
|
|
|
Both server and CLI use a constant-time compare, so timing leakage is not a concern.
|
|
|
|
## JWT Mode (External Issuer)
|
|
|
|
Hand the daemon a public key and it will accept any non-expired JWT signed by the matching private key. Verification is networkless: no JWKS fetch, no callback to the issuer.
|
|
|
|
```bash
|
|
export MIRAGE_AUTH_MODE=jwt
|
|
export MIRAGE_JWT_PUBKEY_FILE=/etc/mirage/issuer-pub.pem
|
|
export MIRAGE_JWT_ALG=RS256
|
|
|
|
# Optional hardening
|
|
export MIRAGE_JWT_ISSUER=https://your-issuer.example
|
|
export MIRAGE_JWT_AUDIENCE=mirage-daemon
|
|
export MIRAGE_JWT_AUTHORIZED_PARTIES=https://app.example,https://cli.example
|
|
export MIRAGE_JWT_CLOCK_SKEW_SECONDS=5
|
|
```
|
|
|
|
Hard rules the daemon enforces:
|
|
|
|
- `alg` is pinned to `MIRAGE_JWT_ALG`. A token signed with a different algorithm is rejected, which defeats alg-confusion attacks.
|
|
- `alg=none` is always rejected.
|
|
- `exp` is mandatory.
|
|
- `typ`, if present, must be `JWT`.
|
|
- Opaque (non-three-segment) values in `Authorization: Bearer` are rejected before key work, so probing is cheap.
|
|
|
|
## Environment Reference
|
|
|
|
| Variable | Modes | Purpose |
|
|
| --- | --- | --- |
|
|
| `MIRAGE_AUTH_MODE` | all | `local` (default), `token`, or `jwt`. |
|
|
| `MIRAGE_AUTH_TOKEN` | local, token | Local-mode override; required in `token` mode. |
|
|
| `MIRAGE_JWT_PUBKEY` | jwt | PEM string of the public key. |
|
|
| `MIRAGE_JWT_PUBKEY_FILE` | jwt | Path to a PEM file (alternative to inline). |
|
|
| `MIRAGE_JWT_ALG` | jwt | Signing algorithm to pin, e.g. `RS256`. |
|
|
| `MIRAGE_JWT_ISSUER` | jwt | Required `iss` claim. |
|
|
| `MIRAGE_JWT_AUDIENCE` | jwt | Required `aud` claim. |
|
|
| `MIRAGE_JWT_AUTHORIZED_PARTIES` | jwt | Comma-separated allow-list for `azp`. |
|
|
| `MIRAGE_JWT_CLOCK_SKEW_SECONDS` | jwt | Default `5`. |
|
|
|
|
## Where to Go Next
|
|
|
|
- [CLI](/home/cli) walks the daily Workspace flow that uses local-mode automatically.
|
|
- [Architecture](/home/architecture) shows where the auth middleware sits in the request path.
|