IAM - Delete Role/InstanceProfile via CloudFormation (#3591)
This commit is contained in:
parent
027d05e21c
commit
1a42b33781
@ -347,8 +347,9 @@ def parse_and_update_resource(logical_id, resource_json, resources_map, region_n
|
||||
return None
|
||||
|
||||
|
||||
def parse_and_delete_resource(resource_name, resource_json, resources_map, region_name):
|
||||
resource_class, resource_json, _ = parse_resource(resource_json, resources_map)
|
||||
def parse_and_delete_resource(resource_name, resource_json, region_name):
|
||||
resource_type = resource_json["Type"]
|
||||
resource_class = resource_class_from_type(resource_type)
|
||||
if not hasattr(
|
||||
resource_class.delete_from_cloudformation_json, "__isabstractmethod__"
|
||||
):
|
||||
@ -663,9 +664,7 @@ class ResourceMap(collections_abc.Mapping):
|
||||
].physical_resource_id
|
||||
else:
|
||||
resource_name = None
|
||||
parse_and_delete_resource(
|
||||
resource_name, resource_json, self, self._region_name
|
||||
)
|
||||
parse_and_delete_resource(resource_name, resource_json, self._region_name)
|
||||
self._parsed_resources.pop(logical_name)
|
||||
|
||||
self._template = template
|
||||
@ -726,7 +725,7 @@ class ResourceMap(collections_abc.Mapping):
|
||||
]
|
||||
|
||||
parse_and_delete_resource(
|
||||
resource_name, resource_json, self, self._region_name,
|
||||
resource_name, resource_json, self._region_name,
|
||||
)
|
||||
|
||||
self._parsed_resources.pop(parsed_resource.logical_resource_id)
|
||||
|
@ -598,6 +598,19 @@ class Role(CloudFormationModel):
|
||||
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
def delete_from_cloudformation_json(
|
||||
cls, resource_name, cloudformation_json, region_name
|
||||
):
|
||||
for profile_name, profile in iam_backend.instance_profiles.items():
|
||||
profile.delete_role(role_name=resource_name)
|
||||
|
||||
for _, role in iam_backend.roles.items():
|
||||
if role.name == resource_name:
|
||||
for arn, policy in role.policies.items():
|
||||
role.delete_policy(arn)
|
||||
iam_backend.delete_role(resource_name)
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return "arn:aws:iam::{0}:role{1}{2}".format(ACCOUNT_ID, self.path, self.name)
|
||||
@ -678,7 +691,7 @@ class Role(CloudFormationModel):
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.id
|
||||
return self.name
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
@ -725,13 +738,22 @@ class InstanceProfile(CloudFormationModel):
|
||||
):
|
||||
properties = cloudformation_json["Properties"]
|
||||
|
||||
role_ids = properties["Roles"]
|
||||
role_names = properties["Roles"]
|
||||
return iam_backend.create_instance_profile(
|
||||
name=resource_physical_name,
|
||||
path=properties.get("Path", "/"),
|
||||
role_ids=role_ids,
|
||||
role_names=role_names,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete_from_cloudformation_json(
|
||||
cls, resource_name, cloudformation_json, region_name
|
||||
):
|
||||
iam_backend.delete_instance_profile(resource_name)
|
||||
|
||||
def delete_role(self, role_name):
|
||||
self.roles = [role for role in self.roles if role.name != role_name]
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return "arn:aws:iam::{0}:instance-profile{1}{2}".format(
|
||||
@ -1878,7 +1900,7 @@ class IAMBackend(BaseBackend):
|
||||
return
|
||||
raise IAMNotFoundException("Policy not found")
|
||||
|
||||
def create_instance_profile(self, name, path, role_ids, tags=None):
|
||||
def create_instance_profile(self, name, path, role_names, tags=None):
|
||||
if self.instance_profiles.get(name):
|
||||
raise IAMConflictException(
|
||||
code="EntityAlreadyExists",
|
||||
@ -1887,7 +1909,7 @@ class IAMBackend(BaseBackend):
|
||||
|
||||
instance_profile_id = random_resource_id()
|
||||
|
||||
roles = [iam_backend.get_role_by_id(role_id) for role_id in role_ids]
|
||||
roles = [iam_backend.get_role(role_name) for role_name in role_names]
|
||||
instance_profile = InstanceProfile(instance_profile_id, name, path, roles, tags)
|
||||
self.instance_profiles[name] = instance_profile
|
||||
return instance_profile
|
||||
|
@ -324,7 +324,7 @@ class IamResponse(BaseResponse):
|
||||
tags = self._get_multi_param("Tags.member")
|
||||
|
||||
profile = iam_backend.create_instance_profile(
|
||||
profile_name, path, role_ids=[], tags=tags
|
||||
profile_name, path, role_names=[], tags=tags
|
||||
)
|
||||
template = self.response_template(CREATE_INSTANCE_PROFILE_TEMPLATE)
|
||||
return template.render(profile=profile)
|
||||
|
@ -948,6 +948,7 @@ def test_iam_roles():
|
||||
"roles"
|
||||
]
|
||||
role_name_to_id = {}
|
||||
role_names = []
|
||||
for role_result in role_results:
|
||||
role = iam_conn.get_role(role_result.role_name)
|
||||
# Role name is not specified, so randomly generated - can't check exact name
|
||||
@ -958,6 +959,7 @@ def test_iam_roles():
|
||||
role_name_to_id["no-path"] = role.role_id
|
||||
role.role_name.should.equal("my-role-no-path-name")
|
||||
role.path.should.equal("/")
|
||||
role_names.append(role.role_name)
|
||||
|
||||
instance_profile_responses = iam_conn.list_instance_profiles()[
|
||||
"list_instance_profiles_response"
|
||||
@ -997,9 +999,7 @@ def test_iam_roles():
|
||||
role_resources = [
|
||||
resource for resource in resources if resource.resource_type == "AWS::IAM::Role"
|
||||
]
|
||||
{r.physical_resource_id for r in role_resources}.should.equal(
|
||||
set(role_name_to_id.values())
|
||||
)
|
||||
{r.physical_resource_id for r in role_resources}.should.equal(set(role_names))
|
||||
|
||||
|
||||
@mock_ec2_deprecated()
|
||||
|
@ -8,6 +8,58 @@ from botocore.exceptions import ClientError
|
||||
from moto import mock_iam, mock_cloudformation, mock_s3, mock_sts
|
||||
from moto.core import ACCOUNT_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
|
||||
@mock_iam
|
||||
@mock_cloudformation
|
||||
@ -1384,3 +1436,62 @@ Resources:
|
||||
|
||||
access_keys = iam_client.list_access_keys(UserName=other_user_name)
|
||||
access_key_id.should_not.equal(access_keys["AccessKeyMetadata"][0]["AccessKeyId"])
|
||||
|
||||
|
||||
@mock_iam
|
||||
@mock_cloudformation
|
||||
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]
|
||||
role["LogicalResourceId"].should.equal("RootRole")
|
||||
role_name = role["PhysicalResourceId"]
|
||||
|
||||
iam_client = boto3.client("iam", region_name="us-east-1")
|
||||
iam_client.list_roles()["Roles"].should.have.length_of(1)
|
||||
|
||||
cf_client.delete_stack(StackName=stack_name)
|
||||
|
||||
iam_client.list_roles()["Roles"].should.have.length_of(0)
|
||||
|
||||
|
||||
@mock_iam
|
||||
@mock_cloudformation
|
||||
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]
|
||||
role["LogicalResourceId"].should.equal("RootRole")
|
||||
role["PhysicalResourceId"].should.equal(role_name)
|
||||
profile = [
|
||||
res for res in resources if res["ResourceType"] == "AWS::IAM::InstanceProfile"
|
||||
][0]
|
||||
profile["LogicalResourceId"].should.equal("RootInstanceProfile")
|
||||
profile["PhysicalResourceId"].should.contain(
|
||||
stack_name
|
||||
) # e.g. MyStack-RootInstanceProfile-73Y4H4ALFW3N
|
||||
profile["PhysicalResourceId"].should.contain("RootInstanceProfile")
|
||||
|
||||
iam_client = boto3.client("iam", region_name="us-east-1")
|
||||
iam_client.list_roles()["Roles"].should.have.length_of(1)
|
||||
|
||||
cf_client.delete_stack(StackName=stack_name)
|
||||
|
||||
iam_client.list_roles()["Roles"].should.have.length_of(0)
|
||||
|
Loading…
Reference in New Issue
Block a user