Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ACMPCA Issue Certificate from Private CA #10213

Merged
merged 30 commits into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6807a0b
private cert issuing
dvdliao Sep 23, 2019
b88a02c
tests
dvdliao Sep 24, 2019
557335d
data resources and docs
dvdliao Sep 24, 2019
f5f1fad
minor
dvdliao Sep 24, 2019
853bdf5
Merge branch 'master' into acmpca-private-cert
dvdliao Sep 24, 2019
142d8be
typos
dvdliao Sep 24, 2019
84b9f92
Merge branch 'master' into acmpca-private-cert
dvdliao Sep 24, 2019
c8fad70
Merge branch 'master' into acmpca-private-cert
dvdliao Sep 26, 2019
74f9787
update changelog
dvdliao Sep 26, 2019
de44fd3
Merge branch 'master' into acmpca-private-cert
dvdliao Sep 27, 2019
6b4fdc1
Merge branch 'master' into acmpca-private-cert
dvdliao Oct 1, 2019
5252c6e
Merge branch 'master' into acmpca-private-cert
dvdliao Oct 4, 2019
eee3a97
fix conflicts
dvdliao Oct 4, 2019
89170da
Merge branch 'master' into acmpca-private-cert
dvdliao Oct 4, 2019
ff75379
Merge branch 'master' into acmpca-private-cert
dvdliao Nov 8, 2019
f682fb0
update changelog
dvdliao Nov 8, 2019
90cf76c
Merge remote-tracking branch 'upstream/master' into acmpca-private-cert
dvdliao May 3, 2020
b4d2f41
pulling in master
dvdliao May 3, 2020
4730486
no changelog
dvdliao May 3, 2020
1700783
fix imports, docs subcat
dvdliao May 3, 2020
9a3be90
update tests to not use tls provider
dvdliao May 3, 2020
d7f8d67
lints
dvdliao May 3, 2020
2857199
fixing tests and add comment
dvdliao May 3, 2020
4b368a7
revert endline
dvdliao May 5, 2020
58142bd
cr
dvdliao May 5, 2020
f768237
Merge branch 'master' into acmpca-private-cert
dvdliao May 5, 2020
5b36e37
lintignore
dvdliao May 5, 2020
1c65502
Update resource_aws_acmpca_private_certificate.go
dvdliao May 5, 2020
b8efe13
Merge branch 'master' into acmpca-private-cert
dvdliao Jun 2, 2020
d4505bc
cr
dvdliao Jun 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions aws/data_source_aws_acmpca_private_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/acmpca"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func dataSourceAwsAcmpcaPrivateCertificate() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsAcmpcaPrivateCertificateRead,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Required: true,
},
"certificate_authority_arn": {
Type: schema.TypeString,
Required: true,
dvdliao marked this conversation as resolved.
Show resolved Hide resolved
},
"certificate": {
Type: schema.TypeString,
Computed: true,
},
"certificate_chain": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceAwsAcmpcaPrivateCertificateRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).acmpcaconn
certificateArn := d.Get("arn").(string)

getCertificateInput := &acmpca.GetCertificateInput{
CertificateArn: aws.String(certificateArn),
CertificateAuthorityArn: aws.String(d.Get("certificate_authority_arn").(string)),
}

log.Printf("[DEBUG] Reading ACMPCA Certificate: %s", getCertificateInput)

certificateOutput, err := conn.GetCertificate(getCertificateInput)
if err != nil {
return fmt.Errorf("error reading ACMPCA Certificate: %s", err)
}

d.SetId(certificateArn)
d.Set("certificate", aws.StringValue(certificateOutput.Certificate))
d.Set("certificate_chain", aws.StringValue(certificateOutput.CertificateChain))

