diff --git a/moto/iam/models.py b/moto/iam/models.py index 58ada80c1..3b31fb338 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -253,7 +253,7 @@ class PolicyVersion(object): return iso_8601_datetime_with_milliseconds(self.create_date) -class ManagedPolicy(Policy): +class ManagedPolicy(Policy, CloudFormationModel): """Managed policy.""" is_attachable = True @@ -312,6 +312,47 @@ class ManagedPolicy(Policy): "supplementaryConfiguration": {}, } + @staticmethod + def cloudformation_name_type(): + return None # Resource never gets named after by template PolicyName! + + @staticmethod + def cloudformation_type(): + return "AWS::IAM::ManagedPolicy" + + @classmethod + def create_from_cloudformation_json( + cls, resource_physical_name, cloudformation_json, region_name + ): + properties = cloudformation_json.get("Properties", {}) + policy_document = json.dumps(properties.get("PolicyDocument")) + name = properties.get("ManagedPolicyName", resource_physical_name) + description = properties.get("Description") + path = properties.get("Path") + group_names = properties.get("Groups", []) + user_names = properties.get("Users", []) + role_names = properties.get("Roles", []) + + policy = iam_backend.create_policy( + description=description, + path=path, + policy_document=policy_document, + policy_name=name, + ) + for group_name in group_names: + iam_backend.attach_group_policy( + group_name=group_name, policy_arn=policy.arn + ) + for user_name in user_names: + iam_backend.attach_user_policy(user_name=user_name, policy_arn=policy.arn) + for role_name in role_names: + iam_backend.attach_role_policy(role_name=role_name, policy_arn=policy.arn) + return policy + + @property + def physical_resource_id(self): + return self.arn + class AWSManagedPolicy(ManagedPolicy): """AWS-managed policy.""" diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 86f682d12..3ca26bfb2 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -125,7 +125,7 @@ class IamResponse(BaseResponse): entity_groups = [] entity_users = [] - if entity == "User": + if not entity or entity == "User": users = iam_backend.list_users(path_prefix, marker, max_items) if users: for user in users: @@ -133,7 +133,7 @@ class IamResponse(BaseResponse): if p == policy_arn: entity_users.append(user.name) - elif entity == "Role": + if not entity or entity == "Role": roles, _ = iam_backend.list_roles(path_prefix, marker, max_items) if roles: for role in roles: @@ -141,7 +141,7 @@ class IamResponse(BaseResponse): if p == policy_arn: entity_roles.append(role.name) - elif entity == "Group": + if not entity or entity == "Group": groups = iam_backend.list_groups() if groups: for group in groups: @@ -149,7 +149,7 @@ class IamResponse(BaseResponse): if p == policy_arn: entity_groups.append(group.name) - elif entity == "LocalManagedPolicy" or entity == "AWSManagedPolicy": + if entity == "LocalManagedPolicy" or entity == "AWSManagedPolicy": users = iam_backend.list_users(path_prefix, marker, max_items) if users: for user in users: diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 558472295..b84ca5718 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -2357,18 +2357,24 @@ def test_list_entities_for_policy(): EntityFilter="Role", ) assert response["PolicyRoles"] == [{"RoleName": "my-role"}] + response["PolicyGroups"].should.equal([]) + response["PolicyUsers"].should.equal([]) response = conn.list_entities_for_policy( PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), EntityFilter="User", ) assert response["PolicyUsers"] == [{"UserName": "testUser"}] + response["PolicyGroups"].should.equal([]) + response["PolicyRoles"].should.equal([]) response = conn.list_entities_for_policy( PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), EntityFilter="Group", ) assert response["PolicyGroups"] == [{"GroupName": "testGroup"}] + response["PolicyRoles"].should.equal([]) + response["PolicyUsers"].should.equal([]) response = conn.list_entities_for_policy( PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID), @@ -2378,6 +2384,14 @@ def test_list_entities_for_policy(): assert response["PolicyUsers"] == [{"UserName": "testUser"}] assert response["PolicyRoles"] == [{"RoleName": "my-role"}] + # Return everything when no entity is specified + response = conn.list_entities_for_policy( + PolicyArn="arn:aws:iam::{}:policy/testPolicy".format(ACCOUNT_ID) + ) + response["PolicyGroups"].should.equal([{"GroupName": "testGroup"}]) + response["PolicyUsers"].should.equal([{"UserName": "testUser"}]) + response["PolicyRoles"].should.equal([{"RoleName": "my-role"}]) + @mock_iam() def test_create_role_no_path(): diff --git a/tests/test_iam/test_iam_cloudformation.py b/tests/test_iam/test_iam_cloudformation.py index 6e777dd57..0196eb07d 100644 --- a/tests/test_iam/test_iam_cloudformation.py +++ b/tests/test_iam/test_iam_cloudformation.py @@ -6,6 +6,7 @@ import pytest from botocore.exceptions import ClientError from moto import mock_iam, mock_cloudformation, mock_s3, mock_sts +from moto.core import ACCOUNT_ID # AWS::IAM::User Tests @mock_iam @@ -281,6 +282,245 @@ Outputs: output_user_arn.should.equal(user_description["Arn"]) +# AWS::IAM::ManagedPolicy Tests +@mock_iam +@mock_cloudformation +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"] + logical_resource_id.should.equal("ThePolicy") + + policy_arn = provisioned_resource["PhysicalResourceId"] + policy_arn.should.match( + "arn:aws:iam::{}:policy/MyStack-ThePolicy-[A-Z0-9]+".format(ACCOUNT_ID) + ) + expected_name = policy_arn.split("/")[1] + + response = iam_client.list_entities_for_policy(PolicyArn=policy_arn) + response.should.have.key("PolicyGroups").equal([]) + response.should.have.key("PolicyUsers").equal([]) + response.should.have.key("PolicyRoles").equal([]) + + policy = iam_client.get_policy(PolicyArn=policy_arn)["Policy"] + policy.should.have.key("Arn").equal(policy_arn) + policy.should.have.key("PolicyName").equal(expected_name) + policy.should.have.key("Description").equal("") + policy.should.have.key("Path").equal("/") + + +@mock_iam +@mock_cloudformation +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"] + logical_resource_id.should.equal("ThePolicy") + + policy_arn = provisioned_resource["PhysicalResourceId"] + policy_arn.should.equal("arn:aws:iam::{}:policy/{}".format(ACCOUNT_ID, name)) + + policy = iam_client.get_policy(PolicyArn=policy_arn)["Policy"] + policy.should.have.key("Arn").equal(policy_arn) + policy.should.have.key("Path").equal("/") + policy.should.have.key("Description").equal(desc) + policy.should.have.key("PolicyName").equal(name) + + +@mock_iam +@mock_cloudformation +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"] + logical_resource_id.should.equal("ThePolicy") + + policy_arn = provisioned_resource["PhysicalResourceId"] + policy_arn.should.match( + "rn:aws:iam::{}:policy/MyStack-ThePolicy-[A-Z0-9]+".format(ACCOUNT_ID) + ) + + response = iam_client.list_entities_for_policy(PolicyArn=policy_arn) + response.should.have.key("PolicyGroups").equal([{"GroupName": group_name}]) + response.should.have.key("PolicyUsers").equal([]) + response.should.have.key("PolicyRoles").equal([]) + + +@mock_iam +@mock_cloudformation +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"] + logical_resource_id.should.equal("ThePolicy") + + policy_arn = provisioned_resource["PhysicalResourceId"] + policy_arn.should.match( + "rn:aws:iam::{}:policy/MyStack-ThePolicy-[A-Z0-9]+".format(ACCOUNT_ID) + ) + + response = iam_client.list_entities_for_policy(PolicyArn=policy_arn) + response.should.have.key("PolicyGroups").equal([]) + response.should.have.key("PolicyUsers").equal([{"UserName": user_name}]) + response.should.have.key("PolicyRoles").equal([]) + + +@mock_iam +@mock_cloudformation +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"] + logical_resource_id.should.equal("ThePolicy") + + policy_arn = provisioned_resource["PhysicalResourceId"] + policy_arn.should.match( + "rn:aws:iam::{}:policy/MyStack-ThePolicy-[A-Z0-9]+".format(ACCOUNT_ID) + ) + + response = iam_client.list_entities_for_policy(PolicyArn=policy_arn) + response.should.have.key("PolicyGroups").equal([]) + response.should.have.key("PolicyUsers").equal([]) + response.should.have.key("PolicyRoles").equal([{"RoleName": role_name}]) + + # AWS::IAM::Policy Tests @mock_s3 @mock_iam