1use jiff::Timestamp;
11use serde::Serialize;
12
13use crate::{
14 Redacted,
15 schema::{BackupTarget, CredentialProcessOutput},
16};
17
18#[derive(Debug, Clone, Serialize)]
21#[serde(rename_all = "PascalCase")]
22pub struct ContainerCreds {
23 pub access_key_id: String,
24 pub secret_access_key: Redacted<String>,
25 pub token: Redacted<String>,
26 pub expiration: Timestamp,
27}
28
29impl From<&CredentialProcessOutput> for ContainerCreds {
30 fn from(c: &CredentialProcessOutput) -> Self {
31 Self {
32 access_key_id: c.access_key_id.clone(),
33 secret_access_key: c.secret_access_key.clone(),
34 token: c.session_token.clone(),
35 expiration: c.expiration,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
44pub enum TargetOutcome {
45 Ready(BackupTarget),
46 Dormant,
47}
48
49#[cfg(test)]
50mod tests {
51 use serde_json::json;
52
53 use super::*;
54
55 #[test]
56 fn container_creds_translate_session_token_to_token() {
57 let creds: CredentialProcessOutput = serde_json::from_value(json!({
58 "Version": 1,
59 "AccessKeyId": "AKIA",
60 "SecretAccessKey": "secret",
61 "SessionToken": "session-token",
62 "Expiration": "2026-05-21T13:00:00Z",
63 }))
64 .unwrap();
65 let container = ContainerCreds::from(&creds);
66 let out = serde_json::to_value(&container).unwrap();
67 assert_eq!(
68 out,
69 json!({
70 "AccessKeyId": "AKIA",
71 "SecretAccessKey": "secret",
72 "Token": "session-token",
73 "Expiration": "2026-05-21T13:00:00Z",
74 })
75 );
76 assert!(out.get("SessionToken").is_none());
78 }
79
80 #[test]
81 fn redacted_debug_does_not_leak() {
82 let creds = ContainerCreds {
83 access_key_id: "AKIA".to_owned(),
84 secret_access_key: Redacted("aws-sk-value-123".to_owned()),
85 token: Redacted("aws-token-value-456".to_owned()),
86 expiration: "2026-05-21T13:00:00Z".parse().unwrap(),
87 };
88 let debug = format!("{creds:?}");
89 assert!(!debug.contains("aws-sk-value-123"));
90 assert!(!debug.contains("aws-token-value-456"));
91 assert!(debug.contains("<redacted>"));
92 }
93}