return nil
}
81 changes: 81 additions & 0 deletions aws/data_source_aws_acmpca_private_certificate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package aws

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccDataSourceAwsAcmpcaPrivateCertificate_Basic(t *testing.T) {
resourceName := "aws_acmpca_private_certificate.test"
datasourceName := "data.aws_acmpca_private_certificate.test"
csr1, _ := tlsRsaX509CertificateRequestPem(4096, "terraformtest1.com")
csr2, _ := tlsRsaX509CertificateRequestPem(4096, "terraformtest2.com")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAwsAcmpcaPrivateCertificateConfig_NonExistent,
ExpectError: regexp.MustCompile(`(AccessDeniedException|ResourceNotFoundException)`),
},
{
Config: testAccDataSourceAwsAcmpcaPrivateCertificateConfig_ARN(tlsPemEscapeNewlines(csr1), tlsPemEscapeNewlines(csr2)),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(datasourceName, "arn", resourceName, "arn"),
resource.TestCheckResourceAttrPair(datasourceName, "certificate", resourceName, "certificate"),
resource.TestCheckResourceAttrPair(datasourceName, "certificate_chain", resourceName, "certificate_chain"),
resource.TestCheckResourceAttrPair(datasourceName, "certificate_authority_arn", resourceName, "certificate_authority_arn"),
),
},
},
})
}

func testAccDataSourceAwsAcmpcaPrivateCertificateConfig_ARN(csr1, csr2 string) string {
return fmt.Sprintf(`
resource "aws_acmpca_certificate_authority" "test" {
permanent_deletion_time_in_days = 7
type = "ROOT"

certificate_authority_configuration {
key_algorithm = "RSA_4096"
signing_algorithm = "SHA512WITHRSA"

subject {
common_name = "terraformtesting.com"
}
}
}

resource "aws_acmpca_private_certificate" "wrong" {
certificate_authority_arn = aws_acmpca_certificate_authority.test.arn
certificate_signing_request = "%[1]s"
signing_algorithm = "SHA256WITHRSA"
validity_length = 1
validity_unit = "YEARS"
}

resource "aws_acmpca_private_certificate" "test" {
certificate_authority_arn = aws_acmpca_certificate_authority.test.arn
certificate_signing_request = "%[2]s"
signing_algorithm = "SHA256WITHRSA"
validity_length = 1
validity_unit = "YEARS"
}

data "aws_acmpca_private_certificate" "test" {
arn = aws_acmpca_private_certificate.test.arn
certificate_authority_arn = aws_acmpca_certificate_authority.test.arn
}`, csr1, csr2)
}

const testAccDataSourceAwsAcmpcaPrivateCertificateConfig_NonExistent = `
data "aws_acmpca_private_certificate" "test" {
arn = "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/does-not-exist/certificate/does-not-exist"
certificate_authority_arn = "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/does-not-exist"
}
`
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func Provider() terraform.ResourceProvider {
DataSourcesMap: map[string]*schema.Resource{
"aws_acm_certificate": dataSourceAwsAcmCertificate(),
"aws_acmpca_certificate_authority": dataSourceAwsAcmpcaCertificateAuthority(),
"aws_acmpca_private_certificate": dataSourceAwsAcmpcaPrivateCertificate(),
"aws_ami": dataSourceAwsAmi(),
"aws_ami_ids": dataSourceAwsAmiIds(),
"aws_api_gateway_api_key": dataSourceAwsApiGatewayApiKey(),
Expand Down Expand Up @@ -349,6 +350,7 @@ func Provider() terraform.ResourceProvider {
"aws_acm_certificate": resourceAwsAcmCertificate(),
"aws_acm_certificate_validation": resourceAwsAcmCertificateValidation(),
"aws_acmpca_certificate_authority": resourceAwsAcmpcaCertificateAuthority(),
"aws_acmpca_private_certificate": resourceAwsAcmpcaPrivateCertificate(),
"aws_ami": resourceAwsAmi(),
"aws_ami_copy": resourceAwsAmiCopy(),
"aws_ami_from_instance": resourceAwsAmiFromInstance(),
Expand Down
195 changes: 195 additions & 0 deletions aws/resource_aws_acmpca_private_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package aws

import (
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/acmpca"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceAwsAcmpcaPrivateCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAcmpcaPrivateCertificateCreate,
Read: resourceAwsAcmpcaPrivateCertificateRead,
Delete: resourceAwsAcmpcaPrivateCertificateRevoke,
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
},
SchemaVersion: 1,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"certificate": {
Type: schema.TypeString,
Computed: true,
},
"certificate_chain": {
Type: schema.TypeString,
Computed: true,
},
"certificate_authority_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateArn,
},
"certificate_signing_request": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: normalizeCert,
},
"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),
},
"validity_length": {
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"validity_unit": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
acmpca.ValidityPeriodTypeAbsolute,
acmpca.ValidityPeriodTypeDays,
acmpca.ValidityPeriodTypeEndDate,
acmpca.ValidityPeriodTypeMonths,
acmpca.ValidityPeriodTypeYears,
}, false),
},
"template_arn": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"arn:aws:acm-pca:::template/EndEntityCertificate/V1",
"arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen0/V1",
"arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen1/V1",
"arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen2/V1",
"arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen3/V1",
"arn:aws:acm-pca:::template/RootCACertificate/V1",
}, false),
Default: "arn:aws:acm-pca:::template/EndEntityCertificate/V1",
},
},
}
}

