feat(ssl): use cfssl engine

This commit is contained in:
Mikaël Cluseau 2018-06-16 22:45:27 +11:00
parent eff93b8908
commit 4fdd0e3dfd
3 changed files with 288 additions and 0 deletions

View File

@ -4,10 +4,13 @@ import (
"bytes" "bytes"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"path/filepath" "path/filepath"
"github.com/cloudflare/cfssl/csr"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"novit.nc/direktil/pkg/clustersconfig" "novit.nc/direktil/pkg/clustersconfig"
"novit.nc/direktil/pkg/config" "novit.nc/direktil/pkg/config"
@ -60,6 +63,53 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
ctxMap := ctx.asMap() ctxMap := ctx.asMap()
sslCfg, err := sslConfig(ctx.clusterConfig)
if err != nil {
return
}
secretData, err := loadSecretData(sslCfg)
if err != nil {
return
}
cluster := ctx.Cluster.Name
getKeyCert := func(name string) (kc *KeyCert, err error) {
req := ctx.clusterConfig.CSR(name)
if req == nil {
err = errors.New("no such certificate request")
return
}
if req.CA == "" {
err = errors.New("CA not defined")
return
}
buf := &bytes.Buffer{}
err = req.Execute(buf, ctxMap, nil)
if err != nil {
return
}
certReq := &csr.CertificateRequest{
KeyRequest: csr.NewBasicKeyRequest(),
}
err = json.Unmarshal(buf.Bytes(), certReq)
if err != nil {
log.Print("unmarshal failed on: ", buf)
return
}
if req.PerHost {
name = name + "/" + ctx.Host.Name
}
return secretData.KeyCert(cluster, req.CA, name, req.Profile, req.Label, certReq)
}
extraFuncs := map[string]interface{}{ extraFuncs := map[string]interface{}{
"static_pods": func(name string) (string, error) { "static_pods": func(name string) (string, error) {
t := ctx.clusterConfig.StaticPodsTemplate(name) t := ctx.clusterConfig.StaticPodsTemplate(name)
@ -76,6 +126,46 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
return buf.String(), nil return buf.String(), nil
}, },
"ca_key": func(name string) (s string, err error) {
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Key)
return
},
"ca_crt": func(name string) (s string, err error) {
ca, err := secretData.CA(cluster, name)
if err != nil {
return
}
s = string(ca.Cert)
return
},
"tls_key": func(name string) (s string, err error) {
kc, err := getKeyCert(name)
if err != nil {
return
}
s = string(kc.Key)
return
},
"tls_crt": func(name string) (s string, err error) {
kc, err := getKeyCert(name)
if err != nil {
return
}
s = string(kc.Cert)
return
},
} }
buf := bytes.NewBuffer(make([]byte, 0, 4096)) buf := bytes.NewBuffer(make([]byte, 0, 4096))
@ -83,6 +173,13 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
return return
} }
if secretData.Changed() {
err = secretData.Save()
if err != nil {
return
}
}
ba = buf.Bytes() ba = buf.Bytes()
cfg = &config.Config{} cfg = &config.Config{}

View File

@ -1,12 +1,21 @@
package main package main
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/initca"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
@ -14,6 +23,180 @@ var (
secrets SecretBackend secrets SecretBackend
) )
type SecretData struct {
clusters map[string]*ClusterSecrets
changed bool
config *config.Config
}
type ClusterSecrets struct {
CAs map[string]*CA
}
type CA struct {
Key []byte
Cert []byte
Signed map[string]*KeyCert
}
type KeyCert struct {
Key []byte
Cert []byte
}
func loadSecretData(config *config.Config) (*SecretData, error) {
sd := &SecretData{
clusters: make(map[string]*ClusterSecrets),
changed: false,
config: config,
}
ba, err := ioutil.ReadFile(filepath.Join(*dataDir, "secret-data.json"))
if err != nil {
if os.IsNotExist(err) {
sd.changed = true
return sd, nil
}
return nil, err
}
if err := json.Unmarshal(ba, &sd.clusters); err != nil {
return nil, err
}
return sd, nil
}
func (sd *SecretData) Changed() bool {
return sd.changed
}
func (sd *SecretData) Save() error {
ba, err := json.Marshal(sd.clusters)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(*dataDir, "secret-data.json"), ba, 0600)
}
func (sd *SecretData) cluster(name string) (cs *ClusterSecrets) {
cs, ok := sd.clusters[name]
if ok {
return
}
cs = &ClusterSecrets{
CAs: make(map[string]*CA),
}
sd.clusters[name] = cs
sd.changed = true
return
}
func (sd *SecretData) CA(cluster, name string) (ca *CA, err error) {
cs := sd.cluster(cluster)
ca, ok := cs.CAs[name]
if ok {
return
}
req := &csr.CertificateRequest{
CN: "Direktil Local Server",
KeyRequest: &csr.BasicKeyRequest{
A: "ecdsa",
S: 521, // 256, 384, 521
},
Names: []csr.Name{
{
C: "NC",
O: "novit.nc",
},
},
}
cert, _, key, err := initca.New(req)
if err != nil {
return
}
ca = &CA{
Key: key,
Cert: cert,
Signed: make(map[string]*KeyCert),
}
cs.CAs[name] = ca
sd.changed = true
return
}
func (sd *SecretData) KeyCert(cluster, caName, name, profile, label string, req *csr.CertificateRequest) (kc *KeyCert, err error) {
if req.CA != nil {
err = errors.New("no CA section allowed here")
return
}
ca, err := sd.CA(cluster, caName)
if err != nil {
return
}
kc, ok := ca.Signed[name]
if ok {
return
}
sgr, err := ca.Signer(sd.config.Signing)
if err != nil {
return
}
generator := &csr.Generator{Validator: func(_ *csr.CertificateRequest) error { return nil }}
csr, key, err := generator.ProcessRequest(req)
if err != nil {
return
}
signReq := signer.SignRequest{
Request: string(csr),
Profile: profile,
Label: label,
}
cert, err := sgr.Sign(signReq)
if err != nil {
return
}
kc = &KeyCert{
Key: key,
Cert: cert,
}
ca.Signed[name] = kc
sd.changed = true
return
}
func (ca *CA) Signer(policy *config.Signing) (result *local.Signer, err error) {
caCert, err := helpers.ParseCertificatePEM(ca.Cert)
if err != nil {
return
}
caKey, err := helpers.ParsePrivateKeyPEM(ca.Key)
if err != nil {
return
}
return local.NewSigner(caKey, caCert, signer.DefaultSigAlgo(caKey), policy)
}
type SecretBackend interface { type SecretBackend interface {
Get(ref string) (string, error) Get(ref string) (string, error)
Set(ref, value string) error Set(ref, value string) error

8
ssl.go
View File

@ -13,6 +13,10 @@ import (
"math/big" "math/big"
"net" "net"
"time" "time"
"github.com/cloudflare/cfssl/config"
"novit.nc/direktil/pkg/clustersconfig"
) )
const ( const (
@ -21,6 +25,10 @@ const (
ECPrivateKeyBlockType = "EC PRIVATE KEY" ECPrivateKeyBlockType = "EC PRIVATE KEY"
) )
func sslConfig(cfg *clustersconfig.Config) (*config.Config, error) {
return config.LoadConfig([]byte(cfg.SSLConfig))
}
func PrivateKeyPEM() (*ecdsa.PrivateKey, []byte) { func PrivateKeyPEM() (*ecdsa.PrivateKey, []byte) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {