use crate::error::{AmiError, Result};
use crate::store::traits::{PolicyStore, RoleStore, UserStore};
use crate::wami::policies::permissions_boundary::{
operations, DeletePermissionsBoundaryRequest, PrincipalType, PutPermissionsBoundaryRequest,
};
use std::sync::{Arc, RwLock};
pub struct PermissionsBoundaryService<S> {
store: Arc<RwLock<S>>,
#[allow(dead_code)] account_id: String,
}
impl<S> PermissionsBoundaryService<S>
where
S: UserStore + RoleStore + PolicyStore,
{
pub fn new(store: Arc<RwLock<S>>, account_id: String) -> Self {
Self { store, account_id }
}
pub async fn put_permissions_boundary(
&self,
request: PutPermissionsBoundaryRequest,
) -> Result<()> {
crate::wami::policies::permissions_boundary::PermissionsBoundary::validate_arn(
&request.permissions_boundary,
)?;
let store = self.store.read().unwrap();
let policy = store
.get_policy(&request.permissions_boundary)
.await?
.ok_or_else(|| AmiError::ResourceNotFound {
resource: format!("Policy: {}", request.permissions_boundary),
})?;
operations::validate_boundary_policy(&policy)?;
drop(store);
match request.principal_type {
PrincipalType::User => {
let mut store = self.store.write().unwrap();
let mut user = store
.get_user(&request.principal_name)
.await?
.ok_or_else(|| AmiError::ResourceNotFound {
resource: format!("User: {}", request.principal_name),
})?;
user.permissions_boundary = Some(request.permissions_boundary);
store.update_user(user).await?;
}
PrincipalType::Role => {
let mut store = self.store.write().unwrap();
let mut role = store
.get_role(&request.principal_name)
.await?
.ok_or_else(|| AmiError::ResourceNotFound {
resource: format!("Role: {}", request.principal_name),
})?;
role.permissions_boundary = Some(request.permissions_boundary);
store.update_role(role).await?;
}
}
Ok(())
}
pub async fn delete_permissions_boundary(
&self,
request: DeletePermissionsBoundaryRequest,
) -> Result<()> {
match request.principal_type {
PrincipalType::User => {
let mut store = self.store.write().unwrap();
let mut user = store
.get_user(&request.principal_name)
.await?
.ok_or_else(|| AmiError::ResourceNotFound {
resource: format!("User: {}", request.principal_name),
})?;
user.permissions_boundary = None;
store.update_user(user).await?;
}
PrincipalType::Role => {
let mut store = self.store.write().unwrap();
let mut role = store
.get_role(&request.principal_name)
.await?
.ok_or_else(|| AmiError::ResourceNotFound {
resource: format!("Role: {}", request.principal_name),
})?;
role.permissions_boundary = None;
store.update_role(role).await?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::provider::AwsProvider;
use crate::store::memory::InMemoryWamiStore;
use crate::wami::identity::role::builder::build_role;
use crate::wami::identity::user::builder::build_user;
use crate::wami::policies::policy::builder::build_policy;
use std::sync::Arc;
#[tokio::test]
async fn test_put_boundary_on_user() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let provider = Arc::new(AwsProvider::new());
let service = PermissionsBoundaryService::new(store.clone(), account_id.to_string());
let user = build_user(
"alice".to_string(),
Some("/".to_string()),
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_user(user.clone()).await.unwrap();
}
let policy_doc = r#"{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}]
}"#;
let policy = build_policy(
"S3Boundary".to_string(),
policy_doc.to_string(),
Some("/".to_string()),
None,
None,
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_policy(policy.clone()).await.unwrap();
}
let request = PutPermissionsBoundaryRequest {
principal_type: PrincipalType::User,
principal_name: "alice".to_string(),
permissions_boundary: policy.arn.clone(),
};
let result = service.put_permissions_boundary(request).await;
match result {
Ok(_) => {
let s = store.read().unwrap();
let updated_user = s.get_user("alice").await.unwrap().unwrap();
assert_eq!(updated_user.permissions_boundary, Some(policy.arn));
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_put_boundary_on_role() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let provider = Arc::new(AwsProvider::new());
let service = PermissionsBoundaryService::new(store.clone(), account_id.to_string());
let assume_policy = r#"{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}"#;
let role = build_role(
"test-role".to_string(),
assume_policy.to_string(),
Some("/".to_string()),
None,
None,
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_role(role.clone()).await.unwrap();
}
let policy_doc = r#"{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}]
}"#;
let policy = build_policy(
"S3Boundary".to_string(),
policy_doc.to_string(),
Some("/".to_string()),
None,
None,
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_policy(policy.clone()).await.unwrap();
}
let request = PutPermissionsBoundaryRequest {
principal_type: PrincipalType::Role,
principal_name: "test-role".to_string(),
permissions_boundary: policy.arn.clone(),
};
service.put_permissions_boundary(request).await.unwrap();
let s = store.read().unwrap();
let updated_role = s.get_role("test-role").await.unwrap().unwrap();
assert_eq!(updated_role.permissions_boundary, Some(policy.arn));
}
#[tokio::test]
async fn test_delete_boundary_from_role() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let provider = Arc::new(AwsProvider::new());
let service = PermissionsBoundaryService::new(store.clone(), account_id.to_string());
let assume_policy = r#"{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}"#;
let mut role = build_role(
"test-role".to_string(),
assume_policy.to_string(),
Some("/".to_string()),
None,
None,
provider.as_ref(),
account_id,
);
role.permissions_boundary = Some("arn:aws:iam::123456789012:policy/boundary".to_string());
{
let mut s = store.write().unwrap();
s.create_role(role.clone()).await.unwrap();
}
let request = DeletePermissionsBoundaryRequest {
principal_type: PrincipalType::Role,
principal_name: "test-role".to_string(),
};
service.delete_permissions_boundary(request).await.unwrap();
let s = store.read().unwrap();
let updated_role = s.get_role("test-role").await.unwrap().unwrap();
assert_eq!(updated_role.permissions_boundary, None);
}
#[tokio::test]
async fn test_put_boundary_invalid_arn() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let service = PermissionsBoundaryService::new(store, account_id.to_string());
let request = PutPermissionsBoundaryRequest {
principal_type: PrincipalType::User,
principal_name: "alice".to_string(),
permissions_boundary: "not-an-arn".to_string(),
};
let result = service.put_permissions_boundary(request).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_put_boundary_nonexistent_policy() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let provider = Arc::new(AwsProvider::new());
let service = PermissionsBoundaryService::new(store.clone(), account_id.to_string());
let user = build_user(
"alice".to_string(),
Some("/".to_string()),
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_user(user).await.unwrap();
}
let request = PutPermissionsBoundaryRequest {
principal_type: PrincipalType::User,
principal_name: "alice".to_string(),
permissions_boundary: "arn:aws:iam::123456789012:policy/nonexistent".to_string(),
};
let result = service.put_permissions_boundary(request).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_put_boundary_nonexistent_user() {
let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
let account_id = "123456789012";
let provider = Arc::new(AwsProvider::new());
let service = PermissionsBoundaryService::new(store.clone(), account_id.to_string());
let policy_doc = r#"{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}]
}"#;
let policy = build_policy(
"S3Boundary".to_string(),
policy_doc.to_string(),
Some("/".to_string()),
None,
None,
provider.as_ref(),
account_id,
);
{
let mut s = store.write().unwrap();
s.create_policy(policy.clone()).await.unwrap();
}
let request = PutPermissionsBoundaryRequest {
principal_type: PrincipalType::User,
principal_name: "nonexistent".to_string(),
permissions_boundary: policy.arn,
};
let result = service.put_permissions_boundary(request).await;
assert!(result.is_err());
}
}