IAM - Implement ServiceLinkedRoles (#5089)
This commit is contained in:
		
							parent
							
								
									578de3d47f
								
							
						
					
					
						commit
						76fe578d95
					
				| @ -2903,7 +2903,7 @@ | |||||||
| 
 | 
 | ||||||
| ## iam | ## iam | ||||||
| <details> | <details> | ||||||
| <summary>71% implemented</summary> | <summary>73% implemented</summary> | ||||||
| 
 | 
 | ||||||
| - [ ] add_client_id_to_open_id_connect_provider | - [ ] add_client_id_to_open_id_connect_provider | ||||||
| - [X] add_role_to_instance_profile | - [X] add_role_to_instance_profile | ||||||
| @ -2922,7 +2922,7 @@ | |||||||
| - [X] create_policy_version | - [X] create_policy_version | ||||||
| - [X] create_role | - [X] create_role | ||||||
| - [X] create_saml_provider | - [X] create_saml_provider | ||||||
| - [ ] create_service_linked_role | - [X] create_service_linked_role | ||||||
| - [ ] create_service_specific_credential | - [ ] create_service_specific_credential | ||||||
| - [X] create_user | - [X] create_user | ||||||
| - [X] create_virtual_mfa_device | - [X] create_virtual_mfa_device | ||||||
| @ -2942,7 +2942,7 @@ | |||||||
| - [X] delete_role_policy | - [X] delete_role_policy | ||||||
| - [X] delete_saml_provider | - [X] delete_saml_provider | ||||||
| - [X] delete_server_certificate | - [X] delete_server_certificate | ||||||
| - [ ] delete_service_linked_role | - [X] delete_service_linked_role | ||||||
| - [ ] delete_service_specific_credential | - [ ] delete_service_specific_credential | ||||||
| - [X] delete_signing_certificate | - [X] delete_signing_certificate | ||||||
| - [X] delete_ssh_public_key | - [X] delete_ssh_public_key | ||||||
| @ -2978,7 +2978,7 @@ | |||||||
| - [X] get_server_certificate | - [X] get_server_certificate | ||||||
| - [ ] get_service_last_accessed_details | - [ ] get_service_last_accessed_details | ||||||
| - [ ] get_service_last_accessed_details_with_entities | - [ ] 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_ssh_public_key | ||||||
| - [X] get_user | - [X] get_user | ||||||
| - [X] get_user_policy | - [X] get_user_policy | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ iam | |||||||
| - [X] create_policy_version | - [X] create_policy_version | ||||||
| - [X] create_role | - [X] create_role | ||||||
| - [X] create_saml_provider | - [X] create_saml_provider | ||||||
| - [ ] create_service_linked_role | - [X] create_service_linked_role | ||||||
| - [ ] create_service_specific_credential | - [ ] create_service_specific_credential | ||||||
| - [X] create_user | - [X] create_user | ||||||
| - [X] create_virtual_mfa_device | - [X] create_virtual_mfa_device | ||||||
| @ -64,7 +64,7 @@ iam | |||||||
| - [X] delete_role_policy | - [X] delete_role_policy | ||||||
| - [X] delete_saml_provider | - [X] delete_saml_provider | ||||||
| - [X] delete_server_certificate | - [X] delete_server_certificate | ||||||
| - [ ] delete_service_linked_role | - [X] delete_service_linked_role | ||||||
| - [ ] delete_service_specific_credential | - [ ] delete_service_specific_credential | ||||||
| - [X] delete_signing_certificate | - [X] delete_signing_certificate | ||||||
| - [X] delete_ssh_public_key | - [X] delete_ssh_public_key | ||||||
| @ -106,7 +106,11 @@ iam | |||||||
| - [X] get_server_certificate | - [X] get_server_certificate | ||||||
| - [ ] get_service_last_accessed_details | - [ ] get_service_last_accessed_details | ||||||
| - [ ] get_service_last_accessed_details_with_entities | - [ ] 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_ssh_public_key | ||||||
| - [X] get_user | - [X] get_user | ||||||
| - [X] get_user_policy | - [X] get_user_policy | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import os | |||||||
| import random | import random | ||||||
| import string | import string | ||||||
| import sys | import sys | ||||||
|  | import uuid | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import json | import json | ||||||
| import re | import re | ||||||
| @ -12,6 +13,7 @@ import time | |||||||
| from cryptography import x509 | from cryptography import x509 | ||||||
| from cryptography.hazmat.backends import default_backend | from cryptography.hazmat.backends import default_backend | ||||||
| 
 | 
 | ||||||
|  | from jinja2 import Template | ||||||
| from urllib import parse | from urllib import parse | ||||||
| from moto.core.exceptions import RESTError | from moto.core.exceptions import RESTError | ||||||
| from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel | from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel | ||||||
| @ -47,6 +49,15 @@ from .utils import ( | |||||||
| from ..utilities.tagging_service import TaggingService | 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): | class MFADevice(object): | ||||||
|     """MFA Device class.""" |     """MFA Device class.""" | ||||||
| 
 | 
 | ||||||
| @ -556,6 +567,7 @@ class Role(CloudFormationModel): | |||||||
|         description, |         description, | ||||||
|         tags, |         tags, | ||||||
|         max_session_duration, |         max_session_duration, | ||||||
|  |         linked_service=None, | ||||||
|     ): |     ): | ||||||
|         self.id = role_id |         self.id = role_id | ||||||
|         self.name = name |         self.name = name | ||||||
| @ -568,6 +580,7 @@ class Role(CloudFormationModel): | |||||||
|         self.description = description |         self.description = description | ||||||
|         self.permissions_boundary = permissions_boundary |         self.permissions_boundary = permissions_boundary | ||||||
|         self.max_session_duration = max_session_duration |         self.max_session_duration = max_session_duration | ||||||
|  |         self._linked_service = linked_service | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def created_iso_8601(self): |     def created_iso_8601(self): | ||||||
| @ -622,6 +635,8 @@ class Role(CloudFormationModel): | |||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def arn(self): |     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) |         return "arn:aws:iam::{0}:role{1}{2}".format(ACCOUNT_ID, self.path, self.name) | ||||||
| 
 | 
 | ||||||
