use std::sync::Arc;
use fakecloud_core::auth::{IamAction, IamDecision, IamPolicyEvaluator, Principal};
use crate::evaluator::{self, Decision, EvalRequest, RequestContext};
use crate::state::SharedIamState;
#[derive(Clone)]
pub struct IamPolicyEvaluatorImpl {
state: SharedIamState,
}
impl IamPolicyEvaluatorImpl {
pub fn new(state: SharedIamState) -> Self {
Self { state }
}
pub fn shared(state: SharedIamState) -> Arc<dyn IamPolicyEvaluator> {
Arc::new(Self::new(state))
}
}
impl IamPolicyEvaluator for IamPolicyEvaluatorImpl {
fn evaluate(&self, principal: &Principal, action: &IamAction) -> IamDecision {
let state = self.state.read();
let policies = evaluator::collect_identity_policies(&state, principal);
let request = EvalRequest {
principal,
action: action.action_string(),
resource: action.resource.clone(),
context: RequestContext::default(),
};
match evaluator::evaluate(&policies, &request) {
Decision::Allow => IamDecision::Allow,
Decision::ImplicitDeny => IamDecision::ImplicitDeny,
Decision::ExplicitDeny => IamDecision::ExplicitDeny,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{IamAccessKey, IamState, IamUser};
use chrono::Utc;
use fakecloud_core::auth::PrincipalType;
use parking_lot::RwLock;
fn principal() -> Principal {
Principal {
arn: "arn:aws:iam::123456789012:user/alice".to_string(),
user_id: "AIDAALICE".to_string(),
account_id: "123456789012".to_string(),
principal_type: PrincipalType::User,
source_identity: None,
}
}
fn setup() -> Arc<RwLock<IamState>> {
let mut state = IamState::new("123456789012");
state.users.insert(
"alice".into(),
IamUser {
user_name: "alice".into(),
user_id: "AIDAALICE".into(),
arn: "arn:aws:iam::123456789012:user/alice".into(),
path: "/".into(),
created_at: Utc::now(),
tags: Vec::new(),
permissions_boundary: None,
},
);
state.access_keys.insert(
"alice".into(),
vec![IamAccessKey {
access_key_id: "FKIAALICE".into(),
secret_access_key: "s".into(),
user_name: "alice".into(),
status: "Active".into(),
created_at: Utc::now(),
}],
);
Arc::new(RwLock::new(state))
}
#[test]
fn allow_policy_produces_allow_decision() {
let state = setup();
state.write().user_inline_policies.insert(
"alice".into(),
std::collections::HashMap::from([(
"AllowGet".into(),
r#"{"Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"*"}]}"#
.into(),
)]),
);
let eval = IamPolicyEvaluatorImpl::new(state);
let action = IamAction {
service: "s3",
action: "GetObject",
resource: "arn:aws:s3:::bucket/key".into(),
};
assert_eq!(eval.evaluate(&principal(), &action), IamDecision::Allow);
}
#[test]
fn explicit_deny_takes_precedence() {
let state = setup();
state.write().user_inline_policies.insert(
"alice".into(),
std::collections::HashMap::from([
(
"AllowAll".into(),
r#"{"Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}"#.into(),
),
(
"DenyGet".into(),
r#"{"Statement":[{"Effect":"Deny","Action":"s3:GetObject","Resource":"*"}]}"#
.into(),
),
]),
);
let eval = IamPolicyEvaluatorImpl::new(state);
let action = IamAction {
service: "s3",
action: "GetObject",
resource: "arn:aws:s3:::bucket/key".into(),
};
assert_eq!(
eval.evaluate(&principal(), &action),
IamDecision::ExplicitDeny
);
}
#[test]
fn empty_policy_set_is_implicit_deny() {
let state = setup();
let eval = IamPolicyEvaluatorImpl::new(state);
let action = IamAction {
service: "s3",
action: "GetObject",
resource: "arn:aws:s3:::bucket/key".into(),
};
assert_eq!(
eval.evaluate(&principal(), &action),
IamDecision::ImplicitDeny
);
}
}