dynamiccert: split into serving cert and CA providers

Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
Monis Khan
2021-03-15 12:24:07 -04:00
parent 4c162be8bf
commit 00694c9cb6
17 changed files with 141 additions and 42 deletions

View File

@@ -19,6 +19,8 @@ import (
"math/big"
"net"
"time"
"go.pinniped.dev/internal/constable"
)
// certBackdate is the amount of time before time.Now() that will be used to set
@@ -71,7 +73,7 @@ func secureEnv() env {
}
// ErrInvalidCACertificate is returned when the contents of the loaded CA certificate do not meet our assumptions.
var ErrInvalidCACertificate = fmt.Errorf("invalid CA certificate")
const ErrInvalidCACertificate = constable.Error("invalid CA certificate")
// Load a certificate authority from an existing certificate and private key (in PEM format).
func Load(certPEM string, keyPEM string) (*CA, error) {
@@ -82,6 +84,13 @@ func Load(certPEM string, keyPEM string) (*CA, error) {
if certCount := len(cert.Certificate); certCount != 1 {
return nil, fmt.Errorf("%w: expected a single certificate, found %d certificates", ErrInvalidCACertificate, certCount)
}
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("failed to parse key pair as x509 cert: %w", err)
}
if !x509Cert.IsCA {
return nil, fmt.Errorf("%w: passed in key pair is not a CA", ErrInvalidCACertificate)
}
return &CA{
caCertBytes: cert.Certificate[0],
signer: cert.PrivateKey.(crypto.Signer),

View File

@@ -11,25 +11,33 @@ import (
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/issuer"
)
// CA is a type capable of issuing certificates.
type CA struct {
// ca is a type capable of issuing certificates.
type ca struct {
provider dynamiccertificates.CertKeyContentProvider
}
// New creates a new CA, ready to issue certs whenever the provided provider has a keypair to
// provide.
func New(provider dynamiccertificates.CertKeyContentProvider) *CA {
return &CA{
// New creates a ClientCertIssuer, ready to issue certs whenever
// the given CertKeyContentProvider has a keypair to provide.
func New(provider dynamiccertificates.CertKeyContentProvider) issuer.ClientCertIssuer {
return &ca{
provider: provider,
}
}
func (c *ca) Name() string {
return c.provider.Name()
}
// IssueClientCertPEM issues a new client certificate for the given identity and duration, returning it as a
// pair of PEM-formatted byte slices for the certificate and private key.
func (c *CA) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
func (c *ca) IssueClientCertPEM(username string, groups []string, ttl time.Duration) ([]byte, []byte, error) {
caCrtPEM, caKeyPEM := c.provider.CurrentCertKeyContent()
// in the future we could split dynamiccert.Private into two interfaces (Private and PrivateRead)
// and have this code take PrivateRead as input. We would then add ourselves as a listener to
// the PrivateRead. This would allow us to only reload the CA contents when they actually change.
ca, err := certauthority.Load(string(caCrtPEM), string(caKeyPEM))
if err != nil {
return nil, nil, err

View File

@@ -10,13 +10,14 @@ import (
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/dynamiccert"
"go.pinniped.dev/internal/issuer"
"go.pinniped.dev/internal/testutil"
)
func TestCAIssuePEM(t *testing.T) {
t.Parallel()
provider := dynamiccert.New(t.Name())
provider := dynamiccert.NewCA(t.Name())
ca := New(provider)
goodCACrtPEM0, goodCAKeyPEM0, err := testutil.CreateCertificate(
@@ -115,7 +116,7 @@ func TestCAIssuePEM(t *testing.T) {
}
}
func issuePEM(provider dynamiccert.Provider, ca *CA, caCrt, caKey []byte) ([]byte, []byte, error) {
func issuePEM(provider dynamiccert.Provider, ca issuer.ClientCertIssuer, caCrt, caKey []byte) ([]byte, []byte, error) {
// if setting fails, look at that error
if caCrt != nil || caKey != nil {
if err := provider.SetCertKeyContent(caCrt, caKey); err != nil {