|     def to_config_dict(self): |     def to_config_dict(self): | ||||||
| @ -722,6 +737,41 @@ class Role(CloudFormationModel): | |||||||
| 
 | 
 | ||||||
|         return html.escape(self.description or "") |         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): | class InstanceProfile(CloudFormationModel): | ||||||
|     def __init__(self, instance_profile_id, name, path, roles, tags=None): |     def __init__(self, instance_profile_id, name, path, roles, tags=None): | ||||||
| @ -1707,6 +1757,7 @@ class IAMBackend(BaseBackend): | |||||||
|         description, |         description, | ||||||
|         tags, |         tags, | ||||||
|         max_session_duration, |         max_session_duration, | ||||||
|  |         linked_service=None, | ||||||
|     ): |     ): | ||||||
|         role_id = random_resource_id() |         role_id = random_resource_id() | ||||||
|         if permissions_boundary and not self.policy_arn_regex.match( |         if permissions_boundary and not self.policy_arn_regex.match( | ||||||
| @ -1733,6 +1784,7 @@ class IAMBackend(BaseBackend): | |||||||
|             description, |             description, | ||||||
|             clean_tags, |             clean_tags, | ||||||
|             max_session_duration, |             max_session_duration, | ||||||
|  |             linked_service=linked_service, | ||||||
|         ) |         ) | ||||||
|         self.roles[role_id] = role |         self.roles[role_id] = role | ||||||
|         return role |         return role | ||||||
| @ -2813,5 +2865,51 @@ class IAMBackend(BaseBackend): | |||||||
| 
 | 
 | ||||||
