IAM - Implement ServiceLinkedRoles (#5089)
This commit is contained in:
		
							parent
							
								
									578de3d47f
								
							
						
					
					
						commit
						76fe578d95
					
				| @ -2903,7 +2903,7 @@ | ||||
| 
 | ||||
| ## iam | ||||
| <details> | ||||
| <summary>71% implemented</summary> | ||||
| <summary>73% implemented</summary> | ||||
| 
 | ||||
| - [ ] add_client_id_to_open_id_connect_provider | ||||
| - [X] add_role_to_instance_profile | ||||
| @ -2922,7 +2922,7 @@ | ||||
| - [X] create_policy_version | ||||
| - [X] create_role | ||||
| - [X] create_saml_provider | ||||
| - [ ] create_service_linked_role | ||||
| - [X] create_service_linked_role | ||||
| - [ ] create_service_specific_credential | ||||
| - [X] create_user | ||||
| - [X] create_virtual_mfa_device | ||||
| @ -2942,7 +2942,7 @@ | ||||
| - [X] delete_role_policy | ||||
| - [X] delete_saml_provider | ||||
| - [X] delete_server_certificate | ||||
| - [ ] delete_service_linked_role | ||||
| - [X] delete_service_linked_role | ||||
| - [ ] delete_service_specific_credential | ||||
| - [X] delete_signing_certificate | ||||
| - [X] delete_ssh_public_key | ||||
| @ -2978,7 +2978,7 @@ | ||||
| - [X] get_server_certificate | ||||
| - [ ] get_service_last_accessed_details | ||||
| - [ ] get_service_last_accessed_details_with_entities | ||||
| - [ ] get_service_linked_role_deletion_status | ||||
| - [X] get_service_linked_role_deletion_status | ||||
| - [X] get_ssh_public_key | ||||
| - [X] get_user | ||||
| - [X] get_user_policy | ||||
|  | ||||
| @ -42,7 +42,7 @@ iam | ||||
| - [X] create_policy_version | ||||
| - [X] create_role | ||||
| - [X] create_saml_provider | ||||
| - [ ] create_service_linked_role | ||||
| - [X] create_service_linked_role | ||||
| - [ ] create_service_specific_credential | ||||
| - [X] create_user | ||||
| - [X] create_virtual_mfa_device | ||||
| @ -64,7 +64,7 @@ iam | ||||
| - [X] delete_role_policy | ||||
| - [X] delete_saml_provider | ||||
| - [X] delete_server_certificate | ||||
| - [ ] delete_service_linked_role | ||||
| - [X] delete_service_linked_role | ||||
| - [ ] delete_service_specific_credential | ||||
| - [X] delete_signing_certificate | ||||
| - [X] delete_ssh_public_key | ||||
| @ -106,7 +106,11 @@ iam | ||||
| - [X] get_server_certificate | ||||
| - [ ] get_service_last_accessed_details | ||||
| - [ ] get_service_last_accessed_details_with_entities | ||||
| - [ ] get_service_linked_role_deletion_status | ||||
| - [X] get_service_linked_role_deletion_status | ||||
|    | ||||
|         This method always succeeds for now - we do not yet keep track of deletions | ||||
|          | ||||
| 
 | ||||
| - [X] get_ssh_public_key | ||||
| - [X] get_user | ||||
| - [X] get_user_policy | ||||
|  | ||||
| @ -4,6 +4,7 @@ import os | ||||
| import random | ||||
| import string | ||||
| import sys | ||||
| import uuid | ||||
| from datetime import datetime | ||||
| import json | ||||
| import re | ||||
| @ -12,6 +13,7 @@ import time | ||||
| from cryptography import x509 | ||||
| from cryptography.hazmat.backends import default_backend | ||||
| 
 | ||||
| from jinja2 import Template | ||||
| from urllib import parse | ||||
| from moto.core.exceptions import RESTError | ||||
| from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel | ||||
| @ -47,6 +49,15 @@ from .utils import ( | ||||
| from ..utilities.tagging_service import TaggingService | ||||
| 
 | ||||
| 
 | ||||
| # Map to convert service names used in ServiceLinkedRoles | ||||
| # The PascalCase should be used as part of the RoleName | ||||
| SERVICE_NAME_CONVERSION = { | ||||
|     "autoscaling": "AutoScaling", | ||||
|     "application-autoscaling": "ApplicationAutoScaling", | ||||
|     "elasticbeanstalk": "ElasticBeanstalk", | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class MFADevice(object): | ||||
|     """MFA Device class.""" | ||||
| 
 | ||||
| @ -556,6 +567,7 @@ class Role(CloudFormationModel): | ||||
|         description, | ||||
|         tags, | ||||
|         max_session_duration, | ||||
|         linked_service=None, | ||||
|     ): | ||||
|         self.id = role_id | ||||
|         self.name = name | ||||
| @ -568,6 +580,7 @@ class Role(CloudFormationModel): | ||||
|         self.description = description | ||||
|         self.permissions_boundary = permissions_boundary | ||||
|         self.max_session_duration = max_session_duration | ||||
|         self._linked_service = linked_service | ||||
| 
 | ||||
|     @property | ||||
|     def created_iso_8601(self): | ||||
| @ -622,6 +635,8 @@ class Role(CloudFormationModel): | ||||
| 
 | ||||
|     @property | ||||
|     def arn(self): | ||||
|         if self._linked_service: | ||||
|             return f"arn:aws:iam::{ACCOUNT_ID}:role/aws-service-role/{self._linked_service}/{self.name}" | ||||
|         return "arn:aws:iam::{0}:role{1}{2}".format(ACCOUNT_ID, self.path, self.name) | ||||
| 
 | ||||
|     def to_config_dict(self): | ||||
| @ -722,6 +737,41 @@ class Role(CloudFormationModel): | ||||
| 
 | ||||
|         return html.escape(self.description or "") | ||||
| 
 | ||||
|     def to_xml(self): | ||||
|         template = Template( | ||||
|             """<Role> | ||||
|       <Path>{{ role.path }}</Path> | ||||
|       <Arn>{{ role.arn }}</Arn> | ||||
|       <RoleName>{{ role.name }}</RoleName> | ||||
|       <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> | ||||
|       {% if role.description is not none %} | ||||
|       <Description>{{ role.description_escaped }}</Description> | ||||
|       {% endif %} | ||||
|       <CreateDate>{{ role.created_iso_8601 }}</CreateDate> | ||||
|       <RoleId>{{ role.id }}</RoleId> | ||||
|       {% if role.max_session_duration %} | ||||
|       <MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration> | ||||
|       {% endif %} | ||||
|       {% if role.permissions_boundary %} | ||||
|       <PermissionsBoundary> | ||||
|           <PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType> | ||||
|           <PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn> | ||||
|       </PermissionsBoundary> | ||||
|       {% endif %} | ||||
|       {% if role.tags %} | ||||
|       <Tags> | ||||
|         {% for tag in role.get_tags() %} | ||||
|         <member> | ||||
|             <Key>{{ tag['Key'] }}</Key> | ||||
|             <Value>{{ tag['Value'] }}</Value> | ||||
|         </member> | ||||
|         {% endfor %} | ||||
|       </Tags> | ||||
|       {% endif %} | ||||
|     </Role>""" | ||||
|         ) | ||||
|         return template.render(role=self) | ||||
| 
 | ||||
| 
 | ||||
| class InstanceProfile(CloudFormationModel): | ||||
|     def __init__(self, instance_profile_id, name, path, roles, tags=None): | ||||
| @ -1707,6 +1757,7 @@ class IAMBackend(BaseBackend): | ||||
|         description, | ||||
|         tags, | ||||
|         max_session_duration, | ||||
|         linked_service=None, | ||||
|     ): | ||||
|         role_id = random_resource_id() | ||||
|         if permissions_boundary and not self.policy_arn_regex.match( | ||||
| @ -1733,6 +1784,7 @@ class IAMBackend(BaseBackend): | ||||
|             description, | ||||
|             clean_tags, | ||||
|             max_session_duration, | ||||
|             linked_service=linked_service, | ||||
|         ) | ||||
|         self.roles[role_id] = role | ||||
|         return role | ||||
| @ -2813,5 +2865,51 @@ class IAMBackend(BaseBackend): | ||||
| 
 | ||||
|         self.tagger.untag_resource_using_names(user.arn, tag_keys) | ||||
| 
 | ||||
|     def create_service_linked_role(self, service_name, description, suffix): | ||||
|         # service.amazonaws.com -> Service | ||||
|         # some-thing.service.amazonaws.com -> Service_SomeThing | ||||
|         service = service_name.split(".")[-3] | ||||
|         prefix = service_name.split(".")[0] | ||||
|         if service != prefix: | ||||
|             prefix = "".join([x.capitalize() for x in prefix.split("-")]) | ||||
|             service = SERVICE_NAME_CONVERSION.get(service, service) + "_" + prefix | ||||
|         else: | ||||
|             service = SERVICE_NAME_CONVERSION.get(service, service) | ||||
|         role_name = f"AWSServiceRoleFor{service}" | ||||
|         if suffix: | ||||
|             role_name = role_name + f"_{suffix}" | ||||
|         assume_role_policy_document = { | ||||
|             "Version": "2012-10-17", | ||||
|             "Statement": [ | ||||
|                 { | ||||
|                     "Action": ["sts:AssumeRole"], | ||||
|                     "Effect": "Allow", | ||||
|                     "Principal": {"Service": [service_name]}, | ||||
|                 } | ||||
|             ], | ||||
|         } | ||||
|         path = f"/aws-service-role/{service_name}/" | ||||
|         return self.create_role( | ||||
|             role_name, | ||||
|             json.dumps(assume_role_policy_document), | ||||
|             path, | ||||
|             permissions_boundary=None, | ||||
|             description=description, | ||||
|             tags=[], | ||||
|             max_session_duration=None, | ||||
|             linked_service=service_name, | ||||
|         ) | ||||
| 
 | ||||
|     def delete_service_linked_role(self, role_name): | ||||
|         self.delete_role(role_name) | ||||
|         deletion_task_id = str(uuid.uuid4()) | ||||
|         return deletion_task_id | ||||
| 
 | ||||
|     def get_service_linked_role_deletion_status(self): | ||||
|         """ | ||||
|         This method always succeeds for now - we do not yet keep track of deletions | ||||
|         """ | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| iam_backend = IAMBackend() | ||||
|  | ||||
| @ -1088,6 +1088,32 @@ class IamResponse(BaseResponse): | ||||
|         template = self.response_template(UNTAG_USER_TEMPLATE) | ||||
|         return template.render() | ||||
| 
 | ||||
|     def create_service_linked_role(self): | ||||
|         service_name = self._get_param("AWSServiceName") | ||||
|         description = self._get_param("Description") | ||||
|         suffix = self._get_param("CustomSuffix") | ||||
| 
 | ||||
|         role = iam_backend.create_service_linked_role(service_name, description, suffix) | ||||
| 
 | ||||
|         template = self.response_template(CREATE_SERVICE_LINKED_ROLE_TEMPLATE) | ||||
|         return template.render(role=role) | ||||
| 
 | ||||
|     def delete_service_linked_role(self): | ||||
|         role_name = self._get_param("RoleName") | ||||
| 
 | ||||
|         deletion_task_id = iam_backend.delete_service_linked_role(role_name) | ||||
| 
 | ||||
|         template = self.response_template(DELETE_SERVICE_LINKED_ROLE_TEMPLATE) | ||||
|         return template.render(deletion_task_id=deletion_task_id) | ||||
| 
 | ||||
|     def get_service_linked_role_deletion_status(self): | ||||
|         iam_backend.get_service_linked_role_deletion_status() | ||||
| 
 | ||||
|         template = self.response_template( | ||||
|             GET_SERVICE_LINKED_ROLE_DELETION_STATUS_TEMPLATE | ||||
|         ) | ||||
|         return template.render() | ||||
| 
 | ||||
| 
 | ||||
| LIST_ENTITIES_FOR_POLICY_TEMPLATE = """<ListEntitiesForPolicyResponse> | ||||
|  <ListEntitiesForPolicyResult> | ||||
| @ -1399,34 +1425,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia | ||||
| 
 | ||||
| CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <CreateRoleResult> | ||||
|     <Role> | ||||
|       <Path>{{ role.path }}</Path> | ||||
|       <Arn>{{ role.arn }}</Arn> | ||||
|       <RoleName>{{ role.name }}</RoleName> | ||||
|       <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> | ||||
|       {% if role.description is not none %} | ||||
|       <Description>{{ role.description_escaped }}</Description> | ||||
|       {% endif %} | ||||
|       <CreateDate>{{ role.created_iso_8601 }}</CreateDate> | ||||
|       <RoleId>{{ role.id }}</RoleId> | ||||
|       <MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration> | ||||
|       {% if role.permissions_boundary %} | ||||
|       <PermissionsBoundary> | ||||
|           <PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType> | ||||
|           <PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn> | ||||
|       </PermissionsBoundary> | ||||
|       {% endif %} | ||||
|       {% if role.tags %} | ||||
|       <Tags> | ||||
|         {% for tag in role.get_tags() %} | ||||
|         <member> | ||||
|             <Key>{{ tag['Key'] }}</Key> | ||||
|             <Value>{{ tag['Value'] }}</Value> | ||||
|         </member> | ||||
|         {% endfor %} | ||||
|       </Tags> | ||||
|       {% endif %} | ||||
|     </Role> | ||||
|     {{ role.to_xml() }} | ||||
|   </CreateRoleResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> | ||||
| @ -1444,6 +1443,33 @@ GET_ROLE_POLICY_TEMPLATE = """<GetRolePolicyResponse xmlns="https://iam.amazonaw | ||||
| </ResponseMetadata> | ||||
| </GetRolePolicyResponse>""" | ||||
| 
 | ||||
| CREATE_SERVICE_LINKED_ROLE_TEMPLATE = """<CreateServiceLinkedRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <CreateServiceLinkedRoleResult> | ||||
|     {{ role.to_xml() }} | ||||
|   </CreateServiceLinkedRoleResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> | ||||
|   </ResponseMetadata> | ||||
| </CreateServiceLinkedRoleResponse>""" | ||||
| 
 | ||||
| DELETE_SERVICE_LINKED_ROLE_TEMPLATE = """<DeleteServiceLinkedRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <DeleteServiceLinkedRoleResult> | ||||
|     <DeletionTaskId>{{ deletion_task_id }}</DeletionTaskId> | ||||
|   </DeleteServiceLinkedRoleResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> | ||||
|   </ResponseMetadata> | ||||
| </DeleteServiceLinkedRoleResponse>""" | ||||
| 
 | ||||
| GET_SERVICE_LINKED_ROLE_DELETION_STATUS_TEMPLATE = """<GetServiceLinkedRoleDeletionStatusResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <GetServiceLinkedRoleDeletionStatusResult> | ||||
|     <Status>SUCCEEDED</Status> | ||||
|   </GetServiceLinkedRoleDeletionStatusResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> | ||||
|   </ResponseMetadata> | ||||
| </GetServiceLinkedRoleDeletionStatusResponse>""" | ||||
| 
 | ||||
| UPDATE_ROLE_TEMPLATE = """<UpdateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <UpdateRoleResult> | ||||
|   </UpdateRoleResult> | ||||
| @ -1454,28 +1480,7 @@ UPDATE_ROLE_TEMPLATE = """<UpdateRoleResponse xmlns="https://iam.amazonaws.com/d | ||||
| 
 | ||||
| UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <UpdateRoleDescriptionResult> | ||||
|     <Role> | ||||
|       <Path>{{ role.path }}</Path> | ||||
|       <Arn>{{ role.arn }}</Arn> | ||||
|       <RoleName>{{ role.name }}</RoleName> | ||||
|       <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> | ||||
|       {% if role.description is not none %} | ||||
|       <Description>{{ role.description_escaped }}</Description> | ||||
|       {% endif %} | ||||
|       <CreateDate>{{ role.created_iso_8601 }}</CreateDate> | ||||
|       <RoleId>{{ role.id }}</RoleId> | ||||
|       <MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration> | ||||
|       {% if role.tags %} | ||||
|       <Tags> | ||||
|         {% for tag in role.get_tags() %} | ||||
|         <member> | ||||
|             <Key>{{ tag['Key'] }}</Key> | ||||
|             <Value>{{ tag['Value'] }}</Value> | ||||
|         </member> | ||||
|         {% endfor %} | ||||
|       </Tags> | ||||
|       {% endif %} | ||||
|     </Role> | ||||
|     {{ role.to_xml() }} | ||||
|   </UpdateRoleDescriptionResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId> | ||||
| @ -1484,34 +1489,7 @@ UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="http | ||||
| 
 | ||||
| GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||
|   <GetRoleResult> | ||||
|     <Role> | ||||
|       <Path>{{ role.path }}</Path> | ||||
|       <Arn>{{ role.arn }}</Arn> | ||||
|       <RoleName>{{ role.name }}</RoleName> | ||||
|       <AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument> | ||||
|       {% if role.description is not none %} | ||||
|       <Description>{{ role.description_escaped }}</Description> | ||||
|       {% endif %} | ||||
|       <CreateDate>{{ role.created_iso_8601 }}</CreateDate> | ||||
|       <RoleId>{{ role.id }}</RoleId> | ||||
|       <MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration> | ||||
|       {% if role.permissions_boundary %} | ||||
|       <PermissionsBoundary> | ||||
|           <PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType> | ||||
|           <PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn> | ||||
|       </PermissionsBoundary> | ||||
|       {% endif %} | ||||
|       {% if role.tags %} | ||||
|       <Tags> | ||||
|         {% for tag in role.get_tags() %} | ||||
|         <member> | ||||
|             <Key>{{ tag['Key'] }}</Key> | ||||
|             <Value>{{ tag['Value'] }}</Value> | ||||
|         </member> | ||||
|         {% endfor %} | ||||
|       </Tags> | ||||
|       {% endif %} | ||||
|     </Role> | ||||
|     {{ role.to_xml() }} | ||||
|   </GetRoleResult> | ||||
|   <ResponseMetadata> | ||||
|     <RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId> | ||||
|  | ||||
| @ -25,7 +25,7 @@ index 51e5d1c9c7..057446ae1d 100644 | ||||
|  		Target:  []string{iam.DeletionTaskStatusTypeSucceeded}, | ||||
|  		Refresh: statusDeleteServiceLinkedRole(conn, deletionTaskID), | ||||
| -		Timeout: 5 * time.Minute,
 | ||||
| +		Timeout: 5 * time.Second,
 | ||||
| +		Timeout: 15 * time.Second,
 | ||||
|  		Delay:   10 * time.Second, | ||||
|  	} | ||||
|   | ||||
|  | ||||
| @ -91,6 +91,7 @@ iam: | ||||
|   - TestAccIAMRolePolicy_ | ||||
|   - TestAccIAMRolePolicyAttachment_ | ||||
|   - TestAccIAMSessionContextDataSource_ | ||||
|   - TestAccIAMServiceLinkedRole | ||||
|   - TestAccIAMUserDataSource_ | ||||
|   - TestAccIAMUserPolicy_ | ||||
|   - TestAccIAMUserPolicyAttachment_ | ||||
| @ -100,6 +101,10 @@ iot: | ||||
|   - TestAccIoTEndpointDataSource | ||||
| kms: | ||||
|   - TestAccKMSAlias | ||||
|   - TestAccKMSKey_Policy_basic | ||||
|   - TestAccKMSKey_Policy_iamRole | ||||
|   - TestAccKMSKey_Policy_iamRoleOrder | ||||
|   - TestAccKMSKey_Policy_iamServiceLinkedRole | ||||
|   - TestAccKMSSecretDataSource | ||||
|   - TestAccKMSSecretsDataSource | ||||
| meta: | ||||
|  | ||||
| @ -4407,3 +4407,82 @@ def test_untag_user_error_unknown_user_name(): | ||||
|     ex.response["Error"]["Message"].should.equal( | ||||
|         "The user with name {} cannot be found.".format(name) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @mock_iam | ||||
| @pytest.mark.parametrize( | ||||
|     "service,cased", | ||||
|     [ | ||||
|         ("autoscaling", "AutoScaling"), | ||||
|         ("elasticbeanstalk", "ElasticBeanstalk"), | ||||
|         ( | ||||
|             "custom-resource.application-autoscaling", | ||||
|             "ApplicationAutoScaling_CustomResource", | ||||
|         ), | ||||
|         ("other", "other"), | ||||
|     ], | ||||
| ) | ||||
| def test_create_service_linked_role(service, cased): | ||||
|     client = boto3.client("iam", region_name="eu-central-1") | ||||
| 
 | ||||
|     resp = client.create_service_linked_role( | ||||
|         AWSServiceName=f"{service}.amazonaws.com", Description="desc" | ||||
|     )["Role"] | ||||
| 
 | ||||
|     resp.should.have.key("RoleName").equals(f"AWSServiceRoleFor{cased}") | ||||
| 
 | ||||
| 
 | ||||
| @mock_iam | ||||
| def test_create_service_linked_role__with_suffix(): | ||||
|     client = boto3.client("iam", region_name="eu-central-1") | ||||
| 
 | ||||
|     resp = client.create_service_linked_role( | ||||
|         AWSServiceName="autoscaling.amazonaws.com", | ||||
|         CustomSuffix="suf", | ||||
|         Description="desc", | ||||
|     )["Role"] | ||||
| 
 | ||||
|     resp.should.have.key("RoleName").match("_suf$") | ||||
|     resp.should.have.key("Description").equals("desc") | ||||
|     resp.should.have.key("AssumeRolePolicyDocument") | ||||
|     policy_doc = resp["AssumeRolePolicyDocument"] | ||||
|     policy_doc.should.have.key("Statement").equals( | ||||
|         [ | ||||
|             { | ||||
|                 "Action": ["sts:AssumeRole"], | ||||
|                 "Effect": "Allow", | ||||
|                 "Principal": {"Service": ["autoscaling.amazonaws.com"]}, | ||||
|             } | ||||
|         ] | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| @mock_iam | ||||
| def test_delete_service_linked_role(): | ||||
|     client = boto3.client("iam", region_name="eu-central-1") | ||||
| 
 | ||||
|     role_name = client.create_service_linked_role( | ||||
|         AWSServiceName="autoscaling.amazonaws.com", | ||||
|         CustomSuffix="suf", | ||||
|         Description="desc", | ||||
|     )["Role"]["RoleName"] | ||||
| 
 | ||||
|     # Role exists | ||||
|     client.get_role(RoleName=role_name) | ||||
| 
 | ||||
|     # Delete role | ||||
|     resp = client.delete_service_linked_role(RoleName=role_name) | ||||
|     resp.should.have.key("DeletionTaskId") | ||||
| 
 | ||||
|     # Role deletion should be successful | ||||
|     resp = client.get_service_linked_role_deletion_status( | ||||
|         DeletionTaskId=resp["DeletionTaskId"] | ||||
|     ) | ||||
|     resp.should.have.key("Status").equals("SUCCEEDED") | ||||
| 
 | ||||
|     # Role no longer exists | ||||
|     with pytest.raises(ClientError) as ex: | ||||
|         client.get_role(RoleName=role_name) | ||||
|     err = ex.value.response["Error"] | ||||
|     err["Code"].should.equal("NoSuchEntity") | ||||
|     err["Message"].should.contain("not found") | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user