Skip to content

Commit bf22d45

Browse files
committed
Fix auth tests, add bootstrap cli command
1 parent 0588cb2 commit bf22d45

21 files changed

Lines changed: 1027 additions & 117 deletions

deploy/config/hadrian.dlq.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ type = "redis"
1010
url = "${REDIS_URL}"
1111

1212
[auth.mode]
13-
type = "api_key"
14-
15-
[auth.api_key]
16-
header_name = "X-API-Key"
17-
key_prefix = "gw_"
18-
cache_ttl_secs = 300
13+
type = "none"
1914

2015
[providers]
2116
default_provider = "test"

deploy/config/hadrian.observability.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ type = "redis"
1010
url = "${REDIS_URL}"
1111

1212
[auth.mode]
13-
type = "api_key"
14-
15-
[auth.api_key]
16-
header_name = "X-API-Key"
17-
key_prefix = "gw_"
18-
cache_ttl_secs = 300
13+
type = "none"
1914

2015
[providers]
2116
default_provider = "test"

deploy/config/hadrian.postgres-ha.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ type = "redis"
1212
url = "${REDIS_URL}"
1313

1414
[auth.mode]
15-
type = "api_key"
16-
17-
[auth.api_key]
18-
header_name = "X-API-Key"
19-
key_prefix = "gw_"
20-
cache_ttl_secs = 300
15+
type = "none"
2116

2217
[providers]
2318
default_provider = "test"

deploy/config/hadrian.postgres.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ type = "redis"
1010
url = "${REDIS_URL}"
1111

1212
[auth.mode]
13-
type = "api_key"
14-
15-
[auth.api_key]
16-
header_name = "X-API-Key"
17-
key_prefix = "gw_"
18-
cache_ttl_secs = 300 # 5 minutes with Redis
13+
type = "none"
1914

2015
# Optional: IAP (Identity-Aware Proxy) authentication
2116
# Trusts identity headers from an authenticating proxy (Cloudflare Access, oauth2-proxy, etc.)

deploy/config/hadrian.provider-health.toml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ path = "/app/data/hadrian.db"
99
type = "memory"
1010

1111
[auth.mode]
12-
type = "api_key"
13-
14-
[auth.api_key]
15-
header_name = "X-API-Key"
16-
key_prefix = "gw_"
12+
type = "none"
1713

1814
[providers]
1915
default_provider = "test"

deploy/config/hadrian.redis-cluster.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@ connection_timeout_secs = 5
1818
response_timeout_secs = 1
1919

2020
[auth.mode]
21-
type = "api_key"
22-
23-
[auth.api_key]
24-
header_name = "X-API-Key"
25-
key_prefix = "gw_"
26-
cache_ttl_secs = 300
21+
type = "none"
2722

2823
[providers]
2924
default_provider = "test"

deploy/config/hadrian.sqlite-redis.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ type = "redis"
1010
url = "${REDIS_URL}"
1111

1212
[auth.mode]
13-
type = "api_key"
14-
15-
[auth.api_key]
16-
header_name = "X-API-Key"
17-
key_prefix = "gw_"
18-
cache_ttl_secs = 300 # 5 minutes with Redis
13+
type = "none"
1914

2015
[providers]
2116
default_provider = "test"

deploy/config/hadrian.sqlite.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@ path = "/app/data/hadrian.db"
99
type = "memory"
1010

1111
[auth.mode]
12-
type = "api_key"
13-
14-
[auth.api_key]
15-
header_name = "X-API-Key"
16-
key_prefix = "gw_"
17-
cache_ttl_secs = 60
12+
type = "none"
1813

1914
[providers]
2015
default_provider = "test"

docs/content/docs/authentication.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ In `idp` mode, per-org SSO configurations automatically enable JWT validation on
230230

231231
<Callout type="info">
232232
Per-org JWT routing only applies in `idp` mode. Per-org SSO configs provide all JWT validation
233-
automatically -- no global `[auth.gateway.jwt]` section is needed.
233+
automatically -- no global JWT configuration is needed in the config file.
234234
</Callout>
235235

236236
<Cards>
237-
<Card title="Gateway Auth Configuration" href="/docs/configuration/auth#gateway-authentication" />
237+
<Card title="Auth Configuration Reference" href="/docs/configuration/auth#api-key-settings" />
238238
</Cards>
239239

240240
## Session-Based Authentication

docs/content/docs/configuration/auth.mdx

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ Configure authentication and authorization for Hadrian in `hadrian.toml` using t
1717

1818
Hadrian uses a single `[auth.mode]` to control authentication for both the API and the web UI. Select exactly one mode.
1919

20-
| Mode | `type` | API Keys | IdP Sessions | Identity Headers | Use Case |
21-
| --------- | --------- | -------- | ------------ | ---------------- | ---------------------------------- |
22-
| None | `none` | No | No | No | Local development |
23-
| API Key | `api_key` | Yes | No | No | Programmatic access only |
24-
| IdP | `idp` | Yes | Yes | No | Full deployment with SSO + API |
25-
| IAP | `iap` | Yes | No | Yes | Behind an identity-aware proxy |
20+
| Mode | `type` | API Keys | IdP Sessions | Identity Headers | Use Case |
21+
| ------- | --------- | -------- | ------------ | ---------------- | ------------------------------ |
22+
| None | `none` | No | No | No | Local development |
23+
| API Key | `api_key` | Yes | No | No | Programmatic access only |
24+
| IdP | `idp` | Yes | Yes | No | Full deployment with SSO + API |
25+
| IAP | `iap` | Yes | No | Yes | Behind an identity-aware proxy |
2626

