IBM-Cloud / terraform-provider-ibm

https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs
Mozilla Public License 2.0
336 stars 646 forks source link

Reordering of alt_names forces delete & replace of ibm_sm_private_certificate #5343

Closed davesteinberg closed 1 month ago

davesteinberg commented 1 month ago

Community Note

Terraform CLI and Terraform IBM Provider Version

$ terraform -v
Terraform v1.5.7
on darwin_amd64
+ provider registry.terraform.io/ibm-cloud/ibm v1.65.0

Affected Resource(s)

Terraform Configuration Files

The following simple Terraform configures the private certificate engine in Secrets Manager and then creates a certificate for use by a web server. Note that the certificate uses alt_names to specify both a raw domain (mysite.com) and a wildcard (*.mysite.com) as its subject common names.

terraform {
  required_version = "~>1.5"

  required_providers {
    ibm = {
      source  = "IBM-Cloud/ibm"
      version = "~>1.65.0"
    }
  }
}

provider "ibm" {
  ibmcloud_api_key = var.ibmcloud_api_key
  region           = var.region
}

variable "ibmcloud_api_key" {
  description = "API key for use by this Terraform module."
  type        = string
  sensitive   = true
}

variable "region" {
  description = "Region where resources will be provisioned."
  type        = string
  default     = "ca-tor"
}

variable "prefix" {
  description = "Name prefix for common resources."
  type        = string
  default     = "bug"
}

# Resource group
resource "ibm_resource_group" "rg" {
  name = var.prefix
}

# Secrets Manager instance
resource "ibm_resource_instance" "sm" {
  name              = "${var.prefix}-sm"
  resource_group_id = ibm_resource_group.rg.id
  service           = "secrets-manager"
  plan              = "trial"
  location          = var.region

  timeouts {
    create = "30m"
  }
}

# Secret group
resource "ibm_sm_secret_group" "sg" {
  instance_id = ibm_resource_instance.sm.guid
  region      = var.region
  name        = var.prefix
}

# Private certificate engine configuration for VPN certificate: Root CA
resource "ibm_sm_private_certificate_configuration_root_ca" "root" {
  instance_id                       = ibm_resource_instance.sm.guid
  region                            = var.region
  name                              = "${var.prefix}-root-ca"
  common_name                       = "${var.prefix}-root-ca"
  max_ttl                           = "${365 * 10}d"
  issuing_certificates_urls_encoded = true
  key_type                          = "rsa"
  key_bits                          = 4096
  crl_disable                       = false
  crl_distribution_points_encoded   = true
}

# Intermediate CA
resource "ibm_sm_private_certificate_configuration_intermediate_ca" "web" {
  instance_id                       = ibm_resource_instance.sm.guid
  region                            = var.region
  name                              = "${var.prefix}-web-ca"
  signing_method                    = "internal"
  issuer                            = ibm_sm_private_certificate_configuration_root_ca.root.name
  common_name                       = "${var.prefix}-web-ca"
  max_ttl                           = "${365 * 10}d"
  issuing_certificates_urls_encoded = true
  key_type                          = "rsa"
  key_bits                          = 4096
  crl_disable                       = false
  crl_distribution_points_encoded   = true
}

# Certificate template
resource "ibm_sm_private_certificate_configuration_template" "web_server" {
  instance_id           = ibm_resource_instance.sm.guid
  region                = var.region
  name                  = "${var.prefix}-web-server-template"
  certificate_authority = ibm_sm_private_certificate_configuration_intermediate_ca.web.name
  ttl                   = "397d"
  key_type              = "rsa"
  key_bits              = 4096
  allowed_secret_groups = ibm_sm_secret_group.sg.secret_group_id
  allowed_domains       = ["mysite.com"]
  allow_bare_domains    = true
  allow_subdomains      = true
  enforce_hostnames     = true
  server_flag           = true
  client_flag           = false
}

# Web server certificate
resource "ibm_sm_private_certificate" "site" {
  instance_id          = ibm_resource_instance.sm.guid
  region               = var.region
  name                 = "${var.prefix}-site-cert"
  secret_group_id      = ibm_sm_secret_group.sg.secret_group_id
  certificate_template = ibm_sm_private_certificate_configuration_template.web_server.name
  ttl                  = "397d"
  common_name          = "mysite.com"
  alt_names            = ["mysite.com", "*.mysite.com"]

  rotation {
    auto_rotate = true
    interval    = 182
    unit        = "day"
  }
}

All goes well the first time this is applied. The problem is that each time it is applied again, the alt_names list comes back with its order reversed, so Terraform wants to update it, which requires that the certificate be deleted and replaced.

Steps to Reproduce

  1. Create a terraform.tfvars with an ibmcloud_api_key for your account.
  2. Run terraform init.
  3. Run terraform apply once.
  4. Run terraform apply a second time.

Expected Behavior

The first apply correctly creates the resources. The second apply does nothing, as nothing has changed.

Actual Behavior

On the first apply, the certificate is created, seemingly with the correct value of alt_names:

  # ibm_sm_private_certificate.site will be created
  + resource "ibm_sm_private_certificate" "site" {
      + alt_names               = [
          + "mysite.com",
          + "*.mysite.com",
        ]
    ...
  }

However, on the second apply, it appears that the list was previously created in the wrong order and Terraform wants to fix it:

  # ibm_sm_private_certificate.site must be replaced
-/+ resource "ibm_sm_private_certificate" "site" {
      ~ alt_names               = [ # forces replacement
          - "*.mysite.com",
            "mysite.com",
          + "*.mysite.com",
        ]
    ...
  }

This requires that the certificate be deleted and replaced, affecting anything that depends on the identity of the certificate (such as ibmcloud ks ingress secret). Even if I let it, this doesn't actually resolve the problem: On the next apply, the order is still wrong and Terraform wants to delete and replace the certificate again.

If I look in the UI, I see the two common names listed in the order I specified (mysite.com, *.mysite.com).

Debug Output

Complete debug output is available in this Gist: https://gist.github.com/davesteinberg/1981ea8ea4fb393618275f0eec825ec0