feat(ssl): use cfssl engine
This commit is contained in:
parent
eff93b8908
commit
4fdd0e3dfd
@ -4,10 +4,13 @@ import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cloudflare/cfssl/csr"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"novit.nc/direktil/pkg/clustersconfig"
|
||||
"novit.nc/direktil/pkg/config"
|
||||
@ -60,6 +63,53 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
|
||||
|
||||
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{}{
|
||||
"static_pods": func(name string) (string, error) {
|
||||
t := ctx.clusterConfig.StaticPodsTemplate(name)
|
||||
@ -76,6 +126,46 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
|
||||
|
||||
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))
|
||||
@ -83,6 +173,13 @@ func (ctx *renderContext) Config() (ba []byte, cfg *config.Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if secretData.Changed() {
|
||||
err = secretData.Save()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ba = buf.Bytes()
|
||||
|
||||
cfg = &config.Config{}
|
||||
|
183
secrets.go
183
secrets.go
@ -1,12 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -14,6 +23,180 @@ var (
|
||||
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 {
|
||||
Get(ref string) (string, error)
|
||||
Set(ref, value string) error
|
||||
|
8
ssl.go
8
ssl.go
@ -13,6 +13,10 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/config"
|
||||
|
||||
"novit.nc/direktil/pkg/clustersconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -21,6 +25,10 @@ const (
|
||||
ECPrivateKeyBlockType = "EC PRIVATE KEY"
|
||||
)
|
||||
|
||||
func sslConfig(cfg *clustersconfig.Config) (*config.Config, error) {
|
||||
return config.LoadConfig([]byte(cfg.SSLConfig))
|
||||
}
|
||||
|
||||
func PrivateKeyPEM() (*ecdsa.PrivateKey, []byte) {
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user