|         self.tagger.untag_resource_using_names(user.arn, tag_keys) |         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() | iam_backend = IAMBackend() | ||||||
|  | |||||||
| @ -1088,6 +1088,32 @@ class IamResponse(BaseResponse): | |||||||
|         template = self.response_template(UNTAG_USER_TEMPLATE) |         template = self.response_template(UNTAG_USER_TEMPLATE) | ||||||
|         return template.render() |         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> | LIST_ENTITIES_FOR_POLICY_TEMPLATE = """<ListEntitiesForPolicyResponse> | ||||||
|  <ListEntitiesForPolicyResult> |  <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/"> | CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||||
|   <CreateRoleResult> |   <CreateRoleResult> | ||||||
|     <Role> |     {{ role.to_xml() }} | ||||||
|       <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> |  | ||||||
|   </CreateRoleResult> |   </CreateRoleResult> | ||||||
|   <ResponseMetadata> |   <ResponseMetadata> | ||||||
|     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> |     <RequestId>4a93ceee-9966-11e1-b624-b1aEXAMPLE7c</RequestId> | ||||||
| @ -1444,6 +1443,33 @@ GET_ROLE_POLICY_TEMPLATE = """<GetRolePolicyResponse xmlns="https://iam.amazonaw | |||||||
| </ResponseMetadata> | </ResponseMetadata> | ||||||
| </GetRolePolicyResponse>""" | </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/"> | UPDATE_ROLE_TEMPLATE = """<UpdateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||||
|   <UpdateRoleResult> |   <UpdateRoleResult> | ||||||
|   </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/"> | UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||||
|   <UpdateRoleDescriptionResult> |   <UpdateRoleDescriptionResult> | ||||||
|     <Role> |     {{ role.to_xml() }} | ||||||
|       <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> |  | ||||||
|   </UpdateRoleDescriptionResult> |   </UpdateRoleDescriptionResult> | ||||||
|   <ResponseMetadata> |   <ResponseMetadata> | ||||||
|     <RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId> |     <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/"> | GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/"> | ||||||
|   <GetRoleResult> |   <GetRoleResult> | ||||||
|     <Role> |     {{ role.to_xml() }} | ||||||
|       <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> |  | ||||||
|   </GetRoleResult> |   </GetRoleResult> | ||||||
|   <ResponseMetadata> |   <ResponseMetadata> | ||||||
|     <RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId> |     <RequestId>df37e965-9967-11e1-a4c3-270EXAMPLE04</RequestId> | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ index 51e5d1c9c7..057446ae1d 100644 | |||||||
|  		Target:  []string{iam.DeletionTaskStatusTypeSucceeded}, |  		Target:  []string{iam.DeletionTaskStatusTypeSucceeded}, | ||||||
|  		Refresh: statusDeleteServiceLinkedRole(conn, deletionTaskID), |  		Refresh: statusDeleteServiceLinkedRole(conn, deletionTaskID), | ||||||
| -		Timeout: 5 * time.Minute,
 | -		Timeout: 5 * time.Minute,
 | ||||||
| +		Timeout: 5 * time.Second,
 | +		Timeout: 15 * time.Second,
 | ||||||
|  		Delay:   10 * time.Second, |  		Delay:   10 * time.Second, | ||||||
|  	} |  	} | ||||||
|   |   | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ iam: | |||||||
|   - TestAccIAMRolePolicy_ |   - TestAccIAMRolePolicy_ | ||||||
|   - TestAccIAMRolePolicyAttachment_ |   - TestAccIAMRolePolicyAttachment_ | ||||||
|   - TestAccIAMSessionContextDataSource_ |   - TestAccIAMSessionContextDataSource_ | ||||||
|  |   - TestAccIAMServiceLinkedRole | ||||||
|   - TestAccIAMUserDataSource_ |   - TestAccIAMUserDataSource_ | ||||||
|   - TestAccIAMUserPolicy_ |   - TestAccIAMUserPolicy_ | ||||||
|   - TestAccIAMUserPolicyAttachment_ |   - TestAccIAMUserPolicyAttachment_ | ||||||
| @ -100,6 +101,10 @@ iot: | |||||||
|   - TestAccIoTEndpointDataSource |   - TestAccIoTEndpointDataSource | ||||||
| kms: | kms: | ||||||
|   - TestAccKMSAlias |   - TestAccKMSAlias | ||||||
|  |   - TestAccKMSKey_Policy_basic | ||||||
|  |   - TestAccKMSKey_Policy_iamRole | ||||||
|  |   - TestAccKMSKey_Policy_iamRoleOrder | ||||||
|  |   - TestAccKMSKey_Policy_iamServiceLinkedRole | ||||||
|   - TestAccKMSSecretDataSource |   - TestAccKMSSecretDataSource | ||||||
|   - TestAccKMSSecretsDataSource |   - TestAccKMSSecretsDataSource | ||||||
| meta: | meta: | ||||||
|  | |||||||
| @ -4407,3 +4407,82 @@ def test_untag_user_error_unknown_user_name(): | |||||||
|     ex.response["Error"]["Message"].should.equal( |     ex.response["Error"]["Message"].should.equal( | ||||||
|         "The user with name {} cannot be found.".format(name) |         "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