use crate::CredentialScope;
use bucketwarden_s3::sigv4::AwsCredentials;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AccessKey {
pub access_key_id: String,
pub principal_id: String,
secret_access_key: String,
#[serde(default)]
pub previous_access_key_id: Option<String>,
#[serde(default)]
pub rotated_at_epoch_seconds: Option<u64>,
#[serde(default)]
pub leaked_at_epoch_seconds: Option<u64>,
pub enabled: bool,
pub expires_at_epoch_seconds: Option<u64>,
pub revoked_at_epoch_seconds: Option<u64>,
pub last_used_epoch_seconds: Option<u64>,
}
impl AccessKey {
pub fn active(
principal_id: impl Into<String>,
access_key_id: impl Into<String>,
secret_access_key: impl Into<String>,
) -> Self {
Self {
access_key_id: access_key_id.into(),
principal_id: principal_id.into(),
secret_access_key: secret_access_key.into(),
previous_access_key_id: None,
rotated_at_epoch_seconds: None,
leaked_at_epoch_seconds: None,
enabled: true,
expires_at_epoch_seconds: None,
revoked_at_epoch_seconds: None,
last_used_epoch_seconds: None,
}
}
pub fn with_expiry(mut self, expires_at_epoch_seconds: u64) -> Self {
self.expires_at_epoch_seconds = Some(expires_at_epoch_seconds);
self
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn revoke(&mut self, revoked_at_epoch_seconds: u64) {
self.enabled = false;
self.revoked_at_epoch_seconds = Some(revoked_at_epoch_seconds);
}
pub fn credentials(&self) -> AwsCredentials {
AwsCredentials::new(self.access_key_id.clone(), self.secret_access_key.clone())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CredentialRotation {
pub old_access_key_id: String,
pub new_access_key_id: String,
pub principal_id: String,
pub rotated_at_epoch_seconds: u64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct LeakedKeyResponse {
pub access_key_id: String,
pub principal_id: String,
pub leaked_at_epoch_seconds: u64,
pub revoked: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MtlsClientCertificate {
pub certificate_fingerprint: String,
pub principal_id: String,
pub enabled: bool,
pub expires_at_epoch_seconds: Option<u64>,
pub revoked_at_epoch_seconds: Option<u64>,
pub last_used_epoch_seconds: Option<u64>,
}
impl MtlsClientCertificate {
pub fn active(
principal_id: impl Into<String>,
certificate_fingerprint: impl Into<String>,
) -> Self {
Self {
certificate_fingerprint: certificate_fingerprint.into(),
principal_id: principal_id.into(),
enabled: true,
expires_at_epoch_seconds: None,
revoked_at_epoch_seconds: None,
last_used_epoch_seconds: None,
}
}
pub fn with_expiry(mut self, expires_at_epoch_seconds: u64) -> Self {
self.expires_at_epoch_seconds = Some(expires_at_epoch_seconds);
self
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn revoke(&mut self, revoked_at_epoch_seconds: u64) {
self.enabled = false;
self.revoked_at_epoch_seconds = Some(revoked_at_epoch_seconds);
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SessionCredential {
pub access_key_id: String,
pub principal_id: String,
pub parent_access_key_id: String,
secret_access_key: String,
session_token: String,
pub expires_at_epoch_seconds: u64,
#[serde(default)]
pub scope: Option<CredentialScope>,
pub revoked_at_epoch_seconds: Option<u64>,
pub last_used_epoch_seconds: Option<u64>,
}
impl SessionCredential {
pub fn new(
principal_id: impl Into<String>,
parent_access_key_id: impl Into<String>,
access_key_id: impl Into<String>,
secret_access_key: impl Into<String>,
session_token: impl Into<String>,
expires_at_epoch_seconds: u64,
) -> Self {
Self {
access_key_id: access_key_id.into(),
principal_id: principal_id.into(),
parent_access_key_id: parent_access_key_id.into(),
secret_access_key: secret_access_key.into(),
session_token: session_token.into(),
expires_at_epoch_seconds,
scope: None,
revoked_at_epoch_seconds: None,
last_used_epoch_seconds: None,
}
}
pub fn with_scope(mut self, scope: CredentialScope) -> Self {
self.scope = Some(scope);
self
}
pub fn revoke(&mut self, revoked_at_epoch_seconds: u64) {
self.revoked_at_epoch_seconds = Some(revoked_at_epoch_seconds);
}
pub fn credentials(&self) -> AwsCredentials {
AwsCredentials::session(
self.access_key_id.clone(),
self.secret_access_key.clone(),
self.session_token.clone(),
)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum CredentialRecord {
AccessKey(AccessKey),
Session(SessionCredential),
}
impl CredentialRecord {
pub fn principal_id(&self) -> &str {
match self {
Self::AccessKey(key) => &key.principal_id,
Self::Session(session) => &session.principal_id,
}
}
pub fn access_key_id(&self) -> &str {
match self {
Self::AccessKey(key) => &key.access_key_id,
Self::Session(session) => &session.access_key_id,
}
}
pub fn credentials(&self) -> AwsCredentials {
match self {
Self::AccessKey(key) => key.credentials(),
Self::Session(session) => session.credentials(),
}
}
pub fn last_used_epoch_seconds(&self) -> Option<u64> {
match self {
Self::AccessKey(key) => key.last_used_epoch_seconds,
Self::Session(session) => session.last_used_epoch_seconds,
}
}
}
pub const CREDENTIAL_RUNTIME_FEATURES: &[&str] = &[
"access-key-secret",
"sigv4",
"temporary-credentials",
"session-tokens",
"mtls-client-certificates",
"jwt",
"service-accounts",
];
pub const CREDENTIAL_ADMIN_SURFACES: &[&str] = &[
"AuthStore::put_access_key",
"AuthStore::put_session",
"AuthStore::put_mtls_client_certificate",
"AuthStore::rotate_access_key",
"AuthStore::revoke_credential",
"AuthStore::report_leaked_access_key",
];
pub const CREDENTIAL_SECURITY_CONTROLS: &[&str] = &[
"unknown credential rejection",
"disabled credential rejection",
"revoked credential rejection",
"expired credential rejection",
"parent access key validation",
"session scope preservation",
"mTLS fingerprint binding",
"last-used audit marker",
];
pub const CREDENTIAL_OBSERVABILITY_FIELDS: &[&str] = &[
"principal_id",
"tenant_id",
"access_key_id",
"certificate_fingerprint",
"session_token",
"scope",
"last_used_epoch_seconds",
"expires_at_epoch_seconds",
"revoked_at_epoch_seconds",
];
pub const CREDENTIAL_FAILURE_MODES: &[&str] = &[
"UnknownAccessKey",
"UnknownClientCertificate",
"UnknownParentAccessKey",
"DisabledAccessKey",
"DisabledClientCertificate",
"RevokedCredential",
"ExpiredCredential",
"NotAccessKey",
];
pub const CREDENTIAL_VALIDATION_TESTS: &[&str] = &[
"crates/bucketwarden-auth/tests/identity_access_keys.rs",
"crates/bucketwarden-auth/tests/session_credentials.rs",
"crates/bucketwarden-auth/tests/sts_scoped_sessions.rs",
"crates/bucketwarden-auth/tests/credential_rotation.rs",
"crates/bucketwarden-auth/tests/credential_support_contract.rs",
];
pub const CREDENTIAL_CAVEATS: &[&str] = &[
"mTLS support authenticates validated client-certificate fingerprints supplied by the listener boundary; TLS handshake termination remains a deployment concern.",
"JWT support is currently exercised through OIDC/SAML identity-provider assertions and scoped session issuance.",
"Credential persistence is in-memory unless the containing runtime snapshot is explicitly persisted.",
];
pub const TEMPORARY_CREDENTIAL_RUNTIME_FEATURES: &[&str] = &[
"parent-identity",
"scoped-credentials",
"expiration",
"revocation",
"policy-inheritance",
];
pub const TEMPORARY_CREDENTIAL_ADMIN_SURFACES: &[&str] = &[
"AuthStore::put_session",
"AuthStore::resolve_credential",
"AuthStore::revoke_credential",
"AuthStore::mark_used",
"AuthStore::assume_role",
"AuthStore::assume_role_with_web_identity",
];
pub const TEMPORARY_CREDENTIAL_SECURITY_CONTROLS: &[&str] = &[
"parent access key must resolve before session creation",
"session credentials preserve parent principal identity",
"session scope is explicit and non-expanding",
"expiration is enforced at credential resolution",
"revocation is enforced before resource access",
"session token is returned through AwsCredentials",
];
pub const TEMPORARY_CREDENTIAL_OBSERVABILITY_FIELDS: &[&str] = &[
"principal_id",
"tenant_id",
"parent_access_key_id",
"access_key_id",
"session_token",
"scope",
"expires_at_epoch_seconds",
"revoked_at_epoch_seconds",
"last_used_epoch_seconds",
];
pub const TEMPORARY_CREDENTIAL_FAILURE_MODES: &[&str] = &[
"UnknownParentAccessKey",
"UnknownAccessKey",
"UnknownPrincipal",
"DisabledPrincipal",
"ExpiredCredential",
"RevokedCredential",
];
pub const TEMPORARY_CREDENTIAL_VALIDATION_TESTS: &[&str] = &[
"crates/bucketwarden-auth/tests/session_credentials.rs",
"crates/bucketwarden-auth/tests/sts_scoped_sessions.rs",
"crates/bucketwarden-auth/tests/temporary_credential_support_contract.rs",
];
pub const TEMPORARY_CREDENTIAL_CAVEATS: &[&str] = &[
"Policy inheritance is represented by explicit session scopes that can narrow access but cannot expand beyond later authorization policy checks.",
"Session credentials are local runtime credentials rather than an AWS STS network endpoint.",
"Credential persistence is in-memory unless the containing runtime snapshot is explicitly persisted.",
];
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CredentialSupportReport {
pub native_support_state: Vec<&'static str>,
pub semantic_parity: &'static str,
pub configuration_admin_surface: Vec<&'static str>,
pub security_governance_impact: Vec<&'static str>,
pub observability_evidence_fields: Vec<&'static str>,
pub failure_modes: Vec<&'static str>,
pub validation_test_coverage: Vec<&'static str>,
pub product_specific_caveats: Vec<&'static str>,
}
impl CredentialSupportReport {
pub fn current() -> Self {
Self {
native_support_state: CREDENTIAL_RUNTIME_FEATURES.to_vec(),
semantic_parity: "All credential mechanisms resolve to a known enabled principal and tenant before authorization; expired, revoked, disabled, or unknown credentials fail closed.",
configuration_admin_surface: CREDENTIAL_ADMIN_SURFACES.to_vec(),
security_governance_impact: CREDENTIAL_SECURITY_CONTROLS.to_vec(),
observability_evidence_fields: CREDENTIAL_OBSERVABILITY_FIELDS.to_vec(),
failure_modes: CREDENTIAL_FAILURE_MODES.to_vec(),
validation_test_coverage: CREDENTIAL_VALIDATION_TESTS.to_vec(),
product_specific_caveats: CREDENTIAL_CAVEATS.to_vec(),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TemporaryCredentialSupportReport {
pub native_support_state: Vec<&'static str>,
pub semantic_parity: &'static str,
pub configuration_admin_surface: Vec<&'static str>,
pub security_governance_impact: Vec<&'static str>,
pub observability_evidence_fields: Vec<&'static str>,
pub failure_modes: Vec<&'static str>,
pub validation_test_coverage: Vec<&'static str>,
pub product_specific_caveats: Vec<&'static str>,
}
impl TemporaryCredentialSupportReport {
pub fn current() -> Self {
Self {
native_support_state: TEMPORARY_CREDENTIAL_RUNTIME_FEATURES.to_vec(),
semantic_parity: "Temporary credentials resolve to the parent principal and tenant, carry a bounded session token, enforce explicit scope, and fail closed on unknown parents, expiration, or revocation.",
configuration_admin_surface: TEMPORARY_CREDENTIAL_ADMIN_SURFACES.to_vec(),
security_governance_impact: TEMPORARY_CREDENTIAL_SECURITY_CONTROLS.to_vec(),
observability_evidence_fields: TEMPORARY_CREDENTIAL_OBSERVABILITY_FIELDS.to_vec(),
failure_modes: TEMPORARY_CREDENTIAL_FAILURE_MODES.to_vec(),
validation_test_coverage: TEMPORARY_CREDENTIAL_VALIDATION_TESTS.to_vec(),
product_specific_caveats: TEMPORARY_CREDENTIAL_CAVEATS.to_vec(),
}
}
}