Skip to content

Commit

Permalink
Merge pull request kubernetes-retired#147 from justinsb/more_certs
Browse files Browse the repository at this point in the history
Make PKI library richer, including in-memory keypairs
  • Loading branch information
justinsb authored Jan 14, 2019
2 parents e7bcbcc + 691cec3 commit de2c9fc
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 82 deletions.
1 change: 1 addition & 0 deletions etcd-manager/pkg/pki/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go_library(
srcs = [
"certs.go",
"fs.go",
"inmem.go",
"store.go",
"utils.go",
],
Expand Down
169 changes: 108 additions & 61 deletions etcd-manager/pkg/pki/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -57,70 +36,138 @@ 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
}

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
}
52 changes: 52 additions & 0 deletions etcd-manager/pkg/pki/inmem.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 0 additions & 21 deletions etcd-manager/pkg/pki/store.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package pki

import (
"path/filepath"
"sync"

certutil "k8s.io/client-go/util/cert"
Expand All @@ -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
Expand Down

0 comments on commit de2c9fc

Please sign in to comment.