func resourceAwsAcmpcaPrivateCertificateCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).acmpcaconn

input := &acmpca.IssueCertificateInput{
CertificateAuthorityArn: aws.String(d.Get("certificate_authority_arn").(string)),
Csr: []byte(d.Get("certificate_signing_request").(string)),
IdempotencyToken: aws.String(resource.UniqueId()),
SigningAlgorithm: aws.String(d.Get("signing_algorithm").(string)),
TemplateArn: aws.String(d.Get("template_arn").(string)),
Validity: &acmpca.Validity{
Type: aws.String(d.Get("validity_unit").(string)),
Value: aws.Int64(int64(d.Get("validity_length").(int))),
},
}

log.Printf("[DEBUG] ACMPCA Issue Certificate: %s", input)
var output *acmpca.IssueCertificateOutput
dvdliao marked this conversation as resolved.
Show resolved Hide resolved
output, err := conn.IssueCertificate(input)
if err != nil {
return fmt.Errorf("error issuing ACMPCA Certificate: %s", err)
}

d.SetId(aws.StringValue(output.CertificateArn))

getCertificateInput := &acmpca.GetCertificateInput{
CertificateArn: output.CertificateArn,
CertificateAuthorityArn: aws.String(d.Get("certificate_authority_arn").(string)),
}

err = conn.WaitUntilCertificateIssued(getCertificateInput)
if err != nil {
return fmt.Errorf("error waiting for ACMPCA to issue Certificate %q, error: %s", d.Id(), err)
}

return resourceAwsAcmpcaPrivateCertificateRead(d, meta)
}

func resourceAwsAcmpcaPrivateCertificateRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).acmpcaconn

getCertificateInput := &acmpca.GetCertificateInput{
CertificateArn: aws.String(d.Id()),
CertificateAuthorityArn: aws.String(d.Get("certificate_authority_arn").(string)),
}

log.Printf("[DEBUG] Reading ACMPCA Certificate: %s", getCertificateInput)

certificateOutput, err := conn.GetCertificate(getCertificateInput)
if err != nil {
if isAWSErr(err, acmpca.ErrCodeResourceNotFoundException, "") {
log.Printf("[WARN] ACMPCA Certificate %q not found - removing from state", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("error reading ACMPCA Certificate: %s", err)
}

d.Set("arn", d.Id())
d.Set("certificate", aws.StringValue(certificateOutput.Certificate))
d.Set("certificate_chain", aws.StringValue(certificateOutput.CertificateChain))

return nil
}

func resourceAwsAcmpcaPrivateCertificateRevoke(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).acmpcaconn

block, _ := pem.Decode([]byte(d.Get("certificate").(string)))
if block == nil {
log.Printf("[WARN] Failed to parse certificate %q", d.Id())
return nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("Failed to parse ACMPCA Certificate: %s", err)
}

input := &acmpca.RevokeCertificateInput{
CertificateAuthorityArn: aws.String(d.Get("certificate_authority_arn").(string)),
CertificateSerial: aws.String(fmt.Sprintf("%x", cert.SerialNumber)),
RevocationReason: aws.String(acmpca.RevocationReasonUnspecified),
}

log.Printf("[DEBUG] Revoking ACMPCA Certificate: %s", input)
_, err = conn.RevokeCertificate(input)
if err != nil {
if isAWSErr(err, acmpca.ErrCodeResourceNotFoundException, "") ||
isAWSErr(err, acmpca.ErrCodeRequestAlreadyProcessedException, "") ||
isAWSErr(err, acmpca.ErrCodeRequestInProgressException, "") {
return nil
}
return fmt.Errorf("error revoking ACMPCA Certificate: %s", err)
}

return nil
}
Loading