From 241eacd9d3eab62ba261d9056e34c225b9401124 Mon Sep 17 00:00:00 2001
From: megan07 <mbang@hashicorp.com>
Date: Fri, 11 Oct 2019 14:39:21 -0500
Subject: [PATCH] add node config shielded instance config (#2381)

Merged PR #2381.
---
 build/terraform                               |   2 +-
 build/terraform-beta                          |   2 +-
 .../resource_container_cluster.go.erb         |  33 ++++++
 .../resource_container_cluster_test.go.erb    | 109 ++++++++++++++++++
 .../resource_container_node_pool_test.go.erb  |  76 ++++++++++++
 .../terraform/utils/node_config.go.erb        |  42 +++++++
 .../docs/r/container_cluster.html.markdown    |  14 +++
 7 files changed, 276 insertions(+), 2 deletions(-)

diff --git a/build/terraform b/build/terraform
index f431592a7ddc..114d7fed816d 160000
--- a/build/terraform
+++ b/build/terraform
@@ -1 +1 @@
-Subproject commit f431592a7ddc26f459edd197a60ace3cedcc954c
+Subproject commit 114d7fed816d36c2f465df122c7dd95532fab8ac
diff --git a/build/terraform-beta b/build/terraform-beta
index e18cfe991b82..407f2b0688e4 160000
--- a/build/terraform-beta
+++ b/build/terraform-beta
@@ -1 +1 @@
-Subproject commit e18cfe991b82fce06b2efa9e241cc76b203bc7a0
+Subproject commit 407f2b0688e4e68c211f5f8ab22a12015a3d36ca
diff --git a/third_party/terraform/resources/resource_container_cluster.go.erb b/third_party/terraform/resources/resource_container_cluster.go.erb
index 6f59edef0cce..9d10ae89ca65 100644
--- a/third_party/terraform/resources/resource_container_cluster.go.erb
+++ b/third_party/terraform/resources/resource_container_cluster.go.erb
@@ -344,6 +344,12 @@ func resourceContainerCluster() *schema.Resource {
 			},
 
 <% unless version == 'ga' -%>
+			"enable_shielded_nodes": {
+				Type:     schema.TypeBool,
+				Optional: true,
+				Default:  false,
+			},
+
 			"authenticator_groups_config": {
 				Type:     schema.TypeList,
 				Optional: true,
@@ -947,6 +953,10 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er
 		IpAllocationPolicy:      expandIPAllocationPolicy(d.Get("ip_allocation_policy")),
 		PodSecurityPolicyConfig: expandPodSecurityPolicyConfig(d.Get("pod_security_policy_config")),
 <% unless version == 'ga' -%>
+		ShieldedNodes: &containerBeta.ShieldedNodes{
+			Enabled:         d.Get("enable_shielded_nodes").(bool),
+			ForceSendFields: []string{"Enabled"},
+		},
 		EnableTpu:               d.Get("enable_tpu").(bool),
 		BinaryAuthorization: &containerBeta.BinaryAuthorization{
 			Enabled:         d.Get("enable_binary_authorization").(bool),
@@ -1200,6 +1210,7 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro
 	d.Set("network", cluster.NetworkConfig.Network)
 	d.Set("subnetwork", cluster.NetworkConfig.Subnetwork)
 <% unless version == 'ga' -%>
+	d.Set("enable_shielded_nodes", cluster.ShieldedNodes.Enabled)
 	d.Set("enable_binary_authorization", cluster.BinaryAuthorization != nil && cluster.BinaryAuthorization.Enabled)
 	d.Set("enable_tpu", cluster.EnableTpu)
 	d.Set("tpu_ipv4_cidr_block", cluster.TpuIpv4CidrBlock)
@@ -1353,6 +1364,28 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er
 		}
 	}
 <% unless version == 'ga' -%>
+	if d.HasChange("enable_shielded_nodes") {
+		enabled := d.Get("enable_shielded_nodes").(bool)
+		req := &containerBeta.UpdateClusterRequest{
+			Update: &containerBeta.ClusterUpdate{
+				DesiredShieldedNodes: &containerBeta.ShieldedNodes{
+					Enabled:         enabled,
+					ForceSendFields: []string{"Enabled"},
+				},
+			},
+		}
+
+		updateF := updateFunc(req, "updating GKE shielded nodes")
+		// Call update serially.
+		if err := lockedCall(lockKey, updateF); err != nil {
+			return err
+		}
+
+		log.Printf("[INFO] GKE cluster %s's shielded nodes has been updated to %v", d.Id(), enabled)
+
+		d.SetPartial("enable_shielded_nodes")
+	}
+
 	if d.HasChange("enable_binary_authorization") {
 		enabled := d.Get("enable_binary_authorization").(bool)
 		req := &containerBeta.UpdateClusterRequest{
diff --git a/third_party/terraform/tests/resource_container_cluster_test.go.erb b/third_party/terraform/tests/resource_container_cluster_test.go.erb
index 7ee435e00323..7e7538e8748c 100644
--- a/third_party/terraform/tests/resource_container_cluster_test.go.erb
+++ b/third_party/terraform/tests/resource_container_cluster_test.go.erb
@@ -754,6 +754,29 @@ func TestAccContainerCluster_withNodeConfigTaints(t *testing.T) {
 }
 <% end -%>
 
+func TestAccContainerCluster_withNodeConfigShieldedInstanceConfig(t *testing.T) {
+	t.Parallel()
+
+	clusterName := fmt.Sprintf("cluster-test-%s", acctest.RandString(10))
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:	  func() { testAccPreCheck(t) },
+		Providers:	  testAccProviders,
+		CheckDestroy: testAccCheckContainerClusterDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccContainerCluster_withNodeConfigShieldedInstanceConfig(clusterName),
+			},
+			{
+				ResourceName:        "google_container_cluster.with_node_config",
+				ImportStateIdPrefix: "us-central1-f/",
+				ImportState:         true,
+				ImportStateVerify:   true,
+			},
+		},
+	})
+}
+
 <% unless version.nil? || version == 'ga' -%>
 func TestAccContainerCluster_withWorkloadMetadataConfig(t *testing.T) {
 	t.Parallel()
@@ -1403,6 +1426,38 @@ func TestAccContainerCluster_withBinaryAuthorization(t *testing.T) {
 	})
 }
 
