Skip to content
This repository was archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
Implement IAM SimulatePrincipalPolicy
Browse files Browse the repository at this point in the history
* Patch moto IAM implementation
* Add integration test

Issue fixed:
localstack#1674 IAM SimulatePrincipalPolicy returns XML error
  • Loading branch information
duongpv7 committed May 29, 2020
1 parent 1a184c0 commit f782dcc
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 54 deletions.
196 changes: 142 additions & 54 deletions localstack/services/iam/iam_starter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,101 @@
import json
import uuid

from moto.iam.responses import IamResponse, GENERIC_EMPTY_TEMPLATE
from moto.iam.models import (
iam_backend as moto_iam_backend, aws_managed_policies, AWSManagedPolicy, IAMNotFoundException, User
iam_backend as moto_iam_backend, aws_managed_policies, AWSManagedPolicy, IAMNotFoundException, Policy, User
)
from localstack import config
from localstack.services.infra import start_moto_server


USER_RESPONSE_TEMPLATE = """<{{ action }}UserResponse>
<{{ action }}UserResult>
<User>
<Path>{{ user.path }}</Path>
<UserName>{{ user.name }}</UserName>
<UserId>{{ user.id }}</UserId>
<Arn>{{ user.arn }}</Arn>
<CreateDate>{{ user.created_iso_8601 }}</CreateDate>
<Tags>
{% for tag in user.tags %}<member>
<Key>{{ tag.Key }}</Key>
<Value>{{ tag.Value }}</Value>
</member>{% endfor %}
</Tags>
</User>
</{{ action }}UserResult>
<ResponseMetadata>
<RequestId>{{request_id}}</RequestId>
</ResponseMetadata>
</{{ action }}UserResponse>"""


ADDITIONAL_MANAGED_POLICIES = {
'AWSLambdaExecute': {
'Arn': 'arn:aws:iam::aws:policy/AWSLambdaExecute',
'Path': '/',
'CreateDate': '2017-10-20T17:23:10+00:00',
'DefaultVersionId': 'v4',
'Document': {
'Version': '2012-10-17',
'Statement': [
{
'Effect': 'Allow',
'Action': [
'logs:*'
],
'Resource': 'arn:aws:logs:*:*:*'
},
{
'Effect': 'Allow',
'Action': [
's3:GetObject',
's3:PutObject'
],
'Resource': 'arn:aws:s3:::*'
}
]
},
'UpdateDate': '2019-05-20T18:22:18+00:00',
}
}


SIMULATE_PRINCIPAL_POLICY_RESPONSE = """
<SimulatePrincipalPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<SimulatePrincipalPolicyResult>
<IsTruncated>false</IsTruncated>
<EvaluationResults>
{% for eval in evaluations %}
<member>
<MatchedStatements>
<member>
<SourcePolicyId>PolicyInputList.1</SourcePolicyId>
<EndPosition>
<Column>4</Column>
<Line>7</Line>
</EndPosition>
<StartPosition>
<Column>16</Column>
<Line>3</Line>
</StartPosition>
</member>
</MatchedStatements>
<MissingContextValues/>
<EvalResourceName>{{eval.resourceName}}</EvalResourceName>
<EvalDecision>{{eval.decision}}</EvalDecision>
<EvalActionName>{{eval.actionName}}</EvalActionName>
</member>
{% endfor %}
</EvaluationResults>
</SimulatePrincipalPolicyResult>
<ResponseMetadata>
<RequestId>004d7059-4c14-11e5-b121-bd8c7EXAMPLE</RequestId>
</ResponseMetadata>
</SimulatePrincipalPolicyResponse>"""


