266 lines
7.6 KiB
Go
266 lines
7.6 KiB
Go
|
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
|
||
|
}
|