Skip to content

Commit d11cae8

Browse files
authored
Merge pull request #17 from zachberger/master
Add Cloud Function that deletes VM instances created without CMEK.
2 parents 529eb56 + b2a4306 commit d11cae8

File tree

5 files changed

+283
-0
lines changed

5 files changed

+283
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Automatic Labelling from Localhost
2+
3+
This example demonstrates how to use the
4+
[root module][root-module] and the
5+
[event-project-log-entry submodule][event-project-log-entry-submodule]
6+
to configure a system
7+
which responds to Compute VM creation events by deleting any VM instances created with disks not encrypted using a customer-managed encryption key.
8+
9+
## Usage
10+
11+
To provision this example, populate `terraform.tfvars` with the [required variables](#inputs) and run the following commands within
12+
this directory:
13+
14+
- `terraform init` to initialize the directory
15+
- `terraform plan` to generate the execution plan
16+
- `terraform apply` to apply the execution plan
17+
- `terraform destroy` to destroy the infrastructure
18+
19+
[^]: (autogen_docs_start)
20+
21+
## Inputs
22+
23+
| Name | Description | Type | Default | Required |
24+
|------|-------------|:----:|:-----:|:-----:|
25+
| project\_id | The ID of the project to which resources will be applied. | string | n/a | yes |
26+
| region | The region in which resources will be applied. | string | n/a | yes |
27+
28+
[^]: (autogen_docs_end)
29+
30+
## Requirements
31+
32+
The following sections describe the requirements which must be met in
33+
order to invoke this module. The requirements of the
34+
[root module][root-module-requirements] and the
35+
[event-project-log-entry submodule][event-project-log-entry-submodule-requirements]
36+
must also be met.
37+
38+
### Software Dependencies
39+
40+
The following software dependencies must be installed on the system
41+
from which this module will be invoked:
42+
43+
- [Terraform][terraform-site] v0.11.Z
44+
45+
### IAM Roles
46+
47+
The Service Account which will be used to invoke this module must have
48+
the following IAM roles:
49+
50+
- Pub/Sub Admin: `roles/pubsub.admin`
51+
- Storage Admin: `roles/storage.admin`
52+
- Cloud Functions Developer: `roles/cloudfunctions.developer`
53+
- Logging Config Writer: `roles/logging.configWriter`
54+
55+
### APIs
56+
57+
The project against which this module will be invoked must have the
58+
following APIs enabled:
59+
60+
- Compute Engine API: `compute.googleapis.com`
61+
62+
[event-project-log-entry-submodule-requirements]: ../../modules/event-project-log-entry/README.md#requirements
63+
[event-project-log-entry-submodule]: ../../modules/event-project-log-entry
64+
[root-module-requirements]: ../../README.md#requirements
65+
[root-module]: ../..
66+
[terraform-site]: https://terraform.io/
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2019 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmek
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"errors"
23+
"fmt"
24+
"log"
25+
"time"
26+
27+
"cloud.google.com/go/pubsub"
28+
"google.golang.org/api/compute/v1"
29+
)
30+
31+
type creationEvent struct {
32+
Resource struct {
33+
Labels struct {
34+
InstanceID string `json:"instance_id"`
35+
ProjectID string `json:"project_id"`
36+
Zone string `json:"zone"`
37+
} `json:"labels"`
38+
} `json:"resource"`
39+
}
40+
41+
var computeService *compute.Service
42+
43+
func init() {
44+
var err error
45+
46+
computeService, err = compute.NewService(context.Background())
47+
if err != nil {
48+
log.Fatalf("Could not create compute service: %v\n", err)
49+
}
50+
}
51+
52+
func ReceiveMessage(ctx context.Context, msg *pubsub.Message) error {
53+
var event creationEvent
54+
json.Unmarshal([]byte(msg.Data), &event)
55+
labels := event.Resource.Labels
56+
fmt.Printf("Checking to see if VM instance has unencrypted disks %v\n", labels)
57+
encrypted, err := isEncrypted(labels.ProjectID, labels.Zone, labels.InstanceID)
58+
if err != nil {
59+
fmt.Printf("Error while trying to determine if VM is encrypted. Error: %v\n", err)
60+
return err
61+
}
62+
if !encrypted {
63+
fmt.Printf("Found VM instance with unencrypted disk %v\n", labels)
64+
fmt.Printf("Deleting VM instance %v\n", labels)
65+
err = deleteVM(labels.ProjectID, labels.Zone, labels.InstanceID)
66+
if err != nil {
67+
fmt.Printf("Error while trying to delete vm instance. Error: %v\n", err)
68+
return err
69+
}
70+
fmt.Printf("Successfully deleted VM instance %v\n", labels)
71+
} else {
72+
fmt.Printf("No unencrypted disks found on VM instance %v\n", labels)
73+
}
74+
return nil
75+
}
76+
77+
func deleteVM(projectID string, zoneID string, instanceID string) error {
78+
operation, err := computeService.Instances.Delete(projectID, zoneID, instanceID).Do()
79+
if err != nil {
80+
return err
81+
}
82+
err = waitForOperation(computeService, projectID, zoneID, operation)
83+
return err
84+
}
85+
86+
func waitForOperation(computeService *compute.Service, projectID string, zoneID string, operation *compute.Operation) error {
87+
for {
88+
operation, err := computeService.ZoneOperations.Get(projectID, zoneID, operation.Name).Do()
89+
if err != nil {
90+
return err
91+
}
92+
93+
if operation.Status != "DONE" {
94+
time.Sleep(2 * time.Second)
95+
} else {
96+
if operation.Error != nil {
97+
fmt.Printf("%v", operation.Error)
98+
return errors.New("Operation error")
99+
}
100+
return nil
101+
}
102+
}
103+
return nil
104+
}
105+
106+
func isEncrypted(projectID string, zoneID string, instanceID string) (bool, error) {
107+
ctx := context.Background()
108+
computeService, err := compute.NewService(ctx)
109+
if err != nil {
110+
return true, err
111+
}
112+
113+
instance, err := computeService.Instances.Get(projectID, zoneID, instanceID).Do()
114+
if err != nil {
115+
return true, err
116+
}
117+
118+
for _, disk := range instance.Disks {
119+
if disk.DiskEncryptionKey == nil {
120+
return false, nil
121+
}
122+
}
123+
124+
return true, nil
125+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/terraform-google-modules/terraform-google-event-function
2+
3+
go 1.11
4+
5+
require (
6+
cloud.google.com/go v0.38.0
7+
google.golang.org/api v0.4.0
8+
)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
terraform {
18+
required_version = "~> 0.11.0"
19+
}
20+
21+
provider "archive" {
22+
version = "~> 1.0"
23+
}
24+
25+
provider "google" {
26+
version = "~> 2.1"
27+
}
28+
29+
provider "random" {
30+
version = "~> 2.0"
31+
}
32+
33+
resource "random_pet" "main" {
34+
separator = "-"
35+
}
36+
37+
module "event_project_log_entry" {
38+
source = "../../modules/event-project-log-entry"
39+
40+
filter = "resource.type=\"gce_instance\" jsonPayload.event_subtype=\"compute.instances.insert\" jsonPayload.event_type=\"GCE_OPERATION_DONE\""
41+
name = "${random_pet.main.id}"
42+
project_id = "${var.project_id}"
43+
}
44+
45+
module "localhost_function" {
46+
source = "../.."
47+
48+
description = "Deletes VMs created with disks not encrypted with CMEK"
49+
entry_point = "ReceiveMessage"
50+
runtime = "go111"
51+
timeout_s = "240"
52+
53+
event_trigger = "${module.event_project_log_entry.function_event_trigger}"
54+
name = "${random_pet.main.id}"
55+
project_id = "${var.project_id}"
56+
region = "${var.region}"
57+
source_directory = "${path.module}/function_source"
58+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
variable "project_id" {
18+
type = "string"
19+
description = "The ID of the project to which resources will be applied."
20+
}
21+
22+
variable "region" {
23+
type = "string"
24+
description = "The region in which resources will be applied."
25+
}
26+

0 commit comments

Comments
 (0)