local-server/cmd/dkl-local-server/secrets.go

417 lines
7.4 KiB
Go
Raw Normal View History

2018-06-12 10:09:47 +00:00
package main
import (
2020-04-22 15:36:04 +00:00
"crypto"
2018-06-19 06:48:28 +00:00
"crypto/rand"
2020-04-22 15:36:04 +00:00
"crypto/x509"
2018-06-19 06:48:28 +00:00
"encoding/base32"
2018-06-16 11:45:27 +00:00
"encoding/json"
"errors"
2018-08-09 13:07:53 +00:00
"fmt"
2018-06-12 10:09:47 +00:00
"io/ioutil"
2018-08-09 13:07:53 +00:00
"net"
2018-06-12 10:09:47 +00:00
"os"
2018-06-16 11:45:27 +00:00
"path/filepath"
2019-04-13 09:36:58 +00:00
"sort"
2019-01-21 22:44:11 +00:00
"sync"
2020-04-22 15:36:04 +00:00
"time"
2018-06-12 10:09:47 +00:00
2019-12-19 15:57:22 +00:00
"github.com/cespare/xxhash"
2020-04-22 15:36:04 +00:00
"github.com/cloudflare/cfssl/certinfo"
2018-06-16 11:45:27 +00:00
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/initca"
2019-01-21 22:44:11 +00:00
"github.com/cloudflare/cfssl/log"
2018-06-16 11:45:27 +00:00
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
2018-08-09 13:07:53 +00:00
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
2018-06-12 10:09:47 +00:00
)
2019-01-21 22:44:11 +00:00
var (
secretData *SecretData
2019-12-03 10:03:20 +00:00
DontSave = false
2019-01-21 22:44:11 +00:00
)
2018-06-16 11:45:27 +00:00
type SecretData struct {
2019-01-21 22:44:11 +00:00
l sync.Mutex
2019-12-19 15:57:22 +00:00
prevHash uint64
2018-06-16 11:45:27 +00:00
clusters map[string]*ClusterSecrets
changed bool
config *config.Config
}
type ClusterSecrets struct {
2019-12-03 10:03:20 +00:00
CAs map[string]*CA
Tokens map[string]string
Passwords map[string]string
SSHKeyPairs map[string][]SSHKeyPair
2018-06-16 11:45:27 +00:00
}
type CA struct {
Key []byte
Cert []byte
Signed map[string]*KeyCert
}
type KeyCert struct {
Key []byte
Cert []byte
ReqHash string
2018-06-16 11:45:27 +00:00
}
2019-01-21 22:44:11 +00:00
func secretDataPath() string {
return filepath.Join(*dataDir, "secret-data.json")
}
func loadSecretData(config *config.Config) (err error) {
log.Info("Loading secret data")
2018-06-16 11:45:27 +00:00
sd := &SecretData{
clusters: make(map[string]*ClusterSecrets),
changed: false,
config: config,
}
2019-01-21 22:44:11 +00:00
ba, err := ioutil.ReadFile(secretDataPath())
2018-06-16 11:45:27 +00:00
if err != nil {
if os.IsNotExist(err) {
sd.changed = true
2019-01-21 22:44:11 +00:00
err = nil
secretData = sd
return
2018-06-16 11:45:27 +00:00
}
2019-01-21 22:44:11 +00:00
return
2018-06-16 11:45:27 +00:00
}
2019-01-21 22:44:11 +00:00
if err = json.Unmarshal(ba, &sd.clusters); err != nil {
return
2018-06-16 11:45:27 +00:00
}
2019-12-19 15:57:22 +00:00
sd.prevHash = xxhash.Sum64(ba)
2019-01-21 22:44:11 +00:00
secretData = sd
return
2018-06-16 11:45:27 +00:00
}
func (sd *SecretData) Changed() bool {
return sd.changed
}
2019-12-19 15:57:22 +00:00
func (sd *SecretData) Save() (err error) {
2019-12-03 10:03:20 +00:00
if DontSave {
2019-12-19 15:57:22 +00:00
return
2019-12-03 10:03:20 +00:00
}
2019-01-21 22:44:11 +00:00
sd.l.Lock()
defer sd.l.Unlock()
2018-06-16 11:45:27 +00:00
ba, err := json.Marshal(sd.clusters)
if err != nil {
2019-12-19 15:57:22 +00:00
return
}
h := xxhash.Sum64(ba)
if h == sd.prevHash {
return
2018-06-16 11:45:27 +00:00
}
2019-12-19 15:57:22 +00:00
log.Info("Saving secret data")
err = ioutil.WriteFile(secretDataPath(), ba, 0600)
if err == nil {
sd.prevHash = h
}
return
2018-06-16 11:45:27 +00:00
}
2019-04-13 09:36:58 +00:00
func newClusterSecrets() *ClusterSecrets {
return &ClusterSecrets{
CAs: make(map[string]*CA),
Tokens: make(map[string]string),
Passwords: make(map[string]string),
}
}
2018-06-16 11:45:27 +00:00
func (sd *SecretData) cluster(name string) (cs *ClusterSecrets) {
cs, ok := sd.clusters[name]
if ok {
return
}
2019-01-21 22:44:11 +00:00
sd.l.Lock()
defer sd.l.Unlock()
log.Info("secret-data: new cluster: ", name)
2019-04-13 09:36:58 +00:00
cs = newClusterSecrets()
2018-06-16 11:45:27 +00:00
sd.clusters[name] = cs
sd.changed = true
return
}
2019-04-13 09:36:58 +00:00
func (sd *SecretData) Passwords(cluster string) (passwords []string) {
cs := sd.cluster(cluster)
passwords = make([]string, 0, len(cs.Passwords))
for name := range cs.Passwords {
passwords = append(passwords, name)
}
sort.Strings(passwords)
return
}
func (sd *SecretData) Password(cluster, name string) (password string) {
cs := sd.cluster(cluster)
if cs.Passwords == nil {
cs.Passwords = make(map[string]string)
}
password = cs.Passwords[name]
return
}
func (sd *SecretData) SetPassword(cluster, name, password string) {
cs := sd.cluster(cluster)
if cs.Passwords == nil {
cs.Passwords = make(map[string]string)
}
cs.Passwords[name] = password
sd.changed = true
}
2018-06-19 06:48:28 +00:00
func (sd *SecretData) Token(cluster, name string) (token string, err error) {
cs := sd.cluster(cluster)
token = cs.Tokens[name]
if token != "" {
return
}
2019-01-21 22:44:11 +00:00
sd.l.Lock()
defer sd.l.Unlock()
log.Info("secret-data: new token in cluster ", cluster, ": ", name)
2018-06-19 06:48:28 +00:00
b := make([]byte, 16)
_, err = rand.Read(b)
if err != nil {
return
}
token = base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b)
cs.Tokens[name] = token
sd.changed = true
return
}
2020-04-22 15:36:04 +00:00
func (sd *SecretData) RenewCACert(cluster, name string) (err error) {
cs := sd.cluster(cluster)
ca := cs.CAs[name]
var cert *x509.Certificate
cert, err = helpers.ParseCertificatePEM(ca.Cert)
if err != nil {
return
}
var signer crypto.Signer
signer, err = helpers.ParsePrivateKeyPEM(ca.Key)
if err != nil {
return
}
newCert, err := initca.RenewFromSigner(cert, signer)
if err != nil {
return
}
sd.l.Lock()
defer sd.l.Unlock()
cs.CAs[name].Cert = newCert
sd.changed = true
return
}
2018-06-16 11:45:27 +00:00
func (sd *SecretData) CA(cluster, name string) (ca *CA, err error) {
cs := sd.cluster(cluster)
ca, ok := cs.CAs[name]
if ok {
2020-04-22 15:36:04 +00:00
checkErr := checkCertUsable(ca.Cert)
2020-04-22 16:40:30 +00:00
if checkErr != nil {
2020-04-22 15:36:04 +00:00
log.Infof("secret-data cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr)
err = sd.RenewCACert(cluster, name)
}
2018-06-16 11:45:27 +00:00
return
}
2019-01-21 22:44:11 +00:00
sd.l.Lock()
defer sd.l.Unlock()
log.Info("secret-data: new CA in cluster ", cluster, ": ", name)
2018-06-16 11:45:27 +00:00
req := &csr.CertificateRequest{
CN: "Direktil Local Server",
2022-04-28 08:01:21 +00:00
KeyRequest: &csr.KeyRequest{
2018-06-16 11:45:27 +00:00
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
}
2020-04-22 15:36:04 +00:00
func checkCertUsable(certPEM []byte) error {
cert, err := certinfo.ParseCertificatePEM(certPEM)
if err != nil {
return err
}
certDuration := cert.NotAfter.Sub(cert.NotBefore)
delayBeforeRegen := certDuration / 3 // TODO allow configuration
if cert.NotAfter.Sub(time.Now()) < delayBeforeRegen {
return errors.New("too old")
}
return nil
}
2018-06-16 11:45:27 +00:00
func (sd *SecretData) KeyCert(cluster, caName, name, profile, label string, req *csr.CertificateRequest) (kc *KeyCert, err error) {
2018-08-09 13:07:53 +00:00
for idx, host := range req.Hosts {
if ip := net.ParseIP(host); ip != nil {
// valid IP (v4 or v6)
continue
}
if host == "*" {
continue
}
if errs := validation.IsDNS1123Subdomain(host); len(errs) == 0 {
continue
}
if errs := validation.IsWildcardDNS1123Subdomain(host); len(errs) == 0 {
continue
}
path := field.NewPath(cluster, name, "hosts").Index(idx)
return nil, fmt.Errorf("%v: %q is not an IP or FQDN", path, host)
}
2018-06-16 11:45:27 +00:00
if req.CA != nil {
err = errors.New("no CA section allowed here")
return
}
ca, err := sd.CA(cluster, caName)
if err != nil {
return
}
2020-04-22 15:36:04 +00:00
logPrefix := fmt.Sprintf("secret-data: cluster %s: CA %s:", cluster, caName)
rh := hash(req)
2018-06-16 11:45:27 +00:00
kc, ok := ca.Signed[name]
if ok && rh == kc.ReqHash {
2020-04-22 15:36:04 +00:00
err = checkCertUsable(kc.Cert)
if err == nil {
return
}
log.Infof("%s regenerating certificate: ", err)
2019-01-21 22:44:11 +00:00
} else if ok {
2020-04-22 15:36:04 +00:00
log.Infof("%s CSR changed for %s: hash=%q previous=%q", name, rh, kc.ReqHash)
2019-01-21 22:44:11 +00:00
} else {
2020-04-22 15:36:04 +00:00
log.Infof("%s new CSR for %s", logPrefix, name)
2018-06-16 11:45:27 +00:00
}
2019-01-21 22:44:11 +00:00
sd.l.Lock()
defer sd.l.Unlock()
2018-06-16 11:45:27 +00:00
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,
ReqHash: rh,
2018-06-16 11:45:27 +00:00
}
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)
}