You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+68-8Lines changed: 68 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
An mTLS HTTP/2 CONNECT proxy that authorizes connections based on custom X.509 certificate extensions.
4
4
5
-
The proxy accepts incoming mTLS connections, extracts a custom extension value from the client certificate, and checks it against a TOML policy file to decide whether the client may connect to the requested destination. If allowed, it opens a raw TCP connection to the destination and tunnels data bidirectionally. The client is responsible for establishing its own TLS session to the destination through the tunnel.
5
+
The proxy accepts incoming mTLS connections, extracts a custom extension value from the client certificate, and checks a PostgreSQL-backed signed permission registry to decide whether the client may connect to the requested destination. If allowed, it opens a raw TCP connection to the destination and tunnels data bidirectionally. The client is responsible for establishing its own TLS session to the destination through the tunnel.
`generate-certs.sh` only creates **server** TLS material (`server-ca.pem`, `server.pem`, ...). Each agent platform enrolls with `./examples/connect.sh`, which starts a local `swtpm`, creates a persistent P-256 signing key in that simulated TPM, and issues `machine-client.pem` for the TPM public key. The gateway needs the generated `machine-client-ca.pem` in `certs/client-ca-bundle.pem` before it can trust that sidecar.
@@ -30,11 +33,12 @@ Typical first-time flow:
30
33
31
34
1.`./examples/generate-certs.sh` and `cp config.example.toml config.toml`.
32
35
2.`./examples/connect.sh --gateway 127.0.0.1:8443 --gateway-ca certs/server-ca.pem` creates `machine-client-ca.pem` under `~/.local/share/agent-gateway/` (or `$XDG_DATA_HOME`). Append that file to `client_ca_path` (for example, `cat ~/.local/share/agent-gateway/machine-client-ca.pem >> certs/client-ca-bundle.pem`).
33
-
3. Start the gateway (`cargo run -- --config config.toml`), then return to the terminal running `connect.sh` and press Enter to start the sidecar and Claude.
36
+
3. Insert a trusted principal signing key, its delegation scope, and signed permission rows into Postgres.
37
+
4. Start the gateway (`cargo run -- --config config.toml`), then return to the terminal running `connect.sh` and press Enter to start the sidecar and Claude.
34
38
35
39
On later runs, start the gateway first, run `connect.sh`, and press Enter after confirming the machine CA is still registered. If `connect.sh` finds an existing simulated TPM key but the saved `machine-client.pem` was issued for a different public key, it reissues `machine-client.pem` for the current TPM key using the existing machine client CA. `--regenerate-certs` creates a fresh simulated TPM state and machine client CA, so the new CA must be appended to `client_ca_path`.
36
40
37
-
Pass a custom policy extension value: `connect.sh ... --extension-value agent-beta`.
41
+
Pass a custom policy extension value: `connect.sh ... --extension-value agent-beta`. The extension value must match `permission_registry.subject_identity` in an active signed permission row.
38
42
39
43
The simulated TPM state lives under `~/.local/share/agent-gateway/swtpm/` unless
40
44
`XDG_DATA_HOME` is set. By default, the sidecar uses TCTI
@@ -56,13 +60,17 @@ Copy `config.example.toml` to `config.toml` and edit it. Key sections:
56
60
|`tls_key_path`| yes | PEM private key for the server cert |
57
61
|`client_ca_path`| yes | PEM bundle of per-machine client CAs (append each `machine-client-ca.pem`) |
58
62
59
-
**`[policy]`** -- Maps certificate extension values to allowed destinations.
63
+
**`[policy]`** -- Configures the certificate identity extension and Postgres registry access.
60
64
61
65
| Field | Description |
62
66
|---|---|
63
67
|`client_ext_oid`| OID of the custom X.509 extension to extract (dotted notation) |
64
-
|`rules[].extension_value`| Value to match in the extension |
65
-
|`rules[].allowed_destinations`| List of `host` or `host:port` entries. Port defaults to `443` if omitted. |
68
+
|`database_url`| Postgres URL for the authorization registry. Prefer `database_url_env` outside local development. |
69
+
|`database_url_env`| Environment variable containing the Postgres URL. |
70
+
|`max_connections`| Maximum Postgres pool connections. Defaults to `5`. |
71
+
|`connect_timeout_ms`| Postgres connection timeout. Defaults to `5000`. |
72
+
|`pool_acquire_timeout_ms`| Pool acquire timeout per authorization check. Defaults to `1000`. |
73
+
|`query_timeout_ms`| Query timeout per registry lookup. Defaults to `500`. |
66
74
67
75
**`[observability]`** -- Logging and tracing.
68
76
@@ -85,8 +93,60 @@ continues the same trace.
85
93
agent_gateway --config config.toml
86
94
```
87
95
96
+
Run migrations explicitly before starting the gateway:
97
+
98
+
```bash
99
+
agent_gateway --config config.toml migrate
100
+
```
101
+
102
+
Gateway startup verifies the authorization registry schema version and fails fast if the database is not migrated. The runtime gateway database role should be read-only for authorization tables; use a separate admin role for migrations and registry writes.
103
+
88
104
Shut down cleanly with `Ctrl-C`.
89
105
106
+
Register a demo principal signing key with:
107
+
108
+
```bash
109
+
./examples/register-principal-key.sh org-alice
110
+
```
111
+
112
+
The script creates a P-256 private key under `certs/principals/`, stores the public key in `principal_signing_keys`, and uses the friendly `key_id` (`org-alice`, `org-bob`, etc.) for the registry row.
113
+
114
+
## Authorization Registry
115
+
116
+
The gateway authorizes a CONNECT only when all of these checks pass:
117
+
118
+
1. The mTLS client certificate has the configured UTF8String identity extension.
119
+
2. The requested authority normalizes to a `host:port` destination.
120
+
3. Postgres contains an active `permission_registry` row for that identity and destination.
121
+
4. The row's `signature` verifies over the canonical permission row fields with the referenced active principal signing key.
122
+
5.`principal_key_permissions` confirms that the signing key was allowed to delegate that identity/destination scope.
123
+
124
+
### Database Structure
125
+
126
+
The authorization registry has three main tables:
127
+
128
+
| Table | Key Columns | Purpose |
129
+
|---|---|---|
130
+
|`principal_signing_keys`|`key_id`, `algorithm`, `public_key_spki_der`, `pubkey_sha256`, `not_before`, `not_after`, `revoked_at`| Stores trusted P-256 public keys that may sign permissions. |
131
+
|`principal_key_permissions`|`signing_key_id`, `subject_identity`, `destination`, `not_before`, `not_after`, `revoked_at`| Defines what each signing key is allowed to delegate. |
132
+
|`permission_registry`|`permission_id`, `signing_key_id`, `subject_identity`, `destination`, `not_before`, `not_after`, `revoked_at`, `signature`| Stores signed permissions that authorize a subject identity to reach a normalized destination. |
133
+
134
+
`principal_key_permissions.signing_key_id` and `permission_registry.signing_key_id` both reference `principal_signing_keys.key_id`. A permission is usable only when the permission row is active, the signing key is active, the signature verifies over the canonical row fields, and the signing key has a matching delegation scope row.
135
+
136
+
The signed bytes are the following UTF-8 text, with fields in this exact order and timestamps formatted as UTC RFC 3339 with six fractional digits:
137
+
138
+
```text
139
+
agent-gateway-permission-v1
140
+
permission_id=perm-1
141
+
signing_key_id=org-alice
142
+
subject_identity=agent-alpha
143
+
destination=api.example.com:443
144
+
not_before=2026-05-01T00:00:00.000000Z
145
+
not_after=2026-06-01T00:00:00.000000Z
146
+
```
147
+
148
+
Destination strings are normalized with the same rules used for CONNECT requests: hostnames are lowercased, omitted ports default to `443`, and IPv6 destinations use bracketed `host:port` form.
149
+
90
150
## Client requirements
91
151
92
152
Clients must:
@@ -116,7 +176,7 @@ Clients must:
116
176
117
177
### Client certificate extension
118
178
119
-
The extension value is matched exactly (case-sensitive) against policy rules. The extension must be an X.509 extension at the configured OID containing a single DER-encoded ASN.1 UTF8String.
179
+
The extension value is matched exactly (case-sensitive) against signed permission rows. The extension must be an X.509 extension at the configured OID containing a single DER-encoded ASN.1 UTF8String.
Database-backed policy and e2e tests require `TEST_DATABASE_URL` to point at a Postgres database that the test process can migrate and write to. The tests cover policy evaluation, signed-permission verification, signer delegation scope enforcement, destination normalization, config validation, TLS PKI generation, and proxy request parsing.
0 commit comments