Skip to content

Commit 4443ae7

Browse files
authored
fix(supervisor): allow withheld credentials at startup (#3438)
* fix(supervisor): allow withheld credentials at startup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(supervisor): accept withheld credentials on reload Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent 8b77925 commit 4443ae7

1 file changed

Lines changed: 120 additions & 2 deletions

File tree

  • crates/openshell-supervisor/src

‎crates/openshell-supervisor/src/lib.rs‎

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2670,7 +2670,7 @@ fn prepare_startup_configuration(
26702670
"Effective configuration admission rejected"
26712671
));
26722672
}
2673-
if provider.readiness_reason != ProviderReadinessReason::Unspecified {
2673+
if !provider_environment_is_installable(provider.readiness_reason) {
26742674
return Err(miette::miette!(
26752675
"Provider credentials are not ready for installation"
26762676
));
@@ -2686,6 +2686,18 @@ fn prepare_startup_configuration(
26862686
Ok((engine, process_policy, credentials))
26872687
}
26882688

2689+
/// Whether the provider response contains a complete environment snapshot that
2690+
/// can be installed. Withheld and expired credentials are intentionally absent,
2691+
/// so those responses remain valid fail-closed snapshots.
2692+
fn provider_environment_is_installable(reason: ProviderReadinessReason) -> bool {
2693+
matches!(
2694+
reason,
2695+
ProviderReadinessReason::Unspecified
2696+
| ProviderReadinessReason::CredentialsWithheld
2697+
| ProviderReadinessReason::CredentialExpired
2698+
)
2699+
}
2700+
26892701
fn prepare_provider_environment(
26902702
provider: &openshell_core::grpc_client::ProviderEnvironmentResult,
26912703
) -> Result<ProviderCredentialState> {
@@ -4296,7 +4308,7 @@ async fn run_policy_poll_loop_with_client<C: PolicyGatewayClient>(
42964308
{
42974309
Ok(provider)
42984310
if EnvironmentIdentity::from_environment(&provider) == desired_identity
4299-
&& provider.readiness_reason == ProviderReadinessReason::Unspecified =>
4311+
&& provider_environment_is_installable(provider.readiness_reason) =>
43004312
{
43014313
provider
43024314
}
@@ -5562,6 +5574,49 @@ network_policies:
55625574
assert_eq!(credentials.revision(), 10);
55635575
}
55645576

5577+
#[test]
5578+
fn startup_configuration_accepts_fail_closed_provider_environment() {
5579+
let policy = proto_policy_fixture();
5580+
let mut snapshot = settings_poll_result(
5581+
Some(policy.clone()),
5582+
1,
5583+
openshell_core::proto::PolicySource::Sandbox,
5584+
);
5585+
snapshot.provider_env_revision = 10;
5586+
5587+
for reason in [
5588+
ProviderReadinessReason::CredentialsWithheld,
5589+
ProviderReadinessReason::CredentialExpired,
5590+
] {
5591+
let mut provider = startup_provider(10);
5592+
provider.readiness_reason = reason;
5593+
provider
5594+
.environment
5595+
.insert("PROJECT_ID".to_string(), "example-project".to_string());
5596+
provider
5597+
.non_secret_environment_keys
5598+
.push("PROJECT_ID".to_string());
5599+
let (_, _, credentials) = prepare_startup_configuration(&snapshot, &policy, &provider)
5600+
.expect("withheld credentials preserve fail-closed startup");
5601+
assert_eq!(credentials.revision(), 10);
5602+
assert!(credentials.snapshot().child_env.contains_key("PROJECT_ID"));
5603+
}
5604+
}
5605+
5606+
#[test]
5607+
fn startup_configuration_rejects_provider_installation_failure() {
5608+
let policy = proto_policy_fixture();
5609+
let snapshot = settings_poll_result(
5610+
Some(policy.clone()),
5611+
1,
5612+
openshell_core::proto::PolicySource::Sandbox,
5613+
);
5614+
let mut provider = startup_provider(0);
5615+
provider.readiness_reason = ProviderReadinessReason::CredentialInstallFailed;
5616+
5617+
assert!(prepare_startup_configuration(&snapshot, &policy, &provider).is_err());
5618+
}
5619+
55655620
#[test]
55665621
fn startup_configuration_revalidates_on_restart_and_accepts_repair() {
55675622
let policy = proto_policy_fixture();
@@ -6153,6 +6208,69 @@ network_policies:
61536208
let _ = task.await;
61546209
}
61556210

6211+
#[tokio::test]
6212+
async fn provider_poll_installs_fail_closed_environment_and_acknowledges_policy() {
6213+
let policy = proto_policy_fixture();
6214+
let initial = settings_poll_result(
6215+
Some(policy.clone()),
6216+
1,
6217+
openshell_core::proto::PolicySource::Sandbox,
6218+
);
6219+
let engine = Arc::new(OpaEngine::from_proto(&policy).unwrap());
6220+
let ctx = policy_poll_test_context(
6221+
engine,
6222+
LoadedPolicyOrigin::Gateway {
6223+
revision: Some(LoadedPolicyRevision::from_snapshot(&initial)),
6224+
has_last_valid_policy: true,
6225+
},
6226+
default_middleware_connector(),
6227+
);
6228+
let credentials = ctx.provider_credentials.clone();
6229+
let (policy_gateway, polls, mut reports) = scripted_policy_gateway();
6230+
let (requests, mut received) = tokio::sync::mpsc::unbounded_channel();
6231+
polls.send(initial).unwrap();
6232+
let task = tokio::spawn(run_policy_poll_loop_with_client(
6233+
ctx,
6234+
ScriptedProviderGateway {
6235+
policy: policy_gateway,
6236+
requests,
6237+
},
6238+
));
6239+
expect_policy_report(&mut reports, 1).await;
6240+
6241+
let mut changed = settings_poll_result(
6242+
Some(policy),
6243+
2,
6244+
openshell_core::proto::PolicySource::Sandbox,
6245+
);
6246+
changed.provider_env_revision = 1;
6247+
polls.send(changed).unwrap();
6248+
let response = timeout(Duration::from_secs(1), received.recv())
6249+
.await
6250+
.expect("provider refresh requested")
6251+
.expect("poll loop active");
6252+
let mut withheld = static_provider_environment(1, None);
6253+
withheld.policy_hash = "hash-v2".to_string();
6254+
withheld.readiness_reason = ProviderReadinessReason::CredentialsWithheld;
6255+
withheld
6256+
.environment
6257+
.insert("PROJECT_ID".to_string(), "example-project".to_string());
6258+
withheld
6259+
.non_secret_environment_keys
6260+
.push("PROJECT_ID".to_string());
6261+
assert!(response.send(Ok(withheld)).is_ok());
6262+
6263+
expect_policy_report(&mut reports, 2).await;
6264+
assert_eq!(credentials.revision(), 1);
6265+
assert!(
6266+
credentials.snapshot().child_env.contains_key("PROJECT_ID"),
6267+
"the reduced snapshot retains non-secret provider configuration"
6268+
);
6269+
6270+
task.abort();
6271+
let _ = task.await;
6272+
}
6273+
61566274
#[tokio::test]
61576275
async fn provider_poll_preserves_static_references_across_rotation_failure_and_detach() {
61586276
let engine = Arc::new(OpaEngine::from_proto(&proto_policy_fixture()).unwrap());

0 commit comments

Comments
 (0)