fix(iam_role_cross_service_confused_deputy_prevention): add ResourceAccount and PrincipalAccount conditions (#2689)

This commit is contained in:
Sergio Garcia
2023-08-09 10:41:48 +02:00
committed by GitHub
parent 887cac1264
commit 36e095c830
4 changed files with 219 additions and 41 deletions

View File

@@ -8,8 +8,15 @@ def is_account_only_allowed_in_condition(
"aws:SourceAccount",
"s3:ResourceAccount",
"aws:PrincipalAccount",
"aws:ResourceAccount",
],
"StringLike": [
"aws:SourceAccount",
"aws:SourceArn",
"aws:PrincipalArn",
"aws:ResourceAccount",
"aws:PrincipalAccount",
],
"StringLike": ["aws:SourceArn", "aws:PrincipalArn"],
"ArnLike": ["aws:SourceArn", "aws:PrincipalArn"],
"ArnEquals": ["aws:SourceArn", "aws:PrincipalArn"],
}

View File

@@ -1,4 +1,7 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.lib.policy_condition_parser.policy_condition_parser import (
is_account_only_allowed_in_condition,
)
from prowler.providers.aws.services.iam.iam_client import iam_client
@@ -27,46 +30,8 @@ class iam_role_cross_service_confused_deputy_prevention(Check):
and "Service" in statement["Principal"]
# Check to see if the appropriate condition statements have been implemented
and "Condition" in statement
and (
(
"StringEquals" in statement["Condition"]
and "aws:SourceAccount"
in statement["Condition"]["StringEquals"]
and iam_client.audited_account
in str(
statement["Condition"]["StringEquals"][
"aws:SourceAccount"
]
)
)
or (
"StringLike" in statement["Condition"]
and "aws:SourceAccount"
in statement["Condition"]["StringLike"]
and iam_client.audited_account
in str(
statement["Condition"]["StringLike"][
"aws:SourceAccount"
]
)
)
or (
"ArnEquals" in statement["Condition"]
and "aws:SourceArn"
in statement["Condition"]["ArnEquals"]
and iam_client.audited_account
in str(
statement["Condition"]["ArnEquals"]["aws:SourceArn"]
)
)
or (
"ArnLike" in statement["Condition"]
and "aws:SourceArn" in statement["Condition"]["ArnLike"]
and iam_client.audited_account
in str(
statement["Condition"]["ArnLike"]["aws:SourceArn"]
)
)
and is_account_only_allowed_in_condition(
statement["Condition"], iam_client.audited_account
)
):
report.status = "PASS"

View File

