Add options to read the roots and federated roots from a bundle.

This commit is contained in:
Mariano Cano 2020-01-10 18:33:48 -08:00
parent 44eccc6bd8
commit e98d7832b9
3 changed files with 124 additions and 60 deletions

View file

@ -17,7 +17,6 @@ import (
"github.com/smallstep/certificates/sshutil" "github.com/smallstep/certificates/sshutil"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -27,24 +26,30 @@ const (
// Authority implements the Certificate Authority internal interface. // Authority implements the Certificate Authority internal interface.
type Authority struct { type Authority struct {
config *Config config *Config
rootX509Certs []*x509.Certificate keyManager kms.KeyManager
intermediateIdentity *x509util.Identity provisioners *provisioner.Collection
keyManager kms.KeyManager db db.AuthDB
x509Signer crypto.Signer
x509Issuer *x509.Certificate // X509 CA
rootX509Certs []*x509.Certificate
federatedX509Certs []*x509.Certificate
x509Signer crypto.Signer
x509Issuer *x509.Certificate
certificates *sync.Map
// SSH CA
sshCAUserCertSignKey ssh.Signer sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer
sshCAUserCerts []ssh.PublicKey sshCAUserCerts []ssh.PublicKey
sshCAHostCerts []ssh.PublicKey sshCAHostCerts []ssh.PublicKey
sshCAUserFederatedCerts []ssh.PublicKey sshCAUserFederatedCerts []ssh.PublicKey
sshCAHostFederatedCerts []ssh.PublicKey sshCAHostFederatedCerts []ssh.PublicKey
certificates *sync.Map
startTime time.Time
provisioners *provisioner.Collection
db db.AuthDB
// Do not re-initialize // Do not re-initialize
initOnce bool initOnce bool
startTime time.Time
// Custom functions // Custom functions
sshBastionFunc func(user, hostname string) (*Bastion, error) sshBastionFunc func(user, hostname string) (*Bastion, error)
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
@ -64,12 +69,19 @@ func New(config *Config, opts ...Option) (*Authority, error) {
certificates: new(sync.Map), certificates: new(sync.Map),
provisioners: provisioner.NewCollection(config.getAudiences()), provisioners: provisioner.NewCollection(config.getAudiences()),
} }
for _, opt := range opts {
opt(a) // Apply options.
for _, fn := range opts {
if err := fn(a); err != nil {
return nil, err
}
} }
// Initialize authority from options or configuration.
if err := a.init(); err != nil { if err := a.init(); err != nil {
return nil, err return nil, err
} }
return a, nil return a, nil
} }
@ -82,6 +94,7 @@ func (a *Authority) init() error {
var err error var err error
// Initialize key manager if it has not been set in the options.
if a.keyManager == nil { if a.keyManager == nil {
a.keyManager, err = kms.New(context.Background(), *a.config.KMS) a.keyManager, err = kms.New(context.Background(), *a.config.KMS)
if err != nil { if err != nil {
@ -97,29 +110,39 @@ func (a *Authority) init() error {
} }
} }
// Load the root certificates and add them to the certificate store // Read root certificates and store them in the certificates map.
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) if len(a.rootX509Certs) == 0 {
for i, path := range a.config.Root { a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
crt, err := pemutil.ReadCertificate(path) for i, path := range a.config.Root {
if err != nil { crt, err := pemutil.ReadCertificate(path)
return err if err != nil {
return err
}
a.rootX509Certs[i] = crt
} }
// Add root certificate to the certificate map
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
a.rootX509Certs[i] = crt
} }
for _, crt := range a.rootX509Certs {
// Add federated roots
for _, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
sum := sha256.Sum256(crt.Raw) sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt) a.certificates.Store(hex.EncodeToString(sum[:]), crt)
} }
// Read federated certificates and store them in the certificates map.
if len(a.federatedX509Certs) == 0 {
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
for i, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path)
if err != nil {
return err
}
a.federatedX509Certs[i] = crt
}
}
for _, crt := range a.federatedX509Certs {
sum := sha256.Sum256(crt.Raw)
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
}
// Read intermediate and create X509 signer.
if a.x509Signer == nil { if a.x509Signer == nil {
crt, err := pemutil.ReadCertificate(a.config.IntermediateCert) crt, err := pemutil.ReadCertificate(a.config.IntermediateCert)
if err != nil { if err != nil {
@ -134,23 +157,6 @@ func (a *Authority) init() error {
} }
a.x509Signer = signer a.x509Signer = signer
a.x509Issuer = crt a.x509Issuer = crt
// Decrypt and load intermediate public / private key pair.
// if len(a.config.Password) > 0 {
// a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(
// a.config.IntermediateCert,
// a.config.IntermediateKey,
// pemutil.WithPassword([]byte(a.config.Password)),
// )
// if err != nil {
// return err
// }
// } else {
// a.intermediateIdentity, err = x509util.LoadIdentityFromDisk(a.config.IntermediateCert, a.config.IntermediateKey)
// if err != nil {
// return err
// }
// }
} }
// Decrypt and load SSH keys // Decrypt and load SSH keys
@ -160,7 +166,6 @@ func (a *Authority) init() error {
SigningKey: a.config.SSH.HostKey, SigningKey: a.config.SSH.HostKey,
Password: a.config.Password, Password: a.config.Password,
}) })
// signer, err := parseCryptoSigner(a.config.SSH.HostKey, a.config.Password)
if err != nil { if err != nil {
return err return err
} }
@ -177,7 +182,6 @@ func (a *Authority) init() error {
SigningKey: a.config.SSH.UserKey, SigningKey: a.config.SSH.UserKey,
Password: a.config.Password, Password: a.config.Password,
}) })
// signer, err := parseCryptoSigner(a.config.SSH.UserKey, a.config.Password)
if err != nil { if err != nil {
return err return err
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"encoding/pem"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
@ -13,37 +14,41 @@ import (
) )
// Option sets options to the Authority. // Option sets options to the Authority.
type Option func(*Authority) type Option func(*Authority) error
// WithDatabase sets an already initialized authority database to a new // WithDatabase sets an already initialized authority database to a new
// authority. This option is intended to be use on graceful reloads. // authority. This option is intended to be use on graceful reloads.
func WithDatabase(db db.AuthDB) Option { func WithDatabase(db db.AuthDB) Option {
return func(a *Authority) { return func(a *Authority) error {
a.db = db a.db = db
return nil
} }
} }
// WithGetIdentityFunc sets a custom function to retrieve the identity from // WithGetIdentityFunc sets a custom function to retrieve the identity from
// an external resource. // an external resource.
func WithGetIdentityFunc(fn func(p provisioner.Interface, email string) (*provisioner.Identity, error)) Option { func WithGetIdentityFunc(fn func(p provisioner.Interface, email string) (*provisioner.Identity, error)) Option {
return func(a *Authority) { return func(a *Authority) error {
a.getIdentityFunc = fn a.getIdentityFunc = fn
return nil
} }
} }
// WithSSHBastionFunc sets a custom function to get the bastion for a // WithSSHBastionFunc sets a custom function to get the bastion for a
// given user-host pair. // given user-host pair.
func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option { func WithSSHBastionFunc(fn func(user, host string) (*Bastion, error)) Option {
return func(a *Authority) { return func(a *Authority) error {
a.sshBastionFunc = fn a.sshBastionFunc = fn
return nil
} }
} }
// WithSSHGetHosts sets a custom function to get the bastion for a // WithSSHGetHosts sets a custom function to get the bastion for a
// given user-host pair. // given user-host pair.
func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option { func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Option {
return func(a *Authority) { return func(a *Authority) error {
a.sshGetHostsFunc = fn a.sshGetHostsFunc = fn
return nil
} }
} }
@ -51,37 +56,91 @@ func WithSSHGetHosts(fn func(cert *x509.Certificate) ([]sshutil.Host, error)) Op
// step ssh enabled. The token is used to validate the request, while the roots // step ssh enabled. The token is used to validate the request, while the roots
// are used to validate the token. // are used to validate the token.
func WithSSHCheckHost(fn func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)) Option { func WithSSHCheckHost(fn func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)) Option {
return func(a *Authority) { return func(a *Authority) error {
a.sshCheckHostFunc = fn a.sshCheckHostFunc = fn
return nil
} }
} }
// WithKeyManager defines the key manager used to get and create keys, and sign // WithKeyManager defines the key manager used to get and create keys, and sign
// certificates. // certificates.
func WithKeyManager(k kms.KeyManager) Option { func WithKeyManager(k kms.KeyManager) Option {
return func(a *Authority) { return func(a *Authority) error {
a.keyManager = k a.keyManager = k
return nil
} }
} }
// WithX509Signer defines the signer used to sign X509 certificates. // WithX509Signer defines the signer used to sign X509 certificates.
func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option { func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option {
return func(a *Authority) { return func(a *Authority) error {
a.x509Issuer = crt a.x509Issuer = crt
a.x509Signer = s a.x509Signer = s
return nil
} }
} }
// WithSSHUserSigner defines the signer used to sign SSH user certificates. // WithSSHUserSigner defines the signer used to sign SSH user certificates.
func WithSSHUserSigner(s ssh.Signer) Option { func WithSSHUserSigner(s ssh.Signer) Option {
return func(a *Authority) { return func(a *Authority) error {
a.sshCAUserCertSignKey = s a.sshCAUserCertSignKey = s
return nil
} }
} }
// WithSSHHostSigner defines the signer used to sign SSH host certificates. // WithSSHHostSigner defines the signer used to sign SSH host certificates.
func WithSSHHostSigner(s ssh.Signer) Option { func WithSSHHostSigner(s ssh.Signer) Option {
return func(a *Authority) { return func(a *Authority) error {
a.sshCAHostCertSignKey = s a.sshCAHostCertSignKey = s
return nil
} }
} }
// WithX509RootBundle is an option that allows to define the list of root
// certificates.
func WithX509RootBundle(pemCerts []byte) Option {
return func(a *Authority) error {
certs, err := readCertificateBundle(pemCerts)
if err != nil {
return err
}
x509.NewCertPool()
a.rootX509Certs = certs
return nil
}
}
// WithX509FederatedBundle is an option that allows to define the list of
// federated certificates.
func WithX509FederatedBundle(pemCerts []byte) Option {
return func(a *Authority) error {
certs, err := readCertificateBundle(pemCerts)
if err != nil {
return err
}
a.federatedX509Certs = certs
return nil
}
}
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
var block *pem.Block
var certs []*x509.Certificate
for len(pemCerts) > 0 {
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certs = append(certs, cert)
}
return certs, nil
}

1
go.sum
View file

@ -576,6 +576,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=