Skip to content

Commit 6d11471

Browse files
committed
Scope principal delegation by destination
Let admins grant principal signing keys destination delegation authority without binding grants to individual agent identities.
1 parent 3ec4819 commit 6d11471

5 files changed

Lines changed: 12 additions & 22 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ The gateway authorizes a CONNECT only when all of these checks pass:
119119
2. The requested authority normalizes to a `host:port` destination.
120120
3. Postgres contains an active `permission_registry` row for that identity and destination.
121121
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.
122+
5. `principal_key_permissions` confirms that the signing key was allowed to delegate that destination.
123123

124124
### Database Structure
125125

@@ -128,10 +128,10 @@ The authorization registry has three main tables:
128128
| Table | Key Columns | Purpose |
129129
|---|---|---|
130130
| `principal_signing_keys` | `key_id`, `algorithm`, `public_key_spki_der`, `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. |
131+
| `principal_key_permissions` | `signing_key_id`, `destination`, `not_before`, `not_after`, `revoked_at` | Defines which destinations each signing key is allowed to delegate. |
132132
| `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. |
133133

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.
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 destination delegation row.
135135

136136
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:
137137

migrations/0001_signed_authorization_registry.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ CREATE TABLE principal_signing_keys (
2121
CREATE TABLE principal_key_permissions (
2222
id BIGSERIAL PRIMARY KEY,
2323
signing_key_id TEXT NOT NULL REFERENCES principal_signing_keys(key_id),
24-
subject_identity TEXT NOT NULL CHECK (subject_identity <> ''),
2524
destination TEXT NOT NULL CHECK (destination <> ''),
2625
not_before TIMESTAMPTZ NOT NULL,
2726
not_after TIMESTAMPTZ NOT NULL,
@@ -51,5 +50,5 @@ CREATE INDEX permission_registry_active_lookup_idx
5150
WHERE revoked_at IS NULL;
5251

5352
CREATE INDEX principal_key_permissions_active_scope_idx
54-
ON principal_key_permissions (signing_key_id, subject_identity, destination)
53+
ON principal_key_permissions (signing_key_id, destination)
5554
WHERE revoked_at IS NULL;

src/policy.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,7 @@ impl PolicyEngine for PostgresPolicyEngine {
151151

152152
let mut last_denial = None;
153153
for candidate in candidates {
154-
match self
155-
.evaluate_candidate(&candidate, &source_identity, &normalized_dest)
156-
.await
157-
{
154+
match self.evaluate_candidate(&candidate, &normalized_dest).await {
158155
Ok(()) => return PolicyDecision::Allow { source_identity },
159156
Err(reason) => last_denial = Some(reason),
160157
}
@@ -176,7 +173,6 @@ impl PostgresPolicyEngine {
176173
async fn evaluate_candidate(
177174
&self,
178175
candidate: &CandidatePermission,
179-
source_identity: &str,
180176
normalized_dest: &str,
181177
) -> Result<(), String> {
182178
if !candidate.signer_active_now {
@@ -200,7 +196,6 @@ impl PostgresPolicyEngine {
200196
.registry
201197
.signer_has_scope(
202198
&candidate.signing_key_id,
203-
source_identity,
204199
normalized_dest,
205200
candidate.permission_not_before,
206201
candidate.permission_not_after,
@@ -209,8 +204,8 @@ impl PostgresPolicyEngine {
209204
.map_err(|e| format!("signer scope lookup failed: {e:#}"))?;
210205
if !has_scope {
211206
return Err(format!(
212-
"signing key {:?} is not allowed to delegate {:?} to {:?}",
213-
candidate.signing_key_id, normalized_dest, source_identity
207+
"signing key {:?} is not allowed to delegate {:?}",
208+
candidate.signing_key_id, normalized_dest
214209
));
215210
}
216211

src/registry.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ impl RegistryStore {
109109
pub async fn signer_has_scope(
110110
&self,
111111
signing_key_id: &str,
112-
subject_identity: &str,
113112
destination: &str,
114113
permission_not_before: DateTime<Utc>,
115114
permission_not_after: DateTime<Utc>,
@@ -120,18 +119,16 @@ impl RegistryStore {
120119
SELECT 1
121120
FROM principal_key_permissions
122121
WHERE signing_key_id = $1
123-
AND subject_identity = $2
124-
AND destination = $3
122+
AND destination = $2
125123
AND revoked_at IS NULL
126124
AND not_before <= now()
127125
AND not_after > now()
128-
AND not_before <= $4
129-
AND not_after >= $5
126+
AND not_before <= $3
127+
AND not_after >= $4
130128
)
131129
"#,
132130
)
133131
.bind(signing_key_id)
134-
.bind(subject_identity)
135132
.bind(destination)
136133
.bind(permission_not_before)
137134
.bind(permission_not_after);

tests/common/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,12 @@ impl TestAuthzRegistry {
472472
sqlx::query(
473473
r#"
474474
INSERT INTO principal_key_permissions (
475-
signing_key_id, subject_identity, destination, not_before, not_after
475+
signing_key_id, destination, not_before, not_after
476476
)
477-
VALUES ($1, $2, $3, $4, $5)
477+
VALUES ($1, $2, $3, $4)
478478
"#,
479479
)
480480
.bind(&self.key_id)
481-
.bind(subject_identity)
482481
.bind(&normalized_destination)
483482
.bind(not_before)
484483
.bind(not_after)

0 commit comments

Comments
 (0)