From e0e7f541e86243a25ce5cf429d2d5bd6e016f12f Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sun, 13 Jan 2019 20:49:59 -0500 Subject: [PATCH] Make PKI library richer, including in-memory keypairs --- etcd-manager/pkg/pki/BUILD.bazel | 1 + etcd-manager/pkg/pki/fs.go | 169 ++++++++++++++++++++----------- etcd-manager/pkg/pki/inmem.go | 52 ++++++++++ etcd-manager/pkg/pki/store.go | 21 ---- 4 files changed, 161 insertions(+), 82 deletions(-) create mode 100644 etcd-manager/pkg/pki/inmem.go diff --git a/etcd-manager/pkg/pki/BUILD.bazel b/etcd-manager/pkg/pki/BUILD.bazel index 8e5c4ab4f..af6f810fd 100644 --- a/etcd-manager/pkg/pki/BUILD.bazel +++ b/etcd-manager/pkg/pki/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "certs.go", "fs.go", + "inmem.go", "store.go", "utils.go", ], diff --git a/etcd-manager/pkg/pki/fs.go b/etcd-manager/pkg/pki/fs.go index a80b31b1e..da03f6bf9 100644 --- a/etcd-manager/pkg/pki/fs.go +++ b/etcd-manager/pkg/pki/fs.go @@ -14,38 +14,17 @@ import ( func LoadCAFromDisk(dir string) (*Keypair, error) { klog.Infof("Loading certificate authority from %v", dir) - certPath := filepath.Join(dir, "ca.crt") - certMaterial, err := ioutil.ReadFile(certPath) - if err != nil { - return nil, fmt.Errorf("unable to read cert %v: %v", certPath, err) - } - keyPath := filepath.Join(dir, "ca.key") - keyMaterial, err := ioutil.ReadFile(keyPath) - if err != nil { - return nil, fmt.Errorf("unable to read key %v: %v", keyPath, err) - } - cert, err := ParseOneCertificate(certMaterial) - if err != nil { - return nil, fmt.Errorf("error parsing cert %q: %v", certPath, err) - } - key, err := certutil.ParsePrivateKeyPEM(keyMaterial) - if err != nil { - return nil, fmt.Errorf("unable to parse private key %q: %v", keyPath, err) - } + keypair := &Keypair{} - rsaKey, ok := key.(*rsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("unexpected private key type in %q: %T", keyPath, key) + if err := loadPrivateKey(filepath.Join(dir, "ca.key"), keypair); err != nil { + return nil, err } - - ca := &Keypair{ - Certificate: cert, - CertificatePEM: certMaterial, - PrivateKey: rsaKey, - PrivateKeyPEM: keyMaterial, + if err := loadCertificate(filepath.Join(dir, "ca.crt"), keypair); err != nil { + return nil, err } - return ca, nil + + return keypair, nil } type MutableKeypairFromFile struct { @@ -57,33 +36,125 @@ var _ MutableKeypair = &MutableKeypairFromFile{} func (s *MutableKeypairFromFile) MutateKeypair(mutator func(keypair *Keypair) error) (*Keypair, error) { keypair := &Keypair{} + if err := loadPrivateKey(s.PrivateKeyPath, keypair); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + // We tolerate a missing key when generating the keypair + } + if err := loadCertificate(s.CertificatePath, keypair); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + // We tolerate a missing cert when generating the keypair + } + + original := *keypair + + if err := mutator(keypair); err != nil { + return nil, err + } + + if !bytes.Equal(original.PrivateKeyPEM, keypair.PrivateKeyPEM) { + if err := os.MkdirAll(filepath.Dir(s.PrivateKeyPath), 0755); err != nil { + return nil, fmt.Errorf("error creating directories for private key file %q: %v", s.PrivateKeyPath, err) + } + + if err := ioutil.WriteFile(s.PrivateKeyPath, keypair.PrivateKeyPEM, 0600); err != nil { + return nil, fmt.Errorf("error writing private key file %q: %v", s.PrivateKeyPath, err) + } + } + + if !bytes.Equal(original.CertificatePEM, keypair.CertificatePEM) { + // TODO: Replace with simpler call to WriteCertificate? + if err := os.MkdirAll(filepath.Dir(s.CertificatePath), 0755); err != nil { + return nil, fmt.Errorf("error creating directories for certificate file %q: %v", s.CertificatePath, err) + } + + if err := ioutil.WriteFile(s.CertificatePath, keypair.CertificatePEM, 0644); err != nil { + return nil, fmt.Errorf("error writing certificate key file %q: %v", s.CertificatePath, err) + } + } + + return keypair, nil +} + +type FSStore struct { + basedir string +} - privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyPath) +var _ Store = &FSStore{} + +func NewFSStore(basedir string) *FSStore { + return &FSStore{ + basedir: basedir, + } +} + +func (s *FSStore) Keypair(name string) MutableKeypair { + return &MutableKeypairFromFile{ + PrivateKeyPath: filepath.Join(s.basedir, name+".key"), + CertificatePath: filepath.Join(s.basedir, name+".crt"), + } +} + +func (s *FSStore) WriteCertificate(name string, keypair *Keypair) error { + p := filepath.Join(s.basedir, name+".crt") + + if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { + return fmt.Errorf("error creating directories for certificate file %q: %v", p, err) + } + + if err := ioutil.WriteFile(p, keypair.CertificatePEM, 0644); err != nil { + return fmt.Errorf("error writing certificate key file %q: %v", p, err) + } + + return nil +} + +func (s *FSStore) LoadKeypair(name string) (*Keypair, error) { + keypair := &Keypair{} + if err := loadPrivateKey(filepath.Join(s.basedir, name+".key"), keypair); err != nil { + return nil, err + } + if err := loadCertificate(filepath.Join(s.basedir, name+".crt"), keypair); err != nil { + return nil, err + } + return keypair, nil +} + +func loadPrivateKey(privateKeyPath string, keypair *Keypair) error { + privateKeyBytes, err := ioutil.ReadFile(privateKeyPath) if err != nil { if !os.IsNotExist(err) { - return nil, fmt.Errorf("unable to read key %v: %v", s.PrivateKeyPath, err) + return fmt.Errorf("unable to read key %v: %v", privateKeyPath, err) + } else { + return err } - privateKeyBytes = nil } if privateKeyBytes != nil { key, err := certutil.ParsePrivateKeyPEM(privateKeyBytes) if err != nil { - return nil, fmt.Errorf("unable to parse private key %q: %v", s.PrivateKeyPath, err) + return fmt.Errorf("unable to parse private key %q: %v", privateKeyPath, err) } rsaKey, ok := key.(*rsa.PrivateKey) if !ok { - return nil, fmt.Errorf("unexpected private key type in %q: %T", s.PrivateKeyPath, key) + return fmt.Errorf("unexpected private key type in %q: %T", privateKeyPath, key) } keypair.PrivateKey = rsaKey keypair.PrivateKeyPEM = privateKeyBytes } - certBytes, err := ioutil.ReadFile(s.CertificatePath) + return nil +} + +func loadCertificate(certificatePath string, keypair *Keypair) error { + certBytes, err := ioutil.ReadFile(certificatePath) if err != nil { if !os.IsNotExist(err) { - return nil, fmt.Errorf("unable to read certificate %v: %v", s.CertificatePath, err) + return fmt.Errorf("unable to read certificate %v: %v", certificatePath, err) } certBytes = nil } @@ -91,36 +162,12 @@ func (s *MutableKeypairFromFile) MutateKeypair(mutator func(keypair *Keypair) er if certBytes != nil { cert, err := ParseOneCertificate(certBytes) if err != nil { - return nil, fmt.Errorf("error parsing certificate data in %q: %v", s.CertificatePath, err) + return fmt.Errorf("error parsing certificate data in %q: %v", certificatePath, err) } keypair.Certificate = cert keypair.CertificatePEM = certBytes } - if err := mutator(keypair); err != nil { - return nil, err - } - - if !bytes.Equal(privateKeyBytes, keypair.PrivateKeyPEM) { - if err := os.MkdirAll(filepath.Dir(s.PrivateKeyPath), 0755); err != nil { - return nil, fmt.Errorf("error creating directories for private key file %q: %v", s.PrivateKeyPath, err) - } - - if err := ioutil.WriteFile(s.PrivateKeyPath, keypair.PrivateKeyPEM, 0600); err != nil { - return nil, fmt.Errorf("error writing private key file %q: %v", s.PrivateKeyPath, err) - } - } - - if !bytes.Equal(certBytes, keypair.CertificatePEM) { - if err := os.MkdirAll(filepath.Dir(s.CertificatePath), 0755); err != nil { - return nil, fmt.Errorf("error creating directories for certificate file %q: %v", s.CertificatePath, err) - } - - if err := ioutil.WriteFile(s.CertificatePath, keypair.CertificatePEM, 0644); err != nil { - return nil, fmt.Errorf("error writing certificate key file %q: %v", s.CertificatePath, err) - } - } - - return keypair, nil + return nil } diff --git a/etcd-manager/pkg/pki/inmem.go b/etcd-manager/pkg/pki/inmem.go new file mode 100644 index 000000000..98cca9eee --- /dev/null +++ b/etcd-manager/pkg/pki/inmem.go @@ -0,0 +1,52 @@ +package pki + +import "sync" + +type InMemoryMutableKeypair struct { + Keypair *Keypair +} + +var _ MutableKeypair = &InMemoryMutableKeypair{} + +func (s *InMemoryMutableKeypair) MutateKeypair(mutator func(keypair *Keypair) error) (*Keypair, error) { + keypair := &Keypair{} + + if s.Keypair != nil { + *keypair = *s.Keypair + } + + if err := mutator(keypair); err != nil { + return nil, err + } + + if s.Keypair == nil { + s.Keypair = &Keypair{} + } + *s.Keypair = *keypair + return keypair, nil +} + +type InMemoryStore struct { + mutex sync.Mutex + data map[string]*InMemoryMutableKeypair +} + +var _ Store = &InMemoryStore{} + +func NewInMemoryStore() *InMemoryStore { + return &InMemoryStore{ + data: make(map[string]*InMemoryMutableKeypair), + } +} + +func (s *InMemoryStore) Keypair(name string) MutableKeypair { + s.mutex.Lock() + defer s.mutex.Unlock() + + k := s.data[name] + if k == nil { + k = &InMemoryMutableKeypair{} + s.data[name] = k + } + return k +} diff --git a/etcd-manager/pkg/pki/store.go b/etcd-manager/pkg/pki/store.go index 86f51a071..35b7dac38 100644 --- a/etcd-manager/pkg/pki/store.go +++ b/etcd-manager/pkg/pki/store.go @@ -1,7 +1,6 @@ package pki import ( - "path/filepath" "sync" certutil "k8s.io/client-go/util/cert" @@ -11,26 +10,6 @@ type Store interface { Keypair(name string) MutableKeypair } -type FSStore struct { - basedir string -} - -var _ Store = &FSStore{} - -func NewFSStore(basedir string) *FSStore { - return &FSStore{ - basedir: basedir, - } -} - -func (s *FSStore) Keypair(name string) MutableKeypair { - p := name - return &MutableKeypairFromFile{ - PrivateKeyPath: filepath.Join(s.basedir, p+".key"), - CertificatePath: filepath.Join(s.basedir, p+".crt"), - } -} - // Keypairs manages a set of keypairs, providing utilities for fetching / creating them type Keypairs struct { Store Store