Skip to content

Commit b0e3ba0

Browse files
authored
feat: support deletion of SCC Notification not in use in organization (#196)
1 parent aca20be commit b0e3ba0

File tree

7 files changed

+190
-11
lines changed

7 files changed

+190
-11
lines changed

‎modules/project_cleanup/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ The following services must be enabled on the project housing the cleanup functi
1616
- Cloud Scheduler (`cloudscheduler.googleapis.com`)
1717
- Cloud Resource Manager (`cloudresourcemanager.googleapis.com`)
1818
- Compute Engine API (`compute.googleapis.com`)
19+
- Security Command Center API (`securitycenter.googleapis.com`)
1920

2021
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
2122
## Inputs
2223

2324
| Name | Description | Type | Default | Required |
2425
|------|-------------|------|---------|:--------:|
26+
| clean\_up\_org\_level\_scc\_notifications | Clean up organization level Security Command Center notifications. | `bool` | `false` | no |
2527
| clean\_up\_org\_level\_tag\_keys | Clean up organization level Tag Keys. | `bool` | `false` | no |
2628
| function\_timeout\_s | The amount of time in seconds allotted for the execution of the function. | `number` | `500` | no |
2729
| job\_schedule | Cleaner function run frequency, in cron syntax | `string` | `"*/5 * * * *"` | no |
30+
| list\_scc\_notifications\_page\_size | The maximum number of notification configs to return in the call to `ListNotificationConfigs` service. The minimun value is 1 and the maximum value is 1000. | `number` | `500` | no |
2831
| max\_project\_age\_in\_hours | The maximum number of hours that a GCP project, selected by `target_tag_name` and `target_tag_value`, can exist | `number` | `6` | no |
2932
| organization\_id | The organization ID whose projects to clean up | `string` | n/a | yes |
3033
| project\_id | The project ID to host the scheduled function in | `string` | n/a | yes |
@@ -33,6 +36,7 @@ The following services must be enabled on the project housing the cleanup functi
3336
| target\_excluded\_tagkeys | List of organization Tag Key short names that won't be deleted. | `list(string)` | `[]` | no |
3437
| target\_folder\_id | Folder ID to delete all projects under. | `string` | `""` | no |
3538
| target\_included\_labels | Map of project lablels that will be deleted. | `map(string)` | `{}` | no |
39+
| target\_included\_scc\_notifications | List of organization Security Command Center notifications names regex that will be deleted. Regex example: `.*/notificationConfigs/scc-notify-.*` | `list(string)` | `[]` | no |
3640
| target\_tag\_name | The name of a tag to filter GCP projects on for consideration by the cleanup utility (legacy, use `target_included_labels` map instead). | `string` | `""` | no |
3741
| target\_tag\_value | The value of a tag to filter GCP projects on for consideration by the cleanup utility (legacy, use `target_included_labels` map instead). | `string` | `""` | no |
3842
| topic\_name | Name of pubsub topic connecting the scheduled projects cleanup function | `string` | `"pubsub_scheduled_project_cleaner"` | no |

‎modules/project_cleanup/function_source/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ The following environment variables may be specified to configure the cleanup ut
2222

2323
## Required Permissions
2424

25-
This Cloud Function must be run as a Service Account with the `Organization Administrator` role.
25+
This Cloud Function must be run as a Service Account with the `Organization Administrator` (`roles/resourcemanager.organizationAdmin`) role.

‎modules/project_cleanup/function_source/go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ module github.com/terraform-google-modules/terraform-google-scheduled-function/m
33
go 1.21
44

55
require (
6+
cloud.google.com/go/securitycenter v1.28.0
67
golang.org/x/net v0.24.0
78
golang.org/x/oauth2 v0.19.0
89
google.golang.org/api v0.177.0
910
)
1011

1112
require (
13+
cloud.google.com/go v0.112.2 // indirect
1214
cloud.google.com/go/auth v0.3.0 // indirect
1315
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
1416
cloud.google.com/go/compute/metadata v0.3.0 // indirect
17+
cloud.google.com/go/iam v1.1.6 // indirect
18+
cloud.google.com/go/longrunning v0.5.5 // indirect
1519
github.com/felixge/httpsnoop v1.0.4 // indirect
1620
github.com/go-logr/logr v1.4.1 // indirect
1721
github.com/go-logr/stdr v1.2.2 // indirect
@@ -22,13 +26,18 @@ require (
2226
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
2327
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
2428
go.opencensus.io v0.24.0 // indirect
29+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
2530
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
2631
go.opentelemetry.io/otel v1.24.0 // indirect
2732
go.opentelemetry.io/otel/metric v1.24.0 // indirect
2833
go.opentelemetry.io/otel/trace v1.24.0 // indirect
2934
golang.org/x/crypto v0.22.0 // indirect
35+
golang.org/x/sync v0.7.0 // indirect
3036
golang.org/x/sys v0.19.0 // indirect
3137
golang.org/x/text v0.14.0 // indirect
38+
golang.org/x/time v0.5.0 // indirect
39+
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
40+
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect
3241
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
3342
google.golang.org/grpc v1.63.2 // indirect
3443
google.golang.org/protobuf v1.34.0 // indirect

‎modules/project_cleanup/function_source/go.sum

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2+
cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
3+
cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
24
cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs=
35
cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w=
46
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
57
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
68
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
79
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
10+
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
11+
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
12+
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
13+
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
14+
cloud.google.com/go/securitycenter v1.28.0 h1:NpEJeFbm3ad3ibpbpIBKXJS7eQq1cZhtt9nrDTMO/QQ=
15+
cloud.google.com/go/securitycenter v1.28.0/go.mod h1:kmS8vAIwPbCIg7dDuiVKF/OTizYfuWe5f0IIW6NihN8=
816
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
917
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
1018
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -69,6 +77,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
6977
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7078
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
7179
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
80+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
81+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
7282
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
7383
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
7484
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
@@ -111,6 +121,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
111121
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
112122
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
113123
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
124+
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
125+
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
114126
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
115127
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
116128
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -125,8 +137,9 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
125137
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
126138
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
127139
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
128-
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
129-
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
140+
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
141+
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc=
142+
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
130143
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
131144
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
132145
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

‎modules/project_cleanup/function_source/main.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ import (
2727
"strings"
2828
"time"
2929

30+
securitycenter "cloud.google.com/go/securitycenter/apiv1"
31+
"cloud.google.com/go/securitycenter/apiv1/securitycenterpb"
3032
"golang.org/x/net/context"
3133
"golang.org/x/oauth2/google"
3234
"google.golang.org/api/cloudresourcemanager/v1"
3335
cloudresourcemanager2 "google.golang.org/api/cloudresourcemanager/v2"
3436
cloudresourcemanager3 "google.golang.org/api/cloudresourcemanager/v3"
3537
"google.golang.org/api/compute/v1"
3638
"google.golang.org/api/googleapi"
39+
"google.golang.org/api/iterator"
3740
"google.golang.org/api/option"
3841
"google.golang.org/api/servicemanagement/v1"
3942
)
@@ -43,23 +46,29 @@ const (
4346
TargetExcludedLabels = "TARGET_EXCLUDED_LABELS"
4447
TargetIncludedLabels = "TARGET_INCLUDED_LABELS"
4548
CleanUpTagKeys = "CLEAN_UP_TAG_KEYS"
49+
CleanUpSCCNotfi = "CLEAN_UP_SCC_NOTIFICATIONS"
4650
TargetExcludedTagKeys = "TARGET_EXCLUDED_TAGKEYS"
51+
TargetIncludedSCCNotfis = "TARGET_INCLUDED_SCC_NOTIFICATIONS"
4752
TargetFolderId = "TARGET_FOLDER_ID"
4853
TargetOrganizationId = "TARGET_ORGANIZATION_ID"
4954
MaxProjectAgeHours = "MAX_PROJECT_AGE_HOURS"
5055
targetFolderRegexp = `^[0-9]+$`
5156
targetOrganizationRegexp = `^[0-9]+$`
57+
SCCNotificationsPageSize = "SCC_NOTIFICATIONS_PAGE_SIZE"
5258
)
5359

5460
var (
5561
logger = log.New(os.Stdout, "", 0)
5662
excludedLabelsMap = getLabelsMapFromEnv(TargetExcludedLabels)
5763
includedLabelsMap = getLabelsMapFromEnv(TargetIncludedLabels)
5864
cleanUpTagKeys = getCleanUpTagKeysOrTerminateExecution()
65+
cleanUpSCCNotfi = getCleanUpSCCNotfiOrTerminateExecution()
5966
excludedTagKeysList = getTagKeysListFromEnv(TargetExcludedTagKeys)
67+
includedSCCNotfisList = getSCCNotfiListFromEnv(TargetIncludedSCCNotfis)
6068
resourceCreationCutoff = getOldTime(int64(getCorrectMaxAgeInHoursOrTerminateExecution()) * 60 * 60)
6169
rootFolderId = getCorrectFolderIdOrTerminateExecution()
6270
organizationId = getCorrectOrganizationIdOrTerminateExecution()
71+
sccPageSize = getSCCNotificationPageSizeOrTerminateExecution()
6372
)
6473

6574
type PubSubMessage struct {
@@ -163,6 +172,18 @@ func checkIfAtLeastOneLabelPresentIfAny(project *cloudresourcemanager.Project, l
163172
return result
164173
}
165174

175+
func checkIfSCCNotificationNameIncluded(notificationName string, includedSCCNotfis []*regexp.Regexp) bool {
176+
if len(includedSCCNotfis) == 0 {
177+
return false
178+
}
179+
for _, name := range includedSCCNotfis {
180+
if name.MatchString(notificationName) {
181+
return true
182+
}
183+
}
184+
return false
185+
}
186+
166187
func checkIfTagKeyShortNameExcluded(shortName string, excludedTagKeys []string) bool {
167188
if len(excludedTagKeys) == 0 {
168189
return false
@@ -194,6 +215,36 @@ func getLabelsMapFromEnv(envVariableName string) map[string]string {
194215
return labels
195216
}
196217

218+
func getSCCNotfiListFromEnv(envVariableName string) []*regexp.Regexp {
219+
var compiledRegEx []*regexp.Regexp
220+
targetExcludedSCCNotfis := os.Getenv(envVariableName)
221+
logger.Println("Try to get SCC Notifications list")
222+
if targetExcludedSCCNotfis == "" {
223+
logger.Printf("No SCC Notifications provided.")
224+
return compiledRegEx
225+
}
226+
227+
var sccNotfis []string
228+
err := json.Unmarshal([]byte(targetExcludedSCCNotfis), &sccNotfis)
229+
if err != nil {
230+
logger.Printf("Failed to get SCC Notifications list from [%s] env variable, error [%s]", envVariableName, err.Error())
231+
return compiledRegEx
232+
} else {
233+
logger.Printf("Got SCC Notifications list [%s] from [%s] env variable", sccNotfis, envVariableName)
234+
}
235+
236+
//build Regexes
237+
for _, r := range sccNotfis {
238+
result, err := regexp.Compile(r)
239+
if err != nil {
240+
logger.Printf("Invalid regular expression [%s] for SCC Notification", r)
241+
} else {
242+
compiledRegEx = append(compiledRegEx, result)
243+
}
244+
}
245+
return compiledRegEx
246+
}
247+
197248
func getTagKeysListFromEnv(envVariableName string) []string {
198249
targetExcludedTagKeys := os.Getenv(envVariableName)
199250
logger.Println("Try to get Tag Keys list")
@@ -224,6 +275,27 @@ func getCleanUpTagKeysOrTerminateExecution() bool {
224275
return result
225276
}
226277

278+
func getCleanUpSCCNotfiOrTerminateExecution() bool {
279+
cleanUpSCCNotfiVal, exists := os.LookupEnv(CleanUpSCCNotfi)
280+
if !exists {
281+
logger.Fatalf("Clean up SCC notifications environment variable [%s] not set, set the environment variable and try again.", CleanUpSCCNotfi)
282+
}
283+
result, err := strconv.ParseBool(cleanUpSCCNotfiVal)
284+
if err != nil {
285+
logger.Fatalf("Invalid Clean up SCC notifications value [%s], specify correct value for environment variable [%s] and try again.", cleanUpSCCNotfiVal, CleanUpSCCNotfi)
286+
}
287+
return result
288+
}
289+
290+
func getSCCNotificationPageSizeOrTerminateExecution() int32 {
291+
pageSize := os.Getenv(SCCNotificationsPageSize)
292+
size, err := strconv.ParseInt(pageSize, 10, 32)
293+
if err != nil {
294+
logger.Fatalf("Invalid page size [%s], specify correct value and try again.", pageSize)
295+
}
296+
return int32(size)
297+
}
298+
227299
func getCorrectFolderIdOrTerminateExecution() string {
228300
targetFolderIdString := os.Getenv(TargetFolderId)
229301
matched, err := regexp.MatchString(targetFolderRegexp, targetFolderIdString)
@@ -290,6 +362,16 @@ func getTagValuesServiceOrTerminateExecution(ctx context.Context, client *http.C
290362
return cloudResourceManagerService.TagValues
291363
}
292364

365+
func getSCCNotificationServiceOrTerminateExecution(ctx context.Context, client *http.Client) *securitycenter.Client {
366+
logger.Println("Try to get SCC Notification Service")
367+
securitycenterClient, err := securitycenter.NewClient(ctx)
368+
if err != nil {
369+
logger.Fatalf("Failed to get SCC Notification Service with error [%s], terminate execution", err.Error())
370+
}
371+
logger.Println("Got SCC Notification Service")
372+
return securitycenterClient
373+
}
374+
293375
func getFirewallPoliciesServiceOrTerminateExecution(ctx context.Context, client *http.Client) *compute.FirewallPoliciesService {
294376
logger.Println("Try to get Firewall Policies Service")
295377
computeService, err := compute.NewService(ctx, option.WithHTTPClient(client))
@@ -315,6 +397,7 @@ func invoke(ctx context.Context) {
315397
cloudResourceManagerService := getResourceManagerServiceOrTerminateExecution(ctx, client)
316398
folderService := getFolderServiceOrTerminateExecution(ctx, client)
317399
tagKeyService := getTagKeysServiceOrTerminateExecution(ctx, client)
400+
sccService := getSCCNotificationServiceOrTerminateExecution(ctx, client)
318401
tagValuesService := getTagValuesServiceOrTerminateExecution(ctx, client)
319402
firewallPoliciesService := getFirewallPoliciesServiceOrTerminateExecution(ctx, client)
320403
endpointService := getServiceManagementServiceOrTerminateExecution(ctx, client)
@@ -338,6 +421,49 @@ func invoke(ctx context.Context) {
338421
return tagKeyCreatedAt.Before(resourceCreationCutoff)
339422
}
340423

424+
projectDeleteRequestedFilter := func(projectID string) bool {
425+
p, err := cloudResourceManagerService.Projects.Get(projectID).Context(ctx).Do()
426+
if err != nil {
427+
logger.Printf("Failed to get project [%s], error [%s]", projectID, err.Error())
428+
return false
429+
}
430+
if p.LifecycleState == "DELETE_REQUESTED" {
431+
return true
432+
}
433+
return false
434+
}
435+
436+
removeSCCNotifications := func(organization string) {
437+
logger.Printf("Try to remove SCC Notifications from organization [%s]", organization)
438+
req := &securitycenterpb.ListNotificationConfigsRequest{
439+
Parent: fmt.Sprintf("organizations/%s", organization),
440+
PageSize: sccPageSize,
441+
}
442+
it := sccService.ListNotificationConfigs(ctx, req)
443+
for {
444+
resp, err := it.Next()
445+
if err == iterator.Done {
446+
break
447+
}
448+
if err != nil {
449+
logger.Printf("failed to list SCC notifications, error [%s]", err.Error())
450+
break
451+
}
452+
projectID := strings.Split(resp.PubsubTopic, "/")[1]
453+
if checkIfSCCNotificationNameIncluded(resp.Name, includedSCCNotfisList) && projectDeleteRequestedFilter(projectID) {
454+
delReq := &securitycenterpb.DeleteNotificationConfigRequest{
455+
Name: resp.Name,
456+
}
457+
err = sccService.DeleteNotificationConfig(ctx, delReq)
458+
if err != nil {
459+
logger.Printf("failed to delete SCC notification [%s], error [%s]", resp.Name, err.Error())
460+
} else {
461+
logger.Printf("SCC notification [%s] deleted", resp.Name)
462+
}
463+
}
464+
}
465+
}
466+
341467
removeTagValues := func(tagKey string) {
342468
logger.Printf("Try to remove Tag Values from TagKey [%s]", tagKey)
343469
tagValuesList, err := tagValuesService.List().Parent(tagKey).Context(ctx).Do()
@@ -518,6 +644,11 @@ func invoke(ctx context.Context) {
518644
if cleanUpTagKeys {
519645
removeTagKeys(organizationId)
520646
}
647+
648+
// only delete Security Command Center notifications from deleted projects
649+
if cleanUpSCCNotfi {
650+
removeSCCNotifications(organizationId)
651+
}
521652
}
522653

523654
func CleanUpProjects(ctx context.Context, m PubSubMessage) error {

‎modules/project_cleanup/main.tf

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ resource "google_organization_iam_member" "main" {
3333
"roles/compute.orgSecurityResourceAdmin",
3434
"roles/compute.orgSecurityPolicyAdmin",
3535
"roles/resourcemanager.tagAdmin",
36-
"roles/viewer"
36+
"roles/viewer",
37+
"roles/securitycenter.notificationConfigEditor"
3738
])
3839

3940
member = "serviceAccount:${google_service_account.project_cleaner_function.email}"
@@ -58,12 +59,15 @@ module "scheduled_project_cleaner" {
5859
function_timeout_s = var.function_timeout_s
5960

6061
function_environment_variables = {
61-
TARGET_ORGANIZATION_ID = var.organization_id
62-
TARGET_FOLDER_ID = var.target_folder_id
63-
TARGET_EXCLUDED_LABELS = jsonencode(var.target_excluded_labels)
64-
TARGET_INCLUDED_LABELS = jsonencode(local.target_included_labels)
65-
MAX_PROJECT_AGE_HOURS = var.max_project_age_in_hours
66-
CLEAN_UP_TAG_KEYS = var.clean_up_org_level_tag_keys
67-
TARGET_EXCLUDED_TAGKEYS = jsonencode(var.target_excluded_tagkeys)
62+
TARGET_ORGANIZATION_ID = var.organization_id
63+
TARGET_FOLDER_ID = var.target_folder_id
64+
TARGET_EXCLUDED_LABELS = jsonencode(var.target_excluded_labels)
65+
TARGET_INCLUDED_LABELS = jsonencode(local.target_included_labels)
66+
MAX_PROJECT_AGE_HOURS = var.max_project_age_in_hours
67+
CLEAN_UP_TAG_KEYS = var.clean_up_org_level_tag_keys
68+
TARGET_EXCLUDED_TAGKEYS = jsonencode(var.target_excluded_tagkeys)
69+
CLEAN_UP_SCC_NOTIFICATIONS = var.clean_up_org_level_scc_notifications
70+
TARGET_INCLUDED_SCC_NOTIFICATIONS = jsonencode(var.target_included_scc_notifications)
71+
SCC_NOTIFICATIONS_PAGE_SIZE = var.list_scc_notifications_page_size
6872
}
6973
}

0 commit comments

Comments
 (0)