From f1bea27e444f7ad2afc9608399eb1743e7cc3252 Mon Sep 17 00:00:00 2001 From: Kay Agahd Date: Tue, 12 Sep 2023 08:19:20 +0100 Subject: [PATCH] feat(iam): add new check iam_role_administratoraccess_policy (#2822) --- .../__init__.py | 0 ...e_administratoraccess_policy.metadata.json | 32 ++ .../iam_role_administratoraccess_policy.py | 28 ++ ...am_role_administratoraccess_policy_test.py | 298 ++++++++++++++++++ 4 files changed, 358 insertions(+) create mode 100644 prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/__init__.py create mode 100644 prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.metadata.json create mode 100644 prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.py create mode 100644 tests/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy_test.py diff --git a/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/__init__.py b/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.metadata.json b/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.metadata.json new file mode 100644 index 00000000..b6f0cd1d --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "iam_role_administratoraccess_policy", + "CheckTitle": "Ensure IAM Roles do not have AdministratorAccess policy attached", + "CheckType": [], + "ServiceName": "iam", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "high", + "ResourceType": "AwsIamRole", + "Description": "Ensure IAM Roles do not have AdministratorAccess policy attached", + "Risk": "The AWS-managed AdministratorAccess policy grants all actions for all AWS services and for all resources in the account and as such exposes the customer to a significant data leakage threat. It should be granted very conservatively. For granting access to 3rd party vendors, consider using alternative managed policies, such as ViewOnlyAccess or SecurityAudit.", + "RelatedUrl": "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Apply the principle of least privilege. Instead of AdministratorAccess, assign only the permissions necessary for specific roles and tasks. Create custom IAM policies with minimal permissions based on the principle of least privilege.", + "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege" + } + }, + "Categories": [ + "trustboundaries" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "CAF Security Epic: IAM" +} diff --git a/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.py b/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.py new file mode 100644 index 00000000..7cb42cd3 --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.iam.iam_client import iam_client + + +class iam_role_administratoraccess_policy(Check): + def execute(self) -> Check_Report_AWS: + findings = [] + for role in iam_client.roles: + if ( + not role.is_service_role + ): # Avoid service roles since they cannot be modified by the user + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_arn = role.arn + report.resource_id = role.name + report.resource_tags = role.tags + report.status = "PASS" + report.status_extended = ( + f"IAM Role {role.name} does not have AdministratorAccess policy." + ) + for policy in role.attached_policies: + if policy["PolicyName"] == "AdministratorAccess": + report.status_extended = f"IAM Role {role.name} has AdministratorAccess policy attached." + report.status = "FAIL" + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy_test.py b/tests/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy_test.py new file mode 100644 index 00000000..2ccf1086 --- /dev/null +++ b/tests/providers/aws/services/iam/iam_role_administratoraccess_policy/iam_role_administratoraccess_policy_test.py @@ -0,0 +1,298 @@ +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.models import AWS_Audit_Info +from prowler.providers.aws.services.iam.iam_service import Role +from prowler.providers.common.models import Audit_Metadata + +AWS_REGION = "us-east-1" +AWS_ACCOUNT_ID = "123456789012" + + +class Test_iam_role_administratoraccess_policy: + 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=AWS_ACCOUNT_ID, + audited_account_arn=f"arn:aws:iam::{AWS_ACCOUNT_ID}:root", + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region=None, + credentials=None, + assumed_role_info=None, + audited_regions=["us-east-1", "eu-west-1"], + organizations_metadata=None, + audit_resources=None, + mfa_enabled=False, + audit_metadata=Audit_Metadata( + services_scanned=0, + expected_checks=[], + completed_checks=0, + audit_progress=0, + ), + ) + + return audit_info + + @mock_iam + def test_no_roles(self): + from prowler.providers.aws.services.iam.iam_service import IAM + + current_audit_info = self.set_mocked_audit_info() + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=IAM(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 0 + + @mock_iam + def test_role_without_administratoraccess_policy(self): + iam = client("iam") + role_name = "test" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": { + "Sid": "test", + "Effect": "Allow", + "Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_ID}:root"}, + "Action": "sts:AssumeRole", + }, + } + response = iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + ) + + current_audit_info = self.set_mocked_audit_info() + from prowler.providers.aws.services.iam.iam_service import IAM + + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=IAM(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "IAM Role test does not have AdministratorAccess policy." + ) + assert result[0].resource_id == "test" + assert result[0].resource_arn == response["Role"]["Arn"] + assert result[0].resource_tags == [] + + @mock_iam + def test_role_with_securityaudit_policy(self): + iam = client("iam") + role_name = "test" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": { + "Sid": "test", + "Effect": "Allow", + "Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_ID}:root"}, + "Action": "sts:AssumeRole", + }, + } + response = iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/SecurityAudit", + ) + + current_audit_info = self.set_mocked_audit_info() + from prowler.providers.aws.services.iam.iam_service import IAM + + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=IAM(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "IAM Role test does not have AdministratorAccess policy." + ) + assert result[0].resource_id == "test" + assert result[0].resource_arn == response["Role"]["Arn"] + assert result[0].resource_tags == [] + + @mock_iam + def test_role_with_administratoraccess_policy(self): + iam = client("iam") + role_name = "test" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": { + "Sid": "test", + "Effect": "Allow", + "Principal": {"AWS": "arn:aws:iam::012345678910:root"}, + "Action": "sts:AssumeRole", + }, + } + response = iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess", + ) + + current_audit_info = self.set_mocked_audit_info() + from prowler.providers.aws.services.iam.iam_service import IAM + + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=IAM(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "IAM Role test has AdministratorAccess policy attached." + ) + assert result[0].resource_id == "test" + assert result[0].resource_arn == response["Role"]["Arn"] + assert result[0].resource_tags == [] + + @mock_iam + def test_asterisk_principal_role_with_administratoraccess_policy(self): + iam = client("iam") + role_name = "test" + assume_role_policy_document = { + "Version": "2012-10-17", + "Statement": { + "Sid": "test", + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": "sts:AssumeRole", + }, + } + response = iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(assume_role_policy_document), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess", + ) + + current_audit_info = self.set_mocked_audit_info() + from prowler.providers.aws.services.iam.iam_service import IAM + + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=IAM(current_audit_info), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "IAM Role test has AdministratorAccess policy attached." + ) + assert result[0].resource_id == "test" + assert result[0].resource_arn == response["Role"]["Arn"] + assert result[0].resource_tags == [] + + @mock_iam + def test_only_aws_service_linked_roles(self): + iam_client = mock.MagicMock + iam_client.roles = [] + iam_client.roles.append( + Role( + name="AWSServiceRoleForAmazonGuardDuty", + arn="arn:aws:iam::106908755756:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty", + assume_role_policy={ + "Version": "2008-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "ec2.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + }, + is_service_role=True, + ) + ) + + current_audit_info = self.set_mocked_audit_info() + + 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_administratoraccess_policy.iam_role_administratoraccess_policy.iam_client", + new=iam_client, + ): + # Test Check + from prowler.providers.aws.services.iam.iam_role_administratoraccess_policy.iam_role_administratoraccess_policy import ( + iam_role_administratoraccess_policy, + ) + + check = iam_role_administratoraccess_policy() + result = check.execute() + assert len(result) == 0