diff --git a/localstack/services/iam/iam_starter.py b/localstack/services/iam/iam_starter.py index 0744e5f30e040..9c6f92c0e4f6f 100644 --- a/localstack/services/iam/iam_starter.py +++ b/localstack/services/iam/iam_starter.py @@ -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.name }} + {{ user.id }} + {{ user.arn }} + {{ user.created_iso_8601 }} + + {% for tag in user.tags %} + {{ tag.Key }} + {{ tag.Value }} + {% endfor %} + + + + + {{request_id}} + +""" + + +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 = """ + + + false + + {% for eval in evaluations %} + + + + PolicyInputList.1 + + 4 + 7 + + + 16 + 3 + + + + + {{eval.resourceName}} + {{eval.decision}} + {{eval.actionName}} + + {% endfor %} + + + + 004d7059-4c14-11e5-b121-bd8c7EXAMPLE + +""" + + def apply_patches(): # Add missing managed polices aws_managed_policies.extend([ @@ -68,6 +157,58 @@ 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 @@ -75,56 +216,3 @@ def start_iam(port=None, asynchronous=False, update_listener=None): 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.name }} - {{ user.id }} - {{ user.arn }} - {{ user.created_iso_8601 }} - - {% for tag in user.tags %} - {{ tag.Key }} - {{ tag.Value }} - {% endfor %} - - - - - {{request_id}} - -""" - - -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', - } -} diff --git a/tests/integration/test_iam.py b/tests/integration/test_iam.py index 08657a3b069ab..36dd8349cb6d8 100755 --- a/tests/integration/test_iam.py +++ b/tests/integration/test_iam.py @@ -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')