mod common;
use alien_permissions::{
generators::AwsCloudFormationPermissionsGenerator, get_permission_set, BindingTarget,
PermissionContext,
};
use common::*;
use insta::assert_json_snapshot;
use rstest::rstest;
use serde_json::json;
#[rstest]
#[case::stack_binding(BindingTarget::Stack)]
#[case::resource_binding(BindingTarget::Resource)]
fn test_aws_cloudformation_storage_data_read_policy_generation(
#[case] binding_target: BindingTarget,
) {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_cloudformation_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, binding_target, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let snapshot_name = format!(
"aws_cloudformation_storage_data_read_{}_binding",
binding_target
);
assert_json_snapshot!(snapshot_name, result);
}
#[test]
fn test_aws_cloudformation_policy_with_conditions() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_lambda_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy with conditions successfully");
assert_json_snapshot!("aws_cloudformation_policy_with_conditions", result);
}
#[test]
fn test_aws_cloudformation_intrinsic_functions() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_cloudformation_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let first_statement = &result.statement[0];
let expected_resources = vec![
json!({"Fn::Sub": "arn:${AWS::Partition}:s3:::${AWS::StackName}-*"}),
json!({"Fn::Sub": "arn:${AWS::Partition}:s3:::${AWS::StackName}-*/*"}),
];
assert_eq!(first_statement.resource, expected_resources);
}
#[test]
fn test_aws_cloudformation_resource_references() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_cloudformation_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Resource, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let first_statement = &result.statement[0];
let expected_resources = vec![
json!({"Fn::Sub": "arn:${AWS::Partition}:s3:::PaymentsDataBucket"}),
json!({"Fn::Sub": "arn:${AWS::Partition}:s3:::PaymentsDataBucket/*"}),
];
assert_eq!(first_statement.resource, expected_resources);
}
#[test]
fn test_aws_cloudformation_statement_id_generation() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let mut permission_set = create_aws_cloudformation_permission_set();
permission_set.id = "complex/permission-set/with-dashes".to_string();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
assert_eq!(result.statement[0].sid, "ComplexPermissionSetWithDashes");
assert_eq!(result.version, "2012-10-17");
assert_eq!(result.statement[0].effect, "Allow");
}
#[test]
fn test_aws_cloudformation_multiple_statements() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_lambda_permission_set(); let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
assert_eq!(result.statement.len(), 2);
assert_eq!(result.statement[0].sid, "WorkerExecuteLambdaInvokeWorker");
assert_eq!(
result.statement[0].action,
vec![json!("lambda:InvokeWorker")]
);
assert_eq!(result.statement[1].sid, "WorkerExecuteReadEcrImages");
assert_eq!(
result.statement[1].action,
vec![
json!("ecr:BatchGetImage"),
json!("ecr:GetDownloadUrlForLayer")
]
);
}
#[test]
fn test_aws_cloudformation_condition_interpolation() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_lambda_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let ecr_statement = &result.statement[1];
assert!(ecr_statement.condition.is_some());
let conditions = ecr_statement.condition.as_ref().unwrap();
let string_equals = conditions.get("StringEquals").unwrap();
assert_eq!(
string_equals.get("sts:ExternalId").unwrap(),
&json!("my-external-id")
);
}
#[test]
fn test_aws_cloudformation_compute_management_can_use_setup_security_group() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set =
get_permission_set("compute-cluster/management").expect("permission set exists");
let context = PermissionContext::new()
.with_stack_prefix("")
.with_aws_region("${AWS::Region}")
.with_aws_account_id("${AWS::AccountId}");
let result = generator
.generate_policy(permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let setup_security_group_statement = result
.statement
.iter()
.find(|statement| {
statement
.resource
.iter()
.any(|resource| resource == &json!({"Fn::Sub": "arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*"}))
&& statement.action.contains(&json!("ec2:RunInstances"))
})
.expect("compute-cluster management should allow setup compute security group use");
let string_equals = setup_security_group_statement
.condition
.as_ref()
.and_then(|condition| condition.get("StringEquals"))
.expect("setup security group permission should be tag-conditioned");
assert_eq!(
string_equals.get("aws:ResourceTag/deployment"),
Some(&json!({"Fn::Sub": "${AWS::StackName}"}))
);
assert_eq!(
string_equals.get("aws:ResourceTag/managed-by"),
Some(&json!("setup"))
);
assert_eq!(
string_equals.get("aws:ResourceTag/resource-type"),
Some(&json!("compute-cluster"))
);
}
#[test]
fn test_aws_cloudformation_compute_management_can_terminate_only_tagged_stack_instances() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set =
get_permission_set("compute-cluster/management").expect("permission set exists");
let context = PermissionContext::new()
.with_stack_prefix("")
.with_aws_region("${AWS::Region}")
.with_aws_account_id("${AWS::AccountId}");
let result = generator
.generate_policy(permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let statement = result
.statement
.iter()
.find(|statement| {
statement
.action
.contains(&json!("autoscaling:TerminateInstanceInAutoScalingGroup"))
})
.expect("compute-cluster management should allow drained instance termination");
assert_eq!(
statement.resource,
[json!({
"Fn::Sub": "arn:${AWS::Partition}:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${AWS::StackName}-*"
})]
);
let string_equals = statement
.condition
.as_ref()
.and_then(|condition| condition.get("StringEquals"))
.expect("instance termination must be tag-conditioned");
assert_eq!(
string_equals.get("aws:ResourceTag/deployment"),
Some(&json!({"Fn::Sub": "${AWS::StackName}"}))
);
assert_eq!(
string_equals.get("aws:ResourceTag/managed-by"),
Some(&json!("runtime"))
);
}
#[test]
fn test_aws_cloudformation_container_provision_can_manage_setup_compute_security_group_ingress() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = get_permission_set("container/provision").expect("permission set exists");
let context = PermissionContext::new()
.with_stack_prefix("")
.with_resource_id("api")
.with_resource_name("alien-manager")
.with_aws_region("${AWS::Region}")
.with_aws_account_id("${AWS::AccountId}");
let result = generator
.generate_policy(permission_set, BindingTarget::Resource, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let setup_compute_ingress_statement = result
.statement
.iter()
.find(|statement| {
if !statement
.action
.contains(&json!("ec2:AuthorizeSecurityGroupIngress"))
|| !statement
.action
.contains(&json!("ec2:RevokeSecurityGroupIngress"))
{
return false;
}
statement
.condition
.as_ref()
.and_then(|condition| condition.get("StringEquals"))
.is_some_and(|string_equals| {
string_equals.get("aws:ResourceTag/managed-by") == Some(&json!("setup"))
&& string_equals.get("aws:ResourceTag/resource-type")
== Some(&json!("compute-cluster"))
})
})
.expect("container provision should manage ingress on setup compute security groups");
assert!(!setup_compute_ingress_statement
.action
.contains(&json!("ec2:DeleteSecurityGroup")));
let string_equals = setup_compute_ingress_statement
.condition
.as_ref()
.and_then(|condition| condition.get("StringEquals"))
.expect("setup compute security group ingress permission should be tag-conditioned");
assert_eq!(
string_equals.get("aws:ResourceTag/deployment"),
Some(&json!({"Fn::Sub": "${AWS::StackName}"}))
);
assert_eq!(
string_equals.get("aws:ResourceTag/managed-by"),
Some(&json!("setup"))
);
assert_eq!(
string_equals.get("aws:ResourceTag/resource-type"),
Some(&json!("compute-cluster"))
);
}
#[test]
fn test_aws_cloudformation_resource_id_interpolates_in_conditions() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = get_permission_set("worker/provision").expect("permission set exists");
let context = PermissionContext::new()
.with_stack_prefix("")
.with_resource_id("job")
.with_resource_name("${AWS::StackName}-job")
.with_aws_region("${AWS::Region}")
.with_aws_account_id("${AWS::AccountId}");
let result = generator
.generate_policy(permission_set, BindingTarget::Resource, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let create_function_statement = result
.statement
.iter()
.find(|statement| {
statement.action.contains(&json!("lambda:CreateFunction"))
&& statement.resource.contains(&json!({
"Fn::Sub": "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}-job"
}))
})
.expect("worker provision should allow creating the physical Lambda function");
let string_equals = create_function_statement
.condition
.as_ref()
.and_then(|condition| condition.get("StringEquals"))
.expect("Lambda creation should be request-tag conditioned");
assert_eq!(
string_equals.get("aws:RequestTag/resource"),
Some(&json!("job"))
);
}
#[test]
fn test_aws_cloudformation_missing_actions_error() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_permission_set_missing_actions();
let context = create_cloudformation_context();
let result = generator.generate_policy(&permission_set, BindingTarget::Stack, &context);
assert!(result.is_err());
let error = result.unwrap_err();
let error_string = error.to_string();
assert!(error_string.contains("AWS permission grant must have 'actions' field"));
}
#[test]
fn test_aws_cloudformation_missing_platform_error() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_gcp_storage_data_read_permission_set(); let context = create_cloudformation_context();
let result = generator.generate_policy(&permission_set, BindingTarget::Stack, &context);
assert!(result.is_err());
let error = result.unwrap_err();
let error_string = error.to_string();
assert!(error_string.contains("Platform 'aws' is not supported"));
}
#[test]
fn test_aws_cloudformation_missing_binding_target_error() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let mut permission_set = create_aws_cloudformation_permission_set();
if let Some(aws_permissions) = &mut permission_set.platforms.aws {
aws_permissions[0].binding.resource = None; }
let context = create_cloudformation_context();
let result = generator.generate_policy(&permission_set, BindingTarget::Resource, &context);
assert!(result.is_err());
let error = result.unwrap_err();
let error_string = error.to_string();
assert!(error_string.contains("Binding target 'resource' is not supported"));
}
#[test]
fn test_aws_cloudformation_managing_account_id_substitution() {
let generator = AwsCloudFormationPermissionsGenerator::new();
let permission_set = create_aws_lambda_permission_set();
let context = create_cloudformation_context();
let result = generator
.generate_policy(&permission_set, BindingTarget::Stack, &context)
.expect("Should generate AWS CloudFormation policy successfully");
let ecr_statement = &result.statement[1];
let expected_resource =
json!({"Fn::Sub": "arn:${AWS::Partition}:ecr:*:${ManagingAccountId}:repository/*"});
assert_eq!(ecr_statement.resource[0], expected_resource);
}