-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(gcp): Add VPC Service Controls check for Cloud Storage #9256
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
Merged
danibarranqueroo
merged 11 commits into
master
from
PROWLER-172-implement-cloud-storage-use-vpc-service-controls-check-for-gcp
Nov 26, 2025
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
4d8f44d
feat(gcp): Add VPC Service Controls check for Cloud Storage
lydiavilchez ecd841e
docs: update CHANGELOG for VPC Service Controls check
lydiavilchez 19596ac
fix: address CodeQL warnings and minor corrections
lydiavilchez 021f9dd
test: add comprehensive service test for accesscontextmanager
lydiavilchez 3e4f809
refactor(gcp): change VPC Service Controls check to project level
lydiavilchez bd13ce8
fix(gcp): Use project number instead of project ID
lydiavilchez 8c85982
Merge branch 'master' into PROWLER-172-implement-cloud-storage-use-vp…
lydiavilchez 6c0677b
fix(gcp): Add audit_configs field and fix merge conflict
lydiavilchez 6f4dda7
test(gcp): Add error handling tests for AccessContextManager service
lydiavilchez bee0245
Merge branch 'master' into PROWLER-172-implement-cloud-storage-use-vp…
danibarranqueroo 379f978
chore: revision
danibarranqueroo 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
Empty file.
6 changes: 6 additions & 0 deletions
6
prowler/providers/gcp/services/accesscontextmanager/accesscontextmanager_client.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,6 @@ | ||
| from prowler.providers.common.provider import Provider | ||
| from prowler.providers.gcp.services.accesscontextmanager.accesscontextmanager_service import ( | ||
| AccessContextManager, | ||
| ) | ||
|
|
||
| accesscontextmanager_client = AccessContextManager(Provider.get_global_provider()) |
101 changes: 101 additions & 0 deletions
101
prowler/providers/gcp/services/accesscontextmanager/accesscontextmanager_service.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,101 @@ | ||
| from pydantic.v1 import BaseModel | ||
|
|
||
| import prowler.providers.gcp.config as config | ||
| from prowler.lib.logger import logger | ||
| from prowler.providers.gcp.gcp_provider import GcpProvider | ||
| from prowler.providers.gcp.lib.service.service import GCPService | ||
| from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_client import ( | ||
| cloudresourcemanager_client, | ||
| ) | ||
|
|
||
|
|
||
| class AccessContextManager(GCPService): | ||
| def __init__(self, provider: GcpProvider): | ||
| super().__init__("accesscontextmanager", provider, api_version="v1") | ||
| self.service_perimeters = [] | ||
| self._get_service_perimeters() | ||
|
|
||
| def _get_service_perimeters(self): | ||
| for org in cloudresourcemanager_client.organizations: | ||
| try: | ||
| access_policies = [] | ||
| try: | ||
| request = self.client.accessPolicies().list( | ||
| parent=f"organizations/{org.id}" | ||
| ) | ||
| while request is not None: | ||
| response = request.execute( | ||
| num_retries=config.DEFAULT_RETRY_ATTEMPTS | ||
| ) | ||
| access_policies.extend(response.get("accessPolicies", [])) | ||
|
|
||
| request = self.client.accessPolicies().list_next( | ||
| previous_request=request, previous_response=response | ||
| ) | ||
| except Exception as error: | ||
| logger.error( | ||
| f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
| ) | ||
| continue | ||
|
|
||
| for policy in access_policies: | ||
| try: | ||
| request = ( | ||
| self.client.accessPolicies() | ||
| .servicePerimeters() | ||
| .list(parent=policy["name"]) | ||
| ) | ||
| while request is not None: | ||
| response = request.execute( | ||
| num_retries=config.DEFAULT_RETRY_ATTEMPTS | ||
| ) | ||
|
|
||
| for perimeter in response.get("servicePerimeters", []): | ||
| status = perimeter.get("status", {}) | ||
| spec = perimeter.get("spec", {}) | ||
|
|
||
| perimeter_config = status if status else spec | ||
|
|
||
| resources = perimeter_config.get("resources", []) | ||
| restricted_services = perimeter_config.get( | ||
| "restrictedServices", [] | ||
| ) | ||
|
|
||
| self.service_perimeters.append( | ||
| ServicePerimeter( | ||
| name=perimeter["name"], | ||
| title=perimeter.get("title", ""), | ||
| perimeter_type=perimeter.get( | ||
| "perimeterType", "" | ||
| ), | ||
| resources=resources, | ||
| restricted_services=restricted_services, | ||
| policy_name=policy["name"], | ||
| ) | ||
| ) | ||
|
|
||
| request = ( | ||
| self.client.accessPolicies() | ||
| .servicePerimeters() | ||
| .list_next( | ||
| previous_request=request, previous_response=response | ||
| ) | ||
| ) | ||
| except Exception as error: | ||
| logger.error( | ||
| f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
| ) | ||
|
|
||
| except Exception as error: | ||
| logger.error( | ||
| f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" | ||
| ) | ||
|
|
||
|
|
||
| class ServicePerimeter(BaseModel): | ||
| name: str | ||
| title: str | ||
| perimeter_type: str | ||
| resources: list[str] | ||
| restricted_services: list[str] | ||
| policy_name: str | ||
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
Empty file.
36 changes: 36 additions & 0 deletions
36
...oudstorage_uses_vpc_service_controls/cloudstorage_uses_vpc_service_controls.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,36 @@ | ||
| { | ||
| "Provider": "gcp", | ||
| "CheckID": "cloudstorage_uses_vpc_service_controls", | ||
| "CheckTitle": "Cloud Storage services are protected by VPC Service Controls", | ||
| "CheckType": [], | ||
| "ServiceName": "cloudstorage", | ||
| "SubServiceName": "", | ||
| "ResourceIdTemplate": "", | ||
| "Severity": "medium", | ||
| "ResourceType": "cloudresourcemanager.googleapis.com/Project", | ||
| "Description": "**GCP Projects** are evaluated to ensure they have **VPC Service Controls** enabled for Cloud Storage. VPC Service Controls establish security boundaries by restricting access to Cloud Storage resources to specific networks and trusted clients, preventing unauthorized data access and exfiltration.", | ||
| "Risk": "Projects without VPC Service Controls protection for Cloud Storage may be vulnerable to unauthorized data access and exfiltration, even with proper IAM policies in place. VPC Service Controls provide an additional layer of network-level security that restricts API access based on the context of the request.", | ||
| "RelatedUrl": "", | ||
| "AdditionalURLs": [ | ||
| "https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudStorage/use-vpc-service-controls.html", | ||
| "https://cloud.google.com/vpc-service-controls/docs/create-service-perimeters" | ||
| ], | ||
| "Remediation": { | ||
| "Code": { | ||
| "CLI": "", | ||
| "NativeIaC": "", | ||
| "Other": "1) Open Google Cloud Console → Security → VPC Service Controls\n2) Create a new service perimeter or select an existing one\n3) Add the relevant GCP projects to the perimeter's protected resources\n4) Add 'storage.googleapis.com' to the list of restricted services\n5) Configure appropriate ingress and egress rules\n6) Save the perimeter configuration", | ||
| "Terraform": "" | ||
| }, | ||
| "Recommendation": { | ||
| "Text": "Enable VPC Service Controls for all Cloud Storage buckets by adding their projects to a service perimeter with storage.googleapis.com as a restricted service. This prevents data exfiltration and ensures API calls are only allowed from authorized networks.", | ||
| "Url": "https://hub.prowler.com/check/cloudstorage_uses_vpc_service_controls" | ||
| } | ||
| }, | ||
| "Categories": [ | ||
| "internet-exposed" | ||
| ], | ||
| "DependsOn": [], | ||
| "RelatedTo": [], | ||
| "Notes": "" | ||
| } |
53 changes: 53 additions & 0 deletions
53
...dstorage/cloudstorage_uses_vpc_service_controls/cloudstorage_uses_vpc_service_controls.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,53 @@ | ||
| from prowler.lib.check.models import Check, Check_Report_GCP | ||
| from prowler.providers.gcp.services.accesscontextmanager.accesscontextmanager_client import ( | ||
| accesscontextmanager_client, | ||
| ) | ||
| from prowler.providers.gcp.services.cloudresourcemanager.cloudresourcemanager_client import ( | ||
| cloudresourcemanager_client, | ||
| ) | ||
|
|
||
|
|
||
| class cloudstorage_uses_vpc_service_controls(Check): | ||
| """ | ||
| Ensure Cloud Storage is protected by VPC Service Controls at project level. | ||
|
|
||
| Reports PASS if a project is in a VPC Service Controls perimeter | ||
| with storage.googleapis.com as a restricted service, otherwise FAIL. | ||
| """ | ||
|
|
||
| def execute(self) -> list[Check_Report_GCP]: | ||
| findings = [] | ||
|
|
||
| protected_projects = {} | ||
| for perimeter in accesscontextmanager_client.service_perimeters: | ||
| if any( | ||
| service == "storage.googleapis.com" | ||
| for service in perimeter.restricted_services | ||
| ): | ||
| for resource in perimeter.resources: | ||
| protected_projects[resource] = perimeter.title | ||
|
|
||
| for project in cloudresourcemanager_client.cloud_resource_manager_projects: | ||
| report = Check_Report_GCP( | ||
| metadata=self.metadata(), | ||
| resource=cloudresourcemanager_client.projects[project.id], | ||
| project_id=project.id, | ||
| location=cloudresourcemanager_client.region, | ||
| resource_name=( | ||
| cloudresourcemanager_client.projects[project.id].name | ||
| if cloudresourcemanager_client.projects[project.id].name | ||
| else "GCP Project" | ||
| ), | ||
| ) | ||
| report.status = "FAIL" | ||
| report.status_extended = f"Project {project.id} does not have VPC Service Controls enabled for Cloud Storage." | ||
| # GCP stores resources by project number, not project ID | ||
| project_resource_id = f"projects/{project.number}" | ||
|
|
||
| if project_resource_id in protected_projects: | ||
| report.status = "PASS" | ||
| report.status_extended = f"Project {project.id} has VPC Service Controls enabled for Cloud Storage in perimeter {protected_projects[project_resource_id]}." | ||
|
|
||
| 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
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.
Uh oh!
There was an error while loading. Please reload this page.