Merge pull request #738 from okomestudio/ts/more_iam_endpoints
Implement IAM managed policy
This commit is contained in:
commit
ac89403807
@ -1,10 +1,57 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from moto.core import BaseBackend
|
||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException
|
||||
from .utils import random_access_key, random_alphanumeric, random_resource_id
|
||||
from datetime import datetime
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from moto.core import BaseBackend
|
||||
|
||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException
|
||||
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
||||
|
||||
|
||||
class Policy(object):
|
||||
|
||||
is_attachable = False
|
||||
|
||||
def __init__(self,
|
||||
name,
|
||||
default_version_id=None,
|
||||
description=None,
|
||||
document=None,
|
||||
path=None):
|
||||
self.document = document or {}
|
||||
self.name = name
|
||||
|
||||
self.attachment_count = 0
|
||||
self.description = description or ''
|
||||
self.id = random_policy_id()
|
||||
self.path = path or '/'
|
||||
self.default_version_id = default_version_id or 'v1'
|
||||
|
||||
self.create_datetime = datetime.now(pytz.utc)
|
||||
self.update_datetime = datetime.now(pytz.utc)
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return 'arn:aws:iam::aws:policy{0}{1}'.format(self.path, self.name)
|
||||
|
||||
|
||||
class ManagedPolicy(Policy):
|
||||
"""Managed policy."""
|
||||
|
||||
is_attachable = True
|
||||
|
||||
def attach_to_role(self, role):
|
||||
self.attachment_count += 1
|
||||
role.managed_policies[self.name] = self
|
||||
|
||||
|
||||
class AWSManagedPolicy(ManagedPolicy):
|
||||
"""AWS-managed policy."""
|
||||
|
||||
|
||||
class InlinePolicy(Policy):
|
||||
"""TODO: is this needed?"""
|
||||
|
||||
|
||||
class Role(object):
|
||||
@ -15,6 +62,7 @@ class Role(object):
|
||||
self.assume_role_policy_document = assume_role_policy_document
|
||||
self.path = path
|
||||
self.policies = {}
|
||||
self.managed_policies = {}
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
@ -225,6 +273,115 @@ class User(object):
|
||||
)
|
||||
|
||||
|
||||
# predefine AWS managed policies
|
||||
aws_managed_policies = [
|
||||
AWSManagedPolicy(
|
||||
'AmazonElasticMapReduceRole',
|
||||
default_version_id='v6',
|
||||
path='/service-role/',
|
||||
document={
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Resource": "*",
|
||||
"Action": [
|
||||
"ec2:AuthorizeSecurityGroupEgress",
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:CancelSpotInstanceRequests",
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:CreateSecurityGroup",
|
||||
"ec2:CreateTags",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:DeleteSecurityGroup",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeDhcpOptions",
|
||||
"ec2:DescribeInstanceStatus",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DescribeNetworkAcls",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribePrefixLists",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSpotInstanceRequests",
|
||||
"ec2:DescribeSpotPriceHistory",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcEndpointServices",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DetachNetworkInterface",
|
||||
"ec2:ModifyImageAttribute",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:RequestSpotInstances",
|
||||
"ec2:RevokeSecurityGroupEgress",
|
||||
"ec2:RunInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:DeleteVolume",
|
||||
"ec2:DescribeVolumeStatus",
|
||||
"ec2:DescribeVolumes",
|
||||
"ec2:DetachVolume",
|
||||
"iam:GetRole",
|
||||
"iam:GetRolePolicy",
|
||||
"iam:ListInstanceProfiles",
|
||||
"iam:ListRolePolicies",
|
||||
"iam:PassRole",
|
||||
"s3:CreateBucket",
|
||||
"s3:Get*",
|
||||
"s3:List*",
|
||||
"sdb:BatchPutAttributes",
|
||||
"sdb:Select",
|
||||
"sqs:CreateQueue",
|
||||
"sqs:Delete*",
|
||||
"sqs:GetQueue*",
|
||||
"sqs:PurgeQueue",
|
||||
"sqs:ReceiveMessage"
|
||||
]
|
||||
}]
|
||||
}
|
||||
),
|
||||
AWSManagedPolicy(
|
||||
'AmazonElasticMapReduceforEC2Role',
|
||||
default_version_id='v2',
|
||||
path='/service-role/',
|
||||
document={
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Resource": "*",
|
||||
"Action": [
|
||||
"cloudwatch:*",
|
||||
"dynamodb:*",
|
||||
"ec2:Describe*",
|
||||
"elasticmapreduce:Describe*",
|
||||
"elasticmapreduce:ListBootstrapActions",
|
||||
"elasticmapreduce:ListClusters",
|
||||
"elasticmapreduce:ListInstanceGroups",
|
||||
"elasticmapreduce:ListInstances",
|
||||
"elasticmapreduce:ListSteps",
|
||||
"kinesis:CreateStream",
|
||||
"kinesis:DeleteStream",
|
||||
"kinesis:DescribeStream",
|
||||
"kinesis:GetRecords",
|
||||
"kinesis:GetShardIterator",
|
||||
"kinesis:MergeShards",
|
||||
"kinesis:PutRecord",
|
||||
"kinesis:SplitShard",
|
||||
"rds:Describe*",
|
||||
"s3:*",
|
||||
"sdb:*",
|
||||
"sns:*",
|
||||
"sqs:*"
|
||||
]
|
||||
}]
|
||||
}
|
||||
)
|
||||
]
|
||||
# TODO: add more predefined AWS managed policies
|
||||
|
||||
|
||||
class IAMBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
@ -234,8 +391,71 @@ class IAMBackend(BaseBackend):
|
||||
self.groups = {}
|
||||
self.users = {}
|
||||
self.credential_report = None
|
||||
self.managed_policies = self._init_managed_policies()
|
||||
super(IAMBackend, self).__init__()
|
||||
|
||||
def _init_managed_policies(self):
|
||||
return dict((p.name, p) for p in aws_managed_policies)
|
||||
|
||||
def attach_role_policy(self, policy_arn, role_name):
|
||||
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
||||
policy = arns[policy_arn]
|
||||
policy.attach_to_role(self.get_role(role_name))
|
||||
|
||||
def create_policy(self, description, path, policy_document, policy_name):
|
||||
policy = ManagedPolicy(
|
||||
policy_name,
|
||||
description=description,
|
||||
document=policy_document,
|
||||
path=path,
|
||||
)
|
||||
self.managed_policies[policy.name] = policy
|
||||
return policy
|
||||
|
||||
def list_attached_role_policies(self, role_name, marker=None, max_items=100, path_prefix='/'):
|
||||
policies = self.get_role(role_name).managed_policies.values()
|
||||
|
||||
if path_prefix:
|
||||
policies = [p for p in policies if p.path.startswith(path_prefix)]
|
||||
|
||||
policies = sorted(policies)
|
||||
start_idx = int(marker) if marker else 0
|
||||
|
||||
policies = policies[start_idx:start_idx + max_items]
|
||||
|
||||
if len(policies) < max_items:
|
||||
marker = None
|
||||
else:
|
||||
marker = str(start_idx + max_items)
|
||||
|
||||
return policies, marker
|
||||
|
||||
def list_policies(self, marker, max_items, only_attached, path_prefix, scope):
|
||||
policies = self.managed_policies.values()
|
||||
|
||||
if only_attached:
|
||||
policies = [p for p in policies if p.attachment_count > 0]
|
||||
|
||||
if scope == 'AWS':
|
||||
policies = [p for p in policies if isinstance(p, AWSManagedPolicy)]
|
||||
elif scope == 'Local':
|
||||
policies = [p for p in policies if not isinstance(p, AWSManagedPolicy)]
|
||||
|
||||
if path_prefix:
|
||||
policies = [p for p in policies if p.path.startswith(path_prefix)]
|
||||
|
||||
policies = sorted(policies)
|
||||
start_idx = int(marker) if marker else 0
|
||||
|
||||
policies = policies[start_idx:start_idx + max_items]
|
||||
|
||||
if len(policies) < max_items:
|
||||
marker = None
|
||||
else:
|
||||
marker = str(start_idx + max_items)
|
||||
|
||||
return policies, marker
|
||||
|
||||
def create_role(self, role_name, assume_role_policy_document, path):
|
||||
role_id = random_resource_id()
|
||||
role = Role(role_id, role_name, assume_role_policy_document, path)
|
||||
|
@ -6,6 +6,41 @@ from .models import iam_backend
|
||||
|
||||
class IamResponse(BaseResponse):
|
||||
|
||||
def attach_role_policy(self):
|
||||
policy_arn = self._get_param('PolicyArn')
|
||||
role_name = self._get_param('RoleName')
|
||||
iam_backend.attach_role_policy(policy_arn, role_name)
|
||||
template = self.response_template(ATTACH_ROLE_POLICY_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def create_policy(self):
|
||||
description = self._get_param('Description')
|
||||
path = self._get_param('Path')
|
||||
policy_document = self._get_param('PolicyDocument')
|
||||
policy_name = self._get_param('PolicyName')
|
||||
policy = iam_backend.create_policy(description, path, policy_document, policy_name)
|
||||
template = self.response_template(CREATE_POLICY_TEMPLATE)
|
||||
return template.render(policy=policy)
|
||||
|
||||
def list_attached_role_policies(self):
|
||||
marker = self._get_param('Marker')
|
||||
max_items = self._get_int_param('MaxItems', 100)
|
||||
path_prefix = self._get_param('PathPrefix', '/')
|
||||
role_name = self._get_param('RoleName')
|
||||
policies, marker = iam_backend.list_attached_role_policies(role_name, marker=marker, max_items=max_items, path_prefix=path_prefix)
|
||||
template = self.response_template(LIST_ATTACHED_ROLE_POLICIES_TEMPLATE)
|
||||
return template.render(policies=policies, marker=marker)
|
||||
|
||||
def list_policies(self):
|
||||
marker = self._get_param('Marker')
|
||||
max_items = self._get_int_param('MaxItems', 100)
|
||||
only_attached = self._get_bool_param('OnlyAttached', False)
|
||||
path_prefix = self._get_param('PathPrefix', '/')
|
||||
scope = self._get_param('Scope', 'All')
|
||||
policies, marker = iam_backend.list_policies(marker, max_items, only_attached, path_prefix, scope)
|
||||
template = self.response_template(LIST_POLICIES_TEMPLATE)
|
||||
return template.render(policies=policies, marker=marker)
|
||||
|
||||
def create_role(self):
|
||||
role_name = self._get_param('RoleName')
|
||||
path = self._get_param('Path')
|
||||
@ -267,6 +302,81 @@ class IamResponse(BaseResponse):
|
||||
template = self.response_template(CREDENTIAL_REPORT)
|
||||
return template.render(report=report)
|
||||
|
||||
|
||||
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</AttachRolePolicyResponse>"""
|
||||
|
||||
CREATE_POLICY_TEMPLATE = """<CreatePolicyResponse>
|
||||
<CreatePolicyResult>
|
||||
<Policy>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
|
||||
<DefaultVersionId>{{ policy.default_version_id }}</DefaultVersionId>
|
||||
<Path>{{ policy.path }}</Path>
|
||||
<PolicyId>{{ policy.id }}</PolicyId>
|
||||
<PolicyName>{{ policy.name }}</PolicyName>
|
||||
<UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
|
||||
</Policy>
|
||||
</CreatePolicyResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreatePolicyResponse>"""
|
||||
|
||||
LIST_ATTACHED_ROLE_POLICIES_TEMPLATE = """<ListAttachedRolePoliciesResponse>
|
||||
<ListAttachedRolePoliciesResult>
|
||||
{% if marker is none %}
|
||||
<IsTruncated>false</IsTruncated>
|
||||
{% else %}
|
||||
<IsTruncated>true</IsTruncated>
|
||||
<Marker>{{ marker }}</Marker>
|
||||
{% endif %}
|
||||
<AttachedPolicies>
|
||||
{% for policy in policies %}
|
||||
<member>
|
||||
<PolicyName>{{ policy.name }}</PolicyName>
|
||||
<PolicyArn>{{ policy.arn }}</PolicyArn>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</AttachedPolicies>
|
||||
</ListAttachedRolePoliciesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListAttachedRolePoliciesResponse>"""
|
||||
|
||||
LIST_POLICIES_TEMPLATE = """<ListPoliciesResponse>
|
||||
<ListPoliciesResult>
|
||||
{% if marker is none %}
|
||||
<IsTruncated>false</IsTruncated>
|
||||
{% else %}
|
||||
<IsTruncated>true</IsTruncated>
|
||||
<Marker>{{ marker }}</Marker>
|
||||
{% endif %}
|
||||
<Policies>
|
||||
{% for policy in policies %}
|
||||
<member>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
|
||||
<DefaultVersionId>{{ policy.default_version_id }}</DefaultVersionId>
|
||||
<Path>{{ policy.path }}</Path>
|
||||
<PolicyId>{{ policy.id }}</PolicyId>
|
||||
<PolicyName>{{ policy.name }}</PolicyName>
|
||||
<UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Policies>
|
||||
</ListPoliciesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListPoliciesResponse>"""
|
||||
|
||||
GENERIC_EMPTY_TEMPLATE = """<{{ name }}Response>
|
||||
<ResponseMetadata>
|
||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||
|
@ -25,3 +25,10 @@ def random_access_key():
|
||||
string.ascii_uppercase + string.digits
|
||||
)) for _ in range(16)
|
||||
)
|
||||
|
||||
|
||||
def random_policy_id():
|
||||
return 'A' + ''.join(
|
||||
random.choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(20)
|
||||
)
|
||||
|
@ -1,14 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
import base64
|
||||
|
||||
import boto
|
||||
import boto3
|
||||
import sure # noqa
|
||||
|
||||
from nose.tools import assert_raises, assert_equals, assert_not_equals
|
||||
from boto.exception import BotoServerError
|
||||
import base64
|
||||
from moto import mock_iam
|
||||
from moto.iam.models import aws_managed_policies
|
||||
from nose.tools import assert_raises, assert_equals, assert_not_equals
|
||||
from nose.tools import raises
|
||||
|
||||
from tests.helpers import requires_boto_gte
|
||||
|
||||
|
||||
@mock_iam()
|
||||
def test_get_all_server_certs():
|
||||
@ -272,3 +275,41 @@ def test_get_credential_report():
|
||||
result = conn.get_credential_report()
|
||||
report = base64.b64decode(result['get_credential_report_response']['get_credential_report_result']['content'].encode('ascii')).decode('ascii')
|
||||
report.should.match(r'.*my-user.*')
|
||||
|
||||
|
||||
@requires_boto_gte('2.39')
|
||||
@mock_iam()
|
||||
def test_managed_policy():
|
||||
conn = boto.connect_iam()
|
||||
|
||||
conn.create_policy(policy_name='UserManagedPolicy',
|
||||
policy_document={'mypolicy': 'test'},
|
||||
path='/mypolicy/',
|
||||
description='my user managed policy')
|
||||
|
||||
aws_policies = conn.list_policies(scope='AWS')['list_policies_response']['list_policies_result']['policies']
|
||||
set(p.name for p in aws_managed_policies).should.equal(set(p['policy_name'] for p in aws_policies))
|
||||
|
||||
user_policies = conn.list_policies(scope='Local')['list_policies_response']['list_policies_result']['policies']
|
||||
set(['UserManagedPolicy']).should.equal(set(p['policy_name'] for p in user_policies))
|
||||
|
||||
all_policies = conn.list_policies()['list_policies_response']['list_policies_result']['policies']
|
||||
set(p['policy_name'] for p in aws_policies + user_policies).should.equal(set(p['policy_name'] for p in all_policies))
|
||||
|
||||
role_name = 'my-role'
|
||||
conn.create_role(role_name, assume_role_policy_document={'policy': 'test'}, path="my-path")
|
||||
for policy_name in ['AmazonElasticMapReduceRole',
|
||||
'AmazonElasticMapReduceforEC2Role']:
|
||||
policy_arn = 'arn:aws:iam::aws:policy/service-role/' + policy_name
|
||||
conn.attach_role_policy(policy_arn, role_name)
|
||||
|
||||
rows = conn.list_policies(only_attached=True)['list_policies_response']['list_policies_result']['policies']
|
||||
rows.should.have.length_of(2)
|
||||
for x in rows:
|
||||
int(x['attachment_count']).should.be.greater_than(0)
|
||||
|
||||
# boto has not implemented this end point but accessible this way
|
||||
resp = conn.get_response('ListAttachedRolePolicies',
|
||||
{'RoleName': role_name},
|
||||
list_marker='AttachedPolicies')
|
||||
resp['list_attached_role_policies_response']['list_attached_role_policies_result']['attached_policies'].should.have.length_of(2)
|
||||
|
Loading…
Reference in New Issue
Block a user