Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/tls: add locally signed certificates #3930

Merged
merged 1 commit into from
Dec 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions builtin/providers/tls/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import (
func Provider() terraform.ResourceProvider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"tls_private_key": resourcePrivateKey(),
"tls_self_signed_cert": resourceSelfSignedCert(),
"tls_cert_request": resourceCertRequest(),
"tls_private_key": resourcePrivateKey(),
"tls_locally_signed_cert": resourceLocallySignedCert(),
"tls_self_signed_cert": resourceSelfSignedCert(),
"tls_cert_request": resourceCertRequest(),
},
}
}
Expand Down
59 changes: 59 additions & 0 deletions builtin/providers/tls/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-----
`
18 changes: 5 additions & 13 deletions builtin/providers/tls/resource_cert_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/hashicorp/terraform/helper/schema"
)

const pemCertReqType = "CERTIFICATE REQUEST"

func resourceCertRequest() *schema.Resource {
return &schema.Resource{
Create: CreateCertRequest,
Expand Down Expand Up @@ -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{})
Expand Down Expand Up @@ -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)
Expand Down
210 changes: 210 additions & 0 deletions builtin/providers/tls/resource_certificate.go
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
}
Loading