Skip to content

When recreating bastion instance, IAP tunnel permissions require second apply #225

@mwarkentin

Description

@mwarkentin

TL;DR

When the bastion-host module is upgraded, resulting in a replacement of the GCE VM, after the first apply, the new instance is missing the IAP tunnel permissions. Running a second apply attaches the proper permissions to the host.

Expected behavior

I expect that a single apply will recreate the bastion host and attach all required permissions including IAP tunnel permissions.

Observed behavior

I need to apply twice in order to enable IAP tunnel permissions for the host.

First apply:

  # module.devinfra-deployment-target[0].module.iap_bastion.google_compute_instance_from_template.bastion_vm[0] must be replaced
-/+ resource "google_compute_instance_from_template" "bastion_vm" {
      + allow_stopping_for_update  = (known after apply)
      ~ can_ip_forward             = false -> (known after apply)
      ~ cpu_platform               = "Intel Broadwell" -> (known after apply)
      ~ creation_timestamp         = "2024-10-23T06:06:49.389-07:00" -> (known after apply)
      ~ current_status             = "RUNNING" -> (known after apply)
      ~ deletion_protection        = false -> (known after apply)
      + description                = (known after apply)
      + desired_status             = (known after apply)
      ~ effective_labels           = {
          + "goog-terraform-provisioned" = "true"
            # (3 unchanged elements hidden)
        }
      ~ enable_display             = false -> (known after apply)
      + hostname                   = (known after apply)
      ~ id                         = "projects/***/zones/us-west1-c/instances/***" -> (known after apply)
      ~ instance_id                = "8582050113197001223" -> (known after apply)
      + key_revocation_action_type = (known after apply)
      ~ label_fingerprint          = "wUX_V1xtcys=" -> (known after apply)
      ~ machine_type               = "e2-micro" -> (known after apply)
      ~ metadata                   = {
          - "enable-oslogin" = "TRUE"
          - "startup-script" = <<-EOT
                systemctl enable --now tinyproxy || {
                    apt install -y --no-install-recommends tinyproxy
                    systemctl enable --now tinyproxy
                }
            EOT
        } -> (known after apply)
      ~ metadata_fingerprint       = "haQtoG1OvUc=" -> (known after apply)
      + metadata_startup_script    = (known after apply)
      + min_cpu_platform           = (known after apply)
        name                       = "***"
      ~ resource_policies          = [] -> (known after apply)
      ~ self_link                  = "https://www.googleapis.com/compute/v1/projects/***/zones/us-west1-c/instances/***" -> (known after apply)
      ~ source_instance_template   = "https://www.googleapis.com/compute/v1/projects/***/global/instanceTemplates/bastion-instance-template-20241023130644287300000001" # forces replacement -> (known after apply) # forces replacement
      ~ tags                       = [
          - "use-nat",
        ] -> (known after apply)
        # (3 unchanged attributes hidden)

      - boot_disk {
          - auto_delete = true -> null
          - device_name = "persistent-disk-0" -> null
          - mode        = "READ_WRITE" -> null
          - source      = "https://www.googleapis.com/compute/v1/projects/***/zones/us-west1-c/disks/***" -> null

          - initialize_params {
              - enable_confidential_compute = false -> null
              - image                       = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-12-bookworm-v20241009" -> null
              - labels                      = {} -> null
              - provisioned_iops            = 0 -> null
              - provisioned_throughput      = 0 -> null
              - resource_manager_tags       = {} -> null
              - resource_policies           = [] -> null
              - size                        = 10 -> null
              - type                        = "pd-standard" -> null
            }
        }

      ~ network_interface {
          ~ internal_ipv6_prefix_length = 0 -> (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          ~ name                        = "nic0" -> (known after apply)
          ~ network                     = "https://www.googleapis.com/compute/v1/projects/***/global/networks/***" -> (known after apply)
          ~ network_ip                  = "10.33.0.2" -> (known after apply)
          + nic_type                    = (known after apply)
          ~ queue_count                 = 0 -> (known after apply)
          ~ stack_type                  = "IPV4_ONLY" -> (known after apply)
            # (2 unchanged attributes hidden)
        }

      - scheduling {
          - automatic_restart   = true -> null
          - min_node_cpus       = 0 -> null
          - on_host_maintenance = "MIGRATE" -> null
          - preemptible         = false -> null
          - provisioning_model  = "STANDARD" -> null
        }

      - service_account {
          - email  = "dicd-sa-b8b86331f959ef4f@***.iam.gserviceaccount.com" -> null
          - scopes = [
              - "https://www.googleapis.com/auth/cloud-platform",
            ] -> null
        }

      - shielded_instance_config {
          - enable_integrity_monitoring = true -> null
          - enable_secure_boot          = true -> null
          - enable_vtpm                 = true -> null
        }
    }

  # module.devinfra-deployment-target[0].module.iap_bastion.module.instance_template.google_compute_instance_template.tpl must be replaced
+/- resource "google_compute_instance_template" "tpl" {
      ~ creation_timestamp      = "2024-10-23T06:06:45.264-07:00" -> (known after apply)
      ~ effective_labels        = {
          + "goog-terraform-provisioned" = "true"
            # (3 unchanged elements hidden)
        }
      ~ id                      = "projects/***/global/instanceTemplates/bastion-instance-template-20241023130644287300000001" -> (known after apply)
      ~ metadata_fingerprint    = "haQtoG1OvUc=" -> (known after apply)
      ~ name                    = "bastion-instance-template-20241023130644287300000001" -> (known after apply)
      ~ region                  = "us-west1" -> (known after apply)
      + resource_policies       = []
      ~ self_link               = "https://www.googleapis.com/compute/beta/projects/***/global/instanceTemplates/bastion-instance-template-20241023130644287300000001" -> (known after apply)
      ~ self_link_unique        = "https://www.googleapis.com/compute/beta/projects/***/global/instanceTemplates/bastion-instance-template-20241023130644287300000001?uniqueId=6348214262220278283" -> (known after apply)
        tags                    = [
            "use-nat",
        ]
        # (7 unchanged attributes hidden)

      ~ advanced_machine_features {
          - threads_per_core             = 0 -> null
          - visible_core_count           = 0 -> null
            # (1 unchanged attribute hidden)
        }

      ~ disk {
          ~ device_name            = "persistent-disk-0" -> (known after apply)
          ~ interface              = "SCSI" -> (known after apply)
          - labels                 = {} -> null
          ~ mode                   = "READ_WRITE" -> (known after apply)
          ~ provisioned_iops       = 0 -> (known after apply)
          ~ provisioned_throughput = 0 -> (known after apply)
          - resource_manager_tags  = {} -> null
          ~ source_image           = "projects/debian-cloud/global/images/family/debian-12" -> "debian-cloud/debian-12"
            # (6 unchanged attributes hidden)
        }

      ~ network_interface {
          ~ internal_ipv6_prefix_length = 0 -> (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          ~ name                        = "nic0" -> (known after apply)
          - network                     = "https://www.googleapis.com/compute/v1/projects/***/global/networks/***" -> null
          + network_attachment          = (known after apply)
          - queue_count                 = 0 -> null
          + stack_type                  = (known after apply)
          ~ subnetwork_project          = "***" -> (known after apply)
            # (1 unchanged attribute hidden)
        }

      + network_performance_config { # forces replacement
          + total_egress_bandwidth_tier = "DEFAULT" # forces replacement
        }

      ~ scheduling {
          - host_error_timeout_seconds = 0 -> null
          - min_node_cpus              = 0 -> null
          ~ provisioning_model         = "STANDARD" -> (known after apply)
            # (3 unchanged attributes hidden)
        }

        # (3 unchanged blocks hidden)
    }

Plan: 2 to add, 1 to change, 2 to destroy.

Second apply:

  # module.devinfra-deployment-target[0].module.iap_bastion.module.iap_tunneling.google_iap_tunnel_instance_iam_binding.enable_iap["*** us-west1-c"] will be updated in-place
  ~ resource "google_iap_tunnel_instance_iam_binding" "enable_iap" {
        id       = "projects/***/iap_tunnel/zones/us-west1-c/instances/***/roles/iap.tunnelResourceAccessor"
      ~ members  = [
          + "serviceAccount:deploy-to-***@***.iam.gserviceaccount.com",
          + "serviceAccount:deploy-to-***@***.iam.gserviceaccount.com",
        ]
        # (5 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Terraform Configuration

The only change was updating the bastion module version from `~> 5.0.1` to `~> 8.0`.

## Before:


module "iap_bastion" {
   source  = "terraform-google-modules/bastion-host/google"
   version = "~> 5.0.1"

   name    = local.name
   project = var.project
  network = var.network
  subnet  = var.subnetwork
  zone    = var.zone
  tags    = var.tags
  labels  = local.labels
  image_family                  = "debian-12"
  machine_type                  = "e2-micro"
  disk_size_gb                  = 10
  create_instance_from_template = var.create_instance_from_template
  # Allows OS Login and IAP access to the bastion host.
  members = var.deployment-members
  # These just need to be unique if this parent module is to be repeated.
  service_account_name       = "dicd-sa-${random_id._.hex}"
  fw_name_allow_ssh_from_iap = "dicd-rule-${random_id._.hex}"
  # We're relying on the defaults - pretty much just the default
  # port of 8888.
  startup_script = <<EOT
systemctl enable --now tinyproxy || {
    apt install -y --no-install-recommends tinyproxy
    systemctl enable --now tinyproxy
}
EOT
}

After

module "iap_bastion" {
   source  = "terraform-google-modules/bastion-host/google"
   version = "~> 8.0"

   name    = local.name
   project = var.project
  network = var.network
  subnet  = var.subnetwork
  zone    = var.zone
  tags    = var.tags
  labels  = local.labels
  image_family                  = "debian-12"
  machine_type                  = "e2-micro"
  disk_size_gb                  = 10
  create_instance_from_template = var.create_instance_from_template
  # Allows OS Login and IAP access to the bastion host.
  members = var.deployment-members
  # These just need to be unique if this parent module is to be repeated.
  service_account_name       = "dicd-sa-${random_id._.hex}"
  fw_name_allow_ssh_from_iap = "dicd-rule-${random_id._.hex}"
  # We're relying on the defaults - pretty much just the default
  # port of 8888.
  startup_script = <<EOT
systemctl enable --now tinyproxy || {
    apt install -y --no-install-recommends tinyproxy
    systemctl enable --now tinyproxy
}
EOT
}


### Terraform Version

```sh
❯ tf version
Terraform v1.5.3
on darwin_amd64
+ provider registry.terraform.io/hashicorp/google v6.12.0
+ provider registry.terraform.io/hashicorp/google-beta v6.12.0
+ provider registry.terraform.io/hashicorp/random v3.1.3


### Additional information

_No response_

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions