Skip to content

Commit

Permalink
Merge pull request #140 from rgooch/aws-role-loop
Browse files Browse the repository at this point in the history
keymaster: add certificate refresh for aws-role-cert mode.
  • Loading branch information
cviecco authored Nov 1, 2021
2 parents b05c5b9 + bde5ae3 commit 473c359
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ endif
BINARY=keymaster

# These are the values we want to pass for Version and BuildTime
VERSION=1.9.2
VERSION=1.10.0
#BUILD_TIME=`date +%FT%T%z`

# Setup the -ldflags option for go build here, interpolate the variable values
Expand Down
24 changes: 20 additions & 4 deletions cmd/keymaster/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
Expand All @@ -17,6 +18,7 @@ import (
"strings"
"time"

"github.com/Cloud-Foundations/Dominator/lib/fsutil"
"github.com/Cloud-Foundations/Dominator/lib/log/cmdlogger"
"github.com/Cloud-Foundations/Dominator/lib/net/rrdialer"
"github.com/Cloud-Foundations/golib/pkg/log"
Expand Down Expand Up @@ -216,7 +218,7 @@ func generateAwsRoleCert(homeDir string,
if err := signers.Wait(); err != nil {
return err
}
certPEM, err := aws_role.GetRoleCertificate(aws_role.Params{
manager, err := aws_role.NewManager(aws_role.Params{
KeymasterServer: targetURLs[0],
Logger: logger,
HttpClient: client,
Expand All @@ -239,10 +241,24 @@ func generateAwsRoleCert(homeDir string,
return err
}
x509CertPath := tlsKeyPath + ".cert"
err = ioutil.WriteFile(x509CertPath, certPEM, 0644)
certPEM, _, err := manager.GetRoleCertificate()
if err != nil {
err := errors.New("Could not write ssh cert")
logger.Fatal(err)
return err
}
if err := ioutil.WriteFile(x509CertPath, certPEM, 0644); err != nil {
return errors.New("Could not write ssh cert")
}
for {
logger.Println("starting loop waiting for certificate refreshes")
manager.WaitForRefresh()
certPEM, _, err := manager.GetRoleCertificate()
if err != nil {
return err
}
err = fsutil.CopyToFile(x509CertPath, 0644, bytes.NewReader(certPEM), 0)
if err != nil {
return errors.New("Could not write ssh cert")
}
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion keymaster.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Name: keymaster
Version: 1.9.2
Version: 1.10.0
Release: 1%{?dist}
Summary: Short term access certificate generator and client

Expand Down
27 changes: 21 additions & 6 deletions lib/client/aws_role/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@ type Params struct {
}

type Manager struct {
Params
mutex sync.RWMutex // Protect everything below.
tlsCert *tls.Certificate
tlsError error
params Params
mutex sync.RWMutex // Protect everything below.
certError error
certPEM []byte
certTLS *tls.Certificate
waiters map[chan<- struct{}]struct{}
}

// GetRoleCertificate requests an AWS role identify certificate from the
Expand All @@ -62,7 +64,8 @@ func GetRoleCertificate(params Params) ([]byte, error) {
// GetRoleCertificateTLS requests an AWS role identify certificate from the
// Keymaster server specified in params. It returns the certificate.
func GetRoleCertificateTLS(params Params) (*tls.Certificate, error) {
return params.getRoleCertificateTLS()
_, certTLS, err := params.getRoleCertificateTLS()
return certTLS, err
}

// NewManager returns a certificate manager which provides AWS role identity
Expand All @@ -72,8 +75,20 @@ func NewManager(params Params) (*Manager, error) {
return newManager(params)
}

// GetClientCertificate returns a valid, cached certificate.
// GetClientCertificate returns a valid, cached certificate. The method
// value may be assigned to the crypto/tls.Config.GetClientCertificate field.
func (m *Manager) GetClientCertificate(cri *tls.CertificateRequestInfo) (
*tls.Certificate, error) {
return m.getClientCertificate(cri)
}

// GetRoleCertificate returns a valid, cached certificate. It returns the
// certificate PEM, TLS certificate and error.
func (m *Manager) GetRoleCertificate() ([]byte, *tls.Certificate, error) {
return m.getRoleCertificate()
}

// WaitForRefresh waits until a successful certificate refresh.
func (m *Manager) WaitForRefresh() {
m.waitForRefresh()
}
87 changes: 59 additions & 28 deletions lib/client/aws_role/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,16 @@ func parseArn(arnString string) (*arn.ARN, error) {
}

func newManager(p Params) (*Manager, error) {
cert, err := p.getRoleCertificateTLS()
certPEM, certTLS, err := p.getRoleCertificateTLS()
if err != nil {
return nil, err
}
p.Logger.Printf("got AWS Role certificate for: %s\n", p.roleArn)
manager := &Manager{
Params: p,
tlsCert: cert,
params: p,
certPEM: certPEM,
certTLS: certTLS,
waiters: make(map[chan<- struct{}]struct{}),
}
go manager.refreshLoop()
return manager, nil
Expand All @@ -64,7 +66,13 @@ func (m *Manager) getClientCertificate(cri *tls.CertificateRequestInfo) (
*tls.Certificate, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.tlsCert, m.tlsError
return m.certTLS, m.certError
}

func (m *Manager) getRoleCertificate() ([]byte, *tls.Certificate, error) {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.certPEM, m.certTLS, m.certError
}

func (m *Manager) refreshLoop() {
Expand All @@ -74,27 +82,47 @@ func (m *Manager) refreshLoop() {
}

func (m *Manager) refreshOnce() {
if m.tlsCert != nil {
refreshTime := m.tlsCert.Leaf.NotBefore.Add(
m.tlsCert.Leaf.NotAfter.Sub(m.tlsCert.Leaf.NotBefore) * 3 / 4)
time.Sleep(time.Until(refreshTime))
}
if cert, err := m.getRoleCertificateTLS(); err != nil {
m.Logger.Println(err)
if m.tlsCert == nil {
if m.certTLS != nil {
refreshTime := m.certTLS.Leaf.NotBefore.Add(
m.certTLS.Leaf.NotAfter.Sub(m.certTLS.Leaf.NotBefore) * 3 / 4)
duration := time.Until(refreshTime)
m.params.Logger.Debugf(1, "sleeping: %s before refresh\n",
(duration + time.Millisecond*50).Truncate(time.Millisecond*100))
time.Sleep(duration)
}
if certPEM, certTLS, err := m.params.getRoleCertificateTLS(); err != nil {
m.params.Logger.Println(err)
if m.certTLS == nil {
m.mutex.Lock()
m.tlsError = err
m.certError = err
m.mutex.Unlock()
}
} else {
m.mutex.Lock()
m.tlsCert = cert
m.tlsError = nil
m.certError = nil
m.certPEM = certPEM
m.certTLS = certTLS
for waiter := range m.waiters {
select {
case waiter <- struct{}{}:
default:
}
delete(m.waiters, waiter)
}
m.mutex.Unlock()
m.Logger.Printf("refreshed AWS Role certificate for: %s\n", m.roleArn)
m.params.Logger.Printf("refreshed AWS Role certificate for: %s\n",
m.params.roleArn)
}
}

func (m *Manager) waitForRefresh() {
ch := make(chan struct{}, 1)
m.mutex.Lock()
m.waiters[ch] = struct{}{}
m.mutex.Unlock()
<-ch
}

// Returns certificate PEM block.
func (p *Params) getRoleCertificate() ([]byte, error) {
if err := p.setupVerify(); err != nil {
Expand All @@ -105,7 +133,7 @@ func (p *Params) getRoleCertificate() ([]byte, error) {
if err != nil {
return nil, err
}
p.Logger.Debugf(1, "presigned URL: %v\n", presignedReq.URL)
p.Logger.Debugf(2, "presigned URL: %v\n", presignedReq.URL)
hostPath := p.KeymasterServer + paths.RequestAwsRoleCertificatePath
body := &bytes.Buffer{}
body.Write(p.pemPubKey)
Expand All @@ -129,27 +157,30 @@ func (p *Params) getRoleCertificate() ([]byte, error) {
return ioutil.ReadAll(resp.Body)
}

func (p *Params) getRoleCertificateTLS() (*tls.Certificate, error) {
// Returns certificate PEM block, TLS certificate and error.
func (p *Params) getRoleCertificateTLS() ([]byte, *tls.Certificate, error) {
certPEM, err := p.getRoleCertificate()
if err != nil {
return nil, err
return nil, nil, err
}
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, fmt.Errorf("unable to decode certificate PEM block")
return nil, nil, fmt.Errorf("unable to decode certificate PEM block")
}
if block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("invalid certificate type: %s", block.Type)
return nil, nil, fmt.Errorf("invalid certificate type: %s", block.Type)
}
x509Cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return &tls.Certificate{
Certificate: [][]byte{block.Bytes},
PrivateKey: p.Signer,
Leaf: x509Cert,
}, nil
return nil, nil, err
}
return certPEM,
&tls.Certificate{
Certificate: [][]byte{block.Bytes},
PrivateKey: p.Signer,
Leaf: x509Cert,
},
nil
}

func (p *Params) setupVerify() error {
Expand Down

0 comments on commit 473c359

Please sign in to comment.