Skip to main content

awsim_secretsmanager/
authz.rs

1use awsim_core::{AccountRegionStore, ResourcePolicyLookup, SecretLookup};
2use awsim_iam_policy::PolicyDocument;
3
4use crate::state::SecretsState;
5
6pub struct SecretsManagerResourcePolicyLookup {
7    store: AccountRegionStore<SecretsState>,
8}
9
10impl SecretsManagerResourcePolicyLookup {
11    pub fn new(store: AccountRegionStore<SecretsState>) -> Self {
12        Self { store }
13    }
14}
15
16fn extract_secret_key(arn: &str) -> Option<String> {
17    let rest = arn.strip_prefix("arn:aws:secretsmanager:")?;
18    let parts: Vec<&str> = rest.splitn(3, ':').collect();
19    if parts.len() < 3 {
20        return None;
21    }
22    let resource = parts[2];
23    let after_secret = resource.strip_prefix("secret:")?;
24    Some(after_secret.to_string())
25}
26
27/// Cross-service helper: implements [`SecretLookup`] so other crates
28/// (ECS repositoryCredentials, container `secrets[]`, etc.) can ask
29/// "does this secret exist in (account, region)?" without taking a
30/// direct dependency on awsim-secretsmanager's internals.
31pub struct SecretsManagerSecretLookup {
32    store: AccountRegionStore<SecretsState>,
33}
34
35impl SecretsManagerSecretLookup {
36    pub fn new(store: AccountRegionStore<SecretsState>) -> Self {
37        Self { store }
38    }
39}
40
41impl SecretLookup for SecretsManagerSecretLookup {
42    fn secret_exists(&self, secret_ref: &str, account: &str, region: &str) -> bool {
43        let state = self.store.get(account, region);
44        if state.secrets.contains_key(secret_ref) {
45            return true;
46        }
47        // ARN form: arn:aws:secretsmanager:{region}:{account}:secret:{name}-{suffix}
48        // The 6-char suffix is generated at CreateSecret time and isn't
49        // part of the canonical name key, so strip it before lookup.
50        if let Some(stored_key) = extract_secret_key(secret_ref) {
51            if state.secrets.contains_key(&stored_key) {
52                return true;
53            }
54            let bare = stored_key
55                .rsplit_once('-')
56                .map(|(n, _)| n.to_string())
57                .unwrap_or(stored_key);
58            if state.secrets.contains_key(&bare) {
59                return true;
60            }
61        }
62        // Last resort: scan secrets for an exact ARN match.
63        state.secrets.iter().any(|e| e.value().arn == secret_ref)
64    }
65}
66
67impl ResourcePolicyLookup for SecretsManagerResourcePolicyLookup {
68    fn lookup(&self, resource_arn: &str) -> Option<PolicyDocument> {
69        for (_, state) in self.store.iter_all() {
70            for entry in state.secrets.iter() {
71                if entry.value().arn == resource_arn {
72                    let name = entry.key();
73                    if let Some(raw) = state.resource_policies.get(name) {
74                        return awsim_iam_policy::parse(raw.value()).ok();
75                    }
76                    return None;
77                }
78            }
79            if let Some(suffixed) = extract_secret_key(resource_arn) {
80                let bare_name = suffixed
81                    .rsplit_once('-')
82                    .map(|(n, _)| n)
83                    .unwrap_or(&suffixed);
84                if let Some(raw) = state.resource_policies.get(bare_name) {
85                    return awsim_iam_policy::parse(raw.value()).ok();
86                }
87            }
88        }
89        None
90    }
91}