Skip to main content

awsim_core/
authz.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use awsim_iam_policy::{
5    AuthzRequest, ContextValue, Decision, EvalContext, PolicyDocument, evaluate,
6};
7
8use crate::error::AwsError;
9use crate::router::RequestContext;
10
11pub struct ResolvedPrincipal {
12    pub arn: String,
13    pub account: String,
14    pub identity_policies: Vec<PolicyDocument>,
15    pub permissions_boundary: Option<PolicyDocument>,
16    pub is_root: bool,
17}
18
19pub trait PrincipalLookup: Send + Sync {
20    fn resolve_access_key(&self, access_key: &str) -> Option<ResolvedPrincipal>;
21}
22
23pub trait ResourcePolicyLookup: Send + Sync {
24    fn lookup(&self, resource_arn: &str) -> Option<PolicyDocument>;
25}
26
27pub trait ScpLookup: Send + Sync {
28    fn lookup(&self, principal_arn: &str) -> Vec<PolicyDocument>;
29}
30
31pub struct NoopPrincipalLookup;
32
33impl PrincipalLookup for NoopPrincipalLookup {
34    fn resolve_access_key(&self, _access_key: &str) -> Option<ResolvedPrincipal> {
35        None
36    }
37}
38
39pub struct AuthzEngine {
40    pub principal_lookup: Arc<dyn PrincipalLookup>,
41    pub resource_policy_lookups: HashMap<String, Arc<dyn ResourcePolicyLookup>>,
42    pub scp_lookup: Option<Arc<dyn ScpLookup>>,
43    pub enabled: bool,
44}
45
46impl AuthzEngine {
47    pub fn new(enabled: bool) -> Self {
48        Self {
49            principal_lookup: Arc::new(NoopPrincipalLookup),
50            resource_policy_lookups: HashMap::new(),
51            scp_lookup: None,
52            enabled,
53        }
54    }
55
56    pub fn from_env() -> Self {
57        let enabled = std::env::var("AWSIM_IAM_ENFORCE")
58            .ok()
59            .as_deref()
60            == Some("true");
61        Self::new(enabled)
62    }
63
64    pub fn check(
65        &self,
66        ctx: &RequestContext,
67        action: &str,
68        resource: &str,
69    ) -> Result<(), AwsError> {
70        if !self.enabled {
71            return Ok(());
72        }
73
74        let access_key = match ctx.access_key.as_deref() {
75            Some(k) if !k.is_empty() => k,
76            _ => {
77                return Err(AwsError::access_denied_for(
78                    action,
79                    "anonymous",
80                    resource,
81                ));
82            }
83        };
84
85        let principal = match self.principal_lookup.resolve_access_key(access_key) {
86            Some(p) => p,
87            None => {
88                return Err(AwsError::access_denied_for(
89                    action,
90                    &format!("AccessKey:{access_key}"),
91                    resource,
92                ));
93            }
94        };
95
96        if principal.is_root {
97            return Ok(());
98        }
99
100        let resource_policy = self
101            .resource_policy_lookups
102            .get(&ctx.service)
103            .and_then(|lookup| lookup.lookup(resource));
104
105        let context: HashMap<String, ContextValue> = HashMap::new();
106
107        let req = AuthzRequest {
108            principal_arn: &principal.arn,
109            principal_account: &principal.account,
110            action,
111            resource_arn: resource,
112            context: &context,
113        };
114
115        let scps: Vec<PolicyDocument> = self
116            .scp_lookup
117            .as_ref()
118            .map(|l| l.lookup(&principal.arn))
119            .unwrap_or_default();
120
121        let eval_ctx = EvalContext {
122            identity_policies: &principal.identity_policies,
123            permissions_boundary: principal.permissions_boundary.as_ref(),
124            resource_policy: resource_policy.as_ref(),
125            scps: &scps,
126            session_policy: None,
127        };
128
129        match evaluate(&req, &eval_ctx) {
130            Decision::Allow => Ok(()),
131            Decision::ExplicitDeny | Decision::ImplicitDeny => Err(
132                AwsError::access_denied_for(action, &principal.arn, resource),
133            ),
134        }
135    }
136}
137
138impl Default for AuthzEngine {
139    fn default() -> Self {
140        Self::new(false)
141    }
142}