2727
### None
2828

@@ -985,6 +985,93 @@ admin_identities = ["alice@acme.com"]
985985
and organizations are not modified.
986986
</Callout>
987987

988+
### Bootstrap API Key Generation
989+
990+
Create an API key during bootstrap for programmatic access:
991+
992+
```toml
993+
[auth.bootstrap]
994+
api_key = "${HADRIAN_BOOTSTRAP_KEY}"
995+
auto_verify_domains = ["acme.com"]
996+
997+
[auth.bootstrap.initial_org]
998+
slug = "acme-corp"
999+
name = "Acme Corporation"
1000+
admin_identities = ["admin@acme.com"]
1001+
1002+
[auth.bootstrap.initial_api_key]
1003+
name = "production-api-key"
1004+
```
1005+
1006+
| Setting | Type | Description |
1007+
| ---------------------- | ------ | ------------------------------------------------------------- |
1008+
| `initial_api_key.name` | string | Name for the auto-created API key (scoped to the initial org) |
1009+
1010+
The generated API key is printed to stdout on first creation. Subsequent runs skip creation if a key with the same name already exists.
1011+
1012+
### Bootstrap SSO Configuration
1013+
1014+
Pre-configure SSO for the initial organization directly in the config file, avoiding manual Admin UI setup:
1015+
1016+
```toml
1017+
[auth.bootstrap.initial_org]
1018+
slug = "acme-corp"
1019+
name = "Acme Corporation"
1020+
admin_identities = ["admin@acme.com"]
1021+
1022+
[auth.bootstrap.initial_org.sso]
1023+
provider_type = "oidc"
1024+
issuer = "https://accounts.google.com"
1025+
client_id = "${OIDC_CLIENT_ID}"
1026+
client_secret = "${OIDC_CLIENT_SECRET}"
1027+
redirect_uri = "https://gateway.example.com/auth/callback"
1028+
discovery_url = "https://accounts.google.com/.well-known/openid-configuration"
1029+
allowed_email_domains = ["acme.com"]
1030+
```
1031+
1032+
| Setting | Type | Description |
1033+
| --------------------------- | -------- | ----------------------------------------------- |
1034+
| `sso.provider_type` | string | `"oidc"` or `"saml"` |
1035+
| `sso.issuer` | string | IdP issuer URL |
1036+
| `sso.client_id` | string | OAuth client ID |
1037+
| `sso.client_secret` | string | OAuth client secret (stored in secrets manager) |
1038+
| `sso.redirect_uri` | string | OAuth redirect URI |
1039+
| `sso.discovery_url` | string | OIDC discovery endpoint (optional) |
1040+
| `sso.allowed_email_domains` | string[] | Restrict SSO login to these email domains |
1041+
1042+
Domains listed in both `auto_verify_domains` and `sso.allowed_email_domains` are automatically verified during bootstrap.
1043+
1044+
### Bootstrap CLI
1045+
1046+
Run bootstrap as a standalone CLI command instead of at server startup. This is the recommended approach for GitOps and IaC workflows:
1047+
1048+
```bash
1049+
# Run bootstrap against the database
1050+
hadrian bootstrap --config hadrian.toml
1051+
1052+
# Preview what would be created without making changes
1053+
hadrian bootstrap --config hadrian.toml --dry-run
1054+
```
1055+
1056+
The `hadrian bootstrap` command:
1057+
1058+
- Connects directly to the database (no HTTP server started)
1059+
- Runs pending migrations before bootstrapping
1060+
- Creates the initial organization, SSO config, and API key as specified in `[auth.bootstrap]`
1061+
- Is fully idempotent — safe to run repeatedly (skips resources that already exist)
1062+
- Prints the generated API key to stdout on first creation (pipe to a secret manager or file)
1063+
1064+
```bash
1065+
# Example: capture the generated API key
1066+
API_KEY=$(hadrian bootstrap --config hadrian.toml 2>/dev/null)
1067+
echo "Generated key: $API_KEY"
1068+
```
1069+
1070+
<Callout type="info">
1071+
Use `--dry-run` to verify your bootstrap configuration before applying it to a production
1072+
database.
1073+
</Callout>
1074+
9881075
## Emergency Access Configuration
9891076

9901077
Emergency access provides break-glass admin access when SSO is unavailable. Unlike bootstrap mode, emergency access remains available indefinitely (when enabled) and is designed for disaster recovery scenarios.
@@ -1212,6 +1299,9 @@ admin_identities = ["admin@acme.com"]
12121299
slug = "acme-corp"
12131300
name = "Acme Corporation"
12141301
admin_identities = ["admin@acme.com"]
1302+
1303+
[auth.bootstrap.initial_api_key]
1304+
name = "admin-key"
12151305
```
12161306

12171307
### Multi-Org with Per-IdP API Authentication

0 commit comments

Comments
 (0)