terraform/builtin/providers/tls/resource_self_signed_cert.go

266 lines
7.6 KiB
Go
Raw Normal View History

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,
Schema: map[string]*schema.Schema{
"dns_names": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Description: "List of DNS names to use as subjects of the certificate",
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"ip_addresses": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Description: "List of IP addresses to use as subjects of the certificate",
ForceNew: true,
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{
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{
Type: schema.TypeString,
Required: true,
Description: "PEM-encoded private key that the certificate will belong to",
ForceNew: true,
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,
},
},
}
}
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)
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)
}
subjectConfs := d.Get("subject").([]interface{})
if len(subjectConfs) != 1 {
return fmt.Errorf("must have exactly one 'subject' block")
}
subjectConf := subjectConfs[0].(map[string]interface{})
subject, err := nameFromResourceData(subjectConf)
if err != nil {
return fmt.Errorf("invalid subject block: %s", err)
}
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))
}
ipAddressesI := d.Get("ip_addresses").([]interface{})
for _, ipStrI := range ipAddressesI {
ip := net.ParseIP(ipStrI.(string))
if ip == nil {
return fmt.Errorf("invalid IP address %#v", ipStrI.(string))
}
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
}