Skip to content

Commit

Permalink
Merge pull request #845 from MikaelSmith/genCaCert
Browse files Browse the repository at this point in the history
Generate CA and certificates from CA
  • Loading branch information
emosbaugh committed Jul 24, 2020
2 parents 8c753b6 + c863c92 commit a5ee157
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
64 changes: 63 additions & 1 deletion pkg/template/static_context.go
Expand Up @@ -51,6 +51,7 @@ type TLSPair struct {
}

var tlsMap = map[string]TLSPair{}
var caMap = map[string]TLSPair{}

func (ctx StaticCtx) FuncMap() template.FuncMap {
funcMap := sprig.TxtFuncMap()
Expand Down Expand Up @@ -82,6 +83,10 @@ func (ctx StaticCtx) FuncMap() template.FuncMap {
funcMap["TLSCert"] = ctx.tlsCert
funcMap["TLSKey"] = ctx.tlsKey

funcMap["TLSCACert"] = ctx.tlsCaCert
funcMap["TLSCertFromCA"] = ctx.tlsCertFromCa
funcMap["TLSKeyFromCA"] = ctx.tlsKeyFromCa

funcMap["IsKurl"] = ctx.isKurl
funcMap["Distribution"] = ctx.distribution
funcMap["NodeCount"] = ctx.nodeCount
Expand Down Expand Up @@ -390,14 +395,71 @@ func (ctx StaticCtx) tlsKey(certName string, args ...interface{}) string {
return p.Key
}

func (ctx StaticCtx) tlsCaCert(caName string, daysValid int) string {
cap, ok := caMap[caName]
if !ok {
cap = genCa(caName, daysValid)
caMap[caName] = cap
}

return cap.Cert
}

func (ctx StaticCtx) tlsCertFromCa(caName, certName, cn string, ips, alternateDNS []interface{}, daysValid int) string {
key := fmt.Sprintf("%s:%s:%s", caName, certName, cn)
if p, ok := tlsMap[key]; ok {
return p.Cert
}

p := genSignedCert(caName, cn, ips, alternateDNS, daysValid)
tlsMap[key] = p
return p.Cert
}

func (ctx StaticCtx) tlsKeyFromCa(caName, certName, cn string, ips, alternateDNS []interface{}, daysValid int) string {
key := fmt.Sprintf("%s:%s:%s", caName, certName, cn)
if p, ok := tlsMap[key]; ok {
return p.Key
}

p := genSignedCert(caName, cn, ips, alternateDNS, daysValid)
tlsMap[key] = p
return p.Key
}

func genCa(cn string, daysValid int) TLSPair {
tmplate := `cert: {{ $i := genCA %q %d }}{{ $i.Cert | b64enc }}
key: {{ $i.Key | b64enc }}`
return genCertAndKey(cn, fmt.Sprintf(tmplate, cn, daysValid))
}

func genSignedCert(ca, cn string, ips []interface{}, alternateDNS []interface{}, daysValid int) TLSPair {
tmplate := `cert: {{ $ca := buildCustomCert %q %q }}{{ $i := genSignedCert %q %s %s %d $ca }}{{ $i.Cert | b64enc }}
key: {{ $i.Key | b64enc }}`

cap, ok := caMap[ca]
if !ok {
cap = genCa(ca, daysValid)
caMap[ca] = cap
}

caCert := base64.StdEncoding.EncodeToString([]byte(cap.Cert))
caKey := base64.StdEncoding.EncodeToString([]byte(cap.Key))
ipList := arrayToTemplateList(ips)
nameList := arrayToTemplateList(alternateDNS)
return genCertAndKey(cn, fmt.Sprintf(tmplate, caCert, caKey, cn, ipList, nameList, daysValid))
}

func genSelfSignedCert(cn string, ips []interface{}, alternateDNS []interface{}, daysValid int) TLSPair {
tmplate := `cert: {{ $i := genSelfSignedCert %q %s %s %d }}{{ $i.Cert | b64enc }}
key: {{ $i.Key | b64enc }}`

ipList := arrayToTemplateList(ips)
nameList := arrayToTemplateList(alternateDNS)
templated := fmt.Sprintf(tmplate, cn, ipList, nameList, daysValid)
return genCertAndKey(cn, fmt.Sprintf(tmplate, cn, ipList, nameList, daysValid))
}

func genCertAndKey(cn, templated string) TLSPair {
parsed, err := template.New("cn").Funcs(sprig.GenericFuncMap()).Parse(templated)
if err != nil {
fmt.Printf("Failed to evaluate cert template: %v\n", err)
Expand Down
88 changes: 88 additions & 0 deletions pkg/template/static_context_test.go
@@ -1,6 +1,9 @@
package template

import (
"crypto/x509"
"encoding/pem"
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -77,3 +80,88 @@ func TestSprigRandom(t *testing.T) {
req.NoError(err)
req.Len(randAlphaNum, 50)
}

func validateAndClearCaCert(req *require.Assertions, builder Builder) {
caCert, err := builder.String(`{{repl TLSCACert "my-ca" 365}}`)
req.NoError(err)

cert, err := getCert(caCert)
req.NoError(err)
req.NotZero(cert.KeyUsage & x509.KeyUsageCertSign)

expected := caMap["my-ca"]
req.Equal(expected.Cert, caCert)
delete(caMap, "my-ca")
}

func TestTlsCaCert(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)

builder := Builder{}
builder.AddCtx(StaticCtx{})
validateAndClearCaCert(req, builder)
}

func TestTlsCertFromCa(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)

builder := Builder{}
builder.AddCtx(StaticCtx{})

cert, err := builder.String(`{{repl TLSCertFromCA "my-ca" "my-cert" "mine.example.com" nil nil 365}}`)
req.NoError(err)

certObj, err := getCert(cert)
req.NoError(err)
req.Equal("CN=mine.example.com", certObj.Subject.String())
req.Equal("CN=my-ca", certObj.Issuer.String())

expected := tlsMap["my-ca:my-cert:mine.example.com"]
req.Equal("mine.example.com", expected.Cn)
req.Equal(expected.Cert, cert)

_, err = builder.String(`{{repl TLSKeyFromCA "my-ca" "my-cert" "mine.example.com" nil nil 365}}`)
req.NoError(err)

validateAndClearCaCert(req, builder)
}

func TestTlsKeyFromCa(t *testing.T) {
scopetest := scopeagent.StartTest(t)
defer scopetest.End()
req := require.New(t)

builder := Builder{}
builder.AddCtx(StaticCtx{})

_, err := builder.String(`{{repl TLSKeyFromCA "my-ca" "my-cert" "mine.example.com" nil nil 365}}`)
req.NoError(err)

cert, err := builder.String(`{{repl TLSCertFromCA "my-ca" "my-cert" "mine.example.com" nil nil 365}}`)
req.NoError(err)

certObj, err := getCert(cert)
req.NoError(err)
req.Equal("CN=mine.example.com", certObj.Subject.String())
req.Equal("CN=my-ca", certObj.Issuer.String())

expected := tlsMap["my-ca:my-cert:mine.example.com"]
req.Equal("mine.example.com", expected.Cn)
req.Equal(expected.Cert, cert)
delete(tlsMap, "my-ca:my-cert:mine.example.com")

validateAndClearCaCert(req, builder)
}

func getCert(s string) (*x509.Certificate, error) {
block, _ := pem.Decode([]byte(s))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM: %s", s)
}

return x509.ParseCertificate(block.Bytes)
}

0 comments on commit a5ee157

Please sign in to comment.