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

TLS utility provider #2778

Merged
merged 1 commit into from
Oct 29, 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
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