Skip to content

Commit

Permalink
Merge pull request #2778 from apparentlymart/tls
Browse files Browse the repository at this point in the history
TLS utility provider
  • Loading branch information
phinze committed Oct 29, 2015
2 parents 371686f + f6fd41e commit 32ae193
Show file tree
Hide file tree
Showing 16 changed files with 1,446 additions and 2 deletions.
12 changes: 12 additions & 0 deletions builtin/bins/provider-tls/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"github.com/hashicorp/terraform/builtin/providers/tls"
"github.com/hashicorp/terraform/plugin"
)

func main() {
plugin.Serve(&plugin.ServeOpts{
ProviderFunc: tls.Provider,
})
}
111 changes: 111 additions & 0 deletions builtin/providers/tls/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package tls

import (
"crypto/sha1"
"crypto/x509/pkix"
"encoding/hex"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

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(),
},
}
}

func hashForState(value string) string {
if value == "" {
return ""
}
hash := sha1.Sum([]byte(strings.TrimSpace(value)))
return hex.EncodeToString(hash[:])
}

func nameFromResourceData(nameMap map[string]interface{}) (*pkix.Name, error) {
result := &pkix.Name{}

if value := nameMap["common_name"]; value != nil {
result.CommonName = value.(string)
}
if value := nameMap["organization"]; value != nil {
result.Organization = []string{value.(string)}
}
if value := nameMap["organizational_unit"]; value != nil {
result.OrganizationalUnit = []string{value.(string)}
}
if value := nameMap["street_address"]; value != nil {
valueI := value.([]interface{})
result.StreetAddress = make([]string, len(valueI))
for i, vi := range valueI {
result.StreetAddress[i] = vi.(string)
}
}
if value := nameMap["locality"]; value != nil {
result.Locality = []string{value.(string)}
}
if value := nameMap["province"]; value != nil {
result.Province = []string{value.(string)}
}
if value := nameMap["country"]; value != nil {
result.Country = []string{value.(string)}
}
if value := nameMap["postal_code"]; value != nil {
result.PostalCode = []string{value.(string)}
}
if value := nameMap["serial_number"]; value != nil {
result.SerialNumber = value.(string)
}

return result, nil
}

var nameSchema *schema.Resource = &schema.Resource{
Schema: map[string]*schema.Schema{
"organization": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"common_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"organizational_unit": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"street_address": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"locality": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"province": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"country": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"postal_code": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"serial_number": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
36 changes: 36 additions & 0 deletions builtin/providers/tls/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tls

import (
"testing"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)

func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}

var testProviders = map[string]terraform.ResourceProvider{
"tls": Provider(),
}

var testPrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDPLaq43D9C596ko9yQipWUf2FbRhFs18D3wBDBqXLIoP7W3rm5
S292/JiNPa+mX76IYFF416zTBGG9J5w4d4VFrROn8IuMWqHgdXsCUf2szN7EnJcV
BsBzTxxWqz4DjX315vbm/PFOLlKzC0Ngs4h1iDiCD9Hk2MajZuFnJiqj1QIDAQAB
AoGAG6eQ3lQn7Zpd0cQ9sN2O0d+e8zwLH2g9TdTJZ9Bijf1Phwb764vyOQPGqTPO
unqVSEbzGRpQ62nuUf1zkOYDV+gKMNO3mj9Zu+qPNr/nQPHIaGZksPdD34qDUnBl
eRWVGNTyEGQsRPNN0RtFj8ifa4+OWiE30n95PBq2bUGZj4ECQQDZvS5X/4jYxnzw
CscaL4vO9OCVd/Fzdpfak0DQE/KCVmZxzcXu6Q8WuhybCynX84WKHQxuFAo+nBvr
kgtWXX7dAkEA85Vs5ehuDujBKCu3NJYI2R5ie49L9fEMFJVZK9FpkKacoAkET5BZ
UzaZrx4Fg3Zhcv1TssZKSyle+2lYiIydWQJBAMW8/aJi6WdcUsg4MXrBZSlsz6xO
AhOGxv90LS8KfnkJd/2wDyoZs19DY4kWSUjZ2hOEr+4j+u3DHcQAnJUxUW0CQGXP
DrUJcPbKUfF4VBqmmwwkpwT938Hr/iCcS6kE3hqXiN9a5XJb4vnk2FdZNPS9hf2J
5HHUbzj7EbgDT/3CyAECQG0qv6LNQaQMm2lmQKmqpi43Bqj9wvx0xGai1qCOvSeL
rpxCHbX0xSJh0s8j7exRHMF8W16DHjjkc265YdWPXWo=
-----END RSA PRIVATE KEY-----
`
135 changes: 135 additions & 0 deletions builtin/providers/tls/resource_cert_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package tls

import (
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"net"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceCertRequest() *schema.Resource {
return &schema.Resource{
Create: CreateCertRequest,
Delete: DeleteCertRequest,
Read: ReadCertRequest,

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,
},
},

"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_request_pem": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

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)
if err != nil {
return fmt.Errorf("failed to decode private_key_pem: %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)
}

certReq := x509.CertificateRequest{
Subject: *subject,
}

dnsNamesI := d.Get("dns_names").([]interface{})
for _, nameI := range dnsNamesI {
certReq.DNSNames = append(certReq.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))
}
certReq.IPAddresses = append(certReq.IPAddresses, ip)
}

certReqBytes, err := x509.CreateCertificateRequest(rand.Reader, &certReq, key)
if err != nil {
fmt.Errorf("Error creating certificate request: %s", err)
}
certReqPem := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: certReqBytes}))

d.SetId(hashForState(string(certReqBytes)))
d.Set("cert_request_pem", certReqPem)

return nil
}

func DeleteCertRequest(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}

func ReadCertRequest(d *schema.ResourceData, meta interface{}) error {
return nil
}
Loading

0 comments on commit 32ae193

Please sign in to comment.