diff --git a/modules/project_cleanup/README.md b/modules/project_cleanup/README.md index a557c03..2518cb0 100644 --- a/modules/project_cleanup/README.md +++ b/modules/project_cleanup/README.md @@ -16,12 +16,14 @@ The following services must be enabled on the project housing the cleanup functi - Cloud Scheduler (`cloudscheduler.googleapis.com`) - Cloud Resource Manager (`cloudresourcemanager.googleapis.com`) - Compute Engine API (`compute.googleapis.com`) +- Security Command Center API (`securitycenter.googleapis.com`) ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| clean\_up\_org\_level\_scc\_notifications | Clean up organization level Security Command Center notifications. | `bool` | `false` | no | | clean\_up\_org\_level\_tag\_keys | Clean up organization level Tag Keys. | `bool` | `false` | no | | function\_timeout\_s | The amount of time in seconds allotted for the execution of the function. | `number` | `500` | no | | job\_schedule | Cleaner function run frequency, in cron syntax | `string` | `"*/5 * * * *"` | no | @@ -33,6 +35,7 @@ The following services must be enabled on the project housing the cleanup functi | target\_excluded\_tagkeys | List of organization Tag Key short names that won't be deleted. | `list(string)` | `[]` | no | | target\_folder\_id | Folder ID to delete all projects under. | `string` | `""` | no | | target\_included\_labels | Map of project lablels that will be deleted. | `map(string)` | `{}` | no | +| 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 | | 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 | | 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 | | topic\_name | Name of pubsub topic connecting the scheduled projects cleanup function | `string` | `"pubsub_scheduled_project_cleaner"` | no | diff --git a/modules/project_cleanup/function_source/README.md b/modules/project_cleanup/function_source/README.md index 01934e3..f51035c 100644 --- a/modules/project_cleanup/function_source/README.md +++ b/modules/project_cleanup/function_source/README.md @@ -22,4 +22,4 @@ The following environment variables may be specified to configure the cleanup ut ## Required Permissions -This Cloud Function must be run as a Service Account with the `Organization Administrator` role. +This Cloud Function must be run as a Service Account with the `Organization Administrator` (`roles/resourcemanager.organizationAdmin`) role. diff --git a/modules/project_cleanup/function_source/go.mod b/modules/project_cleanup/function_source/go.mod index bcd93e8..5fcf31c 100644 --- a/modules/project_cleanup/function_source/go.mod +++ b/modules/project_cleanup/function_source/go.mod @@ -3,14 +3,18 @@ module github.com/terraform-google-modules/terraform-google-scheduled-function/m go 1.21 require ( + cloud.google.com/go/securitycenter v1.28.0 golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.18.0 google.golang.org/api v0.169.0 ) require ( - cloud.google.com/go/compute v1.23.4 // indirect + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/longrunning v0.5.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -21,15 +25,20 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 // indirect - google.golang.org/grpc v1.62.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/grpc v1.62.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/modules/project_cleanup/function_source/go.sum b/modules/project_cleanup/function_source/go.sum index 88c9caa..b265134 100644 --- a/modules/project_cleanup/function_source/go.sum +++ b/modules/project_cleanup/function_source/go.sum @@ -1,8 +1,16 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= -cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/securitycenter v1.28.0 h1:NpEJeFbm3ad3ibpbpIBKXJS7eQq1cZhtt9nrDTMO/QQ= +cloud.google.com/go/securitycenter v1.28.0/go.mod h1:kmS8vAIwPbCIg7dDuiVKF/OTizYfuWe5f0IIW6NihN8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -71,6 +79,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= @@ -127,6 +137,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -145,19 +157,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= -google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= -google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78 h1:Xs9lu+tLXxLIfuci70nG4cpwaRC+mRQPUL7LoIeDJC4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= -google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -169,8 +181,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/modules/project_cleanup/function_source/main.go b/modules/project_cleanup/function_source/main.go index bb579f5..e7b9350 100644 --- a/modules/project_cleanup/function_source/main.go +++ b/modules/project_cleanup/function_source/main.go @@ -27,6 +27,8 @@ import ( "strings" "time" + securitycenter "cloud.google.com/go/securitycenter/apiv1" + "cloud.google.com/go/securitycenter/apiv1/securitycenterpb" "golang.org/x/net/context" "golang.org/x/oauth2/google" "google.golang.org/api/cloudresourcemanager/v1" @@ -34,6 +36,7 @@ import ( cloudresourcemanager3 "google.golang.org/api/cloudresourcemanager/v3" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" + "google.golang.org/api/iterator" "google.golang.org/api/option" "google.golang.org/api/servicemanagement/v1" ) @@ -43,7 +46,9 @@ const ( TargetExcludedLabels = "TARGET_EXCLUDED_LABELS" TargetIncludedLabels = "TARGET_INCLUDED_LABELS" CleanUpTagKeys = "CLEAN_UP_TAG_KEYS" + CleanUpSCCNoti = "CLEAN_UP_SCC_NOTIFICATIONS" TargetExcludedTagKeys = "TARGET_EXCLUDED_TAGKEYS" + TargetIncludedSCCNoti = "TARGET_INCLUDED_SCC_NOTIFICATIONS" TargetFolderId = "TARGET_FOLDER_ID" TargetOrganizationId = "TARGET_ORGANIZATION_ID" MaxProjectAgeHours = "MAX_PROJECT_AGE_HOURS" @@ -56,7 +61,9 @@ var ( excludedLabelsMap = getLabelsMapFromEnv(TargetExcludedLabels) includedLabelsMap = getLabelsMapFromEnv(TargetIncludedLabels) cleanUpTagKeys = getCleanUpTagKeysOrTerminateExecution() + cleanUpSCCNotifi = getCleanUpSCCNotiOrTerminateExecution() excludedTagKeysList = getTagKeysListFromEnv(TargetExcludedTagKeys) + includedSCCNotiList = getSCCNotiListFromEnv(TargetIncludedSCCNoti) resourceCreationCutoff = getOldTime(int64(getCorrectMaxAgeInHoursOrTerminateExecution()) * 60 * 60) rootFolderId = getCorrectFolderIdOrTerminateExecution() organizationId = getCorrectOrganizationIdOrTerminateExecution() @@ -163,6 +170,18 @@ func checkIfAtLeastOneLabelPresentIfAny(project *cloudresourcemanager.Project, l return result } +func checkIfSCCNotificationNameIncluded(notificationName string, includedSCCNotis []*regexp.Regexp) bool { + if len(includedSCCNotis) == 0 { + return false + } + for _, name := range includedSCCNotis { + if name.MatchString(notificationName){ + return true + } + } + return false +} + func checkIfTagKeyShortNameExcluded(shortName string, excludedTagKeys []string) bool { if len(excludedTagKeys) == 0 { return false @@ -194,6 +213,29 @@ func getLabelsMapFromEnv(envVariableName string) map[string]string { return labels } +func getSCCNotiListFromEnv(envVariableName string) []*regexp.Regexp { + targetExcludedSCCNotis := os.Getenv(envVariableName) + logger.Println("Try to get SCC Notifications list") + if targetExcludedSCCNotis == "" { + logger.Printf("No SCC Notifications provided.") + return nil + } + + var sccNotis []string + err := json.Unmarshal([]byte(targetExcludedSCCNotis), &sccNotis) + if err != nil { + logger.Printf("Failed to get SCC Notifications list from [%s] env variable, error [%s]", envVariableName, err.Error()) + } else { + logger.Printf("Got SCC Notifications list [%s] from [%s] env variable", sccNotis, envVariableName) + } + //build Regexes + reg := make([]*regexp.Regexp, len(sccNotis)) + for i, r := range sccNotis { + reg[i] = regexp.MustCompile(r) + } + return reg +} + func getTagKeysListFromEnv(envVariableName string) []string { targetExcludedTagKeys := os.Getenv(envVariableName) logger.Println("Try to get Tag Keys list") @@ -224,6 +266,18 @@ func getCleanUpTagKeysOrTerminateExecution() bool { return result } +func getCleanUpSCCNotiOrTerminateExecution() bool { + cleanUpSCCNoti, exists := os.LookupEnv(CleanUpSCCNoti) + if !exists { + logger.Fatalf("Clean up SCC notifications environment variable [%s] not set, set the environment variable and try again.", CleanUpSCCNoti) + } + result, err := strconv.ParseBool(cleanUpSCCNoti) + if err != nil { + logger.Fatalf("Invalid Clean up SCC notifications value [%s], specify correct value for environment variable [%s] and try again.", cleanUpSCCNoti, CleanUpSCCNoti) + } + return result +} + func getCorrectFolderIdOrTerminateExecution() string { targetFolderIdString := os.Getenv(TargetFolderId) matched, err := regexp.MatchString(targetFolderRegexp, targetFolderIdString) @@ -290,6 +344,16 @@ func getTagValuesServiceOrTerminateExecution(ctx context.Context, client *http.C return cloudResourceManagerService.TagValues } +func getSCCNotificationServiceOrTerminateExecution(ctx context.Context, client *http.Client) *securitycenter.Client { + logger.Println("Try to get SCC Notification Service") + securitycenterClient, err := securitycenter.NewClient(ctx) + if err != nil { + logger.Fatalf("Failed to get SCC Notification Service with error [%s], terminate execution", err.Error()) + } + logger.Println("Got SCC Notification Service") + return securitycenterClient +} + func getFirewallPoliciesServiceOrTerminateExecution(ctx context.Context, client *http.Client) *compute.FirewallPoliciesService { logger.Println("Try to get Firewall Policies Service") computeService, err := compute.NewService(ctx, option.WithHTTPClient(client)) @@ -315,6 +379,7 @@ func invoke(ctx context.Context) { cloudResourceManagerService := getResourceManagerServiceOrTerminateExecution(ctx, client) folderService := getFolderServiceOrTerminateExecution(ctx, client) tagKeyService := getTagKeysServiceOrTerminateExecution(ctx, client) + sccService := getSCCNotificationServiceOrTerminateExecution(ctx, client) tagValuesService := getTagValuesServiceOrTerminateExecution(ctx, client) firewallPoliciesService := getFirewallPoliciesServiceOrTerminateExecution(ctx, client) endpointService := getServiceManagementServiceOrTerminateExecution(ctx, client) @@ -338,6 +403,48 @@ func invoke(ctx context.Context) { return tagKeyCreatedAt.Before(resourceCreationCutoff) } + projectDeleteRequestedFilter := func(projectID string) bool { + p, err := cloudResourceManagerService.Projects.Get(projectID).Context(ctx).Do() + if err != nil { + logger.Printf("Failed to get project [%s], error [%s]", projectID, err.Error()) + return false + } + if p.LifecycleState == "DELETE_REQUESTED" { + return true + } + return false + } + + removeSCCNotifications := func(organization string) { + logger.Printf("Try to remove SCC Notifications from organization [%s]", organization) + req := &securitycenterpb.ListNotificationConfigsRequest{ + Parent: fmt.Sprintf("organizations/%s", organization), + } + it := sccService.ListNotificationConfigs(ctx, req) + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + logger.Printf("failed to list SCC notifications, error [%s]", err.Error()) + break + } + projectID := strings.Split(resp.PubsubTopic, "/")[1] + if checkIfSCCNotificationNameIncluded(resp.Name, includedSCCNotiList) && projectDeleteRequestedFilter(projectID) { + delReq := &securitycenterpb.DeleteNotificationConfigRequest{ + Name: resp.Name, + } + err = sccService.DeleteNotificationConfig(ctx, delReq) + if err != nil { + logger.Printf("failed to delete SCC notification [%s], error [%s]", resp.Name, err.Error()) + } else { + logger.Printf("SCC notification [%s] deleted", resp.Name) + } + } + } + } + removeTagValues := func(tagKey string) { logger.Printf("Try to remove Tag Values from TagKey [%s]", tagKey) tagValuesList, err := tagValuesService.List().Parent(tagKey).Context(ctx).Do() @@ -517,6 +624,11 @@ func invoke(ctx context.Context) { if cleanUpTagKeys { removeTagKeys(organizationId) } + + // only delete Security Command Center notifications from deleted projects + if cleanUpSCCNotifi { + removeSCCNotifications(organizationId) + } } func CleanUpProjects(ctx context.Context, m PubSubMessage) error { diff --git a/modules/project_cleanup/main.tf b/modules/project_cleanup/main.tf index fb188f3..19ea3c0 100644 --- a/modules/project_cleanup/main.tf +++ b/modules/project_cleanup/main.tf @@ -33,7 +33,8 @@ resource "google_organization_iam_member" "main" { "roles/compute.orgSecurityResourceAdmin", "roles/compute.orgSecurityPolicyAdmin", "roles/resourcemanager.tagAdmin", - "roles/viewer" + "roles/viewer", + "roles/securitycenter.notificationConfigEditor" ]) member = "serviceAccount:${google_service_account.project_cleaner_function.email}" @@ -58,12 +59,14 @@ module "scheduled_project_cleaner" { function_timeout_s = var.function_timeout_s function_environment_variables = { - TARGET_ORGANIZATION_ID = var.organization_id - TARGET_FOLDER_ID = var.target_folder_id - TARGET_EXCLUDED_LABELS = jsonencode(var.target_excluded_labels) - TARGET_INCLUDED_LABELS = jsonencode(local.target_included_labels) - MAX_PROJECT_AGE_HOURS = var.max_project_age_in_hours - CLEAN_UP_TAG_KEYS = var.clean_up_org_level_tag_keys - TARGET_EXCLUDED_TAGKEYS = jsonencode(var.target_excluded_tagkeys) + TARGET_ORGANIZATION_ID = var.organization_id + TARGET_FOLDER_ID = var.target_folder_id + TARGET_EXCLUDED_LABELS = jsonencode(var.target_excluded_labels) + TARGET_INCLUDED_LABELS = jsonencode(local.target_included_labels) + MAX_PROJECT_AGE_HOURS = var.max_project_age_in_hours + CLEAN_UP_TAG_KEYS = var.clean_up_org_level_tag_keys + TARGET_EXCLUDED_TAGKEYS = jsonencode(var.target_excluded_tagkeys) + CLEAN_UP_SCC_NOTIFICATIONS = var.clean_up_org_level_scc_notifications + TARGET_INCLUDED_SCC_NOTIFICATIONS = jsonencode(var.target_included_scc_notifications) } } diff --git a/modules/project_cleanup/variables.tf b/modules/project_cleanup/variables.tf index 4b8efdd..3219883 100644 --- a/modules/project_cleanup/variables.tf +++ b/modules/project_cleanup/variables.tf @@ -77,6 +77,18 @@ variable "target_included_labels" { default = {} } +variable "clean_up_org_level_scc_notifications" { + type = bool + description = "Clean up organization level Security Command Center notifications." + default = false +} + +variable "target_included_scc_notifications" { + type = list(string) + description = "List of organization Security Command Center notifications names regex that will be deleted. Regex example: `.*/notificationConfigs/scc-notify-.*` " + default = [] +} + variable "clean_up_org_level_tag_keys" { type = bool description = "Clean up organization level Tag Keys."