Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for the AWS Certificate Manager Private Certificate Authority.
- Loading branch information
1 parent
0696bfc
commit 075df4d
Showing
103 changed files
with
16,300 additions
and
36 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
aws_secret_test.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package aws | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/acmpca" | ||
iface "github.com/aws/aws-sdk-go-v2/service/acmpca/acmpcaiface" | ||
|
||
"github.com/johanbrandhorst/certify" | ||
) | ||
|
||
// Issuer implements the Issuer interface with a | ||
// AWS Certificate Manager Private Certificate Authority backend. | ||
// | ||
// Client and CertificateAuthorityARN are required. | ||
type Issuer struct { | ||
// Client is a pre-created ACMPCA client. It can be created | ||
// via, for example: | ||
// conf, err := external.LoadDefaultAWSConfig() | ||
// if err != nil { | ||
// return nil, err | ||
// } | ||
// conf.Region = endpoints.EuWest2RegionID | ||
// conf.Credentials = aws.NewStaticCredentialsProvider("YOURKEY", "YOURKEYSECRET", "") | ||
// cli := acmpca.New(conf) | ||
Client iface.ACMPCAAPI | ||
// CertificateAuthorityARN specifies the ARN of a pre-created CA | ||
// which will be used to issue the certificates. | ||
CertificateAuthorityARN string | ||
|
||
// TimeToLive configures the lifetime of certificates | ||
// requested from the AWS CA, in number of days. | ||
// If unset, defaults to 30 days. | ||
TimeToLive int | ||
|
||
caCert *x509.Certificate | ||
signAlgo acmpca.SigningAlgorithm | ||
} | ||
|
||
// Issue issues a certificate from the configured AWS CA backend. | ||
func (i Issuer) Issue(ctx context.Context, commonName string, conf *certify.CertConfig) (*tls.Certificate, error) { | ||
if i.caCert == nil { | ||
caReq := i.Client.GetCertificateAuthorityCertificateRequest(&acmpca.GetCertificateAuthorityCertificateInput{ | ||
CertificateAuthorityArn: aws.String(i.CertificateAuthorityARN), | ||
}) | ||
|
||
caResp, err := caReq.Send() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
caBlock, _ := pem.Decode([]byte(*caResp.Certificate)) | ||
if caBlock == nil { | ||
return nil, errors.New("could not parse AWS CA cert") | ||
} | ||
|
||
if caBlock.Type != "CERTIFICATE" { | ||
return nil, errors.New("saw unexpected PEM Type while requesting AWS CA cert: " + caBlock.Type) | ||
} | ||
|
||
i.caCert, err = x509.ParseCertificate(caBlock.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
switch i.caCert.SignatureAlgorithm { | ||
case x509.SHA256WithRSA: | ||
i.signAlgo = acmpca.SigningAlgorithmSha256withrsa | ||
case x509.SHA384WithRSA: | ||
i.signAlgo = acmpca.SigningAlgorithmSha384withrsa | ||
case x509.SHA512WithRSA: | ||
i.signAlgo = acmpca.SigningAlgorithmSha512withrsa | ||
case x509.ECDSAWithSHA256: | ||
i.signAlgo = acmpca.SigningAlgorithmSha256withecdsa | ||
case x509.ECDSAWithSHA384: | ||
i.signAlgo = acmpca.SigningAlgorithmSha384withecdsa | ||
case x509.ECDSAWithSHA512: | ||
i.signAlgo = acmpca.SigningAlgorithmSha512withecdsa | ||
default: | ||
return nil, fmt.Errorf("unsupported CA cert signing algorithm: %T", i.caCert.SignatureAlgorithm) | ||
} | ||
} | ||
|
||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
keyBytes, err := x509.MarshalECPrivateKey(pk) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
keyPEM := pem.EncodeToMemory(&pem.Block{ | ||
Type: "EC PRIVATE KEY", | ||
Bytes: keyBytes, | ||
}) | ||
|
||
template := &x509.CertificateRequest{ | ||
SignatureAlgorithm: x509.ECDSAWithSHA256, | ||
Subject: pkix.Name{ | ||
CommonName: commonName, | ||
}, | ||
} | ||
|
||
if conf != nil { | ||
template.DNSNames = conf.SubjectAlternativeNames | ||
template.IPAddresses = conf.IPSubjectAlternativeNames | ||
} | ||
|
||
csr, err := x509.CreateCertificateRequest(rand.Reader, template, pk) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
csrPEM := pem.EncodeToMemory(&pem.Block{ | ||
Type: "CERTIFICATE REQUEST", | ||
Bytes: csr, | ||
}) | ||
|
||
// Default to 30 days if unset. | ||
ttl := int64(30) | ||
if i.TimeToLive > 0 { | ||
ttl = int64(i.TimeToLive) | ||
} | ||
|
||
csrReq := i.Client.IssueCertificateRequest(&acmpca.IssueCertificateInput{ | ||
CertificateAuthorityArn: aws.String(i.CertificateAuthorityARN), | ||
Csr: csrPEM, | ||
SigningAlgorithm: i.signAlgo, | ||
Validity: &acmpca.Validity{ | ||
Type: acmpca.ValidityPeriodTypeDays, | ||
Value: aws.Int64(ttl), | ||
}, | ||
}) | ||
|
||
csrResp, err := csrReq.Send() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
getReq := &acmpca.GetCertificateInput{ | ||
CertificateArn: csrResp.CertificateArn, | ||
CertificateAuthorityArn: aws.String(i.CertificateAuthorityARN), | ||
} | ||
err = i.Client.WaitUntilCertificateIssuedWithContext(ctx, getReq) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
certReq := i.Client.GetCertificateRequest(getReq) | ||
|
||
certResp, err := certReq.Send() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
caChainPEM := append([]byte(*certResp.Certificate), []byte(*certResp.CertificateChain)...) | ||
|
||
tlsCert, err := tls.X509KeyPair(caChainPEM, keyPEM) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// This can't error since it's called in tls.X509KeyPair above successfully | ||
tlsCert.Leaf, _ = x509.ParseCertificate(tlsCert.Certificate[0]) | ||
return &tlsCert, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package aws_test | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestAws(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "Aws Suite") | ||
} |
Oops, something went wrong.