diff --git a/examples/delete-vms-without-cmek/README.md b/examples/delete-vms-without-cmek/README.md new file mode 100644 index 0000000..a91d717 --- /dev/null +++ b/examples/delete-vms-without-cmek/README.md @@ -0,0 +1,66 @@ +# Automatic Labelling from Localhost + +This example demonstrates how to use the +[root module][root-module] and the +[event-project-log-entry submodule][event-project-log-entry-submodule] +to configure a system +which responds to Compute VM creation events by deleting any VM instances created with disks not encrypted using a customer-managed encryption key. + +## Usage + +To provision this example, populate `terraform.tfvars` with the [required variables](#inputs) and run the following commands within +this directory: + +- `terraform init` to initialize the directory +- `terraform plan` to generate the execution plan +- `terraform apply` to apply the execution plan +- `terraform destroy` to destroy the infrastructure + +[^]: (autogen_docs_start) + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| project\_id | The ID of the project to which resources will be applied. | string | n/a | yes | +| region | The region in which resources will be applied. | string | n/a | yes | + +[^]: (autogen_docs_end) + +## Requirements + +The following sections describe the requirements which must be met in +order to invoke this module. The requirements of the +[root module][root-module-requirements] and the +[event-project-log-entry submodule][event-project-log-entry-submodule-requirements] +must also be met. + +### Software Dependencies + +The following software dependencies must be installed on the system +from which this module will be invoked: + +- [Terraform][terraform-site] v0.11.Z + +### IAM Roles + +The Service Account which will be used to invoke this module must have +the following IAM roles: + +- Pub/Sub Admin: `roles/pubsub.admin` +- Storage Admin: `roles/storage.admin` +- Cloud Functions Developer: `roles/cloudfunctions.developer` +- Logging Config Writer: `roles/logging.configWriter` + +### APIs + +The project against which this module will be invoked must have the +following APIs enabled: + +- Compute Engine API: `compute.googleapis.com` + +[event-project-log-entry-submodule-requirements]: ../../modules/event-project-log-entry/README.md#requirements +[event-project-log-entry-submodule]: ../../modules/event-project-log-entry +[root-module-requirements]: ../../README.md#requirements +[root-module]: ../.. +[terraform-site]: https://terraform.io/ diff --git a/examples/delete-vms-without-cmek/function_source/cmek.go b/examples/delete-vms-without-cmek/function_source/cmek.go new file mode 100644 index 0000000..4afd936 --- /dev/null +++ b/examples/delete-vms-without-cmek/function_source/cmek.go @@ -0,0 +1,125 @@ +/* +Copyright 2019 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmek + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "time" + + "cloud.google.com/go/pubsub" + "google.golang.org/api/compute/v1" +) + +type creationEvent struct { + Resource struct { + Labels struct { + InstanceID string `json:"instance_id"` + ProjectID string `json:"project_id"` + Zone string `json:"zone"` + } `json:"labels"` + } `json:"resource"` +} + +var computeService *compute.Service + +func init() { + var err error + + computeService, err = compute.NewService(context.Background()) + if err != nil { + log.Fatalf("Could not create compute service: %v\n", err) + } +} + +func ReceiveMessage(ctx context.Context, msg *pubsub.Message) error { + var event creationEvent + json.Unmarshal([]byte(msg.Data), &event) + labels := event.Resource.Labels + fmt.Printf("Checking to see if VM instance has unencrypted disks %v\n", labels) + encrypted, err := isEncrypted(labels.ProjectID, labels.Zone, labels.InstanceID) + if err != nil { + fmt.Printf("Error while trying to determine if VM is encrypted. Error: %v\n", err) + return err + } + if !encrypted { + fmt.Printf("Found VM instance with unencrypted disk %v\n", labels) + fmt.Printf("Deleting VM instance %v\n", labels) + err = deleteVM(labels.ProjectID, labels.Zone, labels.InstanceID) + if err != nil { + fmt.Printf("Error while trying to delete vm instance. Error: %v\n", err) + return err + } + fmt.Printf("Successfully deleted VM instance %v\n", labels) + } else { + fmt.Printf("No unencrypted disks found on VM instance %v\n", labels) + } + return nil +} + +func deleteVM(projectID string, zoneID string, instanceID string) error { + operation, err := computeService.Instances.Delete(projectID, zoneID, instanceID).Do() + if err != nil { + return err + } + err = waitForOperation(computeService, projectID, zoneID, operation) + return err +} + +func waitForOperation(computeService *compute.Service, projectID string, zoneID string, operation *compute.Operation) error { + for { + operation, err := computeService.ZoneOperations.Get(projectID, zoneID, operation.Name).Do() + if err != nil { + return err + } + + if operation.Status != "DONE" { + time.Sleep(2 * time.Second) + } else { + if operation.Error != nil { + fmt.Printf("%v", operation.Error) + return errors.New("Operation error") + } + return nil + } + } + return nil +} + +func isEncrypted(projectID string, zoneID string, instanceID string) (bool, error) { + ctx := context.Background() + computeService, err := compute.NewService(ctx) + if err != nil { + return true, err + } + + instance, err := computeService.Instances.Get(projectID, zoneID, instanceID).Do() + if err != nil { + return true, err + } + + for _, disk := range instance.Disks { + if disk.DiskEncryptionKey == nil { + return false, nil + } + } + + return true, nil +} diff --git a/examples/delete-vms-without-cmek/function_source/go.mod b/examples/delete-vms-without-cmek/function_source/go.mod new file mode 100644 index 0000000..a8a3726 --- /dev/null +++ b/examples/delete-vms-without-cmek/function_source/go.mod @@ -0,0 +1,8 @@ +module github.com/terraform-google-modules/terraform-google-event-function + +go 1.11 + +require ( + cloud.google.com/go v0.38.0 + google.golang.org/api v0.4.0 +) diff --git a/examples/delete-vms-without-cmek/main.tf b/examples/delete-vms-without-cmek/main.tf new file mode 100644 index 0000000..32138f1 --- /dev/null +++ b/examples/delete-vms-without-cmek/main.tf @@ -0,0 +1,58 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.11.0" +} + +provider "archive" { + version = "~> 1.0" +} + +provider "google" { + version = "~> 2.1" +} + +provider "random" { + version = "~> 2.0" +} + +resource "random_pet" "main" { + separator = "-" +} + +module "event_project_log_entry" { + source = "../../modules/event-project-log-entry" + + filter = "resource.type=\"gce_instance\" jsonPayload.event_subtype=\"compute.instances.insert\" jsonPayload.event_type=\"GCE_OPERATION_DONE\"" + name = "${random_pet.main.id}" + project_id = "${var.project_id}" +} + +module "localhost_function" { + source = "../.." + + description = "Deletes VMs created with disks not encrypted with CMEK" + entry_point = "ReceiveMessage" + runtime = "go111" + timeout_s = "240" + + event_trigger = "${module.event_project_log_entry.function_event_trigger}" + name = "${random_pet.main.id}" + project_id = "${var.project_id}" + region = "${var.region}" + source_directory = "${path.module}/function_source" +} diff --git a/examples/delete-vms-without-cmek/variables.tf b/examples/delete-vms-without-cmek/variables.tf new file mode 100644 index 0000000..49ee8c3 --- /dev/null +++ b/examples/delete-vms-without-cmek/variables.tf @@ -0,0 +1,26 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + type = "string" + description = "The ID of the project to which resources will be applied." +} + +variable "region" { + type = "string" + description = "The region in which resources will be applied." +} +