feat(ssl): use cfssl engine
This commit is contained in:
parent
eff93b8908
commit
4fdd0e3dfd
@ -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{}
|
||||||
|
183
secrets.go
183
secrets.go
@ -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
8
ssl.go
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user