+func TestAccContainerCluster_withShieldedNodes(t *testing.T) {
+	t.Parallel()
+
+	clusterName := fmt.Sprintf("cluster-test-%s", acctest.RandString(10))
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:	  func() { testAccPreCheck(t) },
+		Providers:	  testAccProviders,
+		CheckDestroy: testAccCheckContainerClusterDestroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccContainerCluster_withShieldedNodes(clusterName, true),
+			},
+			{
+				ResourceName:		 "google_container_cluster.with_shielded_nodes",
+				ImportStateIdPrefix: "us-central1-a/",
+				ImportState:		 true,
+				ImportStateVerify:	 true,
+			},
+			{
+				Config: testAccContainerCluster_withShieldedNodes(clusterName, false),
+			},
+			{
+				ResourceName:		 "google_container_cluster.with_shielded_nodes",
+				ImportStateIdPrefix: "us-central1-a/",
+				ImportState:		 true,
+				ImportStateVerify:	 true,
+			},
+		},
+	})
+}
+
 func TestAccContainerCluster_withFlexiblePodCIDR(t *testing.T) {
 	t.Parallel()
 
@@ -2316,6 +2371,48 @@ resource "google_container_cluster" "with_node_config" {
 }
 <% end -%>
 
+
+func testAccContainerCluster_withNodeConfigShieldedInstanceConfig(clusterName string) string {
+	return fmt.Sprintf(`
+resource "google_container_cluster" "with_node_config" {
+	name = "%s"
+	zone = "us-central1-f"
+	initial_node_count = 1
+
+	node_config {
+		machine_type = "n1-standard-1"
+		disk_size_gb = 15
+		disk_type = "pd-ssd"
+		local_ssd_count = 1
+		oauth_scopes = [
+			"https://www.googleapis.com/auth/monitoring",
+			"https://www.googleapis.com/auth/compute",
+			"https://www.googleapis.com/auth/devstorage.read_only",
+			"https://www.googleapis.com/auth/logging.write"
+		]
+		service_account = "default"
+		metadata = {
+			foo = "bar"
+			disable-legacy-endpoints = "true"
+		}
+		labels = {
+			foo = "bar"
+		}
+		tags = ["foo", "bar"]
+		preemptible = true
+		min_cpu_platform = "Intel Broadwell"
+
+		// Updatable fields
+		image_type = "COS"
+
+		shielded_instance_config {
+			enable_secure_boot          = true
+			enable_integrity_monitoring = true
+		}
+	}
+}`, clusterName)
+}
+
 <% unless version.nil? || version == 'ga' -%>
 func testAccContainerCluster_withWorkloadMetadataConfig() string {
 	return fmt.Sprintf(`
@@ -3124,6 +3221,18 @@ resource "google_container_cluster" "with_binary_authorization" {
 `, clusterName, enabled)
 }
 
+func testAccContainerCluster_withShieldedNodes(clusterName string, enabled bool) string {
+	return fmt.Sprintf(`
+resource "google_container_cluster" "with_shielded_nodes" {
+	name = "%s"
+	zone = "us-central1-a"
+	initial_node_count = 1
+
+	enable_shielded_nodes = %v
+}
+`, clusterName, enabled)
+}
+
 func testAccContainerCluster_withFlexiblePodCIDR(cluster string) string {
 	return fmt.Sprintf(`
 resource "google_compute_network" "container_network" {
diff --git a/third_party/terraform/tests/resource_container_node_pool_test.go.erb b/third_party/terraform/tests/resource_container_node_pool_test.go.erb
index 20e000c5d3fe..802e641a53ad 100644
--- a/third_party/terraform/tests/resource_container_node_pool_test.go.erb
+++ b/third_party/terraform/tests/resource_container_node_pool_test.go.erb
@@ -649,6 +649,39 @@ func TestAccContainerNodePool_EmptyGuestAccelerator(t *testing.T) {
 	})
 }
 
+func TestAccContainerNodePool_shieldedInstanceConfig(t *testing.T) {
+	t.Parallel()
+
+	cluster := fmt.Sprintf("tf-nodepool-test-%s", acctest.RandString(10))
+	np := fmt.Sprintf("tf-nodepool-test-%s", acctest.RandString(10))
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:     func() { testAccPreCheck(t) },
+		Providers:    testAccProviders,
+		CheckDestroy: testAccCheckContainerNodePoolDestroy,
+		Steps: []resource.TestStep{
+			resource.TestStep{
+				Config: testAccContainerNodePool_shieldedInstanceConfig(cluster, np),
+			},
+			resource.TestStep{
+				ResourceName:            "google_container_node_pool.np",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"max_pods_per_node"},
+			},
+			resource.TestStep{
+				Config: testAccContainerNodePool_updateShieldedInstanceConfig(cluster, np),
+			},
+			resource.TestStep{
+				ResourceName:            "google_container_node_pool.np",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"max_pods_per_node"},
+			},
+		},
+	})
+}
+
 func testAccCheckContainerNodePoolDestroy(s *terraform.State) error {
 	config := testAccProvider.Meta().(*Config)
 
@@ -1420,3 +1453,46 @@ resource "google_container_node_pool" "np" {
 	}
 }`, cluster, np)
 }
+
+func testAccContainerNodePool_shieldedInstanceConfig(cluster, np string) string {
+	return fmt.Sprintf(`
+resource "google_container_cluster" "cluster" {
+	name               = "%s"
+	location           = "us-central1-a"
+	initial_node_count = 1
+}
+
+resource "google_container_node_pool" "np" {
+	name               = "%s"
+	location           = "us-central1-a"
+	cluster            = "${google_container_cluster.cluster.name}"
+	initial_node_count = 2
+	node_config {
+		shielded_instance_config {
+			enable_integrity_monitoring = true
+		}
+	}
+}`, cluster, np)
+}
+
+func testAccContainerNodePool_updateShieldedInstanceConfig(cluster, np string) string {
+	return fmt.Sprintf(`
+resource "google_container_cluster" "cluster" {
+	name               = "%s"
+	location           = "us-central1-a"
+	initial_node_count = 1
+}
+
+resource "google_container_node_pool" "np" {
+	name               = "%s"
+	location           = "us-central1-a"
+	cluster            = "${google_container_cluster.cluster.name}"
+	initial_node_count = 2
+	node_config {
+		shielded_instance_config {
+			enable_secure_boot          = true
+			enable_integrity_monitoring = true
+		}
+	}
+}`, cluster, np)
+}
diff --git a/third_party/terraform/utils/node_config.go.erb b/third_party/terraform/utils/node_config.go.erb
index 4116f60d3b53..10cead014348 100644
--- a/third_party/terraform/utils/node_config.go.erb
+++ b/third_party/terraform/utils/node_config.go.erb
@@ -148,6 +148,27 @@ var schemaNodeConfig = &schema.Schema{
 				Elem:     &schema.Schema{Type: schema.TypeString},
 			},
 
+			"shielded_instance_config": &schema.Schema{
+				Type:     schema.TypeList,
+				Optional: true,
+				Computed: true,
+				MaxItems: 1,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"enable_secure_boot": {
+							Type:     schema.TypeBool,
+							Optional: true,
+							Default:  false,
+						},
+						"enable_integrity_monitoring": {
+							Type:     schema.TypeBool,
+							Optional: true,
+							Default:  true,
+						},
+					},
+				},
+			},
+
 			"taint": {
 <% if version.nil? || version == 'ga' -%>
 				Removed:       "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.",
@@ -308,6 +329,15 @@ func expandNodeConfig(v interface{}) *containerBeta.NodeConfig {
 		}
 		nc.Tags = tags
 	}
