diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index dc1e66c96c..d7790292e9 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -31,6 +31,7 @@ The following list includes all the AWS checks with configurable variables that | `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_minutes` | Integer | | `cloudwatch_log_group_no_secrets_in_logs` | `secrets_ignore_patterns` | List of Strings | | `cloudwatch_log_group_retention_policy_specific_days_enabled` | `log_group_retention_days` | Integer | +| `codebuild_github_allowed_organizations` | `github_allowed_organizations` | List of Strings | | `codebuild_project_no_secrets_in_variables` | `excluded_sensitive_environment_variables` | List of Strings | | `codebuild_project_no_secrets_in_variables` | `secrets_ignore_patterns` | List of Strings | | `config_recorder_all_regions_enabled` | `mute_non_default_regions` | Boolean | diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 1385ffef4f..bc55b5e24b 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -417,6 +417,12 @@ aws: {"name": "TwilioKeyDetector"}, ] + # AWS CodeBuild Configuration + # aws.codebuild_project_uses_allowed_github_organizations + codebuild_github_allowed_organizations: + [ + "andoniaf", + ] # Azure Configuration azure: diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.metadata.json b/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.metadata.json new file mode 100644 index 0000000000..7f6bc0c024 --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "codebuild_project_uses_allowed_github_organizations", + "CheckTitle": "Ensure AWS CodeBuild projects using GitHub connect only to allowed organizations", + "CheckType": [], + "ServiceName": "codebuild", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:codebuild:region:account-id:project:project-name", + "Severity": "high", + "ResourceType": "AwsCodeBuildProject", + "Description": "Check for CodeBuild projects using GitHub repositories from untrusted organizations that could lead to backdoored IAM roles", + "Risk": "Attackers can use GitHub Actions in untrusted repositories to backdoor IAM roles used by CodeBuild projects, gaining persistent access to AWS accounts.", + "RelatedUrl": "https://medium.com/@adan.alvarez/gaining-long-term-aws-access-with-codebuild-and-github-873324638784", + "Remediation": { + "Code": { + "NativeIaC": "", + "Terraform": "", + "CLI": "", + "Other": "" + }, + "Recommendation": { + "Text": "Only use GitHub repositories from trusted organizations with CodeBuild projects. Configure the allowed GitHub organizations in your Prowler configuration.", + "Url": "https://docs.aws.amazon.com/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.py b/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.py new file mode 100644 index 0000000000..7accdc6b1f --- /dev/null +++ b/prowler/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.py @@ -0,0 +1,57 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.codebuild.codebuild_client import codebuild_client +from prowler.providers.aws.services.iam.iam_client import iam_client +from prowler.providers.aws.services.iam.lib.policy import ( + has_codebuild_trusted_principal, + is_codebuild_using_allowed_github_org, +) + + +class codebuild_project_uses_allowed_github_organizations(Check): + def execute(self): + findings = [] + allowed_organizations = codebuild_client.audit_config.get( + "codebuild_github_allowed_organizations", [] + ) + + for project in codebuild_client.projects.values(): + report = Check_Report_AWS(metadata=self.metadata(), resource=project) + report.status = "PASS" + + if project.source.type in ("GITHUB", "GITHUB_ENTERPRISE"): + project_github_repo_url = project.source.location + project_role = next( + ( + role + for role in iam_client.roles + if role.arn == project.service_role_arn + ), + None, + ) + project_iam_trust_policy = ( + project_role.assume_role_policy if project_role else None + ) + + if project_iam_trust_policy: + if not has_codebuild_trusted_principal(project_iam_trust_policy): + report.status_extended = f"CodeBuild project {project.name} does not use an IAM role with codebuild.amazonaws.com as a trusted principal, skipping GitHub organization check." + else: + is_allowed, org_name = is_codebuild_using_allowed_github_org( + project_iam_trust_policy, + project_github_repo_url, + allowed_organizations, + ) + if org_name is not None: + if is_allowed: + report.status_extended = f"CodeBuild project {project.name} uses GitHub organization '{org_name}', which is in the allowed organizations." + else: + report.status = "FAIL" + report.status_extended = f"CodeBuild project {project.name} uses GitHub organization '{org_name}', which is not in the allowed organizations." + else: + report.status_extended = f"CodeBuild project {project.name} uses a GitHub repository with an invalid or unrecognized organization in the URL." + else: + report.status_extended = f"CodeBuild project {project.name} does not use an IAM role with codebuild.amazonaws.com as a trusted principal, skipping GitHub organization check." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/codebuild/codebuild_service.py b/prowler/providers/aws/services/codebuild/codebuild_service.py index a1e00790bb..5fcf1d335f 100644 --- a/prowler/providers/aws/services/codebuild/codebuild_service.py +++ b/prowler/providers/aws/services/codebuild/codebuild_service.py @@ -120,6 +120,7 @@ def _batch_get_projects(self, project): stream_name=cloudwatch_logs.get("streamName", ""), ) project.tags = project_info.get("tags", []) + project.service_role_arn = project_info.get("serviceRole", "") except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -220,6 +221,7 @@ class Project(BaseModel): s3_logs: Optional[s3Logs] cloudwatch_logs: Optional[CloudWatchLogs] tags: Optional[list] + service_role_arn: Optional[str] = None class ExportConfig(BaseModel): diff --git a/prowler/providers/aws/services/iam/lib/policy.py b/prowler/providers/aws/services/iam/lib/policy.py index e023b137b5..ba1bdb9e5e 100644 --- a/prowler/providers/aws/services/iam/lib/policy.py +++ b/prowler/providers/aws/services/iam/lib/policy.py @@ -1,5 +1,6 @@ from ipaddress import ip_address, ip_network import re +from typing import Optional, Tuple from prowler.lib.logger import logger from prowler.providers.aws.aws_provider import read_aws_regions_file @@ -570,3 +571,56 @@ def is_valid_aws_service(service): if service in read_aws_regions_file()["services"]: return True return False + + +def is_codebuild_using_allowed_github_org( + trust_policy: dict, github_repo_url: str, allowed_organizations: list +) -> Tuple[bool, Optional[str]]: + """ + Checks if the trust policy allows codebuild.amazonaws.com as a trusted principal and if the GitHub organization + in the repo URL is in the allowed organizations list. + Returns (is_allowed: bool, org_name: str or None) + """ + if not trust_policy or not github_repo_url: + return False, None + + statements = trust_policy.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + + for statement in statements: + if ( + statement.get("Effect") == "Allow" + and "Principal" in statement + and isinstance(statement["Principal"], dict) + and statement["Principal"].get("Service") == "codebuild.amazonaws.com" + ): + # Extract org name from GitHub repo URL + try: + org_name = github_repo_url.split("/")[3] + if not org_name: + org_name = None + except IndexError: + org_name = None + if org_name and org_name in allowed_organizations: + return True, org_name + return False, org_name + return False, None + + +def has_codebuild_trusted_principal(trust_policy: dict) -> bool: + """ + Returns True if the trust policy allows codebuild.amazonaws.com as a trusted principal, otherwise False. + """ + if not trust_policy: + return False + statements = trust_policy.get("Statement", []) + if not isinstance(statements, list): + statements = [statements] + return any( + s.get("Effect") == "Allow" + and "Principal" in s + and isinstance(s["Principal"], dict) + and s["Principal"].get("Service") == "codebuild.amazonaws.com" + for s in statements + ) diff --git a/tests/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations_test.py b/tests/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations_test.py new file mode 100644 index 0000000000..1f1e84dd63 --- /dev/null +++ b/tests/providers/aws/services/codebuild/codebuild_project_uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations_test.py @@ -0,0 +1,475 @@ +from unittest.mock import patch + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_codebuild_project_uses_allowed_github_organizations: + @mock_aws + def test_no_projects(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_project_github_allowed_organization(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-allowed" + role_name = "codebuild-test-role" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB", + "location": "https://github.com/allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert "which is in the allowed organizations" in result[0].status_extended + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_github_not_allowed_organization(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-not-allowed" + role_name = "codebuild-test-role" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB", + "location": "https://github.com/not-allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert ( + "which is not in the allowed organizations" in result[0].status_extended + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_github_no_codebuild_trusted_principal(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-lambda-role" + role_name = "lambda-test-role" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB", + "location": "https://github.com/not-allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert ( + "does not use an IAM role with codebuild.amazonaws.com as a trusted principal" + in result[0].status_extended + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_github_enterprise_allowed_organization(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-enterprise-allowed" + role_name = "codebuild-test-role" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB_ENTERPRISE", + "location": "https://github.enterprise.com/allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert "which is in the allowed organizations" in result[0].status_extended + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_github_enterprise_not_allowed_organization(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-enterprise-not-allowed" + role_name = "codebuild-test-role" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB_ENTERPRISE", + "location": "https://github.enterprise.com/not-allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert ( + "which is not in the allowed organizations" in result[0].status_extended + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws + def test_project_github_enterprise_no_codebuild_trusted_principal(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1) + iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1) + project_name = "test-project-github-enterprise-lambda-role" + role_name = "lambda-test-role-enterprise" + role_arn = iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument="""{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole" + } + ] + }""", + )["Role"]["Arn"] + project_arn = codebuild_client.create_project( + name=project_name, + source={ + "type": "GITHUB_ENTERPRISE", + "location": "https://github.enterprise.com/not-allowed-org/repo", + }, + artifacts={"type": "NO_ARTIFACTS"}, + environment={ + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:4.0", + "computeType": "BUILD_GENERAL1_SMALL", + "environmentVariables": [], + }, + serviceRole=role_arn, + tags=[{"key": "Name", "value": "test"}], + )["project"]["arn"] + + from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client", + new=Codebuild(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client", + new=IAM(aws_provider), + ), + patch( + "prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.codebuild_client.audit_config", + {"codebuild_github_allowed_organizations": ["allowed-org"]}, + ), + ): + from prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations import ( + codebuild_project_uses_allowed_github_organizations, + ) + + check = codebuild_project_uses_allowed_github_organizations() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].resource_id == project_name + assert result[0].resource_arn == project_arn + assert ( + "does not use an IAM role with codebuild.amazonaws.com as a trusted principal" + in result[0].status_extended + ) + assert result[0].region == AWS_REGION_EU_WEST_1 diff --git a/tests/providers/aws/services/iam/lib/policy_test.py b/tests/providers/aws/services/iam/lib/policy_test.py index 02e88739ec..82f03633e6 100644 --- a/tests/providers/aws/services/iam/lib/policy_test.py +++ b/tests/providers/aws/services/iam/lib/policy_test.py @@ -1,6 +1,8 @@ from prowler.providers.aws.services.iam.lib.policy import ( check_admin_access, check_full_service_access, + has_codebuild_trusted_principal, + is_codebuild_using_allowed_github_org, is_condition_block_restrictive, is_condition_block_restrictive_organization, is_condition_block_restrictive_sns_endpoint, @@ -2108,3 +2110,116 @@ def test_check_admin_access_not_resource_with_random_resource(self): ], } assert check_admin_access(policy) + + +def test_is_codebuild_using_allowed_github_org_allows(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + github_repo_url = "https://github.com/allowed-org/repo" + allowed_organizations = ["allowed-org"] + is_allowed, org_name = is_codebuild_using_allowed_github_org( + trust_policy, github_repo_url, allowed_organizations + ) + assert is_allowed is True + assert org_name == "allowed-org" + + +def test_is_codebuild_using_allowed_github_org_denies(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + github_repo_url = "https://github.com/not-allowed-org/repo" + allowed_organizations = ["allowed-org"] + is_allowed, org_name = is_codebuild_using_allowed_github_org( + trust_policy, github_repo_url, allowed_organizations + ) + assert is_allowed is False + assert org_name == "not-allowed-org" + + +def test_is_codebuild_using_allowed_github_org_no_codebuild_principal(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + github_repo_url = "https://github.com/allowed-org/repo" + allowed_organizations = ["allowed-org"] + is_allowed, org_name = is_codebuild_using_allowed_github_org( + trust_policy, github_repo_url, allowed_organizations + ) + assert is_allowed is False + assert org_name is None + + +def test_is_codebuild_using_allowed_github_org_invalid_url(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + github_repo_url = "https://github.com/" + allowed_organizations = ["allowed-org"] + is_allowed, org_name = is_codebuild_using_allowed_github_org( + trust_policy, github_repo_url, allowed_organizations + ) + assert is_allowed is False + assert org_name is None + + +def test_has_codebuild_trusted_principal_true(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "codebuild.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + assert has_codebuild_trusted_principal(trust_policy) is True + + +def test_has_codebuild_trusted_principal_false(): + trust_policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + assert has_codebuild_trusted_principal(trust_policy) is False + + +def test_has_codebuild_trusted_principal_empty(): + trust_policy = {} + assert has_codebuild_trusted_principal(trust_policy) is False