Preserve authoritative execution grant aggregates

This commit is contained in:
ms2k 2026-07-19 02:16:11 +09:00
commit 8a4999344d
2 changed files with 84 additions and 52 deletions

View file

@ -576,22 +576,6 @@ impl CredentialRegistry {
Ok(remora_domain::ports::ActiveCredentialGrant::for_execution_grant(grant_id.clone()))
}
/// Idempotently revoke an execution grant's credential access.
pub async fn revoke_execution_grant(&self, grant_id: &OpaqueId) -> Result<(), CredentialError> {
let now = OffsetDateTime::now_utc();
sqlx::query(
"UPDATE execution_grant_credential_pins
SET status = 'revoked', revoked_at = COALESCE(revoked_at, $2)
WHERE grant_id = $1",
)
.bind(grant_id.as_str())
.bind(now)
.execute(&self.pool)
.await
.map_err(DurableError::from)?;
Ok(())
}
/// Load the durable pin for an active execution grant (fresh activity check).
pub async fn load_active_grant_pin(
&self,

View file

@ -103,12 +103,55 @@ fn token_material(value: &str) -> SecretMaterial {
}
async fn bind_grant(
pool: &PgPool,
registry: &CredentialRegistry,
label: &str,
pin: &CredentialGrantVersion,
) -> ActiveCredentialGrant {
let grant_id = grant_id(label);
let job_id = format!("job-{label}-aaaaaaaa");
let sensitive_source_ref = format!("source-{label}-aaaaaaaa");
let mut tx = pool.begin().await.expect("begin authoritative grant");
sqlx::query(
"INSERT INTO sensitive_source_inputs (sensitive_source_ref, source_input, created_at)
VALUES ($1, '{}'::jsonb, now())",
)
.bind(&sensitive_source_ref)
.execute(&mut *tx)
.await
.expect("insert sensitive source");
sqlx::query(
"INSERT INTO execution_grants (
grant_id, owner_id, target_store, target_bucket, target_prefix,
policy_version, credential_id, credential_version_id, status, created_at
)
VALUES ($1, 'owner-fixture-aaaaaaaa', 'fixture', 'fixture', '[]'::jsonb,
'fixture', $2, $3, 'active', now())",
)
.bind(grant_id.as_str())
.bind(pin.credential_id.as_str())
.bind(pin.version_id.as_str())
.execute(&mut *tx)
.await
.expect("insert authoritative grant");
sqlx::query(
"INSERT INTO jobs (
job_id, owner_id, phase, lifecycle, source_projection, sensitive_source_ref,
target_store, target_bucket, target_prefix, execution_grant_id,
dispatch_needed, created_at, updated_at
)
VALUES ($1, 'owner-fixture-aaaaaaaa', 'queued', 'visible', '{}'::jsonb, $2,
'fixture', 'fixture', '[]'::jsonb, $3, false, now(), now())",
)
.bind(job_id)
.bind(sensitive_source_ref)
.bind(grant_id.as_str())
.execute(&mut *tx)
.await
.expect("insert authoritative job");
tx.commit().await.expect("commit authoritative grant");
registry
.bind_execution_grant(&grant_id(label), pin)
.bind_execution_grant(&grant_id, pin)
.await
.expect("bind grant")
}
@ -182,7 +225,7 @@ async fn read_http_message(stream: &mut TcpStream) -> io::Result<Vec<u8>> {
#[tokio::test]
async fn create_pin_and_exact_version_resolution() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("exact1");
@ -200,7 +243,7 @@ async fn create_pin_and_exact_version_resolution() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin");
let access = bind_grant(&registry, "exact1", &pin).await;
let access = bind_grant(&pool, &registry, "exact1", &pin).await;
let secret = resolver
.resolve_access(&access)
@ -259,7 +302,7 @@ async fn vault_create_only_write_rejects_an_occupied_intent_path() {
#[tokio::test]
async fn rotation_does_not_change_prior_pin_and_rejects_substitution() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("rotate");
@ -277,7 +320,7 @@ async fn rotation_does_not_change_prior_pin_and_rejects_substitution() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("first pin");
let first_access = bind_grant(&registry, "rotate-a", &first_pin).await;
let first_access = bind_grant(&pool, &registry, "rotate-a", &first_pin).await;
registry
.rotate(
@ -291,7 +334,7 @@ async fn rotation_does_not_change_prior_pin_and_rejects_substitution() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("second pin");
let second_access = bind_grant(&registry, "rotate-b", &second_pin).await;
let second_access = bind_grant(&pool, &registry, "rotate-b", &second_pin).await;
assert_eq!(
resolver.resolve_access(&first_access).await.expect("first"),
@ -328,7 +371,7 @@ async fn unbound_grant_handle_cannot_resolve() {
#[tokio::test]
async fn execution_grant_revocation_fails_closed_on_retained_handle() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("grev01");
@ -347,36 +390,44 @@ async fn execution_grant_revocation_fails_closed_on_retained_handle() {
.await
.expect("pin");
let gid = grant_id("grev01");
let access = registry
.bind_execution_grant(&gid, &pin)
.await
.expect("bind");
let access = bind_grant(&pool, &registry, "grev01", &pin).await;
resolver
.resolve_access(&access)
.await
.expect("resolve while active");
registry
.revoke_execution_grant(&gid)
let mut tx = pool.begin().await.expect("begin revocation");
sqlx::query(
"UPDATE execution_grants
SET status = 'revoked', revoked_at = now()
WHERE grant_id = $1",
)
.bind(gid.as_str())
.execute(&mut *tx)
.await
.expect("revoke grant");
.expect("revoke authoritative grant");
sqlx::query(
"UPDATE execution_grant_credential_pins
SET status = 'revoked', revoked_at = now()
WHERE grant_id = $1",
)
.bind(gid.as_str())
.execute(&mut *tx)
.await
.expect("revoke grant pin");
tx.commit().await.expect("commit revocation");
// Same retained handle must fail after durable revocation.
assert!(matches!(
resolver.resolve_access(&access).await,
Err(remora_credentials::CredentialError::GrantInactive)
));
// Idempotent revoke.
registry
.revoke_execution_grant(&gid)
.await
.expect("idempotent");
}
#[tokio::test]
async fn credential_revocation_blocks_new_pins() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("revoke");
@ -394,7 +445,7 @@ async fn credential_revocation_blocks_new_pins() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin before revoke");
let access = bind_grant(&registry, "crevoke", &pin).await;
let access = bind_grant(&pool, &registry, "crevoke", &pin).await;
registry
.revoke(&caller(&owner), &created.id)
@ -479,7 +530,7 @@ async fn pin_and_revoke_serialize_under_lock() {
#[tokio::test]
async fn destroyed_version_fails_closed() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("destroy");
@ -497,7 +548,7 @@ async fn destroyed_version_fails_closed() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin");
let access = bind_grant(&registry, "destroy", &pin).await;
let access = bind_grant(&pool, &registry, "destroy", &pin).await;
registry
.destroy_version(&caller(&owner), &pin.credential_id, &pin.version_id)
@ -518,7 +569,7 @@ async fn destroyed_version_fails_closed() {
#[tokio::test]
async fn pin_after_destroying_prior_version_uses_new_active() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("pindest");
@ -536,7 +587,7 @@ async fn pin_after_destroying_prior_version_uses_new_active() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("first pin");
let first_access = bind_grant(&registry, "pindest-a", &first_pin).await;
let first_access = bind_grant(&pool, &registry, "pindest-a", &first_pin).await;
registry
.rotate(
@ -550,7 +601,7 @@ async fn pin_after_destroying_prior_version_uses_new_active() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("second pin");
let second_access = bind_grant(&registry, "pindest-b", &second_pin).await;
let second_access = bind_grant(&pool, &registry, "pindest-b", &second_pin).await;
registry
.destroy_version(
@ -594,10 +645,7 @@ async fn secret_store_outage_on_resolve_fails_closed() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin");
let access = good_registry
.bind_execution_grant(&grant_id("outage2"), &pin)
.await
.expect("bind");
let access = bind_grant(&pool, &good_registry, "outage2", &pin).await;
let bad_vault = VaultClient::new(VaultConfig {
address: "http://127.0.0.1:1".into(),
@ -682,7 +730,7 @@ async fn concurrent_rotates_all_succeed_and_prior_pin_holds() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("first pin");
let first_access = bind_grant(&registry, "conc01", &first_pin).await;
let first_access = bind_grant(&pool, &registry, "conc01", &first_pin).await;
let registry = Arc::new(registry);
let barrier = Arc::new(Barrier::new(4));
@ -736,7 +784,7 @@ async fn concurrent_rotates_all_succeed_and_prior_pin_holds() {
#[tokio::test]
async fn domain_port_resolve_requires_durable_grant() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let owner = owner("port01");
@ -754,7 +802,7 @@ async fn domain_port_resolve_requires_durable_grant() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin");
let access = bind_grant(&registry, "port01", &pin).await;
let access = bind_grant(&pool, &registry, "port01", &pin).await;
let via_port = CredentialResolver::resolve(&resolver, &access)
.await
.expect("port resolve");
@ -1306,7 +1354,7 @@ async fn version_coordinate_migration_backfills_populated_schema() {
#[tokio::test]
async fn transcript_does_not_contain_secret_literals() {
let Some((_, registry, resolver)) = harness().await else {
let Some((pool, registry, resolver)) = harness().await else {
return;
};
let marker = "unique-secret-marker-9f3c2a1b";
@ -1325,7 +1373,7 @@ async fn transcript_does_not_contain_secret_literals() {
.pin_current_version(&caller(&owner), &created.id)
.await
.expect("pin");
let access = bind_grant(&registry, "redact", &pin).await;
let access = bind_grant(&pool, &registry, "redact", &pin).await;
let secret = resolver.resolve_access(&access).await.expect("resolve");
let transcript = format!("{created:?}{secret:?}");
assert!(!transcript.contains(marker));