+
+	if v, ok := nodeConfig["shielded_instance_config"]; ok && len(v.([]interface{})) > 0 {
+		conf := v.([]interface{})[0].(map[string]interface{})
+		nc.ShieldedInstanceConfig = &containerBeta.ShieldedInstanceConfig{
+			EnableSecureBoot:          conf["enable_secure_boot"].(bool),
+			EnableIntegrityMonitoring: conf["enable_integrity_monitoring"].(bool),
+		}
+	}
+
 	// Preemptible Is Optional+Default, so it always has a value
 	nc.Preemptible = nodeConfig["preemptible"].(bool)
 
@@ -371,6 +401,7 @@ func flattenNodeConfig(c *containerBeta.NodeConfig) []map[string]interface{} {
 		"tags":                     c.Tags,
 		"preemptible":              c.Preemptible,
 		"min_cpu_platform":         c.MinCpuPlatform,
+		"shielded_instance_config": flattenShieldedInstanceConfig(c.ShieldedInstanceConfig),
 <% unless version == 'ga' -%>
 		"taint":                    flattenTaints(c.Taints),
 		"workload_metadata_config": flattenWorkloadMetadataConfig(c.WorkloadMetadataConfig),
@@ -396,6 +427,17 @@ func flattenContainerGuestAccelerators(c []*containerBeta.AcceleratorConfig) []m
 	return result
 }
 
