-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
feat(codepipeline): add new check codepipeline_project_repo_private
#5915
Open
yyyy7246
wants to merge
17
commits into
prowler-cloud:master
Choose a base branch
from
yyyy7246:feat/codepipeline-project-repo-private
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.
+653
−0
Open
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c56453e
feat(codepipeline): add new check codepipeline_project_repo_private
yyyy7246 4879aec
feat(codepipeline): add unit tests
yyyy7246 6dfb8bc
fix: not use boto3
yyyy7246 6c3d2c9
chore: add annotation
yyyy7246 ced24c6
chore: revision
MrCloudSec d604a2b
chore: revision
MrCloudSec 094f930
chore: revision
MrCloudSec 15c2cee
chore: revision
MrCloudSec c5e83d2
chore: revision
MrCloudSec 35ff599
chore: revision
MrCloudSec 2ebff1b
fix: API call list_tags_for_resource
yyyy7246 9819432
chore: revision
MrCloudSec 5c1c360
chore: revision
MrCloudSec 5bfc7f5
fix: codepipeline_service.py, test code
yyyy7246 02aecf6
fix: codepipeline_service
yyyy7246 3a64505
chore: revision
MrCloudSec f709d50
chore: revision
MrCloudSec 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
Empty file.
6 changes: 6 additions & 0 deletions
6
prowler/providers/aws/services/codepipeline/codepipeline_client.py
This file contains 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,6 @@ | ||
from prowler.providers.aws.services.codepipeline.codepipeline_service import ( | ||
CodePipeline, | ||
) | ||
from prowler.providers.common.provider import Provider | ||
|
||
codepipeline_client = CodePipeline(Provider.get_global_provider()) |
Empty file.
30 changes: 30 additions & 0 deletions
30
...ipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.metadata.json
This file contains 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": "codepipeline_project_repo_private", | ||
"CheckTitle": "Ensure that CodePipeline projects do not use public GitHub or GitLab repositories as source.", | ||
"CheckType": [], | ||
"ServiceName": "codepipeline", | ||
"SubServiceName": "", | ||
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", | ||
"Severity": "medium", | ||
"ResourceType": "Other", | ||
"Description": "Ensure that CodePipeline projects do not use public GitHub or GitLab repositories as source.", | ||
"Risk": "Using public Git repositories in CodePipeline projects could expose sensitive deployment configurations and increase the risk of supply chain attacks.", | ||
"RelatedUrl": "https://docs.aws.amazon.com/codepipeline/latest/userguide/connections-github.html", | ||
"Remediation": { | ||
"Code": { | ||
"CLI": "aws codestar-connections create-connection --provider-type GitHub|GitLab --connection-name <connection-name>", | ||
"NativeIaC": "", | ||
"Other": "", | ||
"Terraform": "" | ||
}, | ||
"Recommendation": { | ||
"Text": "Use private Git repositories for CodePipeline sources and ensure proper authentication is configured using AWS CodeStar Connections. Consider using AWS CodeCommit or other private repository solutions for sensitive code.", | ||
"Url": "https://docs.aws.amazon.com/codepipeline/latest/userguide/connections" | ||
} | ||
}, | ||
"Categories": [], | ||
"DependsOn": [], | ||
"RelatedTo": [], | ||
"Notes": "This check supports both GitHub and GitLab repositories through CodeStar Connections" | ||
} |
99 changes: 99 additions & 0 deletions
99
...vices/codepipeline/codepipeline_project_repo_private/codepipeline_project_repo_private.py
This file contains 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,99 @@ | ||
import ssl | ||
import urllib.error | ||
import urllib.request | ||
|
||
from prowler.lib.check.models import Check, Check_Report_AWS | ||
from prowler.providers.aws.services.codepipeline.codepipeline_client import ( | ||
codepipeline_client, | ||
) | ||
|
||
|
||
class codepipeline_project_repo_private(Check): | ||
"""Checks if AWS CodePipeline source repositories are configured as private. | ||
|
||
This check verifies whether source repositories (GitHub or GitLab) connected to | ||
CodePipeline are publicly accessible. It attempts to access the repositories | ||
anonymously to determine their visibility status. | ||
|
||
Attributes: | ||
None | ||
""" | ||
|
||
def execute(self) -> list: | ||
"""Executes the repository privacy check for all CodePipeline sources. | ||
|
||
Iterates through all CodePipeline pipelines and checks if their source | ||
repositories (GitHub/GitLab) are publicly accessible by attempting anonymous | ||
access. | ||
|
||
Returns: | ||
list: List of Check_Report_AWS objects containing the findings for each | ||
pipeline's source repository. | ||
""" | ||
findings = [] | ||
|
||
for pipeline in codepipeline_client.pipelines.values(): | ||
if ( | ||
pipeline.source | ||
and pipeline.source.type == "CodeStarSourceConnection" | ||
and "FullRepositoryId" in str(pipeline.source.configuration) | ||
): | ||
report = Check_Report_AWS(self.metadata()) | ||
report.region = pipeline.region | ||
report.resource_id = pipeline.name | ||
report.resource_arn = pipeline.arn | ||
report.resource_tags = pipeline.tags | ||
|
||
repo_id = pipeline.source.configuration.get("FullRepositoryId", "") | ||
|
||
# Try both GitHub and GitLab URLs | ||
github_url = f"https://github.com/{repo_id}" | ||
gitlab_url = f"https://gitlab.com/{repo_id}" | ||
|
||
is_public_github = self._is_public_repo(github_url) | ||
is_public_gitlab = self._is_public_repo(gitlab_url) | ||
|
||
if is_public_github: | ||
report.status = "FAIL" | ||
report.status_extended = f"CodePipeline {pipeline.name} source repository is public: {github_url}" | ||
elif is_public_gitlab: | ||
report.status = "FAIL" | ||
report.status_extended = f"CodePipeline {pipeline.name} source repository is public: {gitlab_url}" | ||
else: | ||
report.status = "PASS" | ||
report.status_extended = ( | ||
f"CodePipeline {pipeline.name} source repository {repo_id} is private." | ||
) | ||
|
||
findings.append(report) | ||
|
||
return findings | ||
|
||
def _is_public_repo(self, repo_url: str) -> bool: | ||
"""Checks if a repository is publicly accessible. | ||
|
||
Attempts to access the repository URL anonymously to determine if it's | ||
public or private. | ||
|
||
Args: | ||
repo_url: String containing the repository URL to check. | ||
|
||
Returns: | ||
bool: True if the repository is public, False if private or inaccessible. | ||
|
||
Note: | ||
The method considers a repository private if: | ||
- The URL redirects to a sign-in page | ||
- The request fails with HTTP errors | ||
- The URL is not accessible | ||
""" | ||
if repo_url.endswith(".git"): | ||
repo_url = repo_url[:-4] | ||
|
||
try: | ||
context = ssl._create_unverified_context() | ||
req = urllib.request.Request(repo_url, method="HEAD") | ||
response = urllib.request.urlopen(req, context=context) | ||
return not response.geturl().endswith("sign_in") | ||
except (urllib.error.HTTPError, urllib.error.URLError): | ||
return False |
136 changes: 136 additions & 0 deletions
136
prowler/providers/aws/services/codepipeline/codepipeline_service.py
This file contains 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,136 @@ | ||
from typing import Optional | ||
|
||
from botocore.exceptions import ClientError | ||
from pydantic import BaseModel | ||
|
||
from prowler.lib.logger import logger | ||
from prowler.providers.aws.lib.service.service import AWSService | ||
|
||
|
||
class CodePipeline(AWSService): | ||
"""AWS CodePipeline service class for managing pipeline resources. | ||
|
||
This class handles interactions with AWS CodePipeline service, including | ||
listing pipelines and retrieving their states. It manages pipeline resources | ||
and their associated metadata. | ||
|
||
Attributes: | ||
pipelines: Dictionary mapping pipeline ARNs to Pipeline objects. | ||
""" | ||
|
||
def __init__(self, provider): | ||
"""Initializes the CodePipeline service class. | ||
|
||
Args: | ||
provider: AWS provider instance for making API calls. | ||
""" | ||
super().__init__(__class__.__name__, provider) | ||
self.pipelines = {} | ||
self.__threading_call__(self._list_pipelines) | ||
if self.pipelines: | ||
self.__threading_call__(self._get_pipeline_state, self.pipelines.values()) | ||
|
||
def _list_pipelines(self, regional_client): | ||
"""Lists all CodePipeline pipelines in the specified region. | ||
|
||
Retrieves all pipelines using pagination and creates Pipeline objects | ||
for each pipeline found. | ||
|
||
Args: | ||
regional_client: AWS regional client for CodePipeline service. | ||
|
||
Raises: | ||
ClientError: If there is an AWS API error. | ||
""" | ||
logger.info("CodePipeline - Listing pipelines...") | ||
try: | ||
list_pipelines_paginator = regional_client.get_paginator("list_pipelines") | ||
for page in list_pipelines_paginator.paginate(): | ||
for pipeline in page["pipelines"]: | ||
pipeline_arn = f"arn:{self.audited_partition}:codepipeline:{regional_client.region}:{self.audited_account}:pipeline/{pipeline['name']}" | ||
if self.pipelines is None: | ||
self.pipelines = {} | ||
self.pipelines[pipeline_arn] = Pipeline( | ||
name=pipeline["name"], | ||
arn=pipeline_arn, | ||
region=regional_client.region, | ||
) | ||
except ClientError as error: | ||
if error.response["Error"]["Code"] == "AccessDenied": | ||
logger.error( | ||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
) | ||
if not self.pipelines: | ||
self.pipelines = None | ||
else: | ||
logger.error( | ||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
) | ||
except Exception as error: | ||
logger.error( | ||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
) | ||
|
||
def _get_pipeline_state(self, pipeline): | ||
"""Retrieves the current state of a pipeline. | ||
|
||
Gets detailed information about a pipeline including its source configuration | ||
and tags. | ||
|
||
Args: | ||
pipeline: Pipeline object to retrieve state for. | ||
|
||
Raises: | ||
ClientError: If there is an AWS API error. | ||
""" | ||
logger.info("CodePipeline - Getting pipeline state...") | ||
try: | ||
regional_client = self.regional_clients[pipeline.region] | ||
pipeline_info = regional_client.get_pipeline(name=pipeline.name) | ||
source_info = pipeline_info["pipeline"]["stages"][0]["actions"][0] | ||
pipeline.source = Source( | ||
type=source_info["actionTypeId"]["provider"], | ||
location=source_info["configuration"].get("FullRepositoryId", ""), | ||
configuration=source_info["configuration"], | ||
tags=pipeline_info.get("tags", []), | ||
) | ||
except ClientError as error: | ||
logger.error( | ||
f"{pipeline.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
) | ||
except Exception as error: | ||
logger.error( | ||
f"{pipeline.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
) | ||
|
||
|
||
class Source(BaseModel): | ||
"""Model representing a pipeline source configuration. | ||
|
||
Attributes: | ||
type: The type of source provider. | ||
location: The location/path of the source repository. | ||
configuration: Optional dictionary containing additional source configuration. | ||
""" | ||
|
||
type: str | ||
location: str | ||
configuration: Optional[dict] | ||
|
||
|
||
class Pipeline(BaseModel): | ||
"""Model representing an AWS CodePipeline pipeline. | ||
|
||
Attributes: | ||
name: The name of the pipeline. | ||
arn: The ARN (Amazon Resource Name) of the pipeline. | ||
region: The AWS region where the pipeline exists. | ||
source: Optional Source object containing source configuration. | ||
tags: Optional list of pipeline tags. | ||
""" | ||
|
||
name: str | ||
arn: str | ||
region: str | ||
source: Optional[Source] | ||
tags: Optional[list] = [] |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key
tags
does not exist, you have to use the API calllist_tags_for_resource