moto/tests/test_iam/test_iam_cloudformation.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1612 lines
49 KiB
Python
Raw Normal View History

import json
import boto3
import pytest
import yaml
from botocore.exceptions import ClientError
2024-01-07 12:03:33 +00:00
from moto import mock_aws
2022-08-13 09:49:43 +00:00
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
from tests import EXAMPLE_AMI_ID
TEMPLATE_MINIMAL_ROLE = """
AWSTemplateFormatVersion: 2010-09-09
Resources:
RootRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
"""
TEMPLATE_ROLE_INSTANCE_PROFILE = """
AWSTemplateFormatVersion: 2010-09-09
Resources:
RootRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: {0}
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: '*'
Resource: '*'
RootInstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Path: /
Roles:
- !Ref RootRole
"""
# AWS::IAM::User Tests
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_user():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
user_name = "MyUser"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
Properties:
UserName: {0}
""".strip().format(
user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
assert provisioned_resource["LogicalResourceId"] == "TheUser"
assert provisioned_resource["PhysicalResourceId"] == user_name
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_user_no_interruption():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
user_name = provisioned_resource["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
user = iam_client.get_user(UserName=user_name)["User"]
assert user["Path"] == "/"
path = "/MyPath/"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
Properties:
Path: {0}
""".strip().format(
path
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
user = iam_client.get_user(UserName=user_name)["User"]
assert user["Path"] == path
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_user_replacement():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
original_user_name = provisioned_resource["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
user = iam_client.get_user(UserName=original_user_name)["User"]
assert user["Path"] == "/"
new_user_name = "MyUser"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
Properties:
UserName: {0}
""".strip().format(
new_user_name
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
with pytest.raises(ClientError) as e:
iam_client.get_user(UserName=original_user_name)
assert e.value.response["Error"]["Code"] == "NoSuchEntity"
iam_client.get_user(UserName=new_user_name)
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_drop_user():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheFirstUser:
Type: AWS::IAM::User
TheSecondUser:
Type: AWS::IAM::User
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
first_provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheFirstUser"
][0]
second_provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheSecondUser"
][0]
first_user_name = first_provisioned_user["PhysicalResourceId"]
second_user_name = second_provisioned_user["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
iam_client.get_user(UserName=first_user_name)
iam_client.get_user(UserName=second_user_name)
template = """
Resources:
TheSecondUser:
Type: AWS::IAM::User
""".strip()
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
assert len(provisioned_resources) == 1
second_provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheSecondUser"
][0]
assert second_user_name == second_provisioned_user["PhysicalResourceId"]
iam_client.get_user(UserName=second_user_name)
with pytest.raises(ClientError) as e:
iam_client.get_user(UserName=first_user_name)
assert e.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_user():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
user_name = "MyUser"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
Properties:
UserName: {}
""".strip().format(
user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
iam_client = boto3.client("iam", region_name="us-east-1")
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError) as e:
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
assert e.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_user_having_generated_name():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
assert provisioned_resource["LogicalResourceId"] == "TheUser"
user_name = provisioned_resource["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError) as e:
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
assert e.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_user_get_attr():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
user_name = "MyUser"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
Properties:
UserName: {0}
Outputs:
UserName:
Value: !Ref TheUser
UserArn:
Value: !GetAtt TheUser.Arn
""".strip().format(
user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
stack_description = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
output_user_name = [
output["OutputValue"]
for output in stack_description["Outputs"]
if output["OutputKey"] == "UserName"
][0]
output_user_arn = [
output["OutputValue"]
for output in stack_description["Outputs"]
if output["OutputKey"] == "UserArn"
][0]
iam_client = boto3.client("iam", region_name="us-east-1")
user_description = iam_client.get_user(UserName=output_user_name)["User"]
assert output_user_arn == user_description["Arn"]
# AWS::IAM::ManagedPolicy Tests
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_managed_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: '*'
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
policy_arn = provisioned_resource["PhysicalResourceId"]
assert policy_arn.startswith(f"arn:aws:iam::{ACCOUNT_ID}:policy/MyStack-ThePolicy-")
expected_name = policy_arn.split("/")[1]
response = iam_client.list_entities_for_policy(PolicyArn=policy_arn)
assert response["PolicyGroups"] == []
assert response["PolicyUsers"] == []
assert response["PolicyRoles"] == []
policy = iam_client.get_policy(PolicyArn=policy_arn)["Policy"]
assert policy["Arn"] == policy_arn
assert policy["PolicyName"] == expected_name
assert policy["Description"] == ""
assert policy["Path"] == "/"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_managed_policy_with_additional_properties():
iam_client = boto3.client("iam", region_name="us-east-1")
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
name = "FancyManagedPolicy"
desc = "Custom managed policy with name"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: {0}
Path: /
ManagedPolicyName: {1}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: '*'
""".strip().format(
desc, name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
policy_arn = provisioned_resource["PhysicalResourceId"]
assert policy_arn == f"arn:aws:iam::{ACCOUNT_ID}:policy/{name}"
policy = iam_client.get_policy(PolicyArn=policy_arn)["Policy"]
assert policy["Arn"] == policy_arn
assert policy["Path"] == "/"
assert policy["Description"] == desc
assert policy["PolicyName"] == name
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_managed_policy_attached_to_a_group():
iam_client = boto3.client("iam", region_name="us-east-1")
group_name = "MyGroup"
iam_client.create_group(GroupName=group_name)
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
desc = "Custom managed policy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: {0}
Path: /
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: '*'
Groups:
- {1}
""".strip().format(
desc, group_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
policy_arn = provisioned_resource["PhysicalResourceId"]
assert policy_arn.startswith(f"arn:aws:iam::{ACCOUNT_ID}:policy/MyStack-ThePolicy-")
response = iam_client.list_entities_for_policy(PolicyArn=policy_arn)
assert response["PolicyUsers"] == []
assert response["PolicyRoles"] == []
assert response["PolicyGroups"][0]["GroupName"] == group_name
assert "GroupId" in response["PolicyGroups"][0]
2022-02-25 11:28:42 +00:00
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_managed_policy_attached_to_a_user():
iam_client = boto3.client("iam", region_name="us-east-1")
user_name = "MyUser"
iam_client.create_user(UserName=user_name)
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
desc = "Custom managed policy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: {0}
Path: /
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: '*'
Users:
- {1}
""".strip().format(
desc, user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
policy_arn = provisioned_resource["PhysicalResourceId"]
assert policy_arn.startswith(f"arn:aws:iam::{ACCOUNT_ID}:policy/MyStack-ThePolicy-")
response = iam_client.list_entities_for_policy(PolicyArn=policy_arn)
assert response["PolicyGroups"] == []
assert response["PolicyRoles"] == []
assert response["PolicyUsers"][0]["UserName"] == user_name
assert "UserId" in response["PolicyUsers"][0]
2022-02-25 11:28:42 +00:00
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_managed_policy_attached_to_a_role():
iam_client = boto3.client("iam", region_name="us-east-1")
role_name = "MyRole"
iam_client.create_role(RoleName=role_name, AssumeRolePolicyDocument="some policy")
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
desc = "Custom managed policy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: {0}
Path: /
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: '*'
Roles:
- {1}
""".strip().format(
desc, role_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
policy_arn = provisioned_resource["PhysicalResourceId"]
assert policy_arn.startswith(f"arn:aws:iam::{ACCOUNT_ID}:policy/MyStack-ThePolicy-")
response = iam_client.list_entities_for_policy(PolicyArn=policy_arn)
assert response["PolicyGroups"] == []
assert response["PolicyUsers"] == []
2022-02-25 11:28:42 +00:00
assert response["PolicyRoles"][0]["RoleName"] == role_name
assert "RoleId" in response["PolicyRoles"][0]
# AWS::IAM::Policy Tests
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_user_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
user_name = "MyUser"
iam_client.create_user(UserName=user_name)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
2021-10-18 19:44:29 +00:00
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Users:
- {2}
""".strip().format(
policy_name, bucket_arn, user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_user_policy(UserName=user_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_user_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
user_name_1 = "MyUser1"
iam_client.create_user(UserName=user_name_1)
user_name_2 = "MyUser2"
iam_client.create_user(UserName=user_name_2)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Users:
- {2}
""".strip().format(
policy_name, bucket_arn, user_name_1
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_user_policy(UserName=user_name_1, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
# Change template and user
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:ListBuckets
Resource: {1}
Users:
- {2}
""".strip().format(
policy_name, bucket_arn, user_name_2
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_user_policy(UserName=user_name_2, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
with pytest.raises(ClientError):
iam_client.get_user_policy(UserName=user_name_1, PolicyName=policy_name)
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_user_policy_having_generated_name():
iam_client = boto3.client("iam", region_name="us-east-1")
user_name = "MyUser"
iam_client.create_user(UserName=user_name)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
2021-10-18 19:44:29 +00:00
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: MyPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {0}
Users:
- {1}
""".strip().format(
bucket_arn, user_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_user_policy(UserName=user_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError):
iam_client.get_user_policy(UserName=user_name, PolicyName=policy_name)
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_role_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
role_name = "MyRole"
iam_client.create_role(RoleName=role_name, AssumeRolePolicyDocument="{}")
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Roles:
- {2}
""".strip().format(
policy_name, bucket_arn, role_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_role_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
role_name_1 = "MyRole1"
iam_client.create_role(RoleName=role_name_1, AssumeRolePolicyDocument="{}")
role_name_2 = "MyRole2"
iam_client.create_role(RoleName=role_name_2, AssumeRolePolicyDocument="{}")
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Roles:
- {2}
""".strip().format(
policy_name, bucket_arn, role_name_1
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_role_policy(RoleName=role_name_1, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
# Change template and user
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:ListBuckets
Resource: {1}
Roles:
- {2}
""".strip().format(
policy_name, bucket_arn, role_name_2
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_role_policy(RoleName=role_name_2, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
with pytest.raises(ClientError):
iam_client.get_role_policy(RoleName=role_name_1, PolicyName=policy_name)
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_role_policy_having_generated_name():
iam_client = boto3.client("iam", region_name="us-east-1")
role_name = "MyRole"
iam_client.create_role(RoleName=role_name, AssumeRolePolicyDocument="{}")
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: MyPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {0}
Roles:
- {1}
""".strip().format(
bucket_arn, role_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError) as exc:
iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name)
assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_group_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
group_name = "MyGroup"
iam_client.create_group(GroupName=group_name)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Groups:
- {2}
""".strip().format(
policy_name, bucket_arn, group_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_group_policy(GroupName=group_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_group_policy():
iam_client = boto3.client("iam", region_name="us-east-1")
group_name_1 = "MyGroup1"
iam_client.create_group(GroupName=group_name_1)
group_name_2 = "MyGroup2"
iam_client.create_group(GroupName=group_name_2)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {1}
Groups:
- {2}
""".strip().format(
policy_name, bucket_arn, group_name_1
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_group_policy(GroupName=group_name_1, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
# Change template and user
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: {0}
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:ListBuckets
Resource: {1}
Groups:
- {2}
""".strip().format(
policy_name, bucket_arn, group_name_2
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_group_policy(GroupName=group_name_2, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
with pytest.raises(ClientError) as exc:
iam_client.get_group_policy(GroupName=group_name_1, PolicyName=policy_name)
assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_group_policy_having_generated_name():
iam_client = boto3.client("iam", region_name="us-east-1")
group_name = "MyGroup"
iam_client.create_group(GroupName=group_name)
s3_client = boto3.client("s3", region_name="us-east-1")
bucket_name = "my-bucket"
s3_client.create_bucket(Bucket=bucket_name)
bucket_arn = f"arn:aws:s3:::{bucket_name}"
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
policy_name = "MyPolicy"
template = """
Resources:
ThePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: MyPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource: {0}
Groups:
- {1}
""".strip().format(
bucket_arn, group_name
)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resource = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
][0]
logical_resource_id = provisioned_resource["LogicalResourceId"]
assert logical_resource_id == "ThePolicy"
original_policy_document = yaml.load(template, Loader=yaml.FullLoader)["Resources"][
logical_resource_id
]["Properties"]["PolicyDocument"]
policy = iam_client.get_group_policy(GroupName=group_name, PolicyName=policy_name)
assert policy["PolicyDocument"] == original_policy_document
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError) as exc:
iam_client.get_group_policy(GroupName=group_name, PolicyName=policy_name)
assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
# AWS::IAM::User AccessKeys
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_user_with_access_key():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TheUser
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheUser"
][0]
user_name = provisioned_user["PhysicalResourceId"]
provisioned_access_keys = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheAccessKey"
]
assert len(provisioned_access_keys) == 1
iam_client = boto3.client("iam", region_name="us-east-1")
user = iam_client.get_user(UserName=user_name)["User"]
assert user["UserName"] == user_name
access_keys = iam_client.list_access_keys(UserName=user_name)
assert access_keys["AccessKeyMetadata"][0]["UserName"] == user_name
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_access_key_get_attr():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TheUser
Outputs:
AccessKeyId:
Value: !Ref TheAccessKey
SecretKey:
Value: !GetAtt TheAccessKey.SecretAccessKey
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheUser"
][0]
user_name = provisioned_user["PhysicalResourceId"]
stack_description = cf_client.describe_stacks(StackName=stack_name)["Stacks"][0]
output_access_key_id = [
output["OutputValue"]
for output in stack_description["Outputs"]
if output["OutputKey"] == "AccessKeyId"
][0]
output_secret_key = [
output["OutputValue"]
for output in stack_description["Outputs"]
if output["OutputKey"] == "SecretKey"
][0]
sts_client = boto3.client(
"sts",
aws_access_key_id=output_access_key_id,
aws_secret_access_key=output_secret_key,
region_name="us-east-1",
)
caller_identity = sts_client.get_caller_identity()
assert caller_identity["Arn"].split("/")[1] == user_name
pass
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_delete_users_access_key():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TheUser
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheUser"
][0]
user_name = provisioned_user["PhysicalResourceId"]
provisioned_access_keys = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheAccessKey"
]
assert len(provisioned_access_keys) == 1
access_key_id = provisioned_access_keys[0]["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
user = iam_client.get_user(UserName=user_name)["User"]
assert user["UserName"] == user_name
access_keys = iam_client.list_access_keys(UserName=user_name)
assert access_keys["AccessKeyMetadata"][0]["AccessKeyId"] == access_key_id
assert access_keys["AccessKeyMetadata"][0]["UserName"] == user_name
assert access_key_id == access_keys["AccessKeyMetadata"][0]["AccessKeyId"]
cf_client.delete_stack(StackName=stack_name)
with pytest.raises(ClientError) as exc:
iam_client.get_user(UserName=user_name)
assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
with pytest.raises(ClientError) as exc:
iam_client.list_access_keys(UserName=user_name)
assert exc.value.response["Error"]["Code"] == "NoSuchEntity"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_users_access_key_no_interruption():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TheUser
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheUser"
][0]
user_name = provisioned_user["PhysicalResourceId"]
provisioned_access_key = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheAccessKey"
][0]
access_key_id = provisioned_access_key["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
access_keys = iam_client.list_access_keys(UserName=user_name)
assert access_key_id == access_keys["AccessKeyMetadata"][0]["AccessKeyId"]
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
Status: Inactive
""".strip()
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
access_keys = iam_client.list_access_keys(UserName=user_name)
assert access_keys["AccessKeyMetadata"][0]["Status"] == "Inactive"
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_update_users_access_key_replacement():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: !Ref TheUser
""".strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
provisioned_resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
provisioned_user = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheUser"
][0]
user_name = provisioned_user["PhysicalResourceId"]
provisioned_access_key = [
resource
for resource in provisioned_resources
if resource["LogicalResourceId"] == "TheAccessKey"
][0]
access_key_id = provisioned_access_key["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
2021-10-18 19:44:29 +00:00
iam_client.get_user(UserName=user_name)
access_keys = iam_client.list_access_keys(UserName=user_name)
assert access_key_id == access_keys["AccessKeyMetadata"][0]["AccessKeyId"]
other_user_name = "MyUser"
iam_client.create_user(UserName=other_user_name)
template = """
Resources:
TheUser:
Type: AWS::IAM::User
TheAccessKey:
Type: AWS::IAM::AccessKey
Properties:
UserName: {0}
""".strip().format(
other_user_name
)
cf_client.update_stack(StackName=stack_name, TemplateBody=template)
access_keys = iam_client.list_access_keys(UserName=user_name)
assert len(access_keys["AccessKeyMetadata"]) == 0
access_keys = iam_client.list_access_keys(UserName=other_user_name)
assert access_key_id != access_keys["AccessKeyMetadata"][0]["AccessKeyId"]
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_role():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
template = TEMPLATE_MINIMAL_ROLE.strip()
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
role = [res for res in resources if res["ResourceType"] == "AWS::IAM::Role"][0]
assert role["LogicalResourceId"] == "RootRole"
iam_client = boto3.client("iam", region_name="us-east-1")
assert len(iam_client.list_roles()["Roles"]) == 1
cf_client.delete_stack(StackName=stack_name)
assert len(iam_client.list_roles()["Roles"]) == 0
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_cloudformation_create_role_and_instance_profile():
cf_client = boto3.client("cloudformation", region_name="us-east-1")
stack_name = "MyStack"
role_name = "MyUser"
template = TEMPLATE_ROLE_INSTANCE_PROFILE.strip().format(role_name)
cf_client.create_stack(StackName=stack_name, TemplateBody=template)
resources = cf_client.list_stack_resources(StackName=stack_name)[
"StackResourceSummaries"
]
role = [res for res in resources if res["ResourceType"] == "AWS::IAM::Role"][0]
assert role["LogicalResourceId"] == "RootRole"
assert role["PhysicalResourceId"] == role_name
profile = [
res for res in resources if res["ResourceType"] == "AWS::IAM::InstanceProfile"
][0]
assert profile["LogicalResourceId"] == "RootInstanceProfile"
assert (
stack_name in profile["PhysicalResourceId"]
) # e.g. MyStack-RootInstanceProfile-73Y4H4ALFW3N
assert "RootInstanceProfile" in profile["PhysicalResourceId"]
iam_client = boto3.client("iam", region_name="us-east-1")
assert len(iam_client.list_roles()["Roles"]) == 1
cf_client.delete_stack(StackName=stack_name)
assert len(iam_client.list_roles()["Roles"]) == 0
2024-01-07 12:03:33 +00:00
@mock_aws
def test_iam_roles():
iam_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"my-launch-config": {
"Properties": {
"IamInstanceProfile": {"Ref": "my-instance-profile-with-path"},
"ImageId": EXAMPLE_AMI_ID,
"InstanceType": "t2.medium",
},
"Type": "AWS::AutoScaling::LaunchConfiguration",
},
"my-instance-profile-with-path": {
"Properties": {
"Path": "my-path",
"Roles": [{"Ref": "my-role-with-path"}],
},
"Type": "AWS::IAM::InstanceProfile",
},
"my-instance-profile-no-path": {
"Properties": {"Roles": [{"Ref": "my-role-no-path"}]},
"Type": "AWS::IAM::InstanceProfile",
},
"my-role-with-path": {
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": ["sts:AssumeRole"],
"Effect": "Allow",
"Principal": {"Service": ["ec2.amazonaws.com"]},
}
]
},
"Path": "/my-path/",
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": [
"ec2:CreateTags",
"ec2:DescribeInstances",
"ec2:DescribeTags",
],
"Effect": "Allow",
"Resource": ["*"],
}
],
"Version": "2012-10-17",
},
"PolicyName": "EC2_Tags",
},
{
"PolicyDocument": {
"Statement": [
{
"Action": ["sqs:*"],
"Effect": "Allow",
"Resource": ["*"],
}
],
"Version": "2012-10-17",
},
"PolicyName": "SQS",
},
],
},
"Type": "AWS::IAM::Role",
},
"my-role-no-path": {
"Properties": {
"RoleName": "my-role-no-path-name",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": ["sts:AssumeRole"],
"Effect": "Allow",
"Principal": {"Service": ["ec2.amazonaws.com"]},
}
]
},
},
"Type": "AWS::IAM::Role",
},
},
}
iam_template_json = json.dumps(iam_template)
cf = boto3.client("cloudformation", region_name="us-west-1")
cf.create_stack(StackName="test_stack", TemplateBody=iam_template_json)
iam = boto3.client("iam", region_name="us-west-1")
role_results = iam.list_roles()["Roles"]
role_name_to_id = {}
role_names = []
for role_result in role_results:
role = iam.get_role(RoleName=role_result["RoleName"])["Role"]
role_names.append(role["RoleName"])
# Role name is not specified, so randomly generated - can't check exact name
if "with-path" in role["RoleName"]:
role_name_to_id["with-path"] = role["RoleId"]
assert role["Path"] == "/my-path/"
else:
role_name_to_id["no-path"] = role["RoleId"]
assert role["RoleName"] == "my-role-no-path-name"
assert role["Path"] == "/"
instance_profile_responses = iam.list_instance_profiles()["InstanceProfiles"]
assert len(instance_profile_responses) == 2
instance_profile_names = []
for instance_profile_response in instance_profile_responses:
instance_profile = iam.get_instance_profile(
InstanceProfileName=instance_profile_response["InstanceProfileName"]
)["InstanceProfile"]
instance_profile_names.append(instance_profile["InstanceProfileName"])
assert "my-instance-profile" in instance_profile["InstanceProfileName"]
if "with-path" in instance_profile["InstanceProfileName"]:
assert instance_profile["Path"] == "my-path"
assert (
instance_profile["Roles"][0]["RoleId"] == role_name_to_id["with-path"]
)
else:
assert "no-path" in instance_profile["InstanceProfileName"]
assert instance_profile["Roles"][0]["RoleId"] == role_name_to_id["no-path"]
assert instance_profile["Path"] == "/"
autoscale = boto3.client("autoscaling", region_name="us-west-1")
launch_config = autoscale.describe_launch_configurations()["LaunchConfigurations"][
0
]
assert "my-instance-profile-with-path" in launch_config["IamInstanceProfile"]
resources = cf.list_stack_resources(StackName="test_stack")[
"StackResourceSummaries"
]
instance_profile_resources = [
resource
for resource in resources
if resource["ResourceType"] == "AWS::IAM::InstanceProfile"
]
assert {ip["PhysicalResourceId"] for ip in instance_profile_resources} == set(
instance_profile_names
)
role_resources = [
resource
for resource in resources
if resource["ResourceType"] == "AWS::IAM::Role"
]
assert {r["PhysicalResourceId"] for r in role_resources} == set(role_names)