+func flattenShieldedInstanceConfig(c *containerBeta.ShieldedInstanceConfig) []map[string]interface{} {
+	result := []map[string]interface{}{}
+	if c != nil {
+		result = append(result, map[string]interface{}{
+			"enable_secure_boot":          c.EnableSecureBoot,
+			"enable_integrity_monitoring": c.EnableIntegrityMonitoring,
+		})
+	}
+	return result
+}
+
 <% unless version.nil? || version == 'ga' -%>
 func flattenTaints(c []*containerBeta.NodeTaint) []map[string]interface{} {
 	result := []map[string]interface{}{}
diff --git a/third_party/terraform/website/docs/r/container_cluster.html.markdown b/third_party/terraform/website/docs/r/container_cluster.html.markdown
index 57c49b585a42..708f7715300a 100644
--- a/third_party/terraform/website/docs/r/container_cluster.html.markdown
+++ b/third_party/terraform/website/docs/r/container_cluster.html.markdown
@@ -191,6 +191,8 @@ for more details. Structure is documented below.
     will have statically granted permissions beyond those provided by the RBAC configuration or IAM.
     Defaults to `false`
 
+* `enable_shielded_nodes` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) Enable Shielded Nodes features on all nodes in this cluster.  Defaults to `false`.
+
 * `initial_node_count` - (Optional) The number of nodes to create in this
 cluster's default node pool. In regional or multi-zonal clusters, this is the
 number of nodes per zone. Must be set if `node_pool` is not set. If you're using
@@ -557,6 +559,8 @@ The `node_config` block supports:
 
      -> Projects that enable the [Cloud Compute Engine API](https://cloud.google.com/compute/) with Terraform may need these roles added manually to the service account. Projects that enable the API in the Cloud Console should have them added automatically.
 
+* `shielded_instance_config` - (Optional) Shielded Instance options. Structure is documented below.
+
 * `tags` - (Optional) The list of instance tags applied to all nodes. Tags are used to identify
     valid sources or targets for network firewalls.
 
@@ -629,6 +633,16 @@ resource_usage_export_config {
 }
 ```
 
+The `shielded_instance_config` block supports:
+
+* `enable_secure_boot` (Optional) - Defines if the instance has Secure Boot enabled.
+
+Secure Boot helps ensure that the system only runs authentic software by verifying the digital signature of all boot components, and halting the boot process if signature verification fails.  Defaults to `false`.
+
+* `enable_integrity_monitoring` (Optional) - Defines if the instance has integrity monitoring enabled.
+
+Enables monitoring and attestation of the boot integrity of the instance. The attestation is performed against the integrity policy baseline. This baseline is initially derived from the implicitly trusted boot image when the instance is created.  Defaults to `true`.
+
 The `taint` block supports:
 
 * `key` (Required) Key for taint.