Add support for kms in pki package.

Adding support to kms in the pki packages opens the door to use
kms implementations in `step ca init`
This commit is contained in:
Mariano Cano 2021-10-07 17:28:39 -07:00
parent 822a1e3bdb
commit ece67fefff

View file

@ -26,13 +26,14 @@ import (
"github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/cli-utils/config" "go.step.sm/cli-utils/config"
"go.step.sm/cli-utils/errs" "go.step.sm/cli-utils/errs"
"go.step.sm/cli-utils/fileutil" "go.step.sm/cli-utils/fileutil"
"go.step.sm/cli-utils/ui" "go.step.sm/cli-utils/ui"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -168,14 +169,18 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
} }
type options struct { type options struct {
provisioner string provisioner string
pkiOnly bool pkiOnly bool
enableACME bool enableACME bool
enableSSH bool enableSSH bool
enableAdmin bool enableAdmin bool
noDB bool noDB bool
isHelm bool isHelm bool
deploymentType DeploymentType deploymentType DeploymentType
rootKeyURI string
intermediateKeyURI string
hostKeyURI string
userKeyURI string
} }
// Option is the type of a configuration option on the pki constructor. // Option is the type of a configuration option on the pki constructor.
@ -258,6 +263,26 @@ func WithDeploymentType(dt DeploymentType) Option {
} }
} }
// WithKMS enabled the kms with the given name.
func WithKMS(name string) Option {
return func(p *PKI) {
typ := linkedca.KMS_Type_value[strings.ToUpper(name)]
p.Configuration.Kms = &linkedca.KMS{
Type: linkedca.KMS_Type(typ),
}
}
}
// WithKeyURIs defines the key uris for X.509 and SSH keys.
func WithKeyURIs(rootKey, intermediateKey, hostKey, userKey string) Option {
return func(p *PKI) {
p.options.rootKeyURI = rootKey
p.options.intermediateKeyURI = intermediateKey
p.options.hostKeyURI = hostKey
p.options.userKeyURI = userKey
}
}
// PKI represents the Public Key Infrastructure used by a certificate authority. // PKI represents the Public Key Infrastructure used by a certificate authority.
type PKI struct { type PKI struct {
linkedca.Configuration linkedca.Configuration
@ -265,6 +290,7 @@ type PKI struct {
casOptions apiv1.Options casOptions apiv1.Options
caService apiv1.CertificateAuthorityService caService apiv1.CertificateAuthorityService
caCreator apiv1.CertificateAuthorityCreator caCreator apiv1.CertificateAuthorityCreator
keyManager kmsapi.KeyManager
config string config string
defaults string defaults string
ottPublicKey *jose.JSONWebKey ottPublicKey *jose.JSONWebKey
@ -303,8 +329,9 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
Files: make(map[string][]byte), Files: make(map[string][]byte),
}, },
casOptions: o, casOptions: o,
caCreator: caCreator,
caService: caService, caService: caService,
caCreator: caCreator,
keyManager: o.KeyManager,
options: &options{ options: &options{
provisioner: "step-cli", provisioner: "step-cli",
}, },
@ -313,6 +340,11 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
fn(p) fn(p)
} }
// Use default key manager
if p.keyManager != nil {
p.keyManager = kms.Default
}
// Use /home/step as the step path in helm configurations. // Use /home/step as the step path in helm configurations.
// Use the current step path when creating pki in files. // Use the current step path when creating pki in files.
var public, private, config string var public, private, config string
@ -448,11 +480,18 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
// GenerateRootCertificate generates a root certificate with the given name // GenerateRootCertificate generates a root certificate with the given name
// and using the default key type. // and using the default key type.
func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) { func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) {
if uri := p.options.rootKeyURI; uri != "" {
p.RootKey[0] = uri
}
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Root-CA", Name: resource + "-Root-CA",
Type: apiv1.RootCA, Type: apiv1.RootCA,
Lifetime: 10 * 365 * 24 * time.Hour, Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default CreateKey: &apiv1.CreateKeyRequest{
Name: p.RootKey[0],
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
},
Template: &x509.Certificate{ Template: &x509.Certificate{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: name + " Root CA", CommonName: name + " Root CA",
@ -469,6 +508,13 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
return nil, err return nil, err
} }
// Replace key name with the one from the key manager if available. On
// softcas this will be the original filename, on any other kms will be the
// uri to the key.
if resp.KeyName != "" {
p.RootKey[0] = resp.KeyName
}
// PrivateKey will only be set if we have access to it (SoftCAS). // PrivateKey will only be set if we have access to it (SoftCAS).
if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil { if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
return nil, err return nil, err
@ -495,11 +541,18 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
// GenerateIntermediateCertificate generates an intermediate certificate with // GenerateIntermediateCertificate generates an intermediate certificate with
// the given name and using the default key type. // the given name and using the default key type.
func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error { func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error {
if uri := p.options.intermediateKeyURI; uri != "" {
p.IntermediateKey = uri
}
resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{
Name: resource + "-Intermediate-CA", Name: resource + "-Intermediate-CA",
Type: apiv1.IntermediateCA, Type: apiv1.IntermediateCA,
Lifetime: 10 * 365 * 24 * time.Hour, Lifetime: 10 * 365 * 24 * time.Hour,
CreateKey: nil, // use default CreateKey: &apiv1.CreateKeyRequest{
Name: p.IntermediateKey,
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
},
Template: &x509.Certificate{ Template: &x509.Certificate{
Subject: pkix.Name{ Subject: pkix.Name{
CommonName: name + " Intermediate CA", CommonName: name + " Intermediate CA",
@ -519,7 +572,19 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
p.casOptions.CertificateAuthority = resp.Name p.casOptions.CertificateAuthority = resp.Name
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate) p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
// Replace the key name with the one from the key manager. On softcas this
// will be the original filename, on any other kms will be the uri to the
// key.
if resp.KeyName != "" {
p.IntermediateKey = resp.KeyName
}
// If a kms is used it will not have the private key
if resp.PrivateKey != nil {
p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
}
return err return err
} }
@ -564,27 +629,63 @@ func (p *PKI) GetCertificateAuthority() error {
// GenerateSSHSigningKeys generates and encrypts a private key used for signing // GenerateSSHSigningKeys generates and encrypts a private key used for signing
// SSH user certificates and a private key used for signing host certificates. // SSH user certificates and a private key used for signing host certificates.
func (p *PKI) GenerateSSHSigningKeys(password []byte) error { func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey} // Enable SSH
var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey} p.options.enableSSH = true
for i := 0; i < 2; i++ {
pub, priv, err := keyutil.GenerateDefaultKeyPair() // Create SSH key used to sign host certificates. Using
if err != nil { // kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
return err name := p.Ssh.HostPublicKey
} if uri := p.options.hostKeyURI; uri != "" {
if _, ok := priv.(crypto.Signer); !ok { name = uri
return errors.Errorf("key of type %T is not a crypto.Signer", priv) }
} resp, err := p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
sshKey, err := ssh.NewPublicKey(pub) Name: name,
if err != nil { SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
return errors.Wrapf(err, "error converting public key") })
} if err != nil {
p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey) return err
p.Files[privNames[i]], err = encodePrivateKey(priv, password) }
sshKey, err := ssh.NewPublicKey(resp.PublicKey)
if err != nil {
return errors.Wrapf(err, "error converting public key")
}
p.Files[resp.Name] = ssh.MarshalAuthorizedKey(sshKey)
// On softkms we will have the private key
if resp.PrivateKey != nil {
p.Files[p.Ssh.HostKey], err = encodePrivateKey(resp.PrivateKey, password)
if err != nil { if err != nil {
return err return err
} }
} }
p.options.enableSSH = true
// Create SSH key used to sign user certificates. Using
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
name = p.Ssh.UserPublicKey
if uri := p.options.userKeyURI; uri != "" {
name = uri
}
resp, err = p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
Name: name,
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
})
if err != nil {
return err
}
sshKey, err = ssh.NewPublicKey(resp.PublicKey)
if err != nil {
return errors.Wrapf(err, "error converting public key")
}
p.Files[resp.Name] = ssh.MarshalAuthorizedKey(sshKey)
// On softkms we will have the private key
if resp.PrivateKey != nil {
p.Files[p.Ssh.UserKey], err = encodePrivateKey(resp.PrivateKey, password)
if err != nil {
return err
}
}
return nil return nil
} }
@ -684,6 +785,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
config.AuthorityConfig.DeploymentType = LinkedDeployment.String() config.AuthorityConfig.DeploymentType = LinkedDeployment.String()
} }
// Enable KMS if necessary
if p.Kms != nil {
config.KMS = &kmsapi.Options{
Type: strings.ToLower(p.Kms.Type.String()),
}
}
// On standalone deployments add the provisioners to either the ca.json or // On standalone deployments add the provisioners to either the ca.json or
// the database. // the database.
var provisioners []provisioner.Interface var provisioners []provisioner.Interface