From f0e9ea4e728e2d4d65835676cc19950aae2b146c Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 13:47:17 +0200 Subject: [PATCH 01/17] Created tests for policy documents. --- tests/test_iam/test_iam_policies.py | 442 ++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 tests/test_iam/test_iam_policies.py diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py new file mode 100644 index 000000000..2e23d9f91 --- /dev/null +++ b/tests/test_iam/test_iam_policies.py @@ -0,0 +1,442 @@ +import json + +import boto3 +from botocore.exceptions import ClientError +from nose.tools import assert_raises + +from moto import mock_iam + + +@mock_iam +def test_create_policy_with_invalid_policy_documents(): + conn = boto3.client('iam', region_name='us-east-1') + + invalid_documents_test_cases = [ + { + "document": "This is not a json document", + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2008-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2013-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17" + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + "Extra field": "value" + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Extra field": "value" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Id": ["cd3a324d2343d942772346-34234234423404-4c2242343242349d1642ee"], + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Id": {}, + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "invalid", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "invalid", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "invalid resource" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource invalid resource must be in ARN format or "*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [] + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain resources.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Deny" + }, + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + ] + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:iam:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: IAM resource path must either be "*" or start with user/, federated-user/, role/, group/, instance-profile/, mfa/, server-certificate/, policy/, sms-mfa/, saml-provider/, oidc-provider/, report/, access-report/.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource vendor must be fully qualified and cannot contain regexes.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": { + "a": "arn:aws:s3:::example_bucket" + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": [] + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": "a" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "a": "b" + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": "b" + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": [] + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": {}} + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": {}} + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": [ + {"ForAllValues:StringEquals": {"aws:TagKeys": "Department"}} + ] + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:iam:us-east-1::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: IAM resource arn:aws:iam:us-east-1::example_bucket cannot contain region information.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:us-east-1::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource arn:aws:s3:us-east-1::example_bucket can not contain region information.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Sid": {}, + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Sid": [], + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "sdf", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + { + "Sid": "sdf", + "Effect": "Allow" + } + ] + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Statement IDs (SID) in a single policy must be unique.' + } + ] # TODO add more tests + + for test_case in invalid_documents_test_cases: + with assert_raises(ClientError) as ex: + conn.create_policy( + PolicyName="TestCreatePolicy", + PolicyDocument=json.dumps(test_case["document"])) + ex.exception.response['Error']['Code'].should.equal('MalformedPolicyDocument') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal(test_case["error_message"]) \ No newline at end of file From a4b850aab92b834170117e7b8668a14e6eeffb51 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 14:03:18 +0200 Subject: [PATCH 02/17] Added test cases for mutually exclusive elements. --- tests/test_iam/test_iam_policies.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index 2e23d9f91..5a70bb9fc 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -429,6 +429,30 @@ def test_create_policy_with_invalid_policy_documents(): ] }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Statement IDs (SID) in a single policy must be unique.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Action": "iam:dsf", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "NotResource": "*" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' } ] # TODO add more tests From 896f7b6eb2c3ab70e66ac281b52859841851ab47 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 16:36:49 +0200 Subject: [PATCH 03/17] Added more tests. --- tests/test_iam/test_iam_policies.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index 5a70bb9fc..0c224c6ba 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -54,6 +54,13 @@ def test_create_policy_with_invalid_policy_documents(): }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": ["afd"] + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", @@ -135,6 +142,17 @@ def test_create_policy_with_invalid_policy_documents(): }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource invalid resource must be in ARN format or "*".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": ["adf"] + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource adf must be in ARN format or "*".' + }, { "document": { "Version": "2012-10-17", @@ -152,6 +170,17 @@ def test_create_policy_with_invalid_policy_documents(): }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain resources.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [] + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain resources.' + }, { "document": { "Version": "2012-10-17", @@ -247,6 +276,17 @@ def test_create_policy_with_invalid_policy_documents(): }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": "s3:ListBucket", + "Resource": ["adfdf", {}] + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", @@ -453,6 +493,59 @@ def test_create_policy_with_invalid_policy_documents(): } }, "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "denY", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' + }, + { + "document": { + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} + } + } + }, + "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' } ] # TODO add more tests From 99336cbe6a900c41fb1e8d4b78c484a18d833a5f Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 17:04:02 +0200 Subject: [PATCH 04/17] Reorganized tests using a generator method and fixed error messages. --- tests/test_iam/test_iam_policies.py | 1028 ++++++++++++++------------- 1 file changed, 516 insertions(+), 512 deletions(-) diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index 0c224c6ba..aefacf25e 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -7,553 +7,557 @@ from nose.tools import assert_raises from moto import mock_iam -@mock_iam -def test_create_policy_with_invalid_policy_documents(): - conn = boto3.client('iam', region_name='us-east-1') - - invalid_documents_test_cases = [ - { - "document": "This is not a json document", - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' +invalid_documents_test_cases = [ + { + "document": "This is not a json document", + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } }, - { - "document": { - "Statement": { + "error_message": 'Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2008-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2013-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17" + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": ["afd"] + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + "Extra field": "value" + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Extra field": "value" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Id": ["cd3a324d2343d942772346-34234234423404-4c2242343242349d1642ee"], + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Id": {}, + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "invalid", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "invalid", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "invalid resource" + } + }, + "error_message": 'Resource invalid resource must be in ARN format or "*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": ["adf"] + } + }, + "error_message": 'Resource adf must be in ARN format or "*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [] + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket" + } + }, + "error_message": 'Policy statement must contain resources.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [] + } + }, + "error_message": 'Policy statement must contain resources.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow" + } + }, + "error_message": 'Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Deny" + }, + { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::example_bucket" } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' + ] }, - { - "document": { - "Version": "2008-10-17", - "Statement": { + "error_message": 'Policy statement must contain actions.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:iam:::example_bucket" + } + }, + "error_message": 'IAM resource path must either be "*" or start with user/, federated-user/, role/, group/, instance-profile/, mfa/, server-certificate/, policy/, sms-mfa/, saml-provider/, oidc-provider/, report/, access-report/.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3::example_bucket" + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws" + } + }, + "error_message": 'Resource vendor must be fully qualified and cannot contain regexes.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": { + "a": "arn:aws:s3:::example_bucket" + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": "s3:ListBucket", + "Resource": ["adfdf", {}] + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": [] + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": "a" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "a": "b" + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": "b" + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": [] + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": {}} + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": {}} + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": [ + {"ForAllValues:StringEquals": {"aws:TagKeys": "Department"}} + ] + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:iam:us-east-1::example_bucket" + } + }, + "error_message": 'IAM resource arn:aws:iam:us-east-1::example_bucket cannot contain region information.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:us-east-1::example_bucket" + } + }, + "error_message": 'Resource arn:aws:s3:us-east-1::example_bucket can not contain region information.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Sid": {}, "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::example_bucket" } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' }, - { - "document": { - "Version": "2013-10-17", - "Statement": { + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Sid": [], "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::example_bucket" } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' }, - { - "document": { - "Version": "2012-10-17" - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": ["afd"] - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "sdf", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::example_bucket" }, - "Extra field": "value" - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Extra field": "value" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Id": ["cd3a324d2343d942772346-34234234423404-4c2242343242349d1642ee"], - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Id": {}, - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "invalid", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "invalid", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "invalid resource" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource invalid resource must be in ARN format or "*".' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": ["adf"] - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource adf must be in ARN format or "*".' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": [] - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain resources.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": [] - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain resources.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { + { + "Sid": "sdf", "Effect": "Allow" } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' + ] }, - { - "document": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Deny" - }, - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - ] - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy statement must contain actions.' + "error_message": 'Statement IDs (SID) in a single policy must be unique.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Action": "iam:dsf", + "Resource": "arn:aws:s3:::example_bucket" + } }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:iam:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: IAM resource path must either be "*" or start with user/, federated-user/, role/, group/, instance-profile/, mfa/, server-certificate/, policy/, sms-mfa/, saml-provider/, oidc-provider/, report/, access-report/.' + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "NotResource": "*" + } }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "denY", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource vendor must be fully qualified and cannot contain regexes.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": { - "a": "arn:aws:s3:::example_bucket" - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Deny", - "Action": "s3:ListBucket", - "Resource": ["adfdf", {}] - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": [] - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": "a" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "a": "b" - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": "b" - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": [] - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": {"a": {}} - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": {"a": {}} - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": [ - {"ForAllValues:StringEquals": {"aws:TagKeys": "Department"}} - ] - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:iam:us-east-1::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: IAM resource arn:aws:iam:us-east-1::example_bucket cannot contain region information.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:us-east-1::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Resource arn:aws:s3:us-east-1::example_bucket can not contain region information.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Sid": {}, - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Sid": [], - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "sdf", - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" - }, - { - "Sid": "sdf", - "Effect": "Allow" - } - ] - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Statement IDs (SID) in a single policy must be unique.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "NotAction": "s3:ListBucket", - "Action": "iam:dsf", - "Resource": "arn:aws:s3:::example_bucket" - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": { + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::example_bucket", - "NotResource": "*" + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} + } } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Syntax errors in policy.' }, - { - "document": { - "Version": "2012-10-17", - "Statement": { - "Effect": "denY", + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Statement": + { + "Effect": "Allow", "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket" + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} + } } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": {"a": "sdfdsf"} - } + "error_message": 'Policy document must be version 2012-10-17 or greater.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Condition": { + "DateGreaterThan": {"a": "sdfdsf"} } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' + } }, - { - "document": { - "Statement": - { - "Effect": "Allow", - "Action": "s3:ListBucket", - "Resource": "arn:aws:s3:::example_bucket", - "Condition": { - "DateGreaterThan": {"a": "sdfdsf"} - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: Policy document must be version 2012-10-17 or greater.' - }, - { - "document": { - "Version": "2012-10-17", - "Statement": - { - "Effect": "Allow", - "Condition": { - "DateGreaterThan": {"a": "sdfdsf"} - } - } - }, - "error_message": 'An error occurred (MalformedPolicyDocument) when calling the CreatePolicy operation: The policy failed legacy parsing' - } - ] # TODO add more tests + "error_message": 'The policy failed legacy parsing' + } +] # TODO add more tests + +def test_create_policy_with_invalid_policy_documents(): for test_case in invalid_documents_test_cases: - with assert_raises(ClientError) as ex: - conn.create_policy( - PolicyName="TestCreatePolicy", - PolicyDocument=json.dumps(test_case["document"])) - ex.exception.response['Error']['Code'].should.equal('MalformedPolicyDocument') - ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) - ex.exception.response['Error']['Message'].should.equal(test_case["error_message"]) \ No newline at end of file + yield check_create_policy_with_invalid_policy_document, test_case + + +@mock_iam +def check_create_policy_with_invalid_policy_document(test_case): + conn = boto3.client('iam', region_name='us-east-1') + with assert_raises(ClientError) as ex: + conn.create_policy( + PolicyName="TestCreatePolicy", + PolicyDocument=json.dumps(test_case["document"])) + ex.exception.response['Error']['Code'].should.equal('MalformedPolicyDocument') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal(test_case["error_message"]) From ef20b47f979324eed9ed3992e96efe8e2eefa431 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 17:09:55 +0200 Subject: [PATCH 05/17] Implemented checking policy documents for syntax errors. --- moto/iam/exceptions.py | 8 ++ moto/iam/models.py | 4 + moto/iam/policy_validation.py | 148 ++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 moto/iam/policy_validation.py diff --git a/moto/iam/exceptions.py b/moto/iam/exceptions.py index 5b13277da..4b11b0e4d 100644 --- a/moto/iam/exceptions.py +++ b/moto/iam/exceptions.py @@ -34,6 +34,14 @@ class MalformedCertificate(RESTError): 'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert)) +class MalformedPolicyDocument(RESTError): + code = 400 + + def __init__(self, message=""): + super(MalformedPolicyDocument, self).__init__( + 'MalformedPolicyDocument', message) + + class DuplicateTags(RESTError): code = 400 diff --git a/moto/iam/models.py b/moto/iam/models.py index 86eec73f0..dbd66e80d 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -11,6 +11,7 @@ from cryptography.hazmat.backends import default_backend from moto.core.exceptions import RESTError from moto.core import BaseBackend, BaseModel from moto.core.utils import iso_8601_datetime_without_milliseconds, iso_8601_datetime_with_milliseconds +from moto.iam.policy_validation import IAMPolicyDocumentValidator from .aws_managed_policies import aws_managed_policies_data from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate, \ @@ -568,6 +569,9 @@ class IAMBackend(BaseBackend): policy.detach_from(self.get_user(user_name)) def create_policy(self, description, path, policy_document, policy_name): + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) + iam_policy_document_validator.validate() + policy = ManagedPolicy( policy_name, description=description, diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py new file mode 100644 index 000000000..b7b9f26d9 --- /dev/null +++ b/moto/iam/policy_validation.py @@ -0,0 +1,148 @@ +import json + +from six import string_types + +from moto.iam.exceptions import MalformedPolicyDocument + + +ALLOWED_TOP_ELEMENTS = [ + "Version", + "Id", + "Statement", + "Conditions" +] + +ALLOWED_VERSIONS = [ + "2008-10-17", + "2012-10-17" +] + +ALLOWED_STATEMENT_ELEMENTS = [ + "Sid", + "Action", + "NotAction", + "Resource", + "NotResource", + "Effect", + "Condition" +] + +ALLOWED_EFFECTS = [ + "Allow", + "Deny" +] + + +class IAMPolicyDocumentValidator: + + def __init__(self, policy_document): + self._policy_document = policy_document + self._policy_json = {} + self._statements = [] + + def validate(self): + try: + self._validate_syntax() + except Exception: + raise MalformedPolicyDocument("Syntax errors in policy.") + try: + self._validate_version() + except Exception: + raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.") + try: + self._validate_resource_exist() + except Exception: + raise MalformedPolicyDocument("Policy statement must contain resources.") + + def _validate_syntax(self): + self._policy_json = json.loads(self._policy_document) + assert isinstance(self._policy_json, dict) + self._validate_top_elements() + self._validate_version_syntax() + self._validate_id_syntax() + self._validate_statements_syntax() + + def _validate_top_elements(self): + top_elements = self._policy_json.keys() + for element in top_elements: + assert element in ALLOWED_TOP_ELEMENTS + + def _validate_version_syntax(self): + if "Version" in self._policy_json: + assert self._policy_json["Version"] in ALLOWED_VERSIONS + + def _validate_version(self): + assert self._policy_json["Version"] == "2012-10-17" + + def _validate_statements_syntax(self): + assert "Statement" in self._policy_json + assert isinstance(self._policy_json["Statement"], (dict, list)) + + if isinstance(self._policy_json["Statement"], dict): + self._statements.append(self._policy_json["Statement"]) + else: + self._statements += self._policy_json["Statement"] + + assert self._statements + for statement in self._statements: + self._validate_statement_syntax(statement) + + @staticmethod + def _validate_statement_syntax(statement): + assert isinstance(statement, dict) + for statement_element in statement.keys(): + assert statement_element in ALLOWED_STATEMENT_ELEMENTS + + assert ("Resource" not in statement or "NotResource" not in statement) + assert ("Action" not in statement or "NotAction" not in statement) + + IAMPolicyDocumentValidator._validate_effect_syntax(statement) + IAMPolicyDocumentValidator._validate_resource_syntax(statement) + IAMPolicyDocumentValidator._validate_not_resource_syntax(statement) + IAMPolicyDocumentValidator._validate_condition_syntax(statement) + IAMPolicyDocumentValidator._validate_sid_syntax(statement) + + @staticmethod + def _validate_effect_syntax(statement): + assert "Effect" in statement + assert isinstance(statement["Effect"], string_types) + assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in ALLOWED_EFFECTS] + + @staticmethod + def _validate_resource_syntax(statement): + IAMPolicyDocumentValidator._validate_resource_like_syntax(statement, "Resource") + + @staticmethod + def _validate_not_resource_syntax(statement): + IAMPolicyDocumentValidator._validate_resource_like_syntax(statement, "NotResource") + + @staticmethod + def _validate_resource_like_syntax(statement, key): + if key in statement: + assert isinstance(statement[key], (string_types, list)) + if isinstance(statement[key], list): + for resource in statement[key]: + assert isinstance(resource, string_types) + + @staticmethod + def _validate_condition_syntax(statement): + if "Condition" in statement: + assert isinstance(statement["Condition"], dict) + for condition_key, condition_value in statement["Condition"].items(): + assert isinstance(condition_value, dict) + for condition_data_key, condition_data_value in condition_value.items(): + assert isinstance(condition_data_value, (list, string_types)) + + @staticmethod + def _validate_sid_syntax(statement): + if "Sid" in statement: + assert isinstance(statement["Sid"], string_types) + + def _validate_id_syntax(self): + if "Id" in self._policy_document: + assert isinstance(self._policy_document["Id"], string_types) + + def _validate_resource_exist(self): + for statement in self._statements: + assert "Resource" in statement + From 4748c6b073a2214ff9705813e80365af499cca7f Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 17:35:26 +0200 Subject: [PATCH 06/17] Enabled validating policies for all operations similar to CreatePolicy. --- moto/iam/models.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/moto/iam/models.py b/moto/iam/models.py index dbd66e80d..4b6c340e6 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -664,6 +664,9 @@ class IAMBackend(BaseBackend): def put_role_policy(self, role_name, policy_name, policy_json): role = self.get_role(role_name) + + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json) + iam_policy_document_validator.validate() role.put_policy(policy_name, policy_json) def delete_role_policy(self, role_name, policy_name): @@ -764,6 +767,10 @@ class IAMBackend(BaseBackend): policy = self.get_policy(policy_arn) if not policy: raise IAMNotFoundException("Policy not found") + + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) + iam_policy_document_validator.validate() + version = PolicyVersion(policy_arn, policy_document, set_as_default) policy.versions.append(version) version.version_id = 'v{0}'.format(policy.next_version_num) @@ -905,6 +912,9 @@ class IAMBackend(BaseBackend): def put_group_policy(self, group_name, policy_name, policy_json): group = self.get_group(group_name) + + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json) + iam_policy_document_validator.validate() group.put_policy(policy_name, policy_json) def list_group_policies(self, group_name, marker=None, max_items=None): @@ -1065,6 +1075,9 @@ class IAMBackend(BaseBackend): def put_user_policy(self, user_name, policy_name, policy_json): user = self.get_user(user_name) + + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json) + iam_policy_document_validator.validate() user.put_policy(policy_name, policy_json) def delete_user_policy(self, user_name, policy_name): From 55f90402967ff528e2c6a036275bc23b6edae42f Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 17:57:50 +0200 Subject: [PATCH 07/17] Fixed old unit tests in test_iam that didn't use valid IAM policies. --- tests/test_iam/test_iam.py | 87 ++++++++++++++++++++++--------- tests/test_iam/test_iam_groups.py | 20 +++++-- 2 files changed, 78 insertions(+), 29 deletions(-) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 3e1c5914f..a9bf8d4f8 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals import base64 +import json import boto import boto3 @@ -29,6 +30,44 @@ FyDHrtlrS80dPUQWNYHw++oACDpWO01LGLPPrGmuO/7cOdojPEd852q5gd+7W9xt 8vUH+pBa6IBLbvBp+szli51V3TLSWcoyy4ceJNQU2vCkTLoFdS0RLd/7tQ== -----END CERTIFICATE-----""" +MOCK_POLICY = """ +{ + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } +} +""" + +MOCK_POLICY_2 = """ +{ + "Version": "2012-10-17", + "Id": "2", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } +} +""" + +MOCK_POLICY_3 = """ +{ + "Version": "2012-10-17", + "Id": "3", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } +} +""" + @mock_iam_deprecated() def test_get_all_server_certs(): @@ -243,12 +282,12 @@ def test_list_instance_profiles_for_role(): def test_list_role_policies(): conn = boto.connect_iam() conn.create_role("my-role") - conn.put_role_policy("my-role", "test policy", "my policy") + conn.put_role_policy("my-role", "test policy", MOCK_POLICY) role = conn.list_role_policies("my-role") role.policy_names.should.have.length_of(1) role.policy_names[0].should.equal("test policy") - conn.put_role_policy("my-role", "test policy 2", "another policy") + conn.put_role_policy("my-role", "test policy 2", MOCK_POLICY) role = conn.list_role_policies("my-role") role.policy_names.should.have.length_of(2) @@ -266,7 +305,7 @@ def test_put_role_policy(): conn = boto.connect_iam() conn.create_role( "my-role", assume_role_policy_document="some policy", path="my-path") - conn.put_role_policy("my-role", "test policy", "my policy") + conn.put_role_policy("my-role", "test policy", MOCK_POLICY) policy = conn.get_role_policy( "my-role", "test policy")['get_role_policy_response']['get_role_policy_result']['policy_name'] policy.should.equal("test policy") @@ -286,7 +325,7 @@ def test_create_policy(): conn = boto3.client('iam', region_name='us-east-1') response = conn.create_policy( PolicyName="TestCreatePolicy", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestCreatePolicy") @@ -299,19 +338,19 @@ def test_create_policy_versions(): PolicyDocument='{"some":"policy"}') conn.create_policy( PolicyName="TestCreatePolicyVersion", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) version = conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", - PolicyDocument='{"some":"policy"}', + PolicyDocument=MOCK_POLICY, SetAsDefault=True) - version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'}) + version.get('PolicyVersion').get('Document').should.equal(json.loads(MOCK_POLICY)) version.get('PolicyVersion').get('VersionId').should.equal("v2") conn.delete_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", VersionId="v1") version = conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) version.get('PolicyVersion').get('VersionId').should.equal("v3") @@ -320,7 +359,7 @@ def test_get_policy(): conn = boto3.client('iam', region_name='us-east-1') response = conn.create_policy( PolicyName="TestGetPolicy", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) policy = conn.get_policy( PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicy") policy['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestGetPolicy") @@ -342,10 +381,10 @@ def test_get_policy_version(): conn = boto3.client('iam', region_name='us-east-1') conn.create_policy( PolicyName="TestGetPolicyVersion", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) version = conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", - PolicyDocument='{"some":"policy"}') + PolicyDocument=MOCK_POLICY) with assert_raises(ClientError): conn.get_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", @@ -353,7 +392,7 @@ def test_get_policy_version(): retrieved = conn.get_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion", VersionId=version.get('PolicyVersion').get('VersionId')) - retrieved.get('PolicyVersion').get('Document').should.equal({'some': 'policy'}) + retrieved.get('PolicyVersion').get('Document').should.equal(json.loads(MOCK_POLICY)) @mock_iam @@ -396,22 +435,22 @@ def test_list_policy_versions(): PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions") conn.create_policy( PolicyName="TestListPolicyVersions", - PolicyDocument='{"first":"policy"}') + PolicyDocument=MOCK_POLICY) versions = conn.list_policy_versions( PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions") versions.get('Versions')[0].get('VersionId').should.equal('v1') conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions", - PolicyDocument='{"second":"policy"}') + PolicyDocument=MOCK_POLICY_2) conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions", - PolicyDocument='{"third":"policy"}') + PolicyDocument=MOCK_POLICY_3) versions = conn.list_policy_versions( PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions") print(versions.get('Versions')) - versions.get('Versions')[1].get('Document').should.equal({'second': 'policy'}) - versions.get('Versions')[2].get('Document').should.equal({'third': 'policy'}) + versions.get('Versions')[1].get('Document').should.equal(json.loads(MOCK_POLICY_2)) + versions.get('Versions')[2].get('Document').should.equal(json.loads(MOCK_POLICY_3)) @mock_iam @@ -419,10 +458,10 @@ def test_delete_policy_version(): conn = boto3.client('iam', region_name='us-east-1') conn.create_policy( PolicyName="TestDeletePolicyVersion", - PolicyDocument='{"first":"policy"}') + PolicyDocument=MOCK_POLICY) conn.create_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", - PolicyDocument='{"second":"policy"}') + PolicyDocument=MOCK_POLICY) with assert_raises(ClientError): conn.delete_policy_version( PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion", @@ -489,22 +528,20 @@ def test_list_users(): @mock_iam() def test_user_policies(): policy_name = 'UserManagedPolicy' - policy_document = "{'mypolicy': 'test'}" user_name = 'my-user' conn = boto3.client('iam', region_name='us-east-1') conn.create_user(UserName=user_name) conn.put_user_policy( UserName=user_name, PolicyName=policy_name, - PolicyDocument=policy_document + PolicyDocument=MOCK_POLICY ) policy_doc = conn.get_user_policy( UserName=user_name, PolicyName=policy_name ) - test = policy_document in policy_doc['PolicyDocument'] - test.should.equal(True) + policy_doc['PolicyDocument'].should.equal(json.loads(MOCK_POLICY)) policies = conn.list_user_policies(UserName=user_name) len(policies['PolicyNames']).should.equal(1) @@ -665,7 +702,7 @@ def test_managed_policy(): conn = boto.connect_iam() conn.create_policy(policy_name='UserManagedPolicy', - policy_document={'mypolicy': 'test'}, + policy_document=MOCK_POLICY, path='/mypolicy/', description='my user managed policy') @@ -766,7 +803,7 @@ def test_attach_detach_user_policy(): policy_name = 'UserAttachedPolicy' policy = iam.create_policy(PolicyName=policy_name, - PolicyDocument='{"mypolicy": "test"}', + PolicyDocument=MOCK_POLICY, Path='/mypolicy/', Description='my user attached policy') diff --git a/tests/test_iam/test_iam_groups.py b/tests/test_iam/test_iam_groups.py index 0d4756f75..1ca9f2512 100644 --- a/tests/test_iam/test_iam_groups.py +++ b/tests/test_iam/test_iam_groups.py @@ -10,6 +10,18 @@ from nose.tools import assert_raises from boto.exception import BotoServerError from moto import mock_iam, mock_iam_deprecated +MOCK_POLICY = """ +{ + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } +} +""" + @mock_iam_deprecated() def test_create_group(): @@ -101,7 +113,7 @@ def test_get_groups_for_user(): def test_put_group_policy(): conn = boto.connect_iam() conn.create_group('my-group') - conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}') + conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY) @mock_iam @@ -131,7 +143,7 @@ def test_get_group_policy(): with assert_raises(BotoServerError): conn.get_group_policy('my-group', 'my-policy') - conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}') + conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY) conn.get_group_policy('my-group', 'my-policy') @@ -141,7 +153,7 @@ def test_get_all_group_policies(): conn.create_group('my-group') policies = conn.get_all_group_policies('my-group')['list_group_policies_response']['list_group_policies_result']['policy_names'] assert policies == [] - conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}') + conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY) policies = conn.get_all_group_policies('my-group')['list_group_policies_response']['list_group_policies_result']['policy_names'] assert policies == ['my-policy'] @@ -151,5 +163,5 @@ def test_list_group_policies(): conn = boto3.client('iam', region_name='us-east-1') conn.create_group(GroupName='my-group') conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.be.empty - conn.put_group_policy(GroupName='my-group', PolicyName='my-policy', PolicyDocument='{"some": "json"}') + conn.put_group_policy(GroupName='my-group', PolicyName='my-policy', PolicyDocument=MOCK_POLICY) conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.equal(['my-policy']) From 50745fc5c0e79d2fef2469c5a6ac181bd7d56864 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 18:00:16 +0200 Subject: [PATCH 08/17] Fixed resource exist validation and implemented actions exist validation. --- moto/iam/policy_validation.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index b7b9f26d9..6b0f6578b 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -35,9 +35,9 @@ ALLOWED_EFFECTS = [ class IAMPolicyDocumentValidator: - def __init__(self, policy_document): - self._policy_document = policy_document - self._policy_json = {} + def __init__(self, policy_document: str): + self._policy_document: str = policy_document + self._policy_json: dict = {} self._statements = [] def validate(self): @@ -49,6 +49,10 @@ class IAMPolicyDocumentValidator: self._validate_version() except Exception: raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.") + try: + self._validate_action_exist() + except Exception: + raise MalformedPolicyDocument("Policy statement must contain actions.") try: self._validate_resource_exist() except Exception: @@ -139,10 +143,16 @@ class IAMPolicyDocumentValidator: assert isinstance(statement["Sid"], string_types) def _validate_id_syntax(self): - if "Id" in self._policy_document: - assert isinstance(self._policy_document["Id"], string_types) + if "Id" in self._policy_json: + assert isinstance(self._policy_json["Id"], string_types) def _validate_resource_exist(self): for statement in self._statements: assert "Resource" in statement + if isinstance(statement["Resource"], list): + assert statement["Resource"] + + def _validate_action_exist(self): + for statement in self._statements: + assert "Action" in statement From e133344846f68b47224a903becd5602631c6d0d8 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 18:48:27 +0200 Subject: [PATCH 09/17] Implemented validating action prefixes. --- moto/iam/policy_validation.py | 34 ++++++++++-- tests/test_iam/test_iam_policies.py | 80 +++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 6b0f6578b..38bb6e944 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -1,4 +1,5 @@ import json +import re from six import string_types @@ -58,6 +59,8 @@ class IAMPolicyDocumentValidator: except Exception: raise MalformedPolicyDocument("Policy statement must contain resources.") + self._validate_action_prefix() + def _validate_syntax(self): self._policy_json = json.loads(self._policy_document) assert isinstance(self._policy_json, dict) @@ -101,6 +104,8 @@ class IAMPolicyDocumentValidator: assert ("Action" not in statement or "NotAction" not in statement) IAMPolicyDocumentValidator._validate_effect_syntax(statement) + IAMPolicyDocumentValidator._validate_action_syntax(statement) + IAMPolicyDocumentValidator._validate_not_action_syntax(statement) IAMPolicyDocumentValidator._validate_resource_syntax(statement) IAMPolicyDocumentValidator._validate_not_resource_syntax(statement) IAMPolicyDocumentValidator._validate_condition_syntax(statement) @@ -112,16 +117,24 @@ class IAMPolicyDocumentValidator: assert isinstance(statement["Effect"], string_types) assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in ALLOWED_EFFECTS] + @staticmethod + def _validate_action_syntax(statement): + IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "Action") + + @staticmethod + def _validate_not_action_syntax(statement): + IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "NotAction") + @staticmethod def _validate_resource_syntax(statement): - IAMPolicyDocumentValidator._validate_resource_like_syntax(statement, "Resource") + IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "Resource") @staticmethod def _validate_not_resource_syntax(statement): - IAMPolicyDocumentValidator._validate_resource_like_syntax(statement, "NotResource") + IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "NotResource") @staticmethod - def _validate_resource_like_syntax(statement, key): + def _validate_string_or_list_of_strings_syntax(statement, key): if key in statement: assert isinstance(statement[key], (string_types, list)) if isinstance(statement[key], list): @@ -155,4 +168,19 @@ class IAMPolicyDocumentValidator: def _validate_action_exist(self): for statement in self._statements: assert "Action" in statement + if isinstance(statement["Action"], list): + assert statement["Action"] + + def _validate_action_prefix(self): + for statement in self._statements: + action_parts = statement["Action"].split(":") + if len(action_parts) == 1: + raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.") + elif len(action_parts) > 2: + raise MalformedPolicyDocument("Actions/Condition can contain only one colon.") + + vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]') + if vendor_pattern.search(action_parts[0]): + raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0])) + diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index aefacf25e..f1c53a7d0 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -127,6 +127,30 @@ invalid_documents_test_cases = [ }, "error_message": 'Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "a a:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Vendor a a is not valid' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:List:Bucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Actions/Condition can contain only one colon.' + }, { "document": { "Version": "2012-10-17", @@ -149,6 +173,17 @@ invalid_documents_test_cases = [ }, "error_message": 'Resource adf must be in ARN format or "*".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "" + } + }, + "error_message": 'Resource must be in ARN format or "*".' + }, { "document": { "Version": "2012-10-17", @@ -177,6 +212,16 @@ invalid_documents_test_cases = [ }, "error_message": 'Policy statement must contain resources.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "invalid" + } + }, + "error_message": 'Policy statement must contain resources.' + }, { "document": { "Version": "2012-10-17", @@ -206,6 +251,18 @@ invalid_documents_test_cases = [ }, "error_message": 'Policy statement must contain actions.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": [], + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Policy statement must contain actions.' + }, { "document": { "Version": "2012-10-17", @@ -283,6 +340,29 @@ invalid_documents_test_cases = [ }, "error_message": 'Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": [[]], + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": {}, + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", From d2b0812edcdd5301e968796df4bdbe4d70f9eab8 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 20:33:17 +0200 Subject: [PATCH 10/17] Added more tests. --- tests/test_iam/test_iam_policies.py | 144 ++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index f1c53a7d0..1aeccf4f4 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -127,6 +127,18 @@ invalid_documents_test_cases = [ }, "error_message": 'Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.' + }, { "document": { "Version": "2012-10-17", @@ -151,6 +163,24 @@ invalid_documents_test_cases = [ }, "error_message": 'Actions/Condition can contain only one colon.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3s:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + { + "Effect": "Allow", + "Action": "s:3s:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + ] + }, + "error_message": 'Actions/Condition can contain only one colon.' + }, { "document": { "Version": "2012-10-17", @@ -162,6 +192,18 @@ invalid_documents_test_cases = [ }, "error_message": 'Resource invalid resource must be in ARN format or "*".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s:3:ListBucket", + "Resource": "sdfsadf" + } + }, + "error_message": 'Resource sdfsadf must be in ARN format or "*".' + }, { "document": { "Version": "2012-10-17", @@ -184,6 +226,42 @@ invalid_documents_test_cases = [ }, "error_message": 'Resource must be in ARN format or "*".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3s:ListBucket", + "Resource": "a:bsdfdsafsad" + } + }, + "error_message": 'Partition "bsdfdsafsad" is not valid for resource "arn:bsdfdsafsad:*:*:*:*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3s:ListBucket", + "Resource": "a:b:cadfsdf" + } + }, + "error_message": 'Partition "b" is not valid for resource "arn:b:cadfsdf:*:*:*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3s:ListBucket", + "Resource": "a:b:c:d:e:f:g:h" + } + }, + "error_message": 'Partition "b" is not valid for resource "arn:b:c:d:e:f:g:h".' + }, { "document": { "Version": "2012-10-17", @@ -340,6 +418,19 @@ invalid_documents_test_cases = [ }, "error_message": 'Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "NotResource": [] + } + }, + "error_message": 'Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", @@ -351,6 +442,19 @@ invalid_documents_test_cases = [ }, "error_message": 'Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3s:ListBucket", + "Action": [], + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", @@ -546,6 +650,23 @@ invalid_documents_test_cases = [ }, "error_message": 'Statement IDs (SID) in a single policy must be unique.' }, + { + "document": { + "Statement": [ + { + "Sid": "sdf", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + { + "Sid": "sdf", + "Effect": "Allow" + } + ] + }, + "error_message": 'Policy document must be version 2012-10-17 or greater.' + }, { "document": { "Version": "2012-10-17", @@ -622,6 +743,29 @@ invalid_documents_test_cases = [ } }, "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Resource": "arn:aws::::example_bucket" + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": + { + "Effect": "allow", + "Resource": "arn:aws:s3:us-east-1::example_bucket" + } + }, + "error_message": 'The policy failed legacy parsing' } ] # TODO add more tests From 81098e34533ce9d96f79245c22b6015408559866 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Sun, 30 Jun 2019 20:34:01 +0200 Subject: [PATCH 11/17] Implemented every validation point except for legacy parsing. --- moto/iam/policy_validation.py | 159 +++++++++++++++++++++++++++++----- 1 file changed, 135 insertions(+), 24 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 38bb6e944..4cff118ac 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -6,19 +6,19 @@ from six import string_types from moto.iam.exceptions import MalformedPolicyDocument -ALLOWED_TOP_ELEMENTS = [ +VALID_TOP_ELEMENTS = [ "Version", "Id", "Statement", "Conditions" ] -ALLOWED_VERSIONS = [ +VALID_VERSIONS = [ "2008-10-17", "2012-10-17" ] -ALLOWED_STATEMENT_ELEMENTS = [ +VALID_STATEMENT_ELEMENTS = [ "Sid", "Action", "NotAction", @@ -28,11 +28,24 @@ ALLOWED_STATEMENT_ELEMENTS = [ "Condition" ] -ALLOWED_EFFECTS = [ +VALID_EFFECTS = [ "Allow", "Deny" ] +SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS = { + "iam": 'IAM resource {resource} cannot contain region information.', + "s3": 'Resource {resource} can not contain region information.' +} + +VALID_RESOURCE_PATH_STARTING_VALUES = { + "iam": { + "values": ["user/", "federated-user/", "role/", "group/", "instance-profile/", "mfa/", "server-certificate/", + "policy/", "sms-mfa/", "saml-provider/", "oidc-provider/", "report/", "access-report/"], + "error_message": 'IAM resource path must either be "*" or start with {values}.' + } +} + class IAMPolicyDocumentValidator: @@ -51,7 +64,11 @@ class IAMPolicyDocumentValidator: except Exception: raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.") try: - self._validate_action_exist() + self._validate_sid_uniqueness() + except Exception: + raise MalformedPolicyDocument("Statement IDs (SID) in a single policy must be unique.") + try: + self._validate_action_like_exist() except Exception: raise MalformedPolicyDocument("Policy statement must contain actions.") try: @@ -59,7 +76,11 @@ class IAMPolicyDocumentValidator: except Exception: raise MalformedPolicyDocument("Policy statement must contain resources.") - self._validate_action_prefix() + self._validate_resources_for_formats() + self._validate_not_resources_for_formats() + + self._validate_actions_for_prefixes() + self._validate_not_actions_for_prefixes() def _validate_syntax(self): self._policy_json = json.loads(self._policy_document) @@ -72,15 +93,22 @@ class IAMPolicyDocumentValidator: def _validate_top_elements(self): top_elements = self._policy_json.keys() for element in top_elements: - assert element in ALLOWED_TOP_ELEMENTS + assert element in VALID_TOP_ELEMENTS def _validate_version_syntax(self): if "Version" in self._policy_json: - assert self._policy_json["Version"] in ALLOWED_VERSIONS + assert self._policy_json["Version"] in VALID_VERSIONS def _validate_version(self): assert self._policy_json["Version"] == "2012-10-17" + def _validate_sid_uniqueness(self): + sids = [] + for statement in self._statements: + if "Sid" in statement: + assert statement["Sid"] not in sids + sids.append(statement["Sid"]) + def _validate_statements_syntax(self): assert "Statement" in self._policy_json assert isinstance(self._policy_json["Statement"], (dict, list)) @@ -98,7 +126,7 @@ class IAMPolicyDocumentValidator: def _validate_statement_syntax(statement): assert isinstance(statement, dict) for statement_element in statement.keys(): - assert statement_element in ALLOWED_STATEMENT_ELEMENTS + assert statement_element in VALID_STATEMENT_ELEMENTS assert ("Resource" not in statement or "NotResource" not in statement) assert ("Action" not in statement or "NotAction" not in statement) @@ -115,7 +143,7 @@ class IAMPolicyDocumentValidator: def _validate_effect_syntax(statement): assert "Effect" in statement assert isinstance(statement["Effect"], string_types) - assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in ALLOWED_EFFECTS] + assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in VALID_EFFECTS] @staticmethod def _validate_action_syntax(statement): @@ -161,26 +189,109 @@ class IAMPolicyDocumentValidator: def _validate_resource_exist(self): for statement in self._statements: - assert "Resource" in statement - if isinstance(statement["Resource"], list): + assert ("Resource" in statement or "NotResource" in statement) + if "Resource" in statement and isinstance(statement["Resource"], list): assert statement["Resource"] + elif "NotResource" in statement and isinstance(statement["NotResource"], list): + assert statement["NotResource"] - def _validate_action_exist(self): + def _validate_action_like_exist(self): for statement in self._statements: - assert "Action" in statement - if isinstance(statement["Action"], list): + assert ("Action" in statement or "NotAction" in statement) + if "Action" in statement and isinstance(statement["Action"], list): assert statement["Action"] + elif "NotAction" in statement and isinstance(statement["NotAction"], list): + assert statement["NotAction"] - def _validate_action_prefix(self): + def _validate_actions_for_prefixes(self): + self._validate_action_like_for_prefixes("Action") + + def _validate_not_actions_for_prefixes(self): + self._validate_action_like_for_prefixes("NotAction") + + def _validate_action_like_for_prefixes(self, key): for statement in self._statements: - action_parts = statement["Action"].split(":") - if len(action_parts) == 1: - raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.") - elif len(action_parts) > 2: - raise MalformedPolicyDocument("Actions/Condition can contain only one colon.") + if key in statement: + if isinstance(statement[key], string_types): + self._validate_action_prefix(statement[key]) + else: + for action in statement[key]: + self._validate_action_prefix(action) - vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]') - if vendor_pattern.search(action_parts[0]): - raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0])) + @staticmethod + def _validate_action_prefix(action): + action_parts = action.split(":") + if len(action_parts) == 1: + raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.") + elif len(action_parts) > 2: + raise MalformedPolicyDocument("Actions/Condition can contain only one colon.") + + vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]') + if vendor_pattern.search(action_parts[0]): + raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0])) + + def _validate_resources_for_formats(self): + self._validate_resource_like_for_formats("Resource") + + def _validate_not_resources_for_formats(self): + self._validate_resource_like_for_formats("NotResource") + + def _validate_resource_like_for_formats(self, key): + for statement in self._statements: + if key in statement: + if isinstance(statement[key], string_types): + self._validate_resource_format(statement[key]) + else: + for resource in statement[key]: + self._validate_resource_format(resource) + + @staticmethod + def _validate_resource_format(resource): + if resource != "*": + resource_partitions = resource.partition(":") + + if resource_partitions[1] == "": + raise MalformedPolicyDocument('Resource {resource} must be in ARN format or "*".'.format(resource=resource)) + + resource_partitions = resource_partitions[2].partition(":") + if resource_partitions[0] != "aws": + remaining_resource_parts = resource_partitions[2].split(":") + + arn1 = remaining_resource_parts[0] if remaining_resource_parts[0] != "" else "*" + arn2 = remaining_resource_parts[1] if len(remaining_resource_parts) > 1 else "*" + arn3 = remaining_resource_parts[2] if len(remaining_resource_parts) > 2 else "*" + arn4 = ":".join(remaining_resource_parts[3:]) if len(remaining_resource_parts) > 3 else "*" + raise MalformedPolicyDocument( + 'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format( + partition=resource_partitions[0], + arn1=arn1, + arn2=arn2, + arn3=arn3, + arn4=arn4 + )) + + if resource_partitions[1] != ":": + raise MalformedPolicyDocument("Resource vendor must be fully qualified and cannot contain regexes.") + + resource_partitions = resource_partitions[2].partition(":") + + service = resource_partitions[0] + + if service in SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS.keys() and not resource_partitions[2].startswith(":"): + raise MalformedPolicyDocument(SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS[service].format(resource=resource)) + + resource_partitions = resource_partitions[2].partition(":") + resource_partitions = resource_partitions[2].partition(":") + + if service in VALID_RESOURCE_PATH_STARTING_VALUES.keys(): + valid_start = False + for valid_starting_value in VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"]: + if resource_partitions[2].startswith(valid_starting_value): + valid_start = True + break + if not valid_start: + raise MalformedPolicyDocument(VALID_RESOURCE_PATH_STARTING_VALUES[service]["error_message"].format( + values=", ".join(VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"]) + )) From 85efec29b148ed8e9989e0ae0fbe00969eec879c Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 17:30:59 +0200 Subject: [PATCH 12/17] Added more tests. --- tests/test_iam/test_iam_policies.py | 186 ++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index 1aeccf4f4..7cc6e00b1 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -262,6 +262,31 @@ invalid_documents_test_cases = [ }, "error_message": 'Partition "b" is not valid for resource "arn:b:c:d:e:f:g:h".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "aws:s3:::example_bucket" + } + }, + "error_message": 'Partition "s3" is not valid for resource "arn:s3:::example_bucket:*".' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [ + "arn:error:s3:::example_bucket", + "arn:error:s3::example_bucket" + ] + } + }, + "error_message": 'Partition "error" is not valid for resource "arn:error:s3:::example_bucket".' + }, { "document": { "Version": "2012-10-17", @@ -381,6 +406,16 @@ invalid_documents_test_cases = [ }, "error_message": 'The policy failed legacy parsing' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Resource": "arn:aws:s3::example_bucket" + } + }, + "error_message": 'The policy failed legacy parsing' + }, { "document": { "Version": "2012-10-17", @@ -567,6 +602,38 @@ invalid_documents_test_cases = [ }, "error_message": 'Syntax errors in policy.' }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "x": { + "a": "1" + } + } + } + }, + "error_message": 'Syntax errors in policy.' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "ForAnyValue::StringEqualsIfExists": { + "a": "asf" + } + } + } + }, + "error_message": 'Syntax errors in policy.' + }, { "document": { "Version": "2012-10-17", @@ -731,6 +798,16 @@ invalid_documents_test_cases = [ }, "error_message": 'Policy document must be version 2012-10-17 or greater.' }, + { + "document": { + "Statement": { + "Effect": "denY", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + "error_message": 'Policy document must be version 2012-10-17 or greater.' + }, { "document": { "Version": "2012-10-17", @@ -766,6 +843,115 @@ invalid_documents_test_cases = [ } }, "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "sdf", + "Effect": "aLLow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + { + "Sid": "sdf", + "Effect": "Allow" + } + ] + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "NotResource": "arn:aws:s3::example_bucket" + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "234-13" + } + } + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "2016-12-13t2:00:00.593194+1" + } + } + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "2016-12-13t2:00:00.1999999999+10:59" + } + } + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThan": { + "a": "9223372036854775808" + } + } + } + }, + "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:error:s3:::example_bucket", + "Condition": { + "DateGreaterThan": { + "a": "sdfdsf" + } + } + } + }, + "error_message": 'The policy failed legacy parsing' } ] # TODO add more tests From fef22879c58953c8b907f01a7c079eee7e0470b3 Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 17:31:12 +0200 Subject: [PATCH 13/17] Implemented legacy validation (parsing). --- moto/iam/policy_validation.py | 182 +++++++++++++++++++++++++++++++--- 1 file changed, 166 insertions(+), 16 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 4cff118ac..171a51b29 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -33,6 +33,45 @@ VALID_EFFECTS = [ "Deny" ] +VALID_CONDITIONS = [ + "StringEquals", + "StringNotEquals", + "StringEqualsIgnoreCase", + "StringNotEqualsIgnoreCase", + "StringLike", + "StringNotLike", + "NumericEquals", + "NumericNotEquals", + "NumericLessThan", + "NumericLessThanEquals", + "NumericGreaterThan", + "NumericGreaterThanEquals", + "DateEquals", + "DateNotEquals", + "DateLessThan", + "DateLessThanEquals", + "DateGreaterThan", + "DateGreaterThanEquals", + "Bool", + "BinaryEquals", + "IpAddress", + "NotIpAddress", + "ArnEquals", + "ArnLike", + "ArnNotEquals", + "ArnNotLike", + "Null" +] + +VALID_CONDITION_PREFIXES = [ + "ForAnyValue:", + "ForAllValues:" +] + +VALID_CONDITION_POSTFIXES = [ + "IfExists" +] + SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS = { "iam": 'IAM resource {resource} cannot contain region information.', "s3": 'Resource {resource} can not contain region information.' @@ -53,6 +92,7 @@ class IAMPolicyDocumentValidator: self._policy_document: str = policy_document self._policy_json: dict = {} self._statements = [] + self._resource_error = "" # the first resource error found that does not generate a legacy parsing error def validate(self): try: @@ -63,6 +103,12 @@ class IAMPolicyDocumentValidator: self._validate_version() except Exception: raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.") + try: + self._perform_first_legacy_parsing() + self._validate_resources_for_formats() + self._validate_not_resources_for_formats() + except Exception: + raise MalformedPolicyDocument("The policy failed legacy parsing") try: self._validate_sid_uniqueness() except Exception: @@ -76,8 +122,8 @@ class IAMPolicyDocumentValidator: except Exception: raise MalformedPolicyDocument("Policy statement must contain resources.") - self._validate_resources_for_formats() - self._validate_not_resources_for_formats() + if self._resource_error != "": + raise MalformedPolicyDocument(self._resource_error) self._validate_actions_for_prefixes() self._validate_not_actions_for_prefixes() @@ -175,8 +221,25 @@ class IAMPolicyDocumentValidator: assert isinstance(statement["Condition"], dict) for condition_key, condition_value in statement["Condition"].items(): assert isinstance(condition_value, dict) - for condition_data_key, condition_data_value in condition_value.items(): - assert isinstance(condition_data_value, (list, string_types)) + for condition_element_key, condition_element_value in condition_value.items(): + assert isinstance(condition_element_value, (list, string_types)) + + if IAMPolicyDocumentValidator._strip_condition_key(condition_key) not in VALID_CONDITIONS: + assert not condition_value # empty dict + + @staticmethod + def _strip_condition_key(condition_key): + for valid_prefix in VALID_CONDITION_PREFIXES: + if condition_key.startswith(valid_prefix): + condition_key = condition_key.lstrip(valid_prefix) + break # strip only the first match + + for valid_postfix in VALID_CONDITION_POSTFIXES: + if condition_key.startswith(valid_postfix): + condition_key = condition_key.rstrip(valid_postfix) + break # strip only the first match + + return condition_key @staticmethod def _validate_sid_syntax(statement): @@ -242,43 +305,47 @@ class IAMPolicyDocumentValidator: if isinstance(statement[key], string_types): self._validate_resource_format(statement[key]) else: - for resource in statement[key]: + for resource in sorted(statement[key], reverse=True): self._validate_resource_format(resource) + if self._resource_error == "": + IAMPolicyDocumentValidator._legacy_parse_resource_like(statement, key) - @staticmethod - def _validate_resource_format(resource): + def _validate_resource_format(self, resource): if resource != "*": resource_partitions = resource.partition(":") if resource_partitions[1] == "": - raise MalformedPolicyDocument('Resource {resource} must be in ARN format or "*".'.format(resource=resource)) + self._resource_error = 'Resource {resource} must be in ARN format or "*".'.format(resource=resource) + return resource_partitions = resource_partitions[2].partition(":") if resource_partitions[0] != "aws": remaining_resource_parts = resource_partitions[2].split(":") - arn1 = remaining_resource_parts[0] if remaining_resource_parts[0] != "" else "*" + arn1 = remaining_resource_parts[0] if remaining_resource_parts[0] != "" or len(remaining_resource_parts) > 1 else "*" arn2 = remaining_resource_parts[1] if len(remaining_resource_parts) > 1 else "*" arn3 = remaining_resource_parts[2] if len(remaining_resource_parts) > 2 else "*" arn4 = ":".join(remaining_resource_parts[3:]) if len(remaining_resource_parts) > 3 else "*" - raise MalformedPolicyDocument( - 'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format( + self._resource_error = 'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format( partition=resource_partitions[0], arn1=arn1, arn2=arn2, arn3=arn3, arn4=arn4 - )) + ) + return if resource_partitions[1] != ":": - raise MalformedPolicyDocument("Resource vendor must be fully qualified and cannot contain regexes.") + self._resource_error = "Resource vendor must be fully qualified and cannot contain regexes." + return resource_partitions = resource_partitions[2].partition(":") service = resource_partitions[0] if service in SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS.keys() and not resource_partitions[2].startswith(":"): - raise MalformedPolicyDocument(SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS[service].format(resource=resource)) + self._resource_error = SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS[service].format(resource=resource) + return resource_partitions = resource_partitions[2].partition(":") resource_partitions = resource_partitions[2].partition(":") @@ -290,8 +357,91 @@ class IAMPolicyDocumentValidator: valid_start = True break if not valid_start: - raise MalformedPolicyDocument(VALID_RESOURCE_PATH_STARTING_VALUES[service]["error_message"].format( + self._resource_error = VALID_RESOURCE_PATH_STARTING_VALUES[service]["error_message"].format( values=", ".join(VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"]) - )) + ) + def _perform_first_legacy_parsing(self): + """This method excludes legacy parsing resources, since that have to be done later.""" + for statement in self._statements: + self._legacy_parse_statement(statement) + @staticmethod + def _legacy_parse_statement(statement): + assert statement["Effect"] in VALID_EFFECTS # case-sensitive matching + if "Condition" in statement: + for condition_key, condition_value in statement["Condition"]: + IAMPolicyDocumentValidator._legacy_parse_condition(condition_key, condition_value) + + @staticmethod + def _legacy_parse_resource_like(statement, key): + if isinstance(statement[key], string_types): + assert statement[key] == "*" or statement[key].count(":") >= 5 + assert statement[key] == "*" or statement[key].split(":")[2] != "" + else: # list + for resource in statement[key]: + assert resource == "*" or resource.count(":") >= 5 + assert resource == "*" or resource[2] != "" + + @staticmethod + def _legacy_parse_condition(condition_key, condition_value): + stripped_condition_key = IAMPolicyDocumentValidator._strip_condition_key(condition_key) + + if stripped_condition_key.startswith("Date"): + for condition_element_key, condition_element_value in condition_value.items(): + if isinstance(condition_element_value, string_types): + IAMPolicyDocumentValidator._legacy_parse_date_condition_value(condition_element_value) + else: # it has to be a list + for date_condition_value in condition_element_value: + IAMPolicyDocumentValidator._legacy_parse_date_condition_value(date_condition_value) + + @staticmethod + def _legacy_parse_date_condition_value(date_condition_value): + if "t" in date_condition_value.lower() or "-" in date_condition_value: + IAMPolicyDocumentValidator._validate_iso_8601_datetime(date_condition_value.lower()) + else: # timestamp + assert 0 <= int(date_condition_value) <= 9223372036854775807 + + @staticmethod + def _validate_iso_8601_datetime(datetime): + datetime_parts = datetime.partition("t") + date_parts = datetime_parts[0].split("-") + year = date_parts[0] + assert -292275054 <= int(year) <= 292278993 + if len(date_parts) > 1: + month = date_parts[1] + assert 1 <= int(month) <= 12 + if len(date_parts) > 2: + day = date_parts[2] + assert 1 <= int(day) <= 31 + assert len(date_parts) < 4 + + time_parts = datetime_parts[2].split(":") + if time_parts[0] != "": + hours = time_parts[0] + assert 0 <= int(hours) <= 23 + if len(time_parts) > 1: + minutes = time_parts[1] + assert 0 <= int(minutes) <= 59 + if len(time_parts) > 2: + if "z" in time_parts[2]: + seconds_with_decimal_fraction = time_parts[2].partition("z")[0] + assert time_parts[2].partition("z")[2] == "" + elif "+" in time_parts[2]: + seconds_with_decimal_fraction = time_parts[2].partition("+")[0] + time_zone_data = time_parts[2].partition("+")[2].partition(":") + time_zone_hours = time_zone_data[0] + assert len(time_zone_hours) == 2 + assert 0 <= int(time_zone_hours) <= 23 + if time_zone_data[1] == ":": + time_zone_minutes = time_zone_data[2] + assert len(time_zone_minutes) == 2 + assert 0 <= int(time_zone_minutes) <= 59 + else: + seconds_with_decimal_fraction = time_parts[2] + seconds_with_decimal_fraction_partition = seconds_with_decimal_fraction.partition(".") + seconds = seconds_with_decimal_fraction_partition[0] + assert 0 <= int(seconds) <= 59 + if seconds_with_decimal_fraction_partition[1] == ".": + decimal_seconds = seconds_with_decimal_fraction_partition[2] + assert 0 <= int(decimal_seconds) <= 999999999 From 1bda3f221396614cf47674cb5f9a65f553e6a0ef Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 18:21:54 +0200 Subject: [PATCH 14/17] Added tests for valid policy documents. --- tests/test_iam/test_iam.py | 2 - tests/test_iam/test_iam_policies.py | 896 +++++++++++++++++++++++++++- 2 files changed, 892 insertions(+), 6 deletions(-) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index a9bf8d4f8..0d96fd1b1 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -859,7 +859,6 @@ def test_get_access_key_last_used(): @mock_iam def test_get_account_authorization_details(): - import json test_policy = json.dumps({ "Version": "2012-10-17", "Statement": [ @@ -1291,7 +1290,6 @@ def test_update_role(): @mock_iam() def test_list_entities_for_policy(): - import json test_policy = json.dumps({ "Version": "2012-10-17", "Statement": [ diff --git a/tests/test_iam/test_iam_policies.py b/tests/test_iam/test_iam_policies.py index 7cc6e00b1..e1924a559 100644 --- a/tests/test_iam/test_iam_policies.py +++ b/tests/test_iam/test_iam_policies.py @@ -6,8 +6,7 @@ from nose.tools import assert_raises from moto import mock_iam - -invalid_documents_test_cases = [ +invalid_policy_document_test_cases = [ { "document": "This is not a json document", "error_message": 'Syntax errors in policy.' @@ -192,6 +191,35 @@ invalid_documents_test_cases = [ }, "error_message": 'Resource invalid resource must be in ARN format or "*".' }, + { + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EnableDisableHongKong", + "Effect": "Allow", + "Action": [ + "account:EnableRegion", + "account:DisableRegion" + ], + "Resource": "", + "Condition": { + "StringEquals": {"account:TargetRegion": "ap-east-1"} + } + }, + { + "Sid": "ViewConsole", + "Effect": "Allow", + "Action": [ + "aws-portal:ViewAccount", + "account:ListRegions" + ], + "Resource": "" + } + ] + }, + "error_message": 'Resource must be in ARN format or "*".' + }, { "document": { "Version": "2012-10-17", @@ -952,15 +980,867 @@ invalid_documents_test_cases = [ } }, "error_message": 'The policy failed legacy parsing' + }, + { + "document": { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws::fdsasf" + } + }, + "error_message": 'The policy failed legacy parsing' } -] # TODO add more tests +] + +valid_policy_documents = [ + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [ + "arn:aws:s3:::example_bucket" + ] + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "iam: asdf safdsf af ", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [ + "arn:aws:s3:::example_bucket", + "*" + ] + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "*", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + ] + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "service-prefix:action-name", + "Resource": "*", + "Condition": { + "DateGreaterThan": {"aws:CurrentTime": "2017-07-01T00:00:00Z"}, + "DateLessThan": {"aws:CurrentTime": "2017-12-31T23:59:59Z"} + } + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "fsx:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:iam:::user/example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s33:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:fdsasf" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": {} + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": {"ForAllValues:StringEquals": {"aws:TagKeys": "Department"}} + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:cloudwatch:us-east-1::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:ec2:us-east-1::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:invalid-service:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:invalid-service:us-east-1::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"aws:CurrentTime": "2017-07-01T00:00:00Z"}, + "DateLessThan": {"aws:CurrentTime": "2017-12-31T23:59:59Z"} + } + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {} + } + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": {"a": []} + } + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "a": {} + } + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Sid": "dsfsdfsdfsdfsdfsadfsd", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ConsoleDisplay", + "Effect": "Allow", + "Action": [ + "iam:GetRole", + "iam:GetUser", + "iam:ListRoles", + "iam:ListRoleTags", + "iam:ListUsers", + "iam:ListUserTags" + ], + "Resource": "*" + }, + { + "Sid": "AddTag", + "Effect": "Allow", + "Action": [ + "iam:TagUser", + "iam:TagRole" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "aws:RequestTag/CostCenter": [ + "A-123", + "B-456" + ] + }, + "ForAllValues:StringEquals": {"aws:TagKeys": "CostCenter"} + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Deny", + "Action": "s3:*", + "NotResource": [ + "arn:aws:s3:::HRBucket/Payroll", + "arn:aws:s3:::HRBucket/Payroll/*" + ] + } + }, + { + "Version": "2012-10-17", + "Id": "sdfsdfsdf", + "Statement": { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "aaaaaadsfdsafsadfsadfaaaaa:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3-s:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "Action": "s3.s:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + }, + { + "Version": "2012-10-17", + "Statement": + { + "Effect": "Allow", + "NotAction": "s3:ListBucket", + "NotResource": "*" + } + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "sdf", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + }, + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket" + } + ] + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateGreaterThan": { + "a": "01T" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "x": { + }, + "y": {} + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "StringEqualsIfExists": { + "a": "asf" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "ForAnyValue:StringEqualsIfExists": { + "a": "asf" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "2019-07-01T13:20:15Z" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "2016-12-13T21:20:37.593194+00:00" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThanEquals": { + "a": "2016-12-13t2:00:00.593194+23" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::example_bucket", + "Condition": { + "DateLessThan": { + "a": "-292275054" + } + } + } + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowViewAccountInfo", + "Effect": "Allow", + "Action": [ + "iam:GetAccountPasswordPolicy", + "iam:GetAccountSummary", + "iam:ListVirtualMFADevices" + ], + "Resource": "*" + }, + { + "Sid": "AllowManageOwnPasswords", + "Effect": "Allow", + "Action": [ + "iam:ChangePassword", + "iam:GetUser" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnAccessKeys", + "Effect": "Allow", + "Action": [ + "iam:CreateAccessKey", + "iam:DeleteAccessKey", + "iam:ListAccessKeys", + "iam:UpdateAccessKey" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnSigningCertificates", + "Effect": "Allow", + "Action": [ + "iam:DeleteSigningCertificate", + "iam:ListSigningCertificates", + "iam:UpdateSigningCertificate", + "iam:UploadSigningCertificate" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnSSHPublicKeys", + "Effect": "Allow", + "Action": [ + "iam:DeleteSSHPublicKey", + "iam:GetSSHPublicKey", + "iam:ListSSHPublicKeys", + "iam:UpdateSSHPublicKey", + "iam:UploadSSHPublicKey" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnGitCredentials", + "Effect": "Allow", + "Action": [ + "iam:CreateServiceSpecificCredential", + "iam:DeleteServiceSpecificCredential", + "iam:ListServiceSpecificCredentials", + "iam:ResetServiceSpecificCredential", + "iam:UpdateServiceSpecificCredential" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnVirtualMFADevice", + "Effect": "Allow", + "Action": [ + "iam:CreateVirtualMFADevice", + "iam:DeleteVirtualMFADevice" + ], + "Resource": "arn:aws:iam::*:mfa/${aws:username}" + }, + { + "Sid": "AllowManageOwnUserMFA", + "Effect": "Allow", + "Action": [ + "iam:DeactivateMFADevice", + "iam:EnableMFADevice", + "iam:ListMFADevices", + "iam:ResyncMFADevice" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "DenyAllExceptListedIfNoMFA", + "Effect": "Deny", + "NotAction": [ + "iam:CreateVirtualMFADevice", + "iam:EnableMFADevice", + "iam:GetUser", + "iam:ListMFADevices", + "iam:ListVirtualMFADevices", + "iam:ResyncMFADevice", + "sts:GetSessionToken" + ], + "Resource": "*", + "Condition": { + "BoolIfExists": { + "aws:MultiFactorAuthPresent": "false" + } + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ListAndDescribe", + "Effect": "Allow", + "Action": [ + "dynamodb:List*", + "dynamodb:DescribeReservedCapacity*", + "dynamodb:DescribeLimits", + "dynamodb:DescribeTimeToLive" + ], + "Resource": "*" + }, + { + "Sid": "SpecificTable", + "Effect": "Allow", + "Action": [ + "dynamodb:BatchGet*", + "dynamodb:DescribeStream", + "dynamodb:DescribeTable", + "dynamodb:Get*", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:BatchWrite*", + "dynamodb:CreateTable", + "dynamodb:Delete*", + "dynamodb:Update*", + "dynamodb:PutItem" + ], + "Resource": "arn:aws:dynamodb:*:*:table/MyTable" + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:AttachVolume", + "ec2:DetachVolume" + ], + "Resource": [ + "arn:aws:ec2:*:*:volume/*", + "arn:aws:ec2:*:*:instance/*" + ], + "Condition": { + "ArnEquals": {"ec2:SourceInstanceARN": "arn:aws:ec2:*:*:instance/instance-id"} + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:AttachVolume", + "ec2:DetachVolume" + ], + "Resource": "arn:aws:ec2:*:*:instance/*", + "Condition": { + "StringEquals": {"ec2:ResourceTag/Department": "Development"} + } + }, + { + "Effect": "Allow", + "Action": [ + "ec2:AttachVolume", + "ec2:DetachVolume" + ], + "Resource": "arn:aws:ec2:*:*:volume/*", + "Condition": { + "StringEquals": {"ec2:ResourceTag/VolumeUser": "${aws:username}"} + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "StartStopIfTags", + "Effect": "Allow", + "Action": [ + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:DescribeTags" + ], + "Resource": "arn:aws:ec2:region:account-id:instance/*", + "Condition": { + "StringEquals": { + "ec2:ResourceTag/Project": "DataAnalytics", + "aws:PrincipalTag/Department": "Data" + } + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ListYourObjects", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": ["arn:aws:s3:::bucket-name"], + "Condition": { + "StringLike": { + "s3:prefix": ["cognito/application-name/${cognito-identity.amazonaws.com:sub}"] + } + } + }, + { + "Sid": "ReadWriteDeleteYourObjects", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::bucket-name/cognito/application-name/${cognito-identity.amazonaws.com:sub}", + "arn:aws:s3:::bucket-name/cognito/application-name/${cognito-identity.amazonaws.com:sub}/*" + ] + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::bucket-name", + "Condition": { + "StringLike": { + "s3:prefix": [ + "", + "home/", + "home/${aws:userid}/*" + ] + } + } + }, + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": [ + "arn:aws:s3:::bucket-name/home/${aws:userid}", + "arn:aws:s3:::bucket-name/home/${aws:userid}/*" + ] + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ConsoleAccess", + "Effect": "Allow", + "Action": [ + "s3:GetAccountPublicAccessBlock", + "s3:GetBucketAcl", + "s3:GetBucketLocation", + "s3:GetBucketPolicyStatus", + "s3:GetBucketPublicAccessBlock", + "s3:ListAllMyBuckets" + ], + "Resource": "*" + }, + { + "Sid": "ListObjectsInBucket", + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": ["arn:aws:s3:::bucket-name"] + }, + { + "Sid": "AllObjectActions", + "Effect": "Allow", + "Action": "s3:*Object", + "Resource": ["arn:aws:s3:::bucket-name/*"] + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowViewAccountInfo", + "Effect": "Allow", + "Action": [ + "iam:GetAccountPasswordPolicy", + "iam:GetAccountSummary" + ], + "Resource": "*" + }, + { + "Sid": "AllowManageOwnPasswords", + "Effect": "Allow", + "Action": [ + "iam:ChangePassword", + "iam:GetUser" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnAccessKeys", + "Effect": "Allow", + "Action": [ + "iam:CreateAccessKey", + "iam:DeleteAccessKey", + "iam:ListAccessKeys", + "iam:UpdateAccessKey" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnSigningCertificates", + "Effect": "Allow", + "Action": [ + "iam:DeleteSigningCertificate", + "iam:ListSigningCertificates", + "iam:UpdateSigningCertificate", + "iam:UploadSigningCertificate" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnSSHPublicKeys", + "Effect": "Allow", + "Action": [ + "iam:DeleteSSHPublicKey", + "iam:GetSSHPublicKey", + "iam:ListSSHPublicKeys", + "iam:UpdateSSHPublicKey", + "iam:UploadSSHPublicKey" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + }, + { + "Sid": "AllowManageOwnGitCredentials", + "Effect": "Allow", + "Action": [ + "iam:CreateServiceSpecificCredential", + "iam:DeleteServiceSpecificCredential", + "iam:ListServiceSpecificCredentials", + "iam:ResetServiceSpecificCredential", + "iam:UpdateServiceSpecificCredential" + ], + "Resource": "arn:aws:iam::*:user/${aws:username}" + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "ec2:*", + "Resource": "*", + "Effect": "Allow", + "Condition": { + "StringEquals": { + "ec2:Region": "region" + } + } + } + ] + }, + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "rds:*", + "Resource": ["arn:aws:rds:region:*:*"] + }, + { + "Effect": "Allow", + "Action": ["rds:Describe*"], + "Resource": ["*"] + } + ] + } +] def test_create_policy_with_invalid_policy_documents(): - for test_case in invalid_documents_test_cases: + for test_case in invalid_policy_document_test_cases: yield check_create_policy_with_invalid_policy_document, test_case +def test_create_policy_with_valid_policy_documents(): + for valid_policy_document in valid_policy_documents: + yield check_create_policy_with_valid_policy_document, valid_policy_document + + @mock_iam def check_create_policy_with_invalid_policy_document(test_case): conn = boto3.client('iam', region_name='us-east-1') @@ -971,3 +1851,11 @@ def check_create_policy_with_invalid_policy_document(test_case): ex.exception.response['Error']['Code'].should.equal('MalformedPolicyDocument') ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) ex.exception.response['Error']['Message'].should.equal(test_case["error_message"]) + + +@mock_iam +def check_create_policy_with_valid_policy_document(valid_policy_document): + conn = boto3.client('iam', region_name='us-east-1') + conn.create_policy( + PolicyName="TestCreatePolicy", + PolicyDocument=json.dumps(valid_policy_document)) From c46857e3d3f40d3c7d7d18121e1e055213e9b0ff Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 18:22:31 +0200 Subject: [PATCH 15/17] Fixed errors for valid policy documents. --- moto/iam/policy_validation.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 171a51b29..91ee79dab 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -231,12 +231,12 @@ class IAMPolicyDocumentValidator: def _strip_condition_key(condition_key): for valid_prefix in VALID_CONDITION_PREFIXES: if condition_key.startswith(valid_prefix): - condition_key = condition_key.lstrip(valid_prefix) + condition_key = condition_key[len(valid_prefix):] break # strip only the first match for valid_postfix in VALID_CONDITION_POSTFIXES: - if condition_key.startswith(valid_postfix): - condition_key = condition_key.rstrip(valid_postfix) + if condition_key.endswith(valid_postfix): + condition_key = condition_key[:-len(valid_postfix)] break # strip only the first match return condition_key @@ -284,13 +284,13 @@ class IAMPolicyDocumentValidator: @staticmethod def _validate_action_prefix(action): action_parts = action.split(":") - if len(action_parts) == 1: + if len(action_parts) == 1 and action_parts[0] != "*": raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.") elif len(action_parts) > 2: raise MalformedPolicyDocument("Actions/Condition can contain only one colon.") vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]') - if vendor_pattern.search(action_parts[0]): + if action_parts[0] != "*" and vendor_pattern.search(action_parts[0]): raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0])) def _validate_resources_for_formats(self): @@ -370,18 +370,20 @@ class IAMPolicyDocumentValidator: def _legacy_parse_statement(statement): assert statement["Effect"] in VALID_EFFECTS # case-sensitive matching if "Condition" in statement: - for condition_key, condition_value in statement["Condition"]: + for condition_key, condition_value in statement["Condition"].items(): IAMPolicyDocumentValidator._legacy_parse_condition(condition_key, condition_value) @staticmethod def _legacy_parse_resource_like(statement, key): if isinstance(statement[key], string_types): - assert statement[key] == "*" or statement[key].count(":") >= 5 - assert statement[key] == "*" or statement[key].split(":")[2] != "" + if statement[key] != "*": + assert statement[key].count(":") >= 5 or "::" not in statement[key] + assert statement[key].split(":")[2] != "" else: # list for resource in statement[key]: - assert resource == "*" or resource.count(":") >= 5 - assert resource == "*" or resource[2] != "" + if resource != "*": + assert resource.count(":") >= 5 or "::" not in resource + assert resource[2] != "" @staticmethod def _legacy_parse_condition(condition_key, condition_value): @@ -405,8 +407,9 @@ class IAMPolicyDocumentValidator: @staticmethod def _validate_iso_8601_datetime(datetime): datetime_parts = datetime.partition("t") - date_parts = datetime_parts[0].split("-") - year = date_parts[0] + negative_year = datetime_parts[0].startswith("-") + date_parts = datetime_parts[0][1:].split("-") if negative_year else datetime_parts[0].split("-") + year = "-" + date_parts[0] if negative_year else date_parts[0] assert -292275054 <= int(year) <= 292278993 if len(date_parts) > 1: month = date_parts[1] From ed2682582fb94e742ecca8cc050c306004f17d5a Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 18:54:32 +0200 Subject: [PATCH 16/17] Policy validation precedes finding policy for create_policy_version. --- moto/iam/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/moto/iam/models.py b/moto/iam/models.py index 4b6c340e6..4bc7a5447 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -764,13 +764,13 @@ class IAMBackend(BaseBackend): role.tags.pop(ref_key, None) def create_policy_version(self, policy_arn, policy_document, set_as_default): + iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) + iam_policy_document_validator.validate() + policy = self.get_policy(policy_arn) if not policy: raise IAMNotFoundException("Policy not found") - iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document) - iam_policy_document_validator.validate() - version = PolicyVersion(policy_arn, policy_document, set_as_default) policy.versions.append(version) version.version_id = 'v{0}'.format(policy.next_version_num) From 37bdc12f4d7bd8c47ecffd8049b3855d27c28b4e Mon Sep 17 00:00:00 2001 From: acsbendi Date: Mon, 1 Jul 2019 18:58:31 +0200 Subject: [PATCH 17/17] Fixed linting errors. --- moto/iam/policy_validation.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/moto/iam/policy_validation.py b/moto/iam/policy_validation.py index 91ee79dab..0fcf0e5f2 100644 --- a/moto/iam/policy_validation.py +++ b/moto/iam/policy_validation.py @@ -88,7 +88,7 @@ VALID_RESOURCE_PATH_STARTING_VALUES = { class IAMPolicyDocumentValidator: - def __init__(self, policy_document: str): + def __init__(self, policy_document): self._policy_document: str = policy_document self._policy_json: dict = {} self._statements = [] @@ -308,7 +308,7 @@ class IAMPolicyDocumentValidator: for resource in sorted(statement[key], reverse=True): self._validate_resource_format(resource) if self._resource_error == "": - IAMPolicyDocumentValidator._legacy_parse_resource_like(statement, key) + IAMPolicyDocumentValidator._legacy_parse_resource_like(statement, key) def _validate_resource_format(self, resource): if resource != "*": @@ -327,12 +327,12 @@ class IAMPolicyDocumentValidator: arn3 = remaining_resource_parts[2] if len(remaining_resource_parts) > 2 else "*" arn4 = ":".join(remaining_resource_parts[3:]) if len(remaining_resource_parts) > 3 else "*" self._resource_error = 'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format( - partition=resource_partitions[0], - arn1=arn1, - arn2=arn2, - arn3=arn3, - arn4=arn4 - ) + partition=resource_partitions[0], + arn1=arn1, + arn2=arn2, + arn3=arn3, + arn4=arn4 + ) return if resource_partitions[1] != ":":