Fixed some IAM APIs for tagging and role descriptions
This commit is contained in:
parent
1de371ca76
commit
38866bfcef
@ -161,7 +161,7 @@ class InlinePolicy(Policy):
|
|||||||
|
|
||||||
class Role(BaseModel):
|
class Role(BaseModel):
|
||||||
|
|
||||||
def __init__(self, role_id, name, assume_role_policy_document, path, permissions_boundary):
|
def __init__(self, role_id, name, assume_role_policy_document, path, permissions_boundary, description, tags):
|
||||||
self.id = role_id
|
self.id = role_id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.assume_role_policy_document = assume_role_policy_document
|
self.assume_role_policy_document = assume_role_policy_document
|
||||||
@ -169,8 +169,8 @@ class Role(BaseModel):
|
|||||||
self.policies = {}
|
self.policies = {}
|
||||||
self.managed_policies = {}
|
self.managed_policies = {}
|
||||||
self.create_date = datetime.utcnow()
|
self.create_date = datetime.utcnow()
|
||||||
self.tags = {}
|
self.tags = tags
|
||||||
self.description = ""
|
self.description = description
|
||||||
self.permissions_boundary = permissions_boundary
|
self.permissions_boundary = permissions_boundary
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -185,7 +185,9 @@ class Role(BaseModel):
|
|||||||
role_name=resource_name,
|
role_name=resource_name,
|
||||||
assume_role_policy_document=properties['AssumeRolePolicyDocument'],
|
assume_role_policy_document=properties['AssumeRolePolicyDocument'],
|
||||||
path=properties.get('Path', '/'),
|
path=properties.get('Path', '/'),
|
||||||
permissions_boundary=properties.get('PermissionsBoundary', '')
|
permissions_boundary=properties.get('PermissionsBoundary', ''),
|
||||||
|
description=properties.get('Description', ''),
|
||||||
|
tags=properties.get('Tags', {})
|
||||||
)
|
)
|
||||||
|
|
||||||
policies = properties.get('Policies', [])
|
policies = properties.get('Policies', [])
|
||||||
@ -635,12 +637,13 @@ class IAMBackend(BaseBackend):
|
|||||||
|
|
||||||
return policies, marker
|
return policies, marker
|
||||||
|
|
||||||
def create_role(self, role_name, assume_role_policy_document, path, permissions_boundary):
|
def create_role(self, role_name, assume_role_policy_document, path, permissions_boundary, description, tags):
|
||||||
role_id = random_resource_id()
|
role_id = random_resource_id()
|
||||||
if permissions_boundary and not self.policy_arn_regex.match(permissions_boundary):
|
if permissions_boundary and not self.policy_arn_regex.match(permissions_boundary):
|
||||||
raise RESTError('InvalidParameterValue', 'Value ({}) for parameter PermissionsBoundary is invalid.'.format(permissions_boundary))
|
raise RESTError('InvalidParameterValue', 'Value ({}) for parameter PermissionsBoundary is invalid.'.format(permissions_boundary))
|
||||||
|
|
||||||
role = Role(role_id, role_name, assume_role_policy_document, path, permissions_boundary)
|
clean_tags = self._tag_verification(tags)
|
||||||
|
role = Role(role_id, role_name, assume_role_policy_document, path, permissions_boundary, description, clean_tags)
|
||||||
self.roles[role_id] = role
|
self.roles[role_id] = role
|
||||||
return role
|
return role
|
||||||
|
|
||||||
@ -691,6 +694,23 @@ class IAMBackend(BaseBackend):
|
|||||||
role = self.get_role(role_name)
|
role = self.get_role(role_name)
|
||||||
return role.policies.keys()
|
return role.policies.keys()
|
||||||
|
|
||||||
|
def _tag_verification(self, tags):
|
||||||
|
if len(tags) > 50:
|
||||||
|
raise TooManyTags(tags)
|
||||||
|
|
||||||
|
tag_keys = {}
|
||||||
|
for tag in tags:
|
||||||
|
# Need to index by the lowercase tag key since the keys are case insensitive, but their case is retained.
|
||||||
|
ref_key = tag['Key'].lower()
|
||||||
|
self._check_tag_duplicate(tag_keys, ref_key)
|
||||||
|
self._validate_tag_key(tag['Key'])
|
||||||
|
if len(tag['Value']) > 256:
|
||||||
|
raise TagValueTooBig(tag['Value'])
|
||||||
|
|
||||||
|
tag_keys[ref_key] = tag
|
||||||
|
|
||||||
|
return tag_keys
|
||||||
|
|
||||||
def _validate_tag_key(self, tag_key, exception_param='tags.X.member.key'):
|
def _validate_tag_key(self, tag_key, exception_param='tags.X.member.key'):
|
||||||
"""Validates the tag key.
|
"""Validates the tag key.
|
||||||
|
|
||||||
@ -740,23 +760,9 @@ class IAMBackend(BaseBackend):
|
|||||||
return tags, marker
|
return tags, marker
|
||||||
|
|
||||||
def tag_role(self, role_name, tags):
|
def tag_role(self, role_name, tags):
|
||||||
if len(tags) > 50:
|
clean_tags = self._tag_verification(tags)
|
||||||
raise TooManyTags(tags)
|
|
||||||
|
|
||||||
role = self.get_role(role_name)
|
role = self.get_role(role_name)
|
||||||
|
role.tags.update(clean_tags)
|
||||||
tag_keys = {}
|
|
||||||
for tag in tags:
|
|
||||||
# Need to index by the lowercase tag key since the keys are case insensitive, but their case is retained.
|
|
||||||
ref_key = tag['Key'].lower()
|
|
||||||
self._check_tag_duplicate(tag_keys, ref_key)
|
|
||||||
self._validate_tag_key(tag['Key'])
|
|
||||||
if len(tag['Value']) > 256:
|
|
||||||
raise TagValueTooBig(tag['Value'])
|
|
||||||
|
|
||||||
tag_keys[ref_key] = tag
|
|
||||||
|
|
||||||
role.tags.update(tag_keys)
|
|
||||||
|
|
||||||
def untag_role(self, role_name, tag_keys):
|
def untag_role(self, role_name, tag_keys):
|
||||||
if len(tag_keys) > 50:
|
if len(tag_keys) > 50:
|
||||||
|
@ -178,9 +178,11 @@ class IamResponse(BaseResponse):
|
|||||||
'AssumeRolePolicyDocument')
|
'AssumeRolePolicyDocument')
|
||||||
permissions_boundary = self._get_param(
|
permissions_boundary = self._get_param(
|
||||||
'PermissionsBoundary')
|
'PermissionsBoundary')
|
||||||
|
description = self._get_param('Description')
|
||||||
|
tags = self._get_multi_param('Tags.member')
|
||||||
|
|
||||||
role = iam_backend.create_role(
|
role = iam_backend.create_role(
|
||||||
role_name, assume_role_policy_document, path, permissions_boundary)
|
role_name, assume_role_policy_document, path, permissions_boundary, description, tags)
|
||||||
template = self.response_template(CREATE_ROLE_TEMPLATE)
|
template = self.response_template(CREATE_ROLE_TEMPLATE)
|
||||||
return template.render(role=role)
|
return template.render(role=role)
|
||||||
|
|
||||||
@ -1002,6 +1004,7 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
|
<Description>{{role.description}}</Description>
|
||||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
{% if role.permissions_boundary %}
|
{% if role.permissions_boundary %}
|
||||||
@ -1010,6 +1013,16 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
|
|||||||
<PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn>
|
<PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn>
|
||||||
</PermissionsBoundary>
|
</PermissionsBoundary>
|
||||||
{% endif %}
|
{% 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>
|
||||||
</CreateRoleResult>
|
</CreateRoleResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -1043,6 +1056,7 @@ UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="http
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
|
<Description>{{role.description}}</Description>
|
||||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
{% if role.tags %}
|
{% if role.tags %}
|
||||||
@ -1069,6 +1083,7 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
|
<Description>{{role.description}}</Description>
|
||||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
{% if role.tags %}
|
{% if role.tags %}
|
||||||
@ -1759,8 +1774,15 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
|
<Description>{{role.description}}</Description>
|
||||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
|
{% if role.permissions_boundary %}
|
||||||
|
<PermissionsBoundary>
|
||||||
|
<PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType>
|
||||||
|
<PermissionsBoundaryArn>{{ role.permissions_boundary }}</PermissionsBoundaryArn>
|
||||||
|
</PermissionsBoundary>
|
||||||
|
{% endif %}
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Roles>
|
</Roles>
|
||||||
|
@ -944,7 +944,8 @@ def test_get_account_authorization_details():
|
|||||||
})
|
})
|
||||||
|
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
|
boundary = 'arn:aws:iam::123456789012:policy/boundary'
|
||||||
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/", Description='testing', PermissionsBoundary=boundary)
|
||||||
conn.create_user(Path='/', UserName='testUser')
|
conn.create_user(Path='/', UserName='testUser')
|
||||||
conn.create_group(Path='/', GroupName='testGroup')
|
conn.create_group(Path='/', GroupName='testGroup')
|
||||||
conn.create_policy(
|
conn.create_policy(
|
||||||
@ -985,6 +986,11 @@ def test_get_account_authorization_details():
|
|||||||
assert len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
assert len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
|
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
|
||||||
|
assert result['RoleDetailList'][0]['InstanceProfileList'][0]['Roles'][0]['Description'] == 'testing'
|
||||||
|
assert result['RoleDetailList'][0]['InstanceProfileList'][0]['Roles'][0]['PermissionsBoundary'] == {
|
||||||
|
'PermissionsBoundaryType': 'PermissionsBoundaryPolicy',
|
||||||
|
'PermissionsBoundaryArn': 'arn:aws:iam::123456789012:policy/boundary'
|
||||||
|
}
|
||||||
assert len(result['RoleDetailList'][0]['Tags']) == 2
|
assert len(result['RoleDetailList'][0]['Tags']) == 2
|
||||||
assert len(result['RoleDetailList'][0]['RolePolicyList']) == 1
|
assert len(result['RoleDetailList'][0]['RolePolicyList']) == 1
|
||||||
assert len(result['RoleDetailList'][0]['AttachedManagedPolicies']) == 1
|
assert len(result['RoleDetailList'][0]['AttachedManagedPolicies']) == 1
|
||||||
@ -1151,6 +1157,79 @@ def test_delete_saml_provider():
|
|||||||
assert not resp['Certificates']
|
assert not resp['Certificates']
|
||||||
|
|
||||||
|
|
||||||
|
@mock_iam()
|
||||||
|
def test_create_role_with_tags():
|
||||||
|
"""Tests both the tag_role and get_role_tags capability"""
|
||||||
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}", Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': 'somevalue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Key': 'someotherkey',
|
||||||
|
'Value': 'someothervalue'
|
||||||
|
}
|
||||||
|
], Description='testing')
|
||||||
|
|
||||||
|
# Get role:
|
||||||
|
role = conn.get_role(RoleName='my-role')['Role']
|
||||||
|
assert len(role['Tags']) == 2
|
||||||
|
assert role['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert role['Tags'][0]['Value'] == 'somevalue'
|
||||||
|
assert role['Tags'][1]['Key'] == 'someotherkey'
|
||||||
|
assert role['Tags'][1]['Value'] == 'someothervalue'
|
||||||
|
assert role['Description'] == 'testing'
|
||||||
|
|
||||||
|
# Empty is good:
|
||||||
|
conn.create_role(RoleName="my-role2", AssumeRolePolicyDocument="{}", Tags=[
|
||||||
|
{
|
||||||
|
'Key': 'somekey',
|
||||||
|
'Value': ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
tags = conn.list_role_tags(RoleName='my-role2')
|
||||||
|
assert len(tags['Tags']) == 1
|
||||||
|
assert tags['Tags'][0]['Key'] == 'somekey'
|
||||||
|
assert tags['Tags'][0]['Value'] == ''
|
||||||
|
|
||||||
|
# Test creating tags with invalid values:
|
||||||
|
# With more than 50 tags:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
too_many_tags = list(map(lambda x: {'Key': str(x), 'Value': str(x)}, range(0, 51)))
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=too_many_tags)
|
||||||
|
assert 'failed to satisfy constraint: Member must have length less than or equal to 50.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a duplicate tag:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=[{'Key': '0', 'Value': ''}, {'Key': '0', 'Value': ''}])
|
||||||
|
assert 'Duplicate tag keys found. Please note that Tag keys are case insensitive.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# Duplicate tag with different casing:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=[{'Key': 'a', 'Value': ''}, {'Key': 'A', 'Value': ''}])
|
||||||
|
assert 'Duplicate tag keys found. Please note that Tag keys are case insensitive.' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a really big key:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=[{'Key': '0' * 129, 'Value': ''}])
|
||||||
|
assert 'Member must have length less than or equal to 128.' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With a really big value:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=[{'Key': '0', 'Value': '0' * 257}])
|
||||||
|
assert 'Member must have length less than or equal to 256.' in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
# With an invalid character:
|
||||||
|
with assert_raises(ClientError) as ce:
|
||||||
|
conn.create_role(RoleName="my-role3", AssumeRolePolicyDocument="{}", Tags=[{'Key': 'NOWAY!', 'Value': ''}])
|
||||||
|
assert 'Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+' \
|
||||||
|
in ce.exception.response['Error']['Message']
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_tag_role():
|
def test_tag_role():
|
||||||
"""Tests both the tag_role and get_role_tags capability"""
|
"""Tests both the tag_role and get_role_tags capability"""
|
||||||
@ -1338,6 +1417,7 @@ def test_update_role_description():
|
|||||||
|
|
||||||
assert response['Role']['RoleName'] == 'my-role'
|
assert response['Role']['RoleName'] == 'my-role'
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_update_role():
|
def test_update_role():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -1349,6 +1429,7 @@ def test_update_role():
|
|||||||
response = conn.update_role_description(RoleName="my-role", Description="test")
|
response = conn.update_role_description(RoleName="my-role", Description="test")
|
||||||
assert response['Role']['RoleName'] == 'my-role'
|
assert response['Role']['RoleName'] == 'my-role'
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_update_role():
|
def test_update_role():
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
@ -1443,6 +1524,8 @@ def test_create_role_no_path():
|
|||||||
resp = conn.create_role(RoleName='my-role', AssumeRolePolicyDocument='some policy', Description='test')
|
resp = conn.create_role(RoleName='my-role', AssumeRolePolicyDocument='some policy', Description='test')
|
||||||
resp.get('Role').get('Arn').should.equal('arn:aws:iam::123456789012:role/my-role')
|
resp.get('Role').get('Arn').should.equal('arn:aws:iam::123456789012:role/my-role')
|
||||||
resp.get('Role').should_not.have.key('PermissionsBoundary')
|
resp.get('Role').should_not.have.key('PermissionsBoundary')
|
||||||
|
resp.get('Role').get('Description').should.equal('test')
|
||||||
|
|
||||||
|
|
||||||
@mock_iam()
|
@mock_iam()
|
||||||
def test_create_role_with_permissions_boundary():
|
def test_create_role_with_permissions_boundary():
|
||||||
@ -1454,6 +1537,7 @@ def test_create_role_with_permissions_boundary():
|
|||||||
'PermissionsBoundaryArn': boundary
|
'PermissionsBoundaryArn': boundary
|
||||||
}
|
}
|
||||||
resp.get('Role').get('PermissionsBoundary').should.equal(expected)
|
resp.get('Role').get('PermissionsBoundary').should.equal(expected)
|
||||||
|
resp.get('Role').get('Description').should.equal('test')
|
||||||
|
|
||||||
invalid_boundary_arn = 'arn:aws:iam::123456789:not_a_boundary'
|
invalid_boundary_arn = 'arn:aws:iam::123456789:not_a_boundary'
|
||||||
with assert_raises(ClientError):
|
with assert_raises(ClientError):
|
||||||
|
Loading…
Reference in New Issue
Block a user