diff --git a/.changelog/27496.txt b/.changelog/27496.txt new file mode 100644 index 00000000000..2fbbfbeb811 --- /dev/null +++ b/.changelog/27496.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_acmpca_certificate_authority: Add `usage_mode` argument to support [short-lived certificates](https://docs.aws.amazon.com/privateca/latest/userguide/short-lived-certificates.html) +``` + +```release-note:enhancement +data-source/aws_acmpca_certificate_authority: Add `usage_mode` attribute +``` diff --git a/internal/service/acmpca/certificate_authority.go b/internal/service/acmpca/certificate_authority.go index 3a1696a0e02..40eaf24b750 100644 --- a/internal/service/acmpca/certificate_authority.go +++ b/internal/service/acmpca/certificate_authority.go @@ -30,6 +30,7 @@ func ResourceCertificateAuthority() *schema.Resource { Read: resourceCertificateAuthorityRead, Update: resourceCertificateAuthorityUpdate, Delete: resourceCertificateAuthorityDelete, + Importer: &schema.ResourceImporter{ State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set( @@ -40,9 +41,11 @@ func ResourceCertificateAuthority() *schema.Resource { return []*schema.ResourceData{d}, nil }, }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(1 * time.Minute), }, + MigrateState: resourceCertificateAuthorityMigrateState, SchemaVersion: 1, @@ -63,28 +66,16 @@ func ResourceCertificateAuthority() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key_algorithm": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - acmpca.KeyAlgorithmEcPrime256v1, - acmpca.KeyAlgorithmEcSecp384r1, - acmpca.KeyAlgorithmRsa2048, - acmpca.KeyAlgorithmRsa4096, - }, false), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(acmpca.KeyAlgorithm_Values(), false), }, "signing_algorithm": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - acmpca.SigningAlgorithmSha256withecdsa, - acmpca.SigningAlgorithmSha256withrsa, - acmpca.SigningAlgorithmSha384withecdsa, - acmpca.SigningAlgorithmSha384withrsa, - acmpca.SigningAlgorithmSha512withecdsa, - acmpca.SigningAlgorithmSha512withrsa, - }, false), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(acmpca.SigningAlgorithm_Values(), false), }, // https://docs.aws.amazon.com/privateca/latest/APIReference/API_ASN1Subject.html "subject": { @@ -199,6 +190,15 @@ func ResourceCertificateAuthority() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "permanent_deletion_time_in_days": { + Type: schema.TypeInt, + Optional: true, + Default: certificateAuthorityPermanentDeletionTimeInDaysDefault, + ValidateFunc: validation.IntBetween( + certificateAuthorityPermanentDeletionTimeInDaysMin, + certificateAuthorityPermanentDeletionTimeInDaysMax, + ), + }, // https://docs.aws.amazon.com/privateca/latest/APIReference/API_RevocationConfiguration.html "revocation_configuration": { Type: schema.TypeList, @@ -293,26 +293,20 @@ func ResourceCertificateAuthority() *schema.Resource { Computed: true, Deprecated: "The reported value of the \"status\" attribute is often inaccurate. Use the resource's \"enabled\" attribute to explicitly set status.", }, - "permanent_deletion_time_in_days": { - Type: schema.TypeInt, - Optional: true, - Default: certificateAuthorityPermanentDeletionTimeInDaysDefault, - ValidateFunc: validation.IntBetween( - certificateAuthorityPermanentDeletionTimeInDaysMin, - certificateAuthorityPermanentDeletionTimeInDaysMax, - ), - }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), "type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: acmpca.CertificateAuthorityTypeSubordinate, - ValidateFunc: validation.StringInSlice([]string{ - acmpca.CertificateAuthorityTypeRoot, - acmpca.CertificateAuthorityTypeSubordinate, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: acmpca.CertificateAuthorityTypeSubordinate, + ValidateFunc: validation.StringInSlice(acmpca.CertificateAuthorityType_Values(), false), + }, + "usage_mode": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: validation.StringInSlice(acmpca.CertificateAuthorityUsageMode_Values(), false), }, }, @@ -332,6 +326,10 @@ func resourceCertificateAuthorityCreate(d *schema.ResourceData, meta interface{} RevocationConfiguration: expandRevocationConfiguration(d.Get("revocation_configuration").([]interface{})), } + if v, ok := d.GetOk("usage_mode"); ok { + input.UsageMode = aws.String(v.(string)) + } + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } @@ -396,22 +394,19 @@ func resourceCertificateAuthorityRead(d *schema.ResourceData, meta interface{}) } d.Set("arn", certificateAuthority.Arn) - if err := d.Set("certificate_authority_configuration", flattenCertificateAuthorityConfiguration(certificateAuthority.CertificateAuthorityConfiguration)); err != nil { - return fmt.Errorf("setting tags: %s", err) + return fmt.Errorf("setting certificate_authority_configuration: %w", err) } - d.Set("enabled", (aws.StringValue(certificateAuthority.Status) != acmpca.CertificateAuthorityStatusDisabled)) d.Set("not_after", aws.TimeValue(certificateAuthority.NotAfter).Format(time.RFC3339)) d.Set("not_before", aws.TimeValue(certificateAuthority.NotBefore).Format(time.RFC3339)) - if err := d.Set("revocation_configuration", flattenRevocationConfiguration(certificateAuthority.RevocationConfiguration)); err != nil { - return fmt.Errorf("setting tags: %s", err) + return fmt.Errorf("setting revocation_configuration: %w", err) } - d.Set("serial", certificateAuthority.Serial) d.Set("status", certificateAuthority.Status) d.Set("type", certificateAuthority.Type) + d.Set("usage_mode", certificateAuthority.UsageMode) getCertificateAuthorityCertificateInput := &acmpca.GetCertificateAuthorityCertificateInput{ CertificateAuthorityArn: aws.String(d.Id()), diff --git a/internal/service/acmpca/certificate_authority_data_source.go b/internal/service/acmpca/certificate_authority_data_source.go index df30ac83ba1..4f9eca6b5ae 100644 --- a/internal/service/acmpca/certificate_authority_data_source.go +++ b/internal/service/acmpca/certificate_authority_data_source.go @@ -113,6 +113,10 @@ func DataSourceCertificateAuthority() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "usage_mode": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -130,25 +134,24 @@ func dataSourceCertificateAuthorityRead(d *schema.ResourceData, meta interface{} describeCertificateAuthorityOutput, err := conn.DescribeCertificateAuthority(describeCertificateAuthorityInput) if err != nil { - return fmt.Errorf("error reading ACM PCA Certificate Authority: %w", err) + return fmt.Errorf("reading ACM PCA Certificate Authority (%s): %w", certificateAuthorityARN, err) } if describeCertificateAuthorityOutput.CertificateAuthority == nil { - return fmt.Errorf("error reading ACM PCA Certificate Authority: not found") + return fmt.Errorf("reading ACM PCA Certificate Authority: not found") } certificateAuthority := describeCertificateAuthorityOutput.CertificateAuthority d.Set("arn", certificateAuthority.Arn) d.Set("not_after", aws.TimeValue(certificateAuthority.NotAfter).Format(time.RFC3339)) d.Set("not_before", aws.TimeValue(certificateAuthority.NotBefore).Format(time.RFC3339)) - if err := d.Set("revocation_configuration", flattenRevocationConfiguration(certificateAuthority.RevocationConfiguration)); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting revocation_configuration: %w", err) } - d.Set("serial", certificateAuthority.Serial) d.Set("status", certificateAuthority.Status) d.Set("type", certificateAuthority.Type) + d.Set("usage_mode", certificateAuthority.UsageMode) getCertificateAuthorityCertificateInput := &acmpca.GetCertificateAuthorityCertificateInput{ CertificateAuthorityArn: aws.String(certificateAuthorityARN), @@ -161,7 +164,7 @@ func dataSourceCertificateAuthorityRead(d *schema.ResourceData, meta interface{} // Returned when in PENDING_CERTIFICATE status // InvalidStateException: The certificate authority XXXXX is not in the correct state to have a certificate signing request. if !tfawserr.ErrCodeEquals(err, acmpca.ErrCodeInvalidStateException) { - return fmt.Errorf("error reading ACM PCA Certificate Authority Certificate: %w", err) + return fmt.Errorf("reading ACM PCA Certificate Authority Certificate: %w", err) } } @@ -180,7 +183,7 @@ func dataSourceCertificateAuthorityRead(d *schema.ResourceData, meta interface{} getCertificateAuthorityCsrOutput, err := conn.GetCertificateAuthorityCsr(getCertificateAuthorityCsrInput) if err != nil { - return fmt.Errorf("error reading ACM PCA Certificate Authority Certificate Signing Request: %w", err) + return fmt.Errorf("reading ACM PCA Certificate Authority Certificate Signing Request: %w", err) } d.Set("certificate_signing_request", "") @@ -191,11 +194,11 @@ func dataSourceCertificateAuthorityRead(d *schema.ResourceData, meta interface{} tags, err := ListTags(conn, certificateAuthorityARN) if err != nil { - return fmt.Errorf("error listing tags for ACM PCA Certificate Authority (%s): %w", certificateAuthorityARN, err) + return fmt.Errorf("listing tags for ACM PCA Certificate Authority (%s): %w", certificateAuthorityARN, err) } if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { - return fmt.Errorf("error setting tags: %w", err) + return fmt.Errorf("setting tags: %w", err) } d.SetId(certificateAuthorityARN) diff --git a/internal/service/acmpca/certificate_authority_data_source_test.go b/internal/service/acmpca/certificate_authority_data_source_test.go index 6190862c75c..d3a111c79db 100644 --- a/internal/service/acmpca/certificate_authority_data_source_test.go +++ b/internal/service/acmpca/certificate_authority_data_source_test.go @@ -27,7 +27,7 @@ func TestAccACMPCACertificateAuthorityDataSource_basic(t *testing.T) { }, { Config: testAccCertificateAuthorityDataSourceConfig_arn(commonName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(datasourceName, "certificate", resourceName, "certificate"), resource.TestCheckResourceAttrPair(datasourceName, "certificate_chain", resourceName, "certificate_chain"), @@ -41,6 +41,7 @@ func TestAccACMPCACertificateAuthorityDataSource_basic(t *testing.T) { resource.TestCheckResourceAttrPair(datasourceName, "status", resourceName, "status"), resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(datasourceName, "type", resourceName, "type"), + resource.TestCheckResourceAttrPair(datasourceName, "usage_mode", resourceName, "usage_mode"), ), }, }, @@ -64,7 +65,7 @@ func TestAccACMPCACertificateAuthorityDataSource_s3ObjectACL(t *testing.T) { }, { Config: testAccCertificateAuthorityDataSourceConfig_s3ObjectACLARN(commonName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(datasourceName, "certificate", resourceName, "certificate"), resource.TestCheckResourceAttrPair(datasourceName, "certificate_chain", resourceName, "certificate_chain"), @@ -82,6 +83,7 @@ func TestAccACMPCACertificateAuthorityDataSource_s3ObjectACL(t *testing.T) { resource.TestCheckResourceAttrPair(datasourceName, "status", resourceName, "status"), resource.TestCheckResourceAttrPair(datasourceName, "tags.%", resourceName, "tags.%"), resource.TestCheckResourceAttrPair(datasourceName, "type", resourceName, "type"), + resource.TestCheckResourceAttrPair(datasourceName, "usage_mode", resourceName, "usage_mode"), ), }, }, diff --git a/internal/service/acmpca/certificate_authority_test.go b/internal/service/acmpca/certificate_authority_test.go index 21ef86d5d08..150f234955d 100644 --- a/internal/service/acmpca/certificate_authority_test.go +++ b/internal/service/acmpca/certificate_authority_test.go @@ -52,6 +52,7 @@ func TestAccACMPCACertificateAuthority_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "status", "PENDING_CERTIFICATE"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "type", "SUBORDINATE"), + resource.TestCheckResourceAttr(resourceName, "usage_mode", "GENERAL_PURPOSE"), ), }, { @@ -138,6 +139,37 @@ func TestAccACMPCACertificateAuthority_enabledDeprecated(t *testing.T) { }) } +func TestAccACMPCACertificateAuthority_usageMode(t *testing.T) { + var certificateAuthority acmpca.CertificateAuthority + resourceName := "aws_acmpca_certificate_authority.test" + + commonName := acctest.RandomDomainName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, acmpca.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCertificateAuthorityDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCertificateAuthorityConfig_usageMode(commonName, acmpca.CertificateAuthorityTypeRoot, "SHORT_LIVED_CERTIFICATE"), + Check: resource.ComposeTestCheckFunc( + acctest.CheckACMPCACertificateAuthorityExists(resourceName, &certificateAuthority), + resource.TestCheckResourceAttr(resourceName, "usage_mode", "SHORT_LIVED_CERTIFICATE"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "permanent_deletion_time_in_days", + }, + }, + }, + }) +} + func TestAccACMPCACertificateAuthority_deleteFromActiveState(t *testing.T) { var certificateAuthority acmpca.CertificateAuthority resourceName := "aws_acmpca_certificate_authority.test" @@ -201,6 +233,7 @@ func TestAccACMPCACertificateAuthority_RevocationConfiguration_empty(t *testing. resource.TestCheckResourceAttr(resourceName, "status", "PENDING_CERTIFICATE"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "type", "SUBORDINATE"), + resource.TestCheckResourceAttr(resourceName, "usage_mode", "GENERAL_PURPOSE"), ), }, { @@ -743,6 +776,26 @@ resource "aws_acmpca_certificate_authority" "test" { `, enabled, certificateAuthorityType, commonName) } +func testAccCertificateAuthorityConfig_usageMode(commonName, certificateAuthorityType string, usageMode string) string { + return fmt.Sprintf(` +resource "aws_acmpca_certificate_authority" "test" { + enabled = true + usage_mode = %[1]q + permanent_deletion_time_in_days = 7 + type = %[2]q + + certificate_authority_configuration { + key_algorithm = "RSA_4096" + signing_algorithm = "SHA512WITHRSA" + + subject { + common_name = %[3]q + } + } +} +`, usageMode, certificateAuthorityType, commonName) +} + func testAccCertificateAuthorityConfig_root(commonName string) string { return fmt.Sprintf(` resource "aws_acmpca_certificate_authority" "test" { diff --git a/website/docs/d/acmpca_certificate_authority.html.markdown b/website/docs/d/acmpca_certificate_authority.html.markdown index ab4f244ca21..b58b46873ad 100644 --- a/website/docs/d/acmpca_certificate_authority.html.markdown +++ b/website/docs/d/acmpca_certificate_authority.html.markdown @@ -32,6 +32,7 @@ In addition to all arguments above, the following attributes are exported: * `certificate` - Base64-encoded certificate authority (CA) certificate. Only available after the certificate authority certificate has been imported. * `certificate_chain` - Base64-encoded certificate chain that includes any intermediate certificates and chains up to root on-premises certificate that you used to sign your private CA certificate. The chain does not include your private CA certificate. Only available after the certificate authority certificate has been imported. * `certificate_signing_request` - The base64 PEM-encoded certificate signing request (CSR) for your private CA certificate. +* `usage_mode` - Specifies whether the CA issues general-purpose certificates that typically require a revocation mechanism, or short-lived certificates that may optionally omit revocation because they expire quickly. * `not_after` - Date and time after which the certificate authority is not valid. Only available after the certificate authority certificate has been imported. * `not_before` - Date and time before which the certificate authority is not valid. Only available after the certificate authority certificate has been imported. * `revocation_configuration` - Nested attribute containing revocation configuration. diff --git a/website/docs/r/acmpca_certificate_authority.html.markdown b/website/docs/r/acmpca_certificate_authority.html.markdown index f00d92cae6f..796416df591 100644 --- a/website/docs/r/acmpca_certificate_authority.html.markdown +++ b/website/docs/r/acmpca_certificate_authority.html.markdown @@ -31,6 +31,22 @@ resource "aws_acmpca_certificate_authority" "example" { } ``` +### Short-lived certificate + +```terraform +resource "aws_acmpca_certificate_authority" "example" { + usage_mode = "SHORT_LIVED_CERTIFICATE" + certificate_authority_configuration { + key_algorithm = "RSA_4096" + signing_algorithm = "SHA512WITHRSA" + + subject { + common_name = "example.com" + } + } +} +``` + ### Enable Certificate Revocation List ```terraform @@ -94,6 +110,7 @@ The following arguments are supported: * `certificate_authority_configuration` - (Required) Nested argument containing algorithms and certificate subject information. Defined below. * `enabled` - (Optional) Whether the certificate authority is enabled or disabled. Defaults to `true`. * `revocation_configuration` - (Optional) Nested argument containing revocation configuration. Defined below. +* `usage_mode` - (Optional) Specifies whether the CA issues general-purpose certificates that typically require a revocation mechanism, or short-lived certificates that may optionally omit revocation because they expire quickly. Short-lived certificate validity is limited to seven days. Defaults to `GENERAL_PURPOSE`. Valid values: `GENERAL_PURPOSE` and `SHORT_LIVED_CERTIFICATE`. * `tags` - (Optional) Key-value map of user-defined tags that are attached to the certificate authority. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. * `type` - (Optional) Type of the certificate authority. Defaults to `SUBORDINATE`. Valid values: `ROOT` and `SUBORDINATE`. * `permanent_deletion_time_in_days` - (Optional) Number of days to make a CA restorable after it has been deleted, must be between 7 to 30 days, with default to 30 days.