-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
andoniaf
wants to merge
9
commits into
master
Choose a base branch
from
PRWLR-6690-add-check-to-prevent-using-codebuild-and-github-for-persistence
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
18d3bf9
feat(aws): add check to ensure Codebuild Github projects are only usi…
andoniaf 8b4457b
refactor(codebuild): rename check
andoniaf 1115ba9
fix: solve Incomplete URL substring sanitization
andoniaf 7be1dac
docs(aws): add codebuild_github_allowed_organizations to configuratio…
andoniaf 6c52c55
refactor: move policy logic from check to iam policy file
andoniaf 84c90fb
chore: add policy tests
andoniaf e4e6221
refactor: check tests
andoniaf 9d9dabb
Merge branch 'master' into PRWLR-6690-add-check-to-prevent-using-code…
andoniaf 7a177f1
fix: tests
andoniaf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
...ed_github_organizations/codebuild_project_uses_allowed_github_organizations.metadata.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": "" | ||
} |
49 changes: 49 additions & 0 deletions
49
..._uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
..._uses_allowed_github_organizations/codebuild_project_uses_allowed_github_organizations.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.