hashicorp / terraform-provider-google

Terraform Provider for Google Cloud Platform
https://registry.terraform.io/providers/hashicorp/google/latest/docs
Mozilla Public License 2.0
2.29k stars 1.72k forks source link

Provider not respecting specific dependencies #16663

Closed watsonjm closed 9 months ago

watsonjm commented 9 months ago

Community Note

Terraform Version

Terraform v1.6.5 on darwin_arm64

Affected Resource(s)

Terraform Configuration Files

resource "google_pubsub_topic" "notifications" {
  name = "${local.prefix}-notifications"
}

resource "google_scc_notification_config" "medium_plus_findings" {
  config_id    = "${local.prefix}-scc-findings-notification"
  description  = "Continuous export of medium+ findings"
  organization = data.google_project.current.org_id
  pubsub_topic = google_pubsub_topic.notifications.id

  streaming_config {
    filter = "state=\"ACTIVE\" AND NOT mute=\"MUTED\" AND severity=\"CRITICAL\" OR severity=\"HIGH\" OR severity=\"MEDIUM\""
  }
}

resource "google_access_context_manager_service_perimeter" "enforced" {
  name                      = "accessPolicies/${google_access_context_manager_access_policy.this.id}/servicePerimeters/${var.environment}_perimeter"
  parent                    = "accessPolicies/${google_access_context_manager_access_policy.this.id}"
  description               = "VPC Service Perimeter for all services in ${upper(var.environment)} environment."
  perimeter_type            = "PERIMETER_TYPE_REGULAR"
  title                     = "${var.environment}-perimeter"
  use_explicit_dry_run_spec = var.dry_run_perimeter

  status {
    access_levels       = []
    resources           = [google_access_context_manager_access_policy.this.scopes[0]]
    restricted_services = local.perimeter_restricted_apis

    dynamic "ingress_policies" {
      for_each = local.perimeter_ingress_policies
      iterator = policy
      content {
        ingress_from {
          identity_type = try(policy.value.from.identity_type, "")
          identities    = try(policy.value.from.identities, [])
          dynamic "sources" {
            for_each = merge(
              { for level, id in lookup(policy.value.from.sources, "access_levels", []) : id => "access_level" },
              { for resource, name in lookup(policy.value.from.sources, "resources", []) : name => "resource" }
            )
            iterator = source
            content {
              access_level = source.value == "access_level" ? source.key : ""
              resource     = source.value == "resource" ? source.key : ""
            }
          }
        }
        ingress_to {
          resources = policy.value.to.resources
          dynamic "operations" {
            for_each = policy.value.to.operations
            iterator = api
            content {
              service_name = api.key != 0 ? api.key : "*"
              dynamic "method_selectors" {
                for_each = api.key != 0 ? api.value != "*" ? toset(api.value) : toset(["*"]) : []
                iterator = method
                content {
                  method = method.value
                }
              }
            }
          }
        }
      }
    }
    dynamic "egress_policies" {
      for_each = local.perimeter_egress_policies
      iterator = policy
      content {
        egress_from {
          identity_type = try(policy.value.from.identity_type, "")
          identities    = try(policy.value.from.identities, [])
        }
        egress_to {
          resources = policy.value.to.resources
          dynamic "operations" {
            for_each = policy.value.to.operations
            iterator = api
            content {
              service_name = api.key
              dynamic "method_selectors" {
                for_each = api.value != "*" ? toset(api.value) : toset(["*"])
                iterator = method
                content {
                  method = method.value
                }
              }
            }
          }
        }
      }
    }
  }
}

locals {
  #################
  ### INGRESS
  #################
  perimeter_ingress_policies = [
    { # SCC Notification config
      from = {
        identities = ["serviceAccount:${google_scc_notification_config.medium_plus_findings.service_account}"]
        sources = {
          access_levels = ["*"]
        }
      }
      to = {
        resources = [google_access_context_manager_access_policy.this.scopes[0]]
        operations = {
          "pubsub.googleapis.com" = "*"
        }
      }
    },
}

Expected Behavior

Terraform should attempt to update google_pubsub_topic.notifications, google_scc_notification_config.medium_plus_findings, and google_access_context_manager_service_perimeter.enforced, given the id of google_pubsub_topic.notifications has changed.

Actual Behavior

Terraform is not attempting to update google_pubsub_topic.notifications during a targeted apply to google_access_context_manager_service_perimeter.enforced.

I'm running terraform apply -var-file=./config/tfvars.tfvars -target google_access_context_manager_service_perimeter.enforced -input=false -auto-approve to apply the perimeter before the rest of my resources. The perimeter depends on a service account from google_scc_notification_config, which depends on google_pubsub_topic. The value of google_pubsub_topic.notifications.id has changed for this run. My terraform apply wants to change google_access_context_manager_service_perimeter.enforced, and google_scc_notification_config.medium_plus_findings, but NOT google_pubsub_topic.notifications, which it should due to it being a dependency. This causes the update of google_scc_notification_config.medium_plus_findings to fail, since the topic it wants to assign doesn't exist yet.

Steps to Reproduce

Apply the resources above, update the name of google_pubsub_topic.notifications, and then target a terraform apply to google_access_context_manager_service_perimeter.enforced.

Important Factoids

References

Note

I am working around this by hard coding the value of the service account rather than pulling it from the resource, but it seems important for the GPC provider to respect resource dependencies.

edwardmedia commented 9 months ago

@watsonjm would you be able to hardcoded the resources and repro the issue? Please share the config and debug if you can

watsonjm commented 9 months ago

@edwardmedia good news, no bug here! I did a lot of work on this yesterday to troubleshoot this and get good logs for you and it turns out the VPC perimeter was blocking the IAMPolicy.TestIamPermissions method on the pubsub API and I assume that is why I was getting the 403 for unauthorized or not existing. I was still wondering why it wasn't showing in my Terraform plan (it runs in a pipeline only so no easy console access to just check) so I went over what I did last week and I'm pretty sure I glossed right over it when listing pubsub topics with gcloud cli. Thanks for your time!

github-actions[bot] commented 8 months ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.