Skip to content

feat(aws): add check to ensure Codebuild Github projects are only use allowed Github orgs #7595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions prowler/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,12 @@ aws:
{"name": "TwilioKeyDetector"},
]

# AWS CodeBuild Configuration
# aws.codebuild_project_uses_allowed_github_organizations
codebuild_github_allowed_organizations:
[
"",
]

# Azure Configuration
azure:
Expand Down
Original file line number Diff line number Diff line change
@@ -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": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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


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 == "GITHUB":
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_iam_trust_policy:
if isinstance(project_iam_trust_policy["Statement"], list):
for statement in project_iam_trust_policy["Statement"]:
if (
statement["Effect"] == "Allow"
and "codebuild.amazonaws.com"
in statement["Principal"]["Service"]
):
if project_github_repo_url:
org_name = project_github_repo_url.split("/")[3]
if org_name not in allowed_organizations:
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 GitHub organization '{org_name}', which is in the allowed organizations."
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
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
from unittest.mock import patch

from boto3 import client
from moto import mock_aws

from prowler.providers.aws.services.codebuild.codebuild_service import Codebuild
from prowler.providers.aws.services.iam.iam_service import IAM
from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider


class Test_codebuild_project_uses_allowed_github_organizations:
def setup_codebuild_iam_mocks(self, audit_config=None):
"""Helper method to set up common mocks"""
if audit_config is None:
audit_config = {}

aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])

codebuild_mock = Codebuild(aws_provider)
codebuild_mock.audit_config = audit_config

iam_mock = IAM(aws_provider)

return aws_provider, codebuild_mock, iam_mock

def create_codebuild_role(
self, role_name="codebuild-test-role", service="codebuild.amazonaws.com"
):
"""Helper method to create an IAM role"""
iam_client = client("iam")
assume_role_policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": service},
"Action": "sts:AssumeRole",
}
],
}
role = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=str(assume_role_policy_document).replace("'", '"'),
)
return role["Role"]["Arn"]

def create_codebuild_project(
self, project_name, source_type, source_location, role_arn
):
"""Helper method to create a CodeBuild project"""
codebuild_client = client("codebuild", region_name=AWS_REGION_EU_WEST_1)
project = codebuild_client.create_project(
name=project_name,
source={
"type": source_type,
"location": source_location,
},
artifacts={
"type": "NO_ARTIFACTS",
},
environment={
"type": "LINUX_CONTAINER",
"image": "aws/codebuild/standard:4.0",
"computeType": "BUILD_GENERAL1_SMALL",
},
serviceRole=role_arn,
)
return project["project"]["arn"]

@mock_aws
def test_no_projects(self):
aws_provider, codebuild_mock, iam_mock = self.setup_codebuild_iam_mocks()

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",
codebuild_mock,
),
patch(
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
iam_mock,
),
):
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_not_github(self):
role_arn = self.create_codebuild_role()
project_name = "test-project-not-github"
self.create_codebuild_project(
project_name, "S3", "test-bucket/source.zip", role_arn
)

aws_provider, codebuild_mock, iam_mock = self.setup_codebuild_iam_mocks()

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",
codebuild_mock,
),
patch(
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
iam_mock,
),
):
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()

# Non-GitHub projects should not be reported
assert len(result) == 0

@mock_aws
def test_project_github_allowed_organization(self):
role_arn = self.create_codebuild_role()
project_name = "test-project-github-allowed"
project_arn = self.create_codebuild_project(
project_name, "GITHUB", "https://github.com/allowed-org/repo", role_arn
)

aws_provider, codebuild_mock, iam_mock = self.setup_codebuild_iam_mocks(
{"codebuild_github_allowed_organizations": ["allowed-org"]}
)

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",
codebuild_mock,
),
patch(
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
iam_mock,
),
):
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):
role_arn = self.create_codebuild_role()
project_name = "test-project-github-not-allowed"
project_arn = self.create_codebuild_project(
project_name, "GITHUB", "https://github.com/not-allowed-org/repo", role_arn
)

aws_provider, codebuild_mock, iam_mock = self.setup_codebuild_iam_mocks(
{"codebuild_github_allowed_organizations": ["allowed-org"]}
)

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",
codebuild_mock,
),
patch(
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
iam_mock,
),
):
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):
role_arn = self.create_codebuild_role(
"lambda-test-role", "lambda.amazonaws.com"
)
project_name = "test-project-github-lambda-role"
project_arn = self.create_codebuild_project(
project_name, "GITHUB", "https://github.com/not-allowed-org/repo", role_arn
)

aws_provider, codebuild_mock, iam_mock = self.setup_codebuild_iam_mocks(
{"codebuild_github_allowed_organizations": ["allowed-org"]}
)

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",
codebuild_mock,
),
patch(
"prowler.providers.aws.services.codebuild.codebuild_project_uses_allowed_github_organizations.codebuild_project_uses_allowed_github_organizations.iam_client",
iam_mock,
),
):
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
Loading