use std::collections::{BTreeMap, HashSet};
use super::compile::{ProjectionError, RevisionProjection};
use super::tenancy::TenancyProjection;
use crate::config::{Config, Credential, ProjectIdentity, ProviderWire};
use crate::desired_state::credentials::{Credentials, ProviderCredential};
use crate::desired_state::providers::Providers;
use crate::desired_state::{DesiredState, RevisionId, SecretLifecycle, SecretOwner, WireFamily};
#[derive(Debug, Clone, Copy, Default)]
pub struct CredentialProjection;
#[derive(Debug, Clone, Copy, Default)]
pub struct RuntimeProjection;
impl RevisionProjection for RuntimeProjection {
fn name(&self) -> &'static str {
"runtime"
}
fn project(
&self,
bootstrap: &Config,
state: &DesiredState,
source: RevisionId,
) -> Result<Config, ProjectionError> {
let namespaces = TenancyProjection.project(bootstrap, state, source)?;
CredentialProjection.project(&namespaces, state, source)
}
}
impl RevisionProjection for CredentialProjection {
fn name(&self) -> &'static str {
"credentials"
}
fn project(
&self,
bootstrap: &Config,
state: &DesiredState,
_source: RevisionId,
) -> Result<Config, ProjectionError> {
let credentials = Credentials::of(state).map_err(|error| ProjectionError::Body {
reference: error.reference(),
detail: error.to_string(),
})?;
let providers = Providers::of(state).map_err(|error| ProjectionError::Body {
reference: error.reference(),
detail: error.to_string(),
})?;
let mut config = bootstrap.clone();
let namespaces = ProjectedNamespaces::index(bootstrap);
let (owned, inherited): (Vec<&ProviderCredential>, Vec<&ProviderCredential>) = credentials
.all()
.filter(|credential| claims(credential.body.lifecycle()))
.partition(|credential| credential.body.project().is_some());
let mut claimed: HashSet<(String, String)> = HashSet::new();
for credential in &owned {
let serves = serving(&namespaces, credential);
if serves.is_empty() {
continue;
}
let active = credential.body.lifecycle() == SecretLifecycle::Active;
let provider = match provider_id(&config, &providers, credential) {
Ok(provider) => provider,
Err(_) if !active => continue,
Err(error) => return Err(error),
};
for namespace in serves {
claimed.insert((namespace.to_owned(), provider.clone()));
if active {
config
.credential
.push(entry(namespace, &provider, credential));
}
}
}
for credential in &inherited {
if credential.body.lifecycle() != SecretLifecycle::Active {
continue;
}
let serves = serving(&namespaces, credential);
if serves.is_empty() {
continue;
}
let provider = provider_id(&config, &providers, credential)?;
for namespace in serves {
if claimed.contains(&(namespace.to_owned(), provider.clone())) {
continue;
}
config
.credential
.push(entry(namespace, &provider, credential));
}
}
Ok(config)
}
}
const fn claims(lifecycle: SecretLifecycle) -> bool {
match lifecycle {
SecretLifecycle::Active | SecretLifecycle::Disabled | SecretLifecycle::Revoked => true,
SecretLifecycle::Staged | SecretLifecycle::Tombstoned => false,
}
}
fn serving<'a>(
namespaces: &'a ProjectedNamespaces,
credential: &ProviderCredential,
) -> Vec<&'a str> {
let serves = namespaces.serving(credential.body.owner());
if serves.is_empty() {
tracing::debug!(
credential = %credential.reference,
"a credential whose owner has no projected namespace is not projected"
);
}
serves
}
fn entry(namespace: &str, provider: &str, credential: &ProviderCredential) -> Credential {
Credential {
namespace: namespace.to_owned(),
provider: provider.to_owned(),
env: None,
secret: Some(credential.body.secret()),
id: Some(credential.slug.as_str().to_owned()),
weight: 1,
}
}
fn provider_id(
config: &Config,
providers: &Providers,
credential: &ProviderCredential,
) -> Result<String, ProjectionError> {
let declared =
providers
.get(credential.body.provider())
.ok_or_else(|| ProjectionError::Incomplete {
detail: format!(
"{} authenticates to provider `{}`, which this revision does not declare",
credential.reference,
credential.body.provider()
),
})?;
let slug = declared.slug.as_str();
let Some(bootstrap) = config.provider.iter().find(|provider| provider.id == slug) else {
return Err(ProjectionError::Incomplete {
detail: format!(
"{} authenticates to provider `{slug}`, which this deployment does not declare: a \
provider's endpoint and wire family are still bootstrap-owned, so a credential \
for a provider no `[[provider]]` names could not dial anything",
credential.reference
),
});
};
let wire = wire(declared.body.wire_family());
if bootstrap.kind.wire() != wire {
return Err(ProjectionError::Incomplete {
detail: format!(
"{} authenticates to provider `{slug}`, which this revision speaks {} to while \
this deployment declares it as {}: a credential must not be presented in a wire \
family its account does not belong to",
credential.reference,
wire,
bootstrap.kind.wire()
),
});
}
Ok(slug.to_owned())
}
const fn wire(family: WireFamily) -> ProviderWire {
match family {
WireFamily::OpenaiChat => ProviderWire::Openai,
WireFamily::AnthropicMessages => ProviderWire::Anthropic,
}
}
struct ProjectedNamespaces {
by_identity: BTreeMap<ProjectIdentity, String>,
}
impl ProjectedNamespaces {
fn index(config: &Config) -> Self {
Self {
by_identity: config
.namespace
.iter()
.filter_map(|namespace| {
namespace
.project
.map(|identity| (identity, namespace.id.clone()))
})
.collect(),
}
}
fn serving(&self, owner: SecretOwner) -> Vec<&str> {
match owner.project {
Some(project) => self
.by_identity
.get(&ProjectIdentity {
tenant: owner.tenant,
project,
})
.map(String::as_str)
.into_iter()
.collect(),
None => self
.by_identity
.iter()
.filter(|(identity, _)| identity.tenant == owner.tenant)
.map(|(_, id)| id.as_str())
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::super::compile::testing::bootstrap;
use super::*;
use std::collections::HashMap;
use crate::credentials::Credentials as Pools;
use crate::desired_state::credentials::ProviderCredentialBody;
use crate::desired_state::fixtures;
use crate::desired_state::{
ProjectId, ProviderBody, ResourceVersion, SecretRef, Slug, TenantId, WireFamily,
};
const TENANT: u64 = 1;
const PROJECT: u64 = 2;
const CREDENTIAL: u64 = 3;
fn tenant() -> TenantId {
fixtures::tenant_id(TENANT)
}
fn project() -> ProjectId {
fixtures::project_id(PROJECT)
}
fn connection() -> ResourceVersion {
ProviderBody::for_tenant(
fixtures::provider_id(CREDENTIAL),
tenant(),
fixtures::display_name("OpenAI"),
WireFamily::OpenaiChat,
"https://api.openai.com/v1",
)
.version(Slug::parse("openai").expect("fixture slug"))
}
fn credential(
seed: u64,
slug: &str,
owner: SecretOwner,
secret: SecretRef,
lifecycle: SecretLifecycle,
) -> ResourceVersion {
let mut body = ProviderCredentialBody::staged(
fixtures::resource_id(seed),
owner,
fixtures::provider_id(CREDENTIAL),
fixtures::display_name(slug),
secret,
);
if lifecycle != SecretLifecycle::Staged {
body = walk(&body, lifecycle);
}
body.version(Slug::parse(slug).expect("fixture slug"))
}
fn walk(body: &ProviderCredentialBody, lifecycle: SecretLifecycle) -> ProviderCredentialBody {
let active = body
.transitioned(SecretLifecycle::Active)
.expect("staged material may be activated");
match lifecycle {
SecretLifecycle::Staged => body.clone(),
SecretLifecycle::Active => active,
SecretLifecycle::Tombstoned => active
.transitioned(SecretLifecycle::Revoked)
.and_then(|revoked| revoked.transitioned(SecretLifecycle::Tombstoned))
.expect("a revoked version may be tombstoned"),
other => active.transitioned(other).expect("a permitted transition"),
}
}
fn state(credentials: impl IntoIterator<Item = ResourceVersion>) -> DesiredState {
let mut state = DesiredState::new();
state
.insert(fixtures::tenant(TENANT, "acme"))
.and_then(|state| state.insert(fixtures::project(&tenant(), PROJECT, "core")))
.and_then(|state| state.insert(connection()))
.expect("a valid tenancy");
for credential in credentials {
state.insert(credential).expect("a distinct reference");
}
state
}
fn projected(state: &DesiredState) -> Vec<(String, String, String, Option<SecretRef>)> {
RuntimeProjection
.project(&bootstrap(), state, fixtures::revision_id(3))
.expect("a projectable revision")
.credential
.into_iter()
.map(|credential| {
let label = credential.label().to_owned();
(
credential.namespace,
credential.provider,
label,
credential.secret,
)
})
.collect()
}
#[test]
fn an_active_credential_becomes_the_pool_entry_its_namespace_leases_from() {
let entries = projected(&state([credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
)]));
assert_eq!(
entries,
[(
"acme/core".to_owned(),
"openai".to_owned(),
"primary".to_owned(),
Some(fixtures::secret_ref(CREDENTIAL)),
)],
"an active credential serves its project's namespace, pinned to its own version"
);
}
#[test]
fn staged_material_is_not_projected_onto_any_pool() {
assert_eq!(
projected(&state([credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Staged,
)])),
[],
);
}
#[test]
fn disabling_or_revoking_withdraws_a_credential_from_the_next_snapshot() {
for lifecycle in [
SecretLifecycle::Disabled,
SecretLifecycle::Revoked,
SecretLifecycle::Tombstoned,
] {
assert_eq!(
projected(&state([credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
lifecycle,
)])),
[],
"{lifecycle:?} material must not be projected onto a pool"
);
}
}
#[test]
fn a_rotation_moves_the_pool_to_the_successor_version() {
let versions = |secret| {
projected(&state([credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
secret,
SecretLifecycle::Active,
)]))
};
let before = versions(fixtures::secret_ref_at(CREDENTIAL, 1));
let after = versions(fixtures::secret_ref_at(CREDENTIAL, 2));
assert_eq!(before[0].3, Some(fixtures::secret_ref_at(CREDENTIAL, 1)));
assert_eq!(after[0].3, Some(fixtures::secret_ref_at(CREDENTIAL, 2)));
assert_eq!(
(&before[0].0, &before[0].1),
(&after[0].0, &after[0].1),
"a rotation changes the version a pool names, not which pool it is"
);
}
#[test]
fn two_active_credentials_become_two_keys_in_one_pool() {
let entries = projected(&state([
credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref_at(CREDENTIAL, 1),
SecretLifecycle::Active,
),
credential(
CREDENTIAL + 10,
"successor",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL + 10),
SecretLifecycle::Active,
),
]));
assert_eq!(
entries
.iter()
.map(|entry| (entry.2.as_str(), entry.3))
.collect::<Vec<_>>(),
[
("primary", Some(fixtures::secret_ref_at(CREDENTIAL, 1))),
("successor", Some(fixtures::secret_ref(CREDENTIAL + 10))),
],
);
}
#[test]
fn a_projects_own_credential_replaces_its_tenants_default() {
let mut state = state([
credential(
CREDENTIAL,
"tenant-wide",
SecretOwner::tenant(tenant()),
fixtures::secret_ref_at(CREDENTIAL, 1),
SecretLifecycle::Active,
),
credential(
CREDENTIAL + 10,
"project-own",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL + 10),
SecretLifecycle::Active,
),
]);
assert_eq!(
projected(&state)
.iter()
.map(|entry| (entry.0.clone(), entry.2.clone()))
.collect::<Vec<_>>(),
[("acme/core".to_owned(), "project-own".to_owned())],
);
state
.insert(fixtures::project(&tenant(), PROJECT + 20, "labs"))
.expect("a distinct reference");
assert_eq!(
projected(&state)
.iter()
.map(|entry| (entry.0.clone(), entry.2.clone()))
.collect::<Vec<_>>(),
[
("acme/core".to_owned(), "project-own".to_owned()),
("acme/labs".to_owned(), "tenant-wide".to_owned()),
],
);
}
#[test]
fn withdrawing_a_projects_own_credential_does_not_promote_its_tenants_default() {
let tenant_wide = || {
credential(
CREDENTIAL,
"tenant-wide",
SecretOwner::tenant(tenant()),
fixtures::secret_ref_at(CREDENTIAL, 1),
SecretLifecycle::Active,
)
};
let project_own = |lifecycle| {
credential(
CREDENTIAL + 10,
"project-own",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL + 10),
lifecycle,
)
};
for withdrawn in [SecretLifecycle::Disabled, SecretLifecycle::Revoked] {
assert_eq!(
projected(&state([tenant_wide(), project_own(withdrawn)])),
[],
"{withdrawn:?} must empty the pool, not fall back"
);
}
for withdrawn in [SecretLifecycle::Disabled, SecretLifecycle::Revoked] {
let body = ProviderCredentialBody::staged(
fixtures::resource_id(CREDENTIAL + 10),
SecretOwner::project(tenant(), project()),
fixtures::provider_id(CREDENTIAL),
fixtures::display_name("project-own"),
fixtures::secret_ref(CREDENTIAL + 10),
)
.transitioned(withdrawn)
.expect("staged material may be withdrawn without serving");
assert_eq!(
projected(&state([
tenant_wide(),
body.version(Slug::parse("project-own").expect("fixture slug")),
])),
[],
"a never-activated {withdrawn:?} key still holds its pair"
);
}
assert_eq!(
projected(&state([
tenant_wide(),
project_own(SecretLifecycle::Staged)
]))
.iter()
.map(|entry| (entry.0.clone(), entry.2.clone()))
.collect::<Vec<_>>(),
[("acme/core".to_owned(), "tenant-wide".to_owned())],
);
assert_eq!(
projected(&state([
tenant_wide(),
project_own(SecretLifecycle::Tombstoned)
]))
.iter()
.map(|entry| (entry.0.clone(), entry.2.clone()))
.collect::<Vec<_>>(),
[("acme/core".to_owned(), "tenant-wide".to_owned())],
);
}
#[test]
fn a_tenants_credential_never_lands_in_another_tenants_pool() {
let mut state = state([credential(
CREDENTIAL,
"acme-key",
SecretOwner::tenant(tenant()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
)]);
let other = fixtures::tenant_id(9);
state
.insert(fixtures::tenant(9, "globex"))
.and_then(|state| state.insert(fixtures::project(&other, 12, "core")))
.expect("a distinct tenant");
let namespaces: Vec<String> = projected(&state).into_iter().map(|entry| entry.0).collect();
assert_eq!(namespaces, ["acme/core"]);
}
#[test]
fn a_credential_whose_owner_serves_no_namespace_is_skipped_not_refused() {
let mut state = DesiredState::new();
state
.insert(fixtures::tenant(TENANT, "acme"))
.and_then(|state| state.insert(connection()))
.and_then(|state| {
state.insert(credential(
CREDENTIAL,
"primary",
SecretOwner::tenant(tenant()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
))
})
.expect("a valid revision");
assert_eq!(projected(&state), []);
}
#[test]
fn a_credential_serving_no_namespace_does_not_refuse_for_its_provider() {
let mut state = DesiredState::new();
let elsewhere = ProviderBody::for_tenant(
fixtures::provider_id(CREDENTIAL),
tenant(),
fixtures::display_name("Elsewhere"),
WireFamily::OpenaiChat,
"https://elsewhere.example/v1",
)
.version(Slug::parse("elsewhere").expect("fixture slug"));
state
.insert(fixtures::tenant(TENANT, "acme"))
.and_then(|state| state.insert(elsewhere))
.and_then(|state| {
state.insert(credential(
CREDENTIAL,
"primary",
SecretOwner::tenant(tenant()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
))
})
.expect("a valid revision");
assert_eq!(projected(&state), []);
}
#[test]
fn a_credential_for_a_provider_the_deployment_does_not_declare_refuses() {
let mut state = DesiredState::new();
let connection = ProviderBody::for_tenant(
fixtures::provider_id(CREDENTIAL),
tenant(),
fixtures::display_name("Elsewhere"),
WireFamily::OpenaiChat,
"https://elsewhere.example/v1",
)
.version(Slug::parse("elsewhere").expect("fixture slug"));
state
.insert(fixtures::tenant(TENANT, "acme"))
.and_then(|state| state.insert(fixtures::project(&tenant(), PROJECT, "core")))
.and_then(|state| state.insert(connection))
.and_then(|state| {
state.insert(credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
))
})
.expect("a valid revision");
let Err(error) = RuntimeProjection.project(&bootstrap(), &state, fixtures::revision_id(3))
else {
panic!("a credential for an undeclared provider must refuse the candidate");
};
assert!(
matches!(error, ProjectionError::Incomplete { .. }),
"{error:?}"
);
}
#[test]
fn a_credential_whose_provider_speaks_another_wire_family_refuses() {
let mut state = DesiredState::new();
let mismatched = ProviderBody::for_tenant(
fixtures::provider_id(CREDENTIAL),
tenant(),
fixtures::display_name("OpenAI"),
WireFamily::AnthropicMessages,
"https://api.openai.com/v1",
)
.version(Slug::parse("openai").expect("fixture slug"));
state
.insert(fixtures::tenant(TENANT, "acme"))
.and_then(|state| state.insert(fixtures::project(&tenant(), PROJECT, "core")))
.and_then(|state| state.insert(mismatched))
.and_then(|state| {
state.insert(credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
))
})
.expect("a valid revision");
let Err(error) = RuntimeProjection.project(&bootstrap(), &state, fixtures::revision_id(3))
else {
panic!("a wire family mismatch must refuse the candidate");
};
assert!(
matches!(error, ProjectionError::Incomplete { .. }),
"{error:?}"
);
}
#[test]
fn a_pool_entry_whose_version_was_not_resolved_refuses_to_build() {
let config = RuntimeProjection
.project(
&bootstrap(),
&state([credential(
CREDENTIAL,
"primary",
SecretOwner::project(tenant(), project()),
fixtures::secret_ref(CREDENTIAL),
SecretLifecycle::Active,
)]),
fixtures::revision_id(3),
)
.expect("a projectable revision");
let Err(error) = Pools::resolve(
&config,
&HashMap::new(),
&crate::convergence::secrets::ResolvedSecrets::default(),
) else {
panic!("an unresolved version must not yield a pool");
};
let rendered = error.to_string();
assert!(
rendered.contains(&fixtures::secret_ref(CREDENTIAL).to_string()),
"the refusal names the version by reference: {rendered}"
);
}
}