Preserve authoritative execution grant aggregates
This commit is contained in:
parent
0e351c3640
commit
8a4999344d
1 changed files with 84 additions and 52 deletions
|
|
@ -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(®istry, "exact1", &pin).await;
|
||||
let access = bind_grant(&pool, ®istry, "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(®istry, "rotate-a", &first_pin).await;
|
||||
let first_access = bind_grant(&pool, ®istry, "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(®istry, "rotate-b", &second_pin).await;
|
||||
let second_access = bind_grant(&pool, ®istry, "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, ®istry, "grev01", &pin).await;
|
||||
|
||||
resolver
|
||||
.resolve_access(&access)
|
||||
.await
|
||||
.expect("resolve while active");
|
||||
|
||||
registry
|
||||
.revoke_execution_grant(&gid)
|
||||
.await
|
||||
.expect("revoke grant");
|
||||
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 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(®istry, "crevoke", &pin).await;
|
||||
let access = bind_grant(&pool, ®istry, "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(®istry, "destroy", &pin).await;
|
||||
let access = bind_grant(&pool, ®istry, "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(®istry, "pindest-a", &first_pin).await;
|
||||
let first_access = bind_grant(&pool, ®istry, "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(®istry, "pindest-b", &second_pin).await;
|
||||
let second_access = bind_grant(&pool, ®istry, "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(®istry, "conc01", &first_pin).await;
|
||||
let first_access = bind_grant(&pool, ®istry, "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(®istry, "port01", &pin).await;
|
||||
let access = bind_grant(&pool, ®istry, "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(®istry, "redact", &pin).await;
|
||||
let access = bind_grant(&pool, ®istry, "redact", &pin).await;
|
||||
let secret = resolver.resolve_access(&access).await.expect("resolve");
|
||||
let transcript = format!("{created:?}{secret:?}");
|
||||
assert!(!transcript.contains(marker));
|
||||
|
|
|
|||
Loading…
Reference in a new issue