def apply_patches():
# Add missing managed polices
aws_managed_policies.extend([
Expand Down Expand Up @@ -68,63 +157,62 @@ def iam_backend_detach_role_policy(policy_arn, role_name):

moto_iam_backend.detach_role_policy = iam_backend_detach_role_policy

policy_init_orig = Policy.__init__

def iam_response_simulate_principal_policy(self):
def build_evaluation(action_name, resource_name, policy_statements):
for statement in policy_statements:
# TODO Implement evaluation logic here
if action_name in statement['Action'] \
and resource_name in statement['Resource'] \
and statement['Effect'] == 'Allow':

return {
'actionName': action_name,
'resourceName': resource_name,
'decision': 'allowed',
'matchedStatements': []
}

return {
'actionName': action_name,
'resourceName': resource_name,
'decision': 'explicitDeny'
}

policy = moto_iam_backend.get_policy(self._get_param('PolicySourceArn'))
policy_statements = json.loads(policy.document)['Statement']
actions = self._get_multi_param('ActionNames.member')
resource_arns = self._get_multi_param('ResourceArns.member')
evaluations = []
for action in actions:
for resource_arn in resource_arns:
evaluations.append(build_evaluation(action, resource_arn, policy_statements))

template = self.response_template(SIMULATE_PRINCIPAL_POLICY_RESPONSE)
return template.render(evaluations=evaluations)

def policy__init__(
self,
name,
default_version_id=None,
description=None,
document=None,
path=None,
create_date=None,
update_date=None
):
policy_init_orig(self, name, default_version_id, description, document, path, create_date, update_date)
self.document = document

Policy.__init__ = policy__init__

IamResponse.simulate_principal_policy = iam_response_simulate_principal_policy


def start_iam(port=None, asynchronous=False, update_listener=None):
port = port or config.PORT_IAM

apply_patches()
return start_moto_server('iam', port, name='IAM',
asynchronous=asynchronous, update_listener=update_listener)


USER_RESPONSE_TEMPLATE = """<{{ action }}UserResponse>
<{{ action }}UserResult>
<User>
<Path>{{ user.path }}</Path>
<UserName>{{ user.name }}</UserName>
<UserId>{{ user.id }}</UserId>
<Arn>{{ user.arn }}</Arn>
<CreateDate>{{ user.created_iso_8601 }}</CreateDate>
<Tags>
{% for tag in user.tags %}<member>
<Key>{{ tag.Key }}</Key>
<Value>{{ tag.Value }}</Value>
</member>{% endfor %}
</Tags>
</User>
</{{ action }}UserResult>
<ResponseMetadata>
<RequestId>{{request_id}}</RequestId>
</ResponseMetadata>
</{{ action }}UserResponse>"""


ADDITIONAL_MANAGED_POLICIES = {
'AWSLambdaExecute': {
'Arn': 'arn:aws:iam::aws:policy/AWSLambdaExecute',
'Path': '/',
'CreateDate': '2017-10-20T17:23:10+00:00',
'DefaultVersionId': 'v4',
'Document': {
'Version': '2012-10-17',
'Statement': [
{
'Effect': 'Allow',
'Action': [
'logs:*'
],
'Resource': 'arn:aws:logs:*:*:*'
},
{
'Effect': 'Allow',
'Action': [
's3:GetObject',
's3:PutObject'
],
'Resource': 'arn:aws:s3:::*'
}
]
},
'UpdateDate': '2019-05-20T18:22:18+00:00',
}
}
49 changes: 49 additions & 0 deletions tests/integration/test_iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,52 @@ def test_attach_detach_role_policy(self):
self.iam_client.delete_policy(
PolicyArn=policy_arn
)

def test_simulate_principle_policy(self):
policy_name = 'policy-{}'.format(short_uid())
policy_document = {
'Version': '2012-10-17',
'Statement': [
{
'Action': [
's3:GetReplicationConfiguration',
's3:GetObjectVersion',
's3:ListBucket'
],
'Effect': 'Allow',
'Resource': [
'arn:aws:s3:::bucket_name'
]
}
]
}

policy_arn = self.iam_client.create_policy(
PolicyName=policy_name,
Path='/',
PolicyDocument=json.dumps(policy_document)
)['Policy']['Arn']

rs = self.iam_client.simulate_principal_policy(
PolicySourceArn=policy_arn,
ActionNames=[
's3:PutObject',
's3:GetObjectVersion'
],
ResourceArns=[
'arn:aws:s3:::bucket_name'
]
)
self.assertEqual(rs['ResponseMetadata']['HTTPStatusCode'], 200)
evaluation_results = rs['EvaluationResults']
self.assertEqual(len(evaluation_results), 2)

evaluation = [
evaluation for evaluation in evaluation_results if evaluation['EvalActionName'] == 's3:GetObjectVersion'
][0]
self.assertEqual(evaluation['EvalDecision'], 'allowed')

evaluation = [
evaluation for evaluation in evaluation_results if evaluation['EvalActionName'] == 's3:PutObject'
][0]
self.assertEqual(evaluation['EvalDecision'], 'explicitDeny')

0 comments on commit f782dcc

Please sign in to comment.