@@ -32,6 +32,32 @@ class Test_policy_condition_parser:
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_SourceAccount_list(self):
condition_statement = {"StringLike": {"aws:SourceAccount": ["123456789012"]}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_SourceAccount_str(self):
condition_statement = {"StringLike": {"aws:SourceAccount": "123456789012"}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_SourceAccount_list_not_valid(self):
condition_statement = {
"StringLike": {"aws:SourceAccount": ["123456789012", "111222333444"]}
}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_SourceAccount_str_not_valid(self):
condition_statement = {"StringLike": {"aws:SourceAccount": "111222333444"}}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_equals_s3_ResourceAccount_list(self):
condition_statement = {"StringEquals": {"s3:ResourceAccount": ["123456789012"]}}
assert is_account_only_allowed_in_condition(
@@ -86,6 +112,32 @@ class Test_policy_condition_parser:
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_PrincipalAccount_list(self):
condition_statement = {"StringLike": {"aws:PrincipalAccount": ["123456789012"]}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_PrincipalAccount_str(self):
condition_statement = {"StringLike": {"aws:PrincipalAccount": "123456789012"}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_PrincipalAccount_list_not_valid(self):
condition_statement = {
"StringLike": {"aws:PrincipalAccount": ["123456789012", "111222333444"]}
}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_PrincipalAccount_str_not_valid(self):
condition_statement = {"StringLike": {"aws:PrincipalAccount": "111222333444"}}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_arn_like_aws_SourceArn_list(self):
condition_statement = {
"ArnLike": {"aws:SourceArn": ["arn:aws:cloudtrail:*:123456789012:trail/*"]}
@@ -365,3 +417,57 @@ class Test_policy_condition_parser:
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_equals_aws_ResourceAccount_list(self):
condition_statement = {
"StringEquals": {"aws:ResourceAccount": ["123456789012"]}
}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_equals_aws_ResourceAccount_str(self):
condition_statement = {"StringEquals": {"aws:ResourceAccount": "123456789012"}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_equals_aws_ResourceAccount_list_not_valid(self):
condition_statement = {
"StringEquals": {"aws:ResourceAccount": ["123456789012", "111222333444"]}
}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_equals_aws_ResourceAccount_str_not_valid(self):
condition_statement = {"StringEquals": {"aws:ResourceAccount": "111222333444"}}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_ResourceAccount_list(self):
condition_statement = {"StringLike": {"aws:ResourceAccount": ["123456789012"]}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_ResourceAccount_str(self):
condition_statement = {"StringLike": {"aws:ResourceAccount": "123456789012"}}
assert is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_ResourceAccount_list_not_valid(self):
condition_statement = {
"StringLike": {"aws:ResourceAccount": ["123456789012", "111222333444"]}
}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)
def test_condition_parser_string_like_aws_ResourceAccount_str_not_valid(self):
condition_statement = {"StringLike": {"aws:ResourceAccount": "111222333444"}}
assert not is_account_only_allowed_in_condition(
condition_statement, AWS_ACCOUNT_NUMBER
)

View File

@@ -249,3 +249,103 @@ class Test_iam_role_cross_service_confused_deputy_prevention:
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
@mock_iam
def test_iam_service_role_with_cross_service_confused_deputy_prevention_PrincipalAccount(
self,
):
iam_client = client("iam", region_name=AWS_REGION)
policy_document = {
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "workspaces.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {"aws:PrincipalAccount": [AWS_ACCOUNT_ID]}
},
}
],
}
response = iam_client.create_role(
RoleName="test",
AssumeRolePolicyDocument=dumps(policy_document),
)
from prowler.providers.aws.services.iam.iam_service import IAM
current_audit_info = self.set_mocked_audit_info()
current_audit_info.audited_account = AWS_ACCOUNT_ID
with mock.patch(
"prowler.providers.aws.lib.audit_info.audit_info.current_audit_info",
new=current_audit_info,
), mock.patch(
"prowler.providers.aws.services.iam.iam_role_cross_service_confused_deputy_prevention.iam_role_cross_service_confused_deputy_prevention.iam_client",
new=IAM(current_audit_info),
):
# Test Check
from prowler.providers.aws.services.iam.iam_role_cross_service_confused_deputy_prevention.iam_role_cross_service_confused_deputy_prevention import (
iam_role_cross_service_confused_deputy_prevention,
)
check = iam_role_cross_service_confused_deputy_prevention()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Service Role test prevents against a cross-service confused deputy attack"
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
@mock_iam
def test_iam_service_role_with_cross_service_confused_deputy_prevention_ResourceAccount(
self,
):
iam_client = client("iam", region_name=AWS_REGION)
policy_document = {
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "workspaces.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {"aws:ResourceAccount": [AWS_ACCOUNT_ID]}
},
}
],
}
response = iam_client.create_role(
RoleName="test",
AssumeRolePolicyDocument=dumps(policy_document),
)
from prowler.providers.aws.services.iam.iam_service import IAM
current_audit_info = self.set_mocked_audit_info()
current_audit_info.audited_account = AWS_ACCOUNT_ID
with mock.patch(
"prowler.providers.aws.lib.audit_info.audit_info.current_audit_info",
new=current_audit_info,
), mock.patch(
"prowler.providers.aws.services.iam.iam_role_cross_service_confused_deputy_prevention.iam_role_cross_service_confused_deputy_prevention.iam_client",
new=IAM(current_audit_info),
):
# Test Check
from prowler.providers.aws.services.iam.iam_role_cross_service_confused_deputy_prevention.iam_role_cross_service_confused_deputy_prevention import (
iam_role_cross_service_confused_deputy_prevention,
)
check = iam_role_cross_service_confused_deputy_prevention()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Service Role test prevents against a cross-service confused deputy attack"
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]