-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provider/tls: add locally signed certificates
This allows you to generate and sign certificates using a local CA.
- Loading branch information
Showing
9 changed files
with
763 additions
and
230 deletions.
There are no files selected for viewing
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
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,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 | ||
} |
Oops, something went wrong.