diff --git a/.changelog/19790.txt b/.changelog/19790.txt new file mode 100644 index 000000000000..b8865645217a --- /dev/null +++ b/.changelog/19790.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_acm_certificate: Correctly handle SAN entries that match domain_name +``` diff --git a/internal/service/acm/certificate.go b/internal/service/acm/certificate.go index 1ae349f9e52c..f91f37cdc76c 100644 --- a/internal/service/acm/certificate.go +++ b/internal/service/acm/certificate.go @@ -204,6 +204,19 @@ func ResourceCertificate() *schema.Resource { } } + // ACM automatically adds the domain_name value to the list of SANs. Mimic ACM's behavior + // so that the user doesn't need to explicitly set it themselves. + if diff.HasChange("domain_name") || diff.HasChange("subject_alternative_names") { + domain_name := diff.Get("domain_name").(string) + + if sanSet, ok := diff.Get("subject_alternative_names").(*schema.Set); ok { + sanSet.Add(domain_name) + if err := diff.SetNew("subject_alternative_names", sanSet); err != nil { + return fmt.Errorf("error setting new subject_alternative_names diff: %w", err) + } + } + } + return nil }, verify.SetTagsDiff, @@ -338,7 +351,7 @@ func resourceCertificateRead(d *schema.ResourceData, meta interface{}) error { d.Set("arn", resp.Certificate.CertificateArn) d.Set("certificate_authority_arn", resp.Certificate.CertificateAuthorityArn) - if err := d.Set("subject_alternative_names", cleanUpSubjectAlternativeNames(resp.Certificate)); err != nil { + if err := d.Set("subject_alternative_names", flattenSubjectAlternativeNames(resp.Certificate)); err != nil { return resource.NonRetryableError(err) } @@ -434,13 +447,11 @@ func resourceCertificateUpdate(d *schema.ResourceData, meta interface{}) error { return resourceCertificateRead(d, meta) } -func cleanUpSubjectAlternativeNames(cert *acm.CertificateDetail) []string { +func flattenSubjectAlternativeNames(cert *acm.CertificateDetail) []string { sans := cert.SubjectAlternativeNames vs := make([]string, 0) for _, v := range sans { - if aws.StringValue(v) != aws.StringValue(cert.DomainName) { - vs = append(vs, aws.StringValue(v)) - } + vs = append(vs, aws.StringValue(v)) } return vs } diff --git a/internal/service/acm/certificate_test.go b/internal/service/acm/certificate_test.go index b7f64d8f3116..4b7724e0eba6 100644 --- a/internal/service/acm/certificate_test.go +++ b/internal/service/acm/certificate_test.go @@ -139,7 +139,8 @@ func TestAccACMCertificate_privateCert(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "domain_name", certificateDomainName), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusFailed), // FailureReason: PCA_INVALID_STATE (PCA State: PENDING_CERTIFICATE) - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", certificateDomainName), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", "NONE"), resource.TestCheckResourceAttrPair(resourceName, "certificate_authority_arn", certificateAuthorityResourceName, "arn"), @@ -200,7 +201,8 @@ func TestAccACMCertificate_rootAndWildcardSan(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", rootDomain), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), @@ -260,7 +262,8 @@ func TestAccACMCertificate_San_single(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", sanDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), @@ -307,7 +310,8 @@ func TestAccACMCertificate_San_multiple(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "3"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", sanDomain1), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", sanDomain2), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), @@ -350,7 +354,8 @@ func TestAccACMCertificate_San_trailingPeriod(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", strings.TrimSuffix(sanDomain, ".")), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), @@ -365,6 +370,44 @@ func TestAccACMCertificate_San_trailingPeriod(t *testing.T) { }) } +func TestAccACMCertificate_San_matches_domain(t *testing.T) { + resourceName := "aws_acm_certificate.cert" + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + sanDomain := rootDomain + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, acm.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckAcmCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAcmCertificateConfig_subjectAlternativeNames(domain, strconv.Quote(sanDomain), acm.ValidationMethodDns), + Check: resource.ComposeTestCheckFunc( + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile(`certificate/.+`)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), + resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ + "domain_name": domain, + "resource_record_type": "CNAME", + }), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), + resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), + resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccACMCertificate_wildcard(t *testing.T) { resourceName := "aws_acm_certificate.cert" rootDomain := acctest.ACMCertificateDomainFromEnv(t) @@ -387,7 +430,8 @@ func TestAccACMCertificate_wildcard(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), ), @@ -427,8 +471,9 @@ func TestAccACMCertificate_wildcardAndRootSan(t *testing.T) { "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", rootDomain), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), ), @@ -462,7 +507,8 @@ func TestAccACMCertificate_disableCTLogging(t *testing.T) { "domain_name": rootDomain, "resource_record_type": "CNAME", }), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", rootDomain), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns),