diff --git a/google-beta/node_config.go b/google-beta/node_config.go index 4e90e5cac2..4ca0ea9f0c 100644 --- a/google-beta/node_config.go +++ b/google-beta/node_config.go @@ -147,6 +147,27 @@ var schemaNodeConfig = &schema.Schema{ Elem: &schema.Schema{Type: schema.TypeString}, }, + "shielded_instance_config": { + 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": { Type: schema.TypeList, Optional: true, @@ -298,6 +319,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) @@ -357,6 +387,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), "taint": flattenTaints(c.Taints), "workload_metadata_config": flattenWorkloadMetadataConfig(c.WorkloadMetadataConfig), "sandbox_config": flattenSandboxConfig(c.SandboxConfig), @@ -380,6 +411,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 +} + func flattenTaints(c []*containerBeta.NodeTaint) []map[string]interface{} { result := []map[string]interface{}{} for _, taint := range c { diff --git a/google-beta/resource_container_cluster.go b/google-beta/resource_container_cluster.go index d2a8f44c5a..a2e3f14bd8 100644 --- a/google-beta/resource_container_cluster.go +++ b/google-beta/resource_container_cluster.go @@ -326,6 +326,12 @@ func resourceContainerCluster() *schema.Resource { Default: false, }, + "enable_shielded_nodes": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "authenticator_groups_config": { Type: schema.TypeList, Optional: true, @@ -916,7 +922,11 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er EnableKubernetesAlpha: d.Get("enable_kubernetes_alpha").(bool), IpAllocationPolicy: expandIPAllocationPolicy(d.Get("ip_allocation_policy")), PodSecurityPolicyConfig: expandPodSecurityPolicyConfig(d.Get("pod_security_policy_config")), - EnableTpu: d.Get("enable_tpu").(bool), + 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), ForceSendFields: []string{"Enabled"}, @@ -1163,6 +1173,7 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro d.Set("monitoring_service", cluster.MonitoringService) d.Set("network", cluster.NetworkConfig.Network) d.Set("subnetwork", cluster.NetworkConfig.Subnetwork) + 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) @@ -1306,6 +1317,28 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er d.SetPartial("addons_config") } } + 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/google-beta/resource_container_cluster_test.go b/google-beta/resource_container_cluster_test.go index 8f61d7ba67..ef95b2eb31 100644 --- a/google-beta/resource_container_cluster_test.go +++ b/google-beta/resource_container_cluster_test.go @@ -744,6 +744,29 @@ func TestAccContainerCluster_withNodeConfigTaints(t *testing.T) { }) } +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, + }, + }, + }) +} + func TestAccContainerCluster_withWorkloadMetadataConfig(t *testing.T) { t.Parallel() @@ -1389,6 +1412,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() @@ -2283,6 +2338,47 @@ resource "google_container_cluster" "with_node_config" { }`, acctest.RandString(10)) } +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) +} + func testAccContainerCluster_withWorkloadMetadataConfig() string { return fmt.Sprintf(` data "google_container_engine_versions" "central1a" { @@ -3084,6 +3180,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/google-beta/resource_container_node_pool_test.go b/google-beta/resource_container_node_pool_test.go index 30eb7eed0b..a276110983 100644 --- a/google-beta/resource_container_node_pool_test.go +++ b/google-beta/resource_container_node_pool_test.go @@ -642,6 +642,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{ + { + Config: testAccContainerNodePool_shieldedInstanceConfig(cluster, np), + }, + { + ResourceName: "google_container_node_pool.np", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"max_pods_per_node"}, + }, + { + Config: testAccContainerNodePool_updateShieldedInstanceConfig(cluster, np), + }, + { + 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) @@ -1406,3 +1439,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/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index 57c49b585a..708f771530 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/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.