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 %}
+
+
+ {{ action }}UserResult>
+
+ {{request_id}}
+
+{{ 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 = """
+
+
+ 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 %}
-
-
- {{ action }}UserResult>
-
- {{request_id}}
-
-{{ 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',
- }
-}
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')