diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/__init__.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.metadata.json b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.metadata.json new file mode 100644 index 00000000..dc283274 --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "iam_policy_no_full_access_to_cloudtrail", + "CheckTitle": "Ensure IAM policies that allow full \"cloudtrail:*\" privileges are not created", + "CheckType": [ + "Software and Configuration Checks", + "Industry and Regulatory Standards", + "CIS AWS Foundations Benchmark" + ], + "ServiceName": "iam", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsIamPolicy", + "Description": "Ensure IAM policies that allow full \"cloudtrail:*\" privileges are not created", + "Risk": "CloudTrail is a critical service and IAM policies should follow least privilege model for this service in particular", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary; rather than starting with permissions that are too lenient and then trying to tighten them later. List policies an analyze if permissions are the least possible to conduct business activities.", + "Url": "http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py new file mode 100644 index 00000000..b7e7bbb7 --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py @@ -0,0 +1,37 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.iam.iam_client import iam_client + +critical_service = "cloudtrail" + + +class iam_policy_no_full_access_to_cloudtrail(Check): + def execute(self) -> Check_Report_AWS: + findings = [] + for policy in iam_client.policies: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_arn = policy.get("Arn") + report.resource_id = policy.get("PolicyName") + report.status = "PASS" + report.status_extended = f"Policy {policy.get('PolicyName')} does not allow '{critical_service}:*' privileges" + if policy.get("PolicyDocument"): + # Check the statements, if one includes critical_service:* stop iterating over the rest + if type(policy.get("PolicyDocument").get("Statement")) != list: + policy_statements = [policy.get("PolicyDocument").get("Statement")] + else: + policy_statements = policy.get("PolicyDocument").get("Statement") + for statement in policy_statements: + # Check policies with "Effect": "Allow" with "Action": "*" over "Resource": "*". + if ( + statement.get("Effect") == "Allow" + and critical_service + ":*" in statement.get("Action") + and ( + statement.get("Resource") == "*" + or statement.get("Resource") == ["*"] + ) + ): + report.status = "FAIL" + report.status_extended = f"Policy {policy.get('PolicyName')} allows '{critical_service}:*' privileges" + break + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/__init__.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.metadata.json b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.metadata.json new file mode 100644 index 00000000..02db1baa --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "iam_policy_no_full_access_to_kms", + "CheckTitle": "Ensure IAM policies that allow full \"kms:*\" privileges are not created", + "CheckType": [ + "Software and Configuration Checks", + "Industry and Regulatory Standards", + "CIS AWS Foundations Benchmark" + ], + "ServiceName": "iam", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsIamPolicy", + "Description": "Ensure IAM policies that allow full \"kms:*\" privileges are not created", + "Risk": "KMS is a critical service and IAM policies should follow least privilege model for this service in particular", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "It is more secure to start with a minimum set of permissions and grant additional permissions as necessary; rather than starting with permissions that are too lenient and then trying to tighten them later. List policies an analyze if permissions are the least possible to conduct business activities.", + "Url": "http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py new file mode 100644 index 00000000..ee12f177 --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py @@ -0,0 +1,37 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.iam.iam_client import iam_client + +critical_service = "kms" + + +class iam_policy_no_full_access_to_kms(Check): + def execute(self) -> Check_Report_AWS: + findings = [] + for policy in iam_client.policies: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_arn = policy.get("Arn") + report.resource_id = policy.get("PolicyName") + report.status = "PASS" + report.status_extended = f"Policy {policy.get('PolicyName')} does not allow '{critical_service}:*' privileges" + if policy.get("PolicyDocument"): + # Check the statements, if one includes critical_service:* stop iterating over the rest + if type(policy.get("PolicyDocument").get("Statement")) != list: + policy_statements = [policy.get("PolicyDocument").get("Statement")] + else: + policy_statements = policy.get("PolicyDocument").get("Statement") + for statement in policy_statements: + # Check policies with "Effect": "Allow" with "Action": "*" over "Resource": "*". + if ( + statement.get("Effect") == "Allow" + and critical_service + ":*" in statement.get("Action") + and ( + statement.get("Resource") == "*" + or statement.get("Resource") == ["*"] + ) + ): + report.status = "FAIL" + report.status_extended = f"Policy {policy.get('PolicyName')} allows '{critical_service}:*' privileges" + break + findings.append(report) + return findings diff --git a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py new file mode 100644 index 00000000..b4a66422 --- /dev/null +++ b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py @@ -0,0 +1,155 @@ +from json import dumps +from unittest import mock + +from boto3 import client, session +from moto import mock_iam + +from prowler.providers.aws.lib.audit_info.audit_info import AWS_Audit_Info +from prowler.providers.aws.services.iam.iam_service import IAM + + +class Test_iam_policy_no_full_access_to_cloudtrail: + + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + session_config=None, + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=None, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region="us-east-1", + credentials=None, + assumed_role_info=None, + audited_regions=None, + organizations_metadata=None, + audit_resources=None, + ) + return audit_info + + @mock_iam + def test_policy_full_access_to_cloudtrail(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_cloudtrail_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "cloudtrail:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import ( + iam_policy_no_full_access_to_cloudtrail, + ) + + check = iam_policy_no_full_access_to_cloudtrail() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Policy {policy_name} allows 'cloudtrail:*' privileges" + ) + assert result[0].resource_id == "policy_cloudtrail_full" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1" + + @mock_iam + def test_policy_no_full_access_to_cloudtrail(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_no_cloudtrail_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "ec2:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import ( + iam_policy_no_full_access_to_cloudtrail, + ) + + check = iam_policy_no_full_access_to_cloudtrail() + result = check.execute() + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Policy {policy_name} does not allow 'cloudtrail:*' privileges" + ) + assert result[0].resource_id == "policy_no_cloudtrail_full" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1" + + @mock_iam + def test_policy_mixed(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_mixed" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["ec2:*", "cloudtrail:*"], + "Resource": "*", + }, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import ( + iam_policy_no_full_access_to_cloudtrail, + ) + + check = iam_policy_no_full_access_to_cloudtrail() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Policy {policy_name} allows 'cloudtrail:*' privileges" + ) + assert result[0].resource_id == "policy_mixed" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1" diff --git a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py new file mode 100644 index 00000000..4df5acef --- /dev/null +++ b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py @@ -0,0 +1,151 @@ +from json import dumps +from unittest import mock + +from boto3 import client, session +from moto import mock_iam + +from prowler.providers.aws.lib.audit_info.audit_info import AWS_Audit_Info +from prowler.providers.aws.services.iam.iam_service import IAM + + +class Test_iam_policy_no_full_access_to_kms: + + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + session_config=None, + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=None, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region="us-east-1", + credentials=None, + assumed_role_info=None, + audited_regions=None, + organizations_metadata=None, + audit_resources=None, + ) + return audit_info + + @mock_iam + def test_policy_full_access_to_kms(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_kms_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "kms:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import ( + iam_policy_no_full_access_to_kms, + ) + + check = iam_policy_no_full_access_to_kms() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Policy {policy_name} allows 'kms:*' privileges" + ) + assert result[0].resource_id == "policy_kms_full" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1" + + @mock_iam + def test_policy_no_full_access_to_kms(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_no_kms_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "ec2:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import ( + iam_policy_no_full_access_to_kms, + ) + + check = iam_policy_no_full_access_to_kms() + result = check.execute() + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Policy {policy_name} does not allow 'kms:*' privileges" + ) + assert result[0].resource_id == "policy_no_kms_full" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1" + + @mock_iam + def test_policy_mixed(self): + audit_info = self.set_mocked_audit_info() + iam_client = client("iam") + policy_name = "policy_mixed" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": ["ec2:*", "kms:*"], "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client", + new=IAM(audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import ( + iam_policy_no_full_access_to_kms, + ) + + check = iam_policy_no_full_access_to_kms() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Policy {policy_name} allows 'kms:*' privileges" + ) + assert result[0].resource_id == "policy_mixed" + assert result[0].resource_arn == arn + assert result[0].region == "us-east-1"