provider/tls: add locally signed certificates
This allows you to generate and sign certificates using a local CA.
This commit is contained in:
parent
2dbd9b47e4
commit
309e697a52
|
@ -14,6 +14,7 @@ func Provider() terraform.ResourceProvider {
|
|||
return &schema.Provider{
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"tls_private_key": resourcePrivateKey(),
|
||||
"tls_locally_signed_cert": resourceLocallySignedCert(),
|
||||
"tls_self_signed_cert": resourceSelfSignedCert(),
|
||||
"tls_cert_request": resourceCertRequest(),
|
||||
},
|
||||
|
|
|
@ -34,3 +34,62 @@ DrUJcPbKUfF4VBqmmwwkpwT938Hr/iCcS6kE3hqXiN9a5XJb4vnk2FdZNPS9hf2J
|
|||
rpxCHbX0xSJh0s8j7exRHMF8W16DHjjkc265YdWPXWo=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
var testCertRequest = `
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICYDCCAckCAQAwgcUxFDASBgNVBAMMC2V4YW1wbGUuY29tMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVBpcmF0ZSBIYXJib3IxGTAXBgNVBAkM
|
||||
EDU4NzkgQ290dG9uIExpbmsxEzARBgNVBBEMCjk1NTU5LTEyMjcxFTATBgNVBAoM
|
||||
DEV4YW1wbGUsIEluYzEoMCYGA1UECwwfRGVwYXJ0bWVudCBvZiBUZXJyYWZvcm0g
|
||||
VGVzdGluZzEKMAgGA1UEBRMBMjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
||||
qLFq7Tpmlt0uDCCn5bA/oTj4v16/pXXaD+Ice2bS4rBH2UUM2gca5U4j8QCxrIxh
|
||||
91mBvloE4VS5xrIGotAwoMgwK3E2md5kzQJToDve/hm8JNOcms+OAOjfjajPc40e
|
||||
+ue9roT8VjWGU0wz7ttQNuao56GXYr5kOpcfiZMs7RcCAwEAAaBaMFgGCSqGSIb3
|
||||
DQEJDjFLMEkwLwYDVR0RBCgwJoILZXhhbXBsZS5jb22CC2V4YW1wbGUubmV0hwR/
|
||||
AAABhwR/AAACMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAGEDWUYnGygtnvScamz3o4PuVMFubBfqIdWCu02hBgzL3Hi3/UkOEsV028GM
|
||||
M3YMB+it7U8eDdT2XjzBDlvpxWT1hXWnmJFu6z6B8N/JFk8fOkaP7U6YjZlG5N9m
|
||||
L1A4WtQz0SgXcnIujKisqIaymYrvpANnm4IsqTKsnwZD7CsQ
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
`
|
||||
|
||||
var testCAPrivateKey = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC7QNFtw54heoD9KL2s2Qr7utKZFM/8GXYHh3Y5/Zis9USlJ7Mc
|
||||
Lorbmm9Lopnr5zUBZULAxAgX51X0FbifK8Re3JIZvpFRyxNw8aWYBnOk/sX7UhUH
|
||||
pI139dSAhkNAMkRQd1ySpDP+4okCptgZPs7h0bXwoYmWMNFKlaRZHuAQLQIDAQAB
|
||||
AoGAQ/YwjLAU8n2t1zQ0M0nLDLYvvVOqcQskpXLq2/1Irm2OborMHQxfZXjVsBPh
|
||||
3ZbazBjec2wyq8pQjfhcO5j8+fj9zLtRNDpWEa9t/VDky0MSGezQyLL1J5+htFDJ
|
||||
JDCkKK441IWKGCMC31hoVP6PvE/3G2+vWAkrkT4U7ekLQVkCQQD1/RKMxDFJ57Qr
|
||||
Zlu1y72dnGLsGqoxeNaco6G5JXAEEcWTx8qXghKQX0uHxooeRYQRupOGLBo1Js1p
|
||||
/AZDR8inAkEAwt/J0GDsojV89RbpJ0h7C1kcxNULooCYQZs/rmJcVXSs6pUIIFdI
|
||||
oYQIEGnRsfQUPo6EUUGMKh8sSEjF6R8nCwJBAMKYuoT7a9aAYwp2RhTSIaW+oo8P
|
||||
JRZP9s8hr31tPWkqufeHdSBYOOFXUcQObxM1gR4ZUD0zRGRJ1vSB+F5fOj8CQEuG
|
||||
HZnTpoHrBuWZnnyp+33XaG3kP2EYQ2nRuClmV3CLCmTTo1WdXjmyiMmLqUg1Vw8z
|
||||
fpZbN+4vLKNLCOCjQScCQDWmNDrie4Omd5wWKV5B+LVZO8/xMlub6IEioZpMfDGZ
|
||||
q1Ov/Qw2ge3yumfO+6GzKG0k13yYEn1AcatF5lP8BYY=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
var testCACert = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDVTCCAr6gAwIBAgIJALLsVgWAcCvxMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
|
||||
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNUGlyYXRlIEhhcmJvcjEVMBMG
|
||||
A1UEChMMRXhhbXBsZSwgSW5jMSEwHwYDVQQLExhEZXBhcnRtZW50IG9mIENBIFRl
|
||||
c3RpbmcxDTALBgNVBAMTBHJvb3QwHhcNMTUxMTE0MTY1MTQ0WhcNMTUxMjE0MTY1
|
||||
MTQ0WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVBpcmF0
|
||||
ZSBIYXJib3IxFTATBgNVBAoTDEV4YW1wbGUsIEluYzEhMB8GA1UECxMYRGVwYXJ0
|
||||
bWVudCBvZiBDQSBUZXN0aW5nMQ0wCwYDVQQDEwRyb290MIGfMA0GCSqGSIb3DQEB
|
||||
AQUAA4GNADCBiQKBgQC7QNFtw54heoD9KL2s2Qr7utKZFM/8GXYHh3Y5/Zis9USl
|
||||
J7McLorbmm9Lopnr5zUBZULAxAgX51X0FbifK8Re3JIZvpFRyxNw8aWYBnOk/sX7
|
||||
UhUHpI139dSAhkNAMkRQd1ySpDP+4okCptgZPs7h0bXwoYmWMNFKlaRZHuAQLQID
|
||||
AQABo4HgMIHdMB0GA1UdDgQWBBQyrsMhTd85ATqm9vNybTtAbwnGkDCBrQYDVR0j
|
||||
BIGlMIGigBQyrsMhTd85ATqm9vNybTtAbwnGkKF/pH0wezELMAkGA1UEBhMCVVMx
|
||||
CzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1QaXJhdGUgSGFyYm9yMRUwEwYDVQQKEwxF
|
||||
eGFtcGxlLCBJbmMxITAfBgNVBAsTGERlcGFydG1lbnQgb2YgQ0EgVGVzdGluZzEN
|
||||
MAsGA1UEAxMEcm9vdIIJALLsVgWAcCvxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
|
||||
AQEFBQADgYEAuJ7JGZlSzbQOuAFz2t3c1pQzUIiS74blFbg6RPvNPSSjoBg3Ly61
|
||||
FbliR8P3qiSWA/X03/XSMTH1XkHU8re+P0uILUzLJkKBkdHJfdwfk8kifDjdO14+
|
||||
tffPaqAEFUkwhbiQUoj9aeTOOS6kEjbMV6+o7fsz5pPUHbj/l4idys0=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
const pemCertReqType = "CERTIFICATE REQUEST"
|
||||
|
||||
func resourceCertRequest() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: CreateCertRequest,
|
||||
|
@ -71,19 +73,9 @@ func resourceCertRequest() *schema.Resource {
|
|||
}
|
||||
|
||||
func CreateCertRequest(d *schema.ResourceData, meta interface{}) error {
|
||||
keyAlgoName := d.Get("key_algorithm").(string)
|
||||
var keyFunc keyParser
|
||||
var ok bool
|
||||
if keyFunc, ok = keyParsers[keyAlgoName]; !ok {
|
||||
return fmt.Errorf("invalid key_algorithm %#v", keyAlgoName)
|
||||
}
|
||||
keyBlock, _ := pem.Decode([]byte(d.Get("private_key_pem").(string)))
|
||||
if keyBlock == nil {
|
||||
return fmt.Errorf("no PEM block found in private_key_pem")
|
||||
}
|
||||
key, err := keyFunc(keyBlock.Bytes)
|
||||
key, err := parsePrivateKey(d, "private_key_pem", "key_algorithm")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode private_key_pem: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
subjectConfs := d.Get("subject").([]interface{})
|
||||
|
@ -117,7 +109,7 @@ func CreateCertRequest(d *schema.ResourceData, meta interface{}) error {
|
|||
if err != nil {
|
||||
fmt.Errorf("Error creating certificate request: %s", err)
|
||||
}
|
||||
certReqPem := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: certReqBytes}))
|
||||
certReqPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertReqType, Bytes: certReqBytes}))
|
||||
|
||||
d.SetId(hashForState(string(certReqBytes)))
|
||||
d.Set("cert_request_pem", certReqPem)
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
const pemCertType = "CERTIFICATE"
|
||||
|
||||
var keyUsages map[string]x509.KeyUsage = map[string]x509.KeyUsage{
|
||||
"digital_signature": x509.KeyUsageDigitalSignature,
|
||||
"content_commitment": x509.KeyUsageContentCommitment,
|
||||
"key_encipherment": x509.KeyUsageKeyEncipherment,
|
||||
"data_encipherment": x509.KeyUsageDataEncipherment,
|
||||
"key_agreement": x509.KeyUsageKeyAgreement,
|
||||
"cert_signing": x509.KeyUsageCertSign,
|
||||
"crl_signing": x509.KeyUsageCRLSign,
|
||||
"encipher_only": x509.KeyUsageEncipherOnly,
|
||||
"decipher_only": x509.KeyUsageDecipherOnly,
|
||||
}
|
||||
|
||||
var extKeyUsages map[string]x509.ExtKeyUsage = map[string]x509.ExtKeyUsage{
|
||||
"any_extended": x509.ExtKeyUsageAny,
|
||||
"server_auth": x509.ExtKeyUsageServerAuth,
|
||||
"client_auth": x509.ExtKeyUsageClientAuth,
|
||||
"code_signing": x509.ExtKeyUsageCodeSigning,
|
||||
"email_protection": x509.ExtKeyUsageEmailProtection,
|
||||
"ipsec_end_system": x509.ExtKeyUsageIPSECEndSystem,
|
||||
"ipsec_tunnel": x509.ExtKeyUsageIPSECTunnel,
|
||||
"ipsec_user": x509.ExtKeyUsageIPSECUser,
|
||||
"timestamping": x509.ExtKeyUsageTimeStamping,
|
||||
"ocsp_signing": x509.ExtKeyUsageOCSPSigning,
|
||||
"microsoft_server_gated_crypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
||||
"netscape_server_gated_crypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
||||
}
|
||||
|
||||
// rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key.
|
||||
type rsaPublicKey struct {
|
||||
N *big.Int
|
||||
E int
|
||||
}
|
||||
|
||||
// generateSubjectKeyID generates a SHA-1 hash of the subject public key.
|
||||
func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
|
||||
var publicKeyBytes []byte
|
||||
var err error
|
||||
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
publicKeyBytes, err = asn1.Marshal(rsaPublicKey{N: pub.N, E: pub.E})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
publicKeyBytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
||||
default:
|
||||
return nil, errors.New("only RSA and ECDSA public keys supported")
|
||||
}
|
||||
|
||||
hash := sha1.Sum(publicKeyBytes)
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
func resourceCertificateCommonSchema() map[string]*schema.Schema {
|
||||
return map[string]*schema.Schema{
|
||||
"validity_period_hours": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Description: "Number of hours that the certificate will remain valid for",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"early_renewal_hours": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 0,
|
||||
Description: "Number of hours before the certificates expiry when a new certificate will be generated",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"is_ca_certificate": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether the generated certificate will be usable as a CA certificate",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"allowed_uses": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Description: "Uses that are allowed for the certificate",
|
||||
ForceNew: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
"cert_pem": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"validity_start_time": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"validity_end_time": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createCertificate(d *schema.ResourceData, template, parent *x509.Certificate, pub crypto.PublicKey, priv interface{}) error {
|
||||
var err error
|
||||
|
||||
template.NotBefore = time.Now()
|
||||
template.NotAfter = template.NotBefore.Add(time.Duration(d.Get("validity_period_hours").(int)) * time.Hour)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
template.SerialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %s", err)
|
||||
}
|
||||
|
||||
keyUsesI := d.Get("allowed_uses").([]interface{})
|
||||
for _, keyUseI := range keyUsesI {
|
||||
keyUse := keyUseI.(string)
|
||||
if usage, ok := keyUsages[keyUse]; ok {
|
||||
template.KeyUsage |= usage
|
||||
}
|
||||
if usage, ok := extKeyUsages[keyUse]; ok {
|
||||
template.ExtKeyUsage = append(template.ExtKeyUsage, usage)
|
||||
}
|
||||
}
|
||||
|
||||
if d.Get("is_ca_certificate").(bool) {
|
||||
template.IsCA = true
|
||||
|
||||
template.SubjectKeyId, err = generateSubjectKeyID(pub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set subject key identifier: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
|
||||
if err != nil {
|
||||
fmt.Errorf("error creating certificate: %s", err)
|
||||
}
|
||||
certPem := string(pem.EncodeToMemory(&pem.Block{Type: pemCertType, Bytes: certBytes}))
|
||||
|
||||
validFromBytes, err := template.NotBefore.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error serializing validity_start_time: %s", err)
|
||||
}
|
||||
validToBytes, err := template.NotAfter.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error serializing validity_end_time: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(template.SerialNumber.String())
|
||||
d.Set("cert_pem", certPem)
|
||||
d.Set("validity_start_time", string(validFromBytes))
|
||||
d.Set("validity_end_time", string(validToBytes))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteCertificate(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadCertificate(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
endTimeStr := d.Get("validity_end_time").(string)
|
||||
endTime := time.Now()
|
||||
err := endTime.UnmarshalText([]byte(endTimeStr))
|
||||
if err != nil {
|
||||
// If end time is invalid then we'll just throw away the whole
|
||||
// thing so we can generate a new one.
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
earlyRenewalPeriod := time.Duration(-d.Get("early_renewal_hours").(int)) * time.Hour
|
||||
endTime = endTime.Add(earlyRenewalPeriod)
|
||||
|
||||
if time.Now().After(endTime) {
|
||||
// Treat an expired certificate as not existing, so we'll generate
|
||||
// a new one with the next plan.
|
||||
d.SetId("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resourceLocallySignedCert() *schema.Resource {
|
||||
s := resourceCertificateCommonSchema()
|
||||
|
||||
s["cert_request_pem"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "PEM-encoded certificate request",
|
||||
ForceNew: true,
|
||||
StateFunc: func(v interface{}) string {
|
||||
return hashForState(v.(string))
|
||||
},
|
||||
}
|
||||
|
||||
s["ca_key_algorithm"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the algorithm used to generate the certificate's private key",
|
||||
ForceNew: true,
|
||||
}
|
||||
|
||||
s["ca_private_key_pem"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "PEM-encoded CA private key used to sign the certificate",
|
||||
ForceNew: true,
|
||||
StateFunc: func(v interface{}) string {
|
||||
return hashForState(v.(string))
|
||||
},
|
||||
}
|
||||
|
||||
s["ca_cert_pem"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "PEM-encoded CA certificate",
|
||||
ForceNew: true,
|
||||
StateFunc: func(v interface{}) string {
|
||||
return hashForState(v.(string))
|
||||
},
|
||||
}
|
||||
|
||||
return &schema.Resource{
|
||||
Create: CreateLocallySignedCert,
|
||||
Delete: DeleteCertificate,
|
||||
Read: ReadCertificate,
|
||||
Schema: s,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateLocallySignedCert(d *schema.ResourceData, meta interface{}) error {
|
||||
certReq, err := parseCertificateRequest(d, "cert_request_pem")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caKey, err := parsePrivateKey(d, "ca_private_key_pem", "ca_key_algorithm")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
caCert, err := parseCertificate(d, "ca_cert_pem")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cert := x509.Certificate{
|
||||
Subject: certReq.Subject,
|
||||
DNSNames: certReq.DNSNames,
|
||||
IPAddresses: certReq.IPAddresses,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
return createCertificate(d, &cert, caCert, certReq.PublicKey, caKey)
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
r "github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestLocallySignedCert(t *testing.T) {
|
||||
r.Test(t, r.TestCase{
|
||||
Providers: testProviders,
|
||||
Steps: []r.TestStep{
|
||||
r.TestStep{
|
||||
Config: fmt.Sprintf(`
|
||||
resource "tls_locally_signed_cert" "test" {
|
||||
cert_request_pem = <<EOT
|
||||
%s
|
||||
EOT
|
||||
|
||||
validity_period_hours = 1
|
||||
|
||||
allowed_uses = [
|
||||
"key_encipherment",
|
||||
"digital_signature",
|
||||
"server_auth",
|
||||
"client_auth",
|
||||
]
|
||||
|
||||
ca_key_algorithm = "RSA"
|
||||
ca_cert_pem = <<EOT
|
||||
%s
|
||||
EOT
|
||||
ca_private_key_pem = <<EOT
|
||||
%s
|
||||
EOT
|
||||
}
|
||||
output "cert_pem" {
|
||||
value = "${tls_locally_signed_cert.test.cert_pem}"
|
||||
}
|
||||
`, testCertRequest, testCACert, testCAPrivateKey),
|
||||
Check: func(s *terraform.State) error {
|
||||
got := s.RootModule().Outputs["cert_pem"]
|
||||
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") {
|
||||
return fmt.Errorf("key is missing cert PEM preamble")
|
||||
}
|
||||
block, _ := pem.Decode([]byte(got))
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing cert: %s", err)
|
||||
}
|
||||
if expected, got := "2", cert.Subject.SerialNumber; got != expected {
|
||||
return fmt.Errorf("incorrect subject serial number: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "example.com", cert.Subject.CommonName; got != expected {
|
||||
return fmt.Errorf("incorrect subject common name: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "Example, Inc", cert.Subject.Organization[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject organization: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "Department of Terraform Testing", cert.Subject.OrganizationalUnit[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject organizational unit: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "5879 Cotton Link", cert.Subject.StreetAddress[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject street address: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "Pirate Harbor", cert.Subject.Locality[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject locality: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "CA", cert.Subject.Province[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject province: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "US", cert.Subject.Country[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject country: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "95559-1227", cert.Subject.PostalCode[0]; got != expected {
|
||||
return fmt.Errorf("incorrect subject postal code: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 2, len(cert.DNSNames); got != expected {
|
||||
return fmt.Errorf("incorrect number of DNS names: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "example.com", cert.DNSNames[0]; got != expected {
|
||||
return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "example.net", cert.DNSNames[1]; got != expected {
|
||||
return fmt.Errorf("incorrect DNS name 0: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 2, len(cert.IPAddresses); got != expected {
|
||||
return fmt.Errorf("incorrect number of IP addresses: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "127.0.0.1", cert.IPAddresses[0].String(); got != expected {
|
||||
return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := "127.0.0.2", cert.IPAddresses[1].String(); got != expected {
|
||||
return fmt.Errorf("incorrect IP address 0: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := []byte{50, 174, 195, 33, 77, 223, 57, 1, 58, 166, 246, 243, 114, 109, 59, 64, 111, 9, 198, 144}, cert.AuthorityKeyId; !bytes.Equal(got, expected) {
|
||||
return fmt.Errorf("incorrect AuthorityKeyId: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := 2, len(cert.ExtKeyUsage); got != expected {
|
||||
return fmt.Errorf("incorrect number of ExtKeyUsage: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := x509.ExtKeyUsageServerAuth, cert.ExtKeyUsage[0]; got != expected {
|
||||
return fmt.Errorf("incorrect ExtKeyUsage[0]: expected %v, got %v", expected, got)
|
||||
}
|
||||
if expected, got := x509.ExtKeyUsageClientAuth, cert.ExtKeyUsage[1]; got != expected {
|
||||
return fmt.Errorf("incorrect ExtKeyUsage[1]: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
if expected, got := x509.KeyUsageKeyEncipherment|x509.KeyUsageDigitalSignature, cert.KeyUsage; got != expected {
|
||||
return fmt.Errorf("incorrect KeyUsage: expected %v, got %v", expected, got)
|
||||
}
|
||||
|
||||
// This time checking is a bit sloppy to avoid inconsistent test results
|
||||
// depending on the power of the machine running the tests.
|
||||
now := time.Now()
|
||||
if cert.NotBefore.After(now) {
|
||||
return fmt.Errorf("certificate validity begins in the future")
|
||||
}
|
||||
if now.Sub(cert.NotBefore) > (2 * time.Minute) {
|
||||
return fmt.Errorf("certificate validity begins more than two minutes in the past")
|
||||
}
|
||||
if cert.NotAfter.Sub(cert.NotBefore) != time.Hour {
|
||||
return fmt.Errorf("certificate validity is not one hour")
|
||||
}
|
||||
|
||||
caBlock, _ := pem.Decode([]byte(testCACert))
|
||||
caCert, err := x509.ParseCertificate(caBlock.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing ca cert: %s", err)
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
|
||||
// Verify certificate
|
||||
_, err = cert.Verify(x509.VerifyOptions{Roots: certPool})
|
||||
if err == nil {
|
||||
return errors.New("incorrectly verified certificate")
|
||||
} else if _, ok := err.(x509.UnknownAuthorityError); !ok {
|
||||
return fmt.Errorf("incorrect verify error: expected UnknownAuthorityError, got %v", err)
|
||||
}
|
||||
certPool.AddCert(caCert)
|
||||
if _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}); err != nil {
|
||||
return fmt.Errorf("verify failed: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
|
@ -1,53 +1,24 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
var keyUsages map[string]x509.KeyUsage = map[string]x509.KeyUsage{
|
||||
"digital_signature": x509.KeyUsageDigitalSignature,
|
||||
"content_commitment": x509.KeyUsageContentCommitment,
|
||||
"key_encipherment": x509.KeyUsageKeyEncipherment,
|
||||
"data_encipherment": x509.KeyUsageDataEncipherment,
|
||||
"key_agreement": x509.KeyUsageKeyAgreement,
|
||||
"cert_signing": x509.KeyUsageCertSign,
|
||||
"crl_signing": x509.KeyUsageCRLSign,
|
||||
"encipher_only": x509.KeyUsageEncipherOnly,
|
||||
"decipher_only": x509.KeyUsageDecipherOnly,
|
||||
}
|
||||
|
||||
var extKeyUsages map[string]x509.ExtKeyUsage = map[string]x509.ExtKeyUsage{
|
||||
"any_extended": x509.ExtKeyUsageAny,
|
||||
"server_auth": x509.ExtKeyUsageServerAuth,
|
||||
"client_auth": x509.ExtKeyUsageClientAuth,
|
||||
"code_signing": x509.ExtKeyUsageCodeSigning,
|
||||
"email_protection": x509.ExtKeyUsageEmailProtection,
|
||||
"ipsec_end_system": x509.ExtKeyUsageIPSECEndSystem,
|
||||
"ipsec_tunnel": x509.ExtKeyUsageIPSECTunnel,
|
||||
"ipsec_user": x509.ExtKeyUsageIPSECUser,
|
||||
"timestamping": x509.ExtKeyUsageTimeStamping,
|
||||
"ocsp_signing": x509.ExtKeyUsageOCSPSigning,
|
||||
"microsoft_server_gated_crypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
|
||||
"netscape_server_gated_crypto": x509.ExtKeyUsageNetscapeServerGatedCrypto,
|
||||
}
|
||||
|
||||
func resourceSelfSignedCert() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: CreateSelfSignedCert,
|
||||
Delete: DeleteSelfSignedCert,
|
||||
Read: ReadSelfSignedCert,
|
||||
s := resourceCertificateCommonSchema()
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
s["subject"] = &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Elem: nameSchema,
|
||||
ForceNew: true,
|
||||
}
|
||||
|
||||
"dns_names": &schema.Schema{
|
||||
s["dns_names"] = &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Description: "List of DNS names to use as subjects of the certificate",
|
||||
|
@ -55,9 +26,9 @@ func resourceSelfSignedCert() *schema.Resource {
|
|||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
"ip_addresses": &schema.Schema{
|
||||
s["ip_addresses"] = &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Description: "List of IP addresses to use as subjects of the certificate",
|
||||
|
@ -65,48 +36,16 @@ func resourceSelfSignedCert() *schema.Resource {
|
|||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
"validity_period_hours": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Description: "Number of hours that the certificate will remain valid for",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"early_renewal_hours": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 0,
|
||||
Description: "Number of hours before the certificates expiry when a new certificate will be generated",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"is_ca_certificate": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether the generated certificate will be usable as a CA certificate",
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"allowed_uses": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Description: "Uses that are allowed for the certificate",
|
||||
ForceNew: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
|
||||
"key_algorithm": &schema.Schema{
|
||||
s["key_algorithm"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Name of the algorithm to use to generate the certificate's private key",
|
||||
ForceNew: true,
|
||||
},
|
||||
}
|
||||
|
||||
"private_key_pem": &schema.Schema{
|
||||
s["private_key_pem"] = &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "PEM-encoded private key that the certificate will belong to",
|
||||
|
@ -114,56 +53,20 @@ func resourceSelfSignedCert() *schema.Resource {
|
|||
StateFunc: func(v interface{}) string {
|
||||
return hashForState(v.(string))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
"subject": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Required: true,
|
||||
Elem: nameSchema,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"cert_pem": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"validity_start_time": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"validity_end_time": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
return &schema.Resource{
|
||||
Create: CreateSelfSignedCert,
|
||||
Delete: DeleteCertificate,
|
||||
Read: ReadCertificate,
|
||||
Schema: s,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
|
||||
keyAlgoName := d.Get("key_algorithm").(string)
|
||||
var keyFunc keyParser
|
||||
var ok bool
|
||||
if keyFunc, ok = keyParsers[keyAlgoName]; !ok {
|
||||
return fmt.Errorf("invalid key_algorithm %#v", keyAlgoName)
|
||||
}
|
||||
keyBlock, _ := pem.Decode([]byte(d.Get("private_key_pem").(string)))
|
||||
if keyBlock == nil {
|
||||
return fmt.Errorf("no PEM block found in private_key_pem")
|
||||
}
|
||||
key, err := keyFunc(keyBlock.Bytes)
|
||||
key, err := parsePrivateKey(d, "private_key_pem", "key_algorithm")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode private_key_pem: %s", err)
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(time.Duration(d.Get("validity_period_hours").(int)) * time.Hour)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate serial number: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
subjectConfs := d.Get("subject").([]interface{})
|
||||
|
@ -177,24 +80,10 @@ func CreateSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
|
|||
}
|
||||
|
||||
cert := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: *subject,
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
keyUsesI := d.Get("allowed_uses").([]interface{})
|
||||
for _, keyUseI := range keyUsesI {
|
||||
keyUse := keyUseI.(string)
|
||||
if usage, ok := keyUsages[keyUse]; ok {
|
||||
cert.KeyUsage |= usage
|
||||
}
|
||||
if usage, ok := extKeyUsages[keyUse]; ok {
|
||||
cert.ExtKeyUsage = append(cert.ExtKeyUsage, usage)
|
||||
}
|
||||
}
|
||||
|
||||
dnsNamesI := d.Get("dns_names").([]interface{})
|
||||
for _, nameI := range dnsNamesI {
|
||||
cert.DNSNames = append(cert.DNSNames, nameI.(string))
|
||||
|
@ -208,58 +97,5 @@ func CreateSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
|
|||
cert.IPAddresses = append(cert.IPAddresses, ip)
|
||||
}
|
||||
|
||||
if d.Get("is_ca_certificate").(bool) {
|
||||
cert.IsCA = true
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, publicKey(key), key)
|
||||
if err != nil {
|
||||
fmt.Errorf("Error creating certificate: %s", err)
|
||||
}
|
||||
certPem := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}))
|
||||
|
||||
validFromBytes, err := notBefore.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error serializing validity_start_time: %s", err)
|
||||
}
|
||||
validToBytes, err := notAfter.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error serializing validity_end_time: %s", err)
|
||||
}
|
||||
|
||||
d.SetId(serialNumber.String())
|
||||
d.Set("cert_pem", certPem)
|
||||
d.Set("validity_start_time", string(validFromBytes))
|
||||
d.Set("validity_end_time", string(validToBytes))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadSelfSignedCert(d *schema.ResourceData, meta interface{}) error {
|
||||
|
||||
endTimeStr := d.Get("validity_end_time").(string)
|
||||
endTime := time.Now()
|
||||
err := endTime.UnmarshalText([]byte(endTimeStr))
|
||||
if err != nil {
|
||||
// If end time is invalid then we'll just throw away the whole
|
||||
// thing so we can generate a new one.
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
earlyRenewalPeriod := time.Duration(-d.Get("early_renewal_hours").(int)) * time.Hour
|
||||
endTime = endTime.Add(earlyRenewalPeriod)
|
||||
|
||||
if time.Now().After(endTime) {
|
||||
// Treat an expired certificate as not existing, so we'll generate
|
||||
// a new one with the next plan.
|
||||
d.SetId("")
|
||||
}
|
||||
|
||||
return nil
|
||||
return createCertificate(d, &cert, &cert, publicKey(key), key)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func decodePEM(d *schema.ResourceData, pemKey, pemType string) (*pem.Block, error) {
|
||||
block, _ := pem.Decode([]byte(d.Get(pemKey).(string)))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("no PEM block found in %s", pemKey)
|
||||
}
|
||||
if pemType != "" && block.Type != pemType {
|
||||
return nil, fmt.Errorf("invalid PEM type in %s: %s", pemKey, block.Type)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func parsePrivateKey(d *schema.ResourceData, pemKey, algoKey string) (interface{}, error) {
|
||||
algoName := d.Get(algoKey).(string)
|
||||
|
||||
keyFunc, ok := keyParsers[algoName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid %s: %#v", algoKey, algoName)
|
||||
}
|
||||
|
||||
block, err := decodePEM(d, pemKey, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := keyFunc(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode %s: %s", pemKey, err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func parseCertificate(d *schema.ResourceData, pemKey string) (*x509.Certificate, error) {
|
||||
block, err := decodePEM(d, pemKey, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %s", pemKey, err)
|
||||
}
|
||||
if len(certs) < 1 {
|
||||
return nil, fmt.Errorf("no certificates found in %s", pemKey)
|
||||
}
|
||||
if len(certs) > 1 {
|
||||
return nil, fmt.Errorf("multiple certificates found in %s", pemKey)
|
||||
}
|
||||
|
||||
return certs[0], nil
|
||||
}
|
||||
|
||||
func parseCertificateRequest(d *schema.ResourceData, pemKey string) (*x509.CertificateRequest, error) {
|
||||
block, err := decodePEM(d, pemKey, pemCertReqType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certReq, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse %s: %s", pemKey, err)
|
||||
}
|
||||
|
||||
return certReq, nil
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
layout: "tls"
|
||||
page_title: "TLS: tls_locally_signed_cert"
|
||||
sidebar_current: "docs-tls-resourse-locally-signed-cert"
|
||||
description: |-
|
||||
Creates a locally-signed TLS certificate in PEM format.
|
||||
---
|
||||
|
||||
# tls\_locally\_signed\_cert
|
||||
|
||||
Generates a TLS ceritifcate using a *Certificate Signing Request* (CSR) and
|
||||
signs it with a provided certificate authority (CA) private key.
|
||||
|
||||
Locally-signed certificates are generally only trusted by client software when
|
||||
setup to use the provided CA. They are normally used in development environments
|
||||
or when deployed internally to an organization.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "tls_locally_signed_cert" "example" {
|
||||
cert_request_pem = "${file(\"cert_request.pem\")}"
|
||||
|
||||
ca_key_algorithm = "ECDSA"
|
||||
ca_private_key_pem = "${file(\"ca_private_key.pem\")}"
|
||||
ca_cert_pem = "${file(\"ca_cert.pem\")}"
|
||||
|
||||
validity_period_hours = 12
|
||||
|
||||
allowed_uses = [
|
||||
"key_encipherment",
|
||||
"digital_signature",
|
||||
"server_auth",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `cert_request_pem` - (Required) PEM-encoded request certificate data.
|
||||
|
||||
* `ca_key_algorithm` - (Required) The name of the algorithm for the key provided
|
||||
in `ca_private_key_pem`.
|
||||
|
||||
* `ca_private_key_pem` - (Required) PEM-encoded private key data for the CA.
|
||||
This can be read from a separate file using the ``file`` interpolation
|
||||
function.
|
||||
|
||||
* `ca_cert_pem` - (Required) PEM-encoded certificate data for the CA.
|
||||
|
||||
* `validity_period_hours` - (Required) The number of hours after initial issuing that the
|
||||
certificate will become invalid.
|
||||
|
||||
* `allowed_uses` - (Required) List of keywords each describing a use that is permitted
|
||||
for the issued certificate. The valid keywords are listed below.
|
||||
|
||||
* `early_renewal_hours` - (Optional) If set, the resource will consider the certificate to
|
||||
have expired the given number of hours before its actual expiry time. This can be useful
|
||||
to deploy an updated certificate in advance of the expiration of the current certificate.
|
||||
Note however that the old certificate remains valid until its true expiration time, since
|
||||
this resource does not (and cannot) support certificate revocation. Note also that this
|
||||
advance update can only be performed should the Terraform configuration be applied during the
|
||||
early renewal period.
|
||||
|
||||
* `is_ca_certificate` - (Optional) Boolean controlling whether the CA flag will be set in the
|
||||
generated certificate. Defaults to `false`, meaning that the certificate does not represent
|
||||
a certificate authority.
|
||||
|
||||
The `allowed_uses` list accepts the following keywords, combining the set of flags defined by
|
||||
both [Key Usage](https://tools.ietf.org/html/rfc5280#section-4.2.1.3) and
|
||||
[Extended Key Usage](https://tools.ietf.org/html/rfc5280#section-4.2.1.12) in
|
||||
[RFC5280](https://tools.ietf.org/html/rfc5280):
|
||||
|
||||
* `digital_signature`
|
||||
* `content_commitment`
|
||||
* `key_encipherment`
|
||||
* `data_encipherment`
|
||||
* `key_agreement`
|
||||
* `cert_signing`
|
||||
* `encipher_only`
|
||||
* `decipher_only`
|
||||
* `any_extended`
|
||||
* `server_auth`
|
||||
* `client_auth`
|
||||
* `code_signing`
|
||||
* `email_protection`
|
||||
* `ipsec_end_system`
|
||||
* `ipsec_tunnel`
|
||||
* `ipsec_user`
|
||||
* `timestamping`
|
||||
* `ocsp_signing`
|
||||
* `microsoft_server_gated_crypto`
|
||||
* `netscape_server_gated_crypto`
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `cert_pem` - The certificate data in PEM format.
|
||||
* `validity_start_time` - The time after which the certificate is valid, as an
|
||||
[RFC3339](https://tools.ietf.org/html/rfc3339) timestamp.
|
||||
* `validity_end_time` - The time until which the certificate is invalid, as an
|
||||
[RFC3339](https://tools.ietf.org/html/rfc3339) timestamp.
|
||||
|
||||
## Automatic Renewal
|
||||
|
||||
This resource considers its instances to have been deleted after either their validity
|
||||
periods ends or the early renewal period is reached. At this time, applying the
|
||||
Terraform configuration will cause a new certificate to be generated for the instance.
|
||||
|
||||
Therefore in a development environment with frequent deployments it may be convenient
|
||||
to set a relatively-short expiration time and use early renewal to automatically provision
|
||||
a new certificate when the current one is about to expire.
|
||||
|
||||
The creation of a new certificate may of course cause dependent resources to be updated
|
||||
or replaced, depending on the lifecycle rules applying to those resources.
|
Loading…
Reference in New Issue