Skip to content

Security_posture_mode should have a default value to avoid "Error 400: Must specify a field to update" on apply #2583

@tybe-sacha

Description

@tybe-sacha

TL;DR

When security_posture_mode is not provided, the module generates an empty
security_posture_config {} block. The terraform plan succeeds but the apply
fails with:

Error: googleapi: Error 400: Must specify a field to update

This is not actionable from the error message alone. Setting a default value
(e.g. "DISABLED" or "BASIC") on the security_posture_mode variable would
either prevent the empty block generation or make the behavior explicit.

Steps to reproduce:

  1. Call the module without setting security_posture_mode
  2. terraform plan → no error
  3. terraform apply → Error 400

Expected behavior

Either a plan-time validation error or a sensible default

Observed behavior

Silent plan + cryptic apply error causing cluster disruption

Terraform Configuration

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google//modules/private-cluster"
  version = "~> 44.0"

  project_id                 = var.project_id
  name                       = var.name
  region                     = var.region
  regional                   = var.regional
  zones                      = var.zones
  network                    = var.network
  subnetwork                 = var.subnetwork
  ip_range_pods              = var.ip_range_pods
  ip_range_services          = var.ip_range_services
  master_ipv4_cidr_block     = var.master_ipv4_cidr_block
  security_posture_mode      = var.security_posture_mode
  kubernetes_version         = var.kubernetes_version
  cluster_resource_labels    = local.labels
  master_authorized_networks = var.master_authorized_networks
  deletion_protection        = var.deletion_protection
  http_load_balancing        = var.http_load_balancing
  horizontal_pod_autoscaling = var.horizontal_pod_autoscaling
  service_account            = var.service_account
  node_metadata              = var.node_metadata
  node_pools_labels          = local.node_pools_labels
  node_pools_oauth_scopes    = var.node_pools_oauth_scopes
  timeouts = {
    create = lookup(var.timeouts, "create", "45m")
    update = lookup(var.timeouts, "update", "100m")
    delete = lookup(var.timeouts, "delete", "45m")
  }
  create_service_account              = false
  enable_private_nodes                = true
  network_policy                      = true
  master_global_access_enabled        = false
  enable_vertical_pod_autoscaling     = true
  enable_intranode_visibility         = true
  security_posture_vulnerability_mode = "VULNERABILITY_MODE_UNSPECIFIED"
  release_channel                     = "UNSPECIFIED"
  maintenance_start_time              = "2020-04-18T00:00:00Z"
  maintenance_end_time                = "2020-04-18T04:00:00Z"
  maintenance_recurrence              = "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU"

  node_pools = [
    for np in values(var.node_pools) : merge(
      np,
      {
        pod_range = var.ip_range_pods
      }
    )
  ]

  node_pools_metadata = {
    default_values = {
      cluster_name = false
      node_pool    = false
    }
  }
}

dev.tfvars : 
clusters = {
  "dev-gke-cluster" = {
    region                 = "europe-west3"
    zones                  = ["europe-west3-a"]
    regional               = false
    network                = "dev"
    subnetwork             = "dev"
    ip_range_pods          = "dev-pods"
    ip_range_services      = "dev-services"
    kubernetes_version     = "1.33.8-gke.1026000"
    deletion_protection    = true
    node_metadata          = "GCE_METADATA"
    security_posture_mode  = "DISABLED"

    node_pools = {
      "spot-migrated-pool" = {
        name                 = "spot-migrated-pool"
        machine_type         = "n1-standard-4"
        node_locations       = "europe-west3-a"
        disk_size_gb         = 50
        disk_type            = "pd-balanced"
        spot                 = true
        location_policy      = "BALANCED"
        max_count            = 20
        min_count            = 4
        auto_upgrade         = false
        enable_private_nodes = true

        cpu_cfs_quota                          = false
        insecure_kubelet_readonly_port_enabled = "TRUE"
        max_parallel_image_pulls               = 3
        cpu_manager_policy                     = ""
      }
    }

    node_pools_oauth_scopes = {
      all = [
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/userinfo.email"
      ]
    }
  }
}

terraform plan : 
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place
Terraform will perform the following actions:
  # module.gke["dev-gke-cluster"].module.gke.google_container_cluster.primary will be updated in-place
  ~ resource "google_container_cluster" "primary" {
      + min_master_version                       = "1.33.8-gke.1026000"
        name                                     = "dev-gke-cluster"
      + remove_default_node_pool                 = false
...
      + security_posture_config {}
        # (26 unchanged blocks hidden)
}

Terraform Version

Terraform v1.14.3

Terraform Provider Versions

- Installed hashicorp/google v7.30.0 (signed by HashiCorp)
- Installed hashicorp/random v3.8.1 (signed by HashiCorp)
- Installed hashicorp/kubernetes v3.1.0 (signed by HashiCorp)

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions