diff --git a/.changelog/3853.txt b/.changelog/3853.txt new file mode 100644 index 00000000000..c860222c167 --- /dev/null +++ b/.changelog/3853.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_acm_certificate: Add `validation_option` argument +``` \ No newline at end of file diff --git a/internal/service/acm/certificate.go b/internal/service/acm/certificate.go index 65ff3826f80..b5999d419ff 100644 --- a/internal/service/acm/certificate.go +++ b/internal/service/acm/certificate.go @@ -177,6 +177,26 @@ func ResourceCertificate() *schema.Resource { ValidateFunc: validation.StringInSlice(append(acm.ValidationMethod_Values(), certificateValidationMethodNone), false), ConflictsWith: []string{"certificate_authority_arn", "certificate_body", "certificate_chain", "private_key"}, }, + "validation_option": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "validation_domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + ConflictsWith: []string{"certificate_body", "certificate_chain", "private_key"}, + }, }, CustomizeDiff: customdiff.Sequence( @@ -271,6 +291,10 @@ func resourceCertificateCreate(d *schema.ResourceData, meta interface{}) error { input.ValidationMethod = aws.String(v.(string)) } + if v, ok := d.GetOk("validation_option"); ok && v.(*schema.Set).Len() > 0 { + input.DomainValidationOptions = expandDomainValidationOptions(v.(*schema.Set).List()) + } + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } @@ -486,6 +510,50 @@ func flattenCertificateOptions(apiObject *acm.CertificateOptions) map[string]int return tfMap } +func expandDomainValidationOption(tfMap map[string]interface{}) *acm.DomainValidationOption { + if tfMap == nil { + return nil + } + + apiObject := &acm.DomainValidationOption{} + + if v, ok := tfMap["domain_name"].(string); ok && v != "" { + apiObject.DomainName = aws.String(v) + } + + if v, ok := tfMap["validation_domain"].(string); ok && v != "" { + apiObject.ValidationDomain = aws.String(v) + } + + return apiObject +} + +func expandDomainValidationOptions(tfList []interface{}) []*acm.DomainValidationOption { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*acm.DomainValidationOption + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandDomainValidationOption(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + func flattenDomainValidation(apiObject *acm.DomainValidation) (map[string]interface{}, []string) { if apiObject == nil { return nil, nil diff --git a/internal/service/acm/certificate_test.go b/internal/service/acm/certificate_test.go index d78e2317511..73211c2be65 100644 --- a/internal/service/acm/certificate_test.go +++ b/internal/service/acm/certificate_test.go @@ -41,6 +41,7 @@ func TestAccACMCertificate_emailValidation(t *testing.T) { acctest.CheckResourceAttrGreaterThanValue(resourceName, "validation_emails.#", "0"), resource.TestMatchResourceAttr(resourceName, "validation_emails.0", regexp.MustCompile(`^[^@]+@.+$`)), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodEmail), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -80,6 +81,7 @@ func TestAccACMCertificate_dnsValidation(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -118,6 +120,7 @@ func TestAccACMCertificate_root(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", rootDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -129,6 +132,44 @@ func TestAccACMCertificate_root(t *testing.T) { }) } +func TestAccACMCertificate_validationOptions(t *testing.T) { + resourceName := "aws_acm_certificate.test" + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + var v acm.CertificateDetail + + 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: testAccAcmCertificateValidationOptionsConfig(rootDomain, domain), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAcmCertificateExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), + resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "0"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), + resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), + acctest.CheckResourceAttrGreaterThanValue(resourceName, "validation_emails.#", "0"), + resource.TestMatchResourceAttr(resourceName, "validation_emails.0", regexp.MustCompile(`^[^@]+@.+$`)), + resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodEmail), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"validation_option"}, + }, + }, + }) +} + func TestAccACMCertificate_privateCert(t *testing.T) { certificateAuthorityResourceName := "aws_acmpca_certificate_authority.test" resourceName := "aws_acm_certificate.test" @@ -154,6 +195,7 @@ func TestAccACMCertificate_privateCert(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", certificateDomainName), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", "NONE"), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), resource.TestCheckResourceAttrPair(resourceName, "certificate_authority_arn", certificateAuthorityResourceName, "arn"), ), }, @@ -750,12 +792,26 @@ func testAccCheckAcmCertificateDestroy(s *terraform.State) error { func testAccAcmCertificateConfig(domainName, validationMethod string) string { return fmt.Sprintf(` resource "aws_acm_certificate" "test" { - domain_name = "%s" - validation_method = "%s" + domain_name = %[1]q + validation_method = %[2]q } `, domainName, validationMethod) } +func testAccAcmCertificateValidationOptionsConfig(rootDomainName, domainName string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + domain_name = %[2]q + validation_method = "EMAIL" + + validation_option { + domain_name = %[2]q + validation_domain = %[1]q + } +} +`, rootDomainName, domainName) +} + func testAccAcmCertificatePrivateCertConfig(commonName, certificateDomainName string) string { return fmt.Sprintf(` resource "aws_acmpca_certificate_authority" "test" { diff --git a/website/docs/r/acm_certificate.html.markdown b/website/docs/r/acm_certificate.html.markdown index 2211c6a2fee..1a532f9ec34 100644 --- a/website/docs/r/acm_certificate.html.markdown +++ b/website/docs/r/acm_certificate.html.markdown @@ -45,6 +45,20 @@ resource "aws_acm_certificate" "cert" { } ``` +### Custom Domain Validation Options + +```terraform +resource "aws_acm_certificate" "cert" { + domain_name = "testing.example.com" + validation_method = "EMAIL" + + validation_option { + domain_name = "testing.example.com" + validation_domain = "example.com" + } +} +``` + ### Existing Certificate Body Import ```terraform @@ -108,6 +122,7 @@ The following arguments are supported: * `subject_alternative_names` - (Optional) Set of domains that should be SANs in the issued certificate. To remove all elements of a previously configured list, set this value equal to an empty list (`[]`) or use the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) to trigger recreation. * `validation_method` - (Required) Which method to use for validation. `DNS` or `EMAIL` are valid, `NONE` can be used for certificates that were imported into ACM and then into Terraform. * `options` - (Optional) Configuration block used to set certificate options. Detailed below. + * `validation_option` - (Optional) Configuration block used to specify information about the initial validation of each domain name. Detailed below. * Importing an existing certificate * `private_key` - (Required) The certificate's PEM-formatted private key * `certificate_body` - (Required) The certificate's PEM-formatted public key @@ -124,6 +139,13 @@ Supported nested arguments for the `options` configuration block: * `certificate_transparency_logging_preference` - (Optional) Specifies whether certificate details should be added to a certificate transparency log. Valid values are `ENABLED` or `DISABLED`. See https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency for more details. +## validation_option Configuration Block + +Supported nested arguments for the `validation_option` configuration block: + +* `domain_name` - (Required) A fully qualified domain name (FQDN) in the certificate. +* `validation_domain` - (Required) The domain name that you want ACM to use to send you validation emails. This domain name is the suffix of the email addresses that you want ACM to use. This must be the same as the `domain_name` value or a superdomain of the `domain_name` value. For example, if you request a certificate for `"testing.example.com"`, you can specify `"example.com"` for this value. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: