Skip to content

Commit 2cb8866

Browse files
authored
feat(securitycenter): Add Resource SCC Mgmt API Org SHA Custom Modules (Create, Get, List, Delete, Update) (GoogleCloudPlatform#13004)
* feat(securitycenter): Add Resource SCC Mgt API Org SHA Cust Modules (Create, Get, Delete, List, Update) * fix lint and address comments * fix lint errors * fix the ci python version errors * lint fix * fix linting * Refactor the filename of the testfile * Trigger CI pipeline * Refactor the cleaning up of created custom modules in the current test session * Refactor the module creation and clean up * Remove commented code
1 parent 234fec2 commit 2cb8866

File tree

5 files changed

+549
-0
lines changed

5 files changed

+549
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Default TEST_CONFIG_OVERRIDE for python repos.
16+
17+
# You can copy this file into your directory, then it will be inported from
18+
# the noxfile.py.
19+
20+
# The source of truth:
21+
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py
22+
23+
TEST_CONFIG_OVERRIDE = {
24+
# You can opt out from the test for specific Python versions.
25+
"ignored_versions": ["2.7", "3.7", "3.9", "3.10", "3.11"],
26+
# An envvar key for determining the project id to use. Change it
27+
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
28+
# build specific Cloud project. You can also use your own string
29+
# to use your own Cloud project.
30+
"gcloud_project_env": "GOOGLE_CLOUD_PROJECT",
31+
# "gcloud_project_env": "BUILD_SPECIFIC_GCLOUD_PROJECT",
32+
# A dictionary you want to inject into your test. Don't put any
33+
# secrets here. These values will override predefined values.
34+
"envs": {
35+
"GCLOUD_ORGANIZATION": "1081635000895",
36+
"GCLOUD_PROJECT": "project-a-id",
37+
"GCLOUD_PUBSUB_TOPIC": "projects/project-a-id/topics/notifications-sample-topic",
38+
"GCLOUD_PUBSUB_SUBSCRIPTION": "projects/project-a-id/subscriptions/notification-sample-subscription",
39+
"GCLOUD_LOCATION": "global",
40+
},
41+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
backoff==2.2.1
2+
pytest==8.2.0
3+
google-cloud-bigquery==3.11.4
4+
google-cloud-securitycentermanagement==0.1.17
5+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
google-cloud-securitycentermanagement==0.1.17
2+
google-cloud-bigquery==3.11.4
3+
google-cloud-pubsub==2.21.5
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2025 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import uuid
18+
19+
from google.api_core.exceptions import GoogleAPICallError, NotFound
20+
from google.cloud import securitycentermanagement_v1
21+
22+
23+
# [START securitycenter_create_security_health_analytics_custom_module]
24+
def create_security_health_analytics_custom_module(parent: str) -> securitycentermanagement_v1.SecurityHealthAnalyticsCustomModule:
25+
"""
26+
Creates a Security Health Analytics custom module.
27+
28+
This custom module evaluates Cloud KMS CryptoKeys to ensure their rotation period exceeds 30 days (2592000 seconds),
29+
as per security best practices. A shorter rotation period helps reduce the risk of exposure in the event of a compromise.
30+
31+
Args:
32+
parent: Use any one of the following options:
33+
- organizations/{organization_id}/locations/{location_id}
34+
- folders/{folder_id}/locations/{location_id}
35+
- projects/{project_id}/locations/{location_id}
36+
Returns:
37+
Dict: Created custom module details.
38+
"""
39+
client = securitycentermanagement_v1.SecurityCenterManagementClient()
40+
41+
try:
42+
# Generate a unique suffix
43+
unique_suffix = str(uuid.uuid4()).replace("-", "_")
44+
# Generate a unique display name
45+
display_name = f"python_sample_sha_custom_module_{unique_suffix}"
46+
47+
# Define the custom module configuration
48+
custom_module = {
49+
"display_name": display_name,
50+
"enablement_state": "ENABLED",
51+
"custom_config": {
52+
"description": (
53+
"Sample custom module for testing purposes. This custom module evaluates "
54+
"Cloud KMS CryptoKeys to ensure their rotation period exceeds 30 days (2592000 seconds)."
55+
),
56+
"predicate": {
57+
"expression": "has(resource.rotationPeriod) && (resource.rotationPeriod > duration('2592000s'))",
58+
"title": "Cloud KMS CryptoKey Rotation Period",
59+
"description": (
60+
"Evaluates whether the rotation period of a Cloud KMS CryptoKey exceeds 30 days. "
61+
"A longer rotation period might increase the risk of exposure."
62+
),
63+
},
64+
"recommendation": (
65+
"Review and adjust the rotation period for Cloud KMS CryptoKeys to align with your security policies. "
66+
"Consider setting a shorter rotation period if possible."
67+
),
68+
"resource_selector": {"resource_types": ["cloudkms.googleapis.com/CryptoKey"]},
69+
"severity": "CRITICAL",
70+
"custom_output": {
71+
"properties": [
72+
{
73+
"name": "example_property",
74+
"value_expression": {
75+
"description": "The resource name of the CryptoKey being evaluated.",
76+
"expression": "resource.name",
77+
"location": "global",
78+
"title": "CryptoKey Resource Name",
79+
},
80+
}
81+
]
82+
},
83+
},
84+
}
85+
86+
request = securitycentermanagement_v1.CreateSecurityHealthAnalyticsCustomModuleRequest(
87+
parent=parent,
88+
security_health_analytics_custom_module=custom_module,
89+
)
90+
91+
response = client.create_security_health_analytics_custom_module(request=request)
92+
print(f"Created SecurityHealthAnalytics Custom Module: {response.name}")
93+
return response
94+
95+
except GoogleAPICallError as e:
96+
print(f"Failed to create EventThreatDetectionCustomModule: {e}")
97+
raise
98+
# [END securitycenter_create_security_health_analytics_custom_module]
99+
100+
101+
# [START securitycenter_get_security_health_analytics_custom_module]
102+
def get_security_health_analytics_custom_module(parent: str, module_id: str):
103+
"""
104+
Retrieves a Security Health Analytics custom module.
105+
Args:
106+
parent: Use any one of the following options:
107+
- organizations/{organization_id}/locations/{location_id}
108+
- folders/{folder_id}/locations/{location_id}
109+
- projects/{project_id}/locations/{location_id}
110+
Returns:
111+
The retrieved Security Health Analytics custom module.
112+
Raises:
113+
NotFound: If the specified custom module does not exist.
114+
"""
115+
client = securitycentermanagement_v1.SecurityCenterManagementClient()
116+
117+
try:
118+
request = securitycentermanagement_v1.GetSecurityHealthAnalyticsCustomModuleRequest(
119+
name=f"{parent}/securityHealthAnalyticsCustomModules/{module_id}",
120+
)
121+
122+
response = client.get_security_health_analytics_custom_module(request=request)
123+
print(f"Retrieved Security Health Analytics Custom Module: {response.name}")
124+
return response
125+
except NotFound as e:
126+
print(f"Custom Module not found: {response.name}")
127+
raise e
128+
# [END securitycenter_get_security_health_analytics_custom_module]
129+
130+
131+
# [START securitycenter_list_security_health_analytics_custom_module]
132+
def list_security_health_analytics_custom_module(parent: str):
133+
"""
134+
Retrieves list of Security Health Analytics custom module.
135+
Args:
136+
parent: Use any one of the following options:
137+
- organizations/{organization_id}/locations/{location_id}
138+
- folders/{folder_id}/locations/{location_id}
139+
- projects/{project_id}/locations/{location_id}
140+
Returns:
141+
List of retrieved Security Health Analytics custom modules.
142+
Raises:
143+
NotFound: If the specified custom module does not exist.
144+
"""
145+
146+
client = securitycentermanagement_v1.SecurityCenterManagementClient()
147+
148+
try:
149+
request = securitycentermanagement_v1.ListSecurityHealthAnalyticsCustomModulesRequest(
150+
parent=parent,
151+
)
152+
153+
response = client.list_security_health_analytics_custom_modules(request=request)
154+
155+
custom_modules = []
156+
for custom_module in response:
157+
print(f"Custom Module: {custom_module.name}")
158+
custom_modules.append(custom_module)
159+
return custom_modules
160+
except NotFound as e:
161+
print(f"Parent resource not found: {parent}")
162+
raise e
163+
except Exception as e:
164+
print(f"An error occurred while listing custom modules: {e}")
165+
raise e
166+
# [END securitycenter_list_security_health_analytics_custom_module]
167+
168+
169+
# [START securitycenter_delete_security_health_analytics_custom_module]
170+
def delete_security_health_analytics_custom_module(parent: str, module_id: str):
171+
"""
172+
Deletes a Security Health Analytics custom module.
173+
Args:
174+
parent: Use any one of the following options:
175+
- organizations/{organization_id}/locations/{location_id}
176+
- folders/{folder_id}/locations/{location_id}
177+
- projects/{project_id}/locations/{location_id}
178+
Returns:
179+
The deleted Security Health Analytics custom module.
180+
Raises:
181+
NotFound: If the specified custom module does not exist.
182+
"""
183+
client = securitycentermanagement_v1.SecurityCenterManagementClient()
184+
185+
try:
186+
request = securitycentermanagement_v1.DeleteSecurityHealthAnalyticsCustomModuleRequest(
187+
name=f"{parent}/securityHealthAnalyticsCustomModules/{module_id}",
188+
)
189+
190+
client.delete_security_health_analytics_custom_module(request=request)
191+
print(f"Deleted SecurityHealthAnalyticsCustomModule Successfully: {module_id}")
192+
except NotFound as e:
193+
print(f"Custom Module not found: {module_id}")
194+
raise e
195+
# [END securitycenter_delete_security_health_analytics_custom_module]
196+
197+
198+
# [START securitycenter_update_security_health_analytics_custom_module]
199+
def update_security_health_analytics_custom_module(parent: str, module_id: str):
200+
"""
201+
Updates Security Health Analytics custom module.
202+
Args:
203+
parent: Use any one of the following options:
204+
- organizations/{organization_id}/locations/{location_id}
205+
- folders/{folder_id}/locations/{location_id}
206+
- projects/{project_id}/locations/{location_id}
207+
Returns:
208+
The updated Security Health Analytics custom module.
209+
Raises:
210+
NotFound: If the specified custom module does not exist.
211+
"""
212+
from google.protobuf.field_mask_pb2 import FieldMask
213+
214+
client = securitycentermanagement_v1.SecurityCenterManagementClient()
215+
try:
216+
# Define the custom module configuration
217+
custom_module = securitycentermanagement_v1.SecurityHealthAnalyticsCustomModule(
218+
name=f"{parent}/securityHealthAnalyticsCustomModules/{module_id}",
219+
enablement_state=securitycentermanagement_v1.SecurityHealthAnalyticsCustomModule.EnablementState.DISABLED,
220+
)
221+
222+
# Prepare the update request
223+
request = securitycentermanagement_v1.UpdateSecurityHealthAnalyticsCustomModuleRequest(
224+
security_health_analytics_custom_module=custom_module,
225+
update_mask=FieldMask(paths=["enablement_state"]),
226+
)
227+
228+
# Execute the update request
229+
response = client.update_security_health_analytics_custom_module(request=request)
230+
231+
print(f"Updated Security Health Analytics Custom Module: {response.name}")
232+
return response
233+
except NotFound:
234+
print(f"Custom Module not found: {custom_module.name}")
235+
raise
236+
except Exception as e:
237+
print(f"An error occurred while updating the custom module: {e}")
238+
raise
239+
240+
# [END securitycenter_update_security_health_analytics_custom_module]

0 commit comments

Comments
 (0)