forked from TrueCloudLab/certificates
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:
parent
822a1e3bdb
commit
ece67fefff
1 changed files with 144 additions and 36 deletions
138
pki/pki.go
138
pki/pki.go
|
@ -26,13 +26,14 @@ import (
|
|||
"github.com/smallstep/certificates/cas"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/kms"
|
||||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/cli-utils/config"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
"go.step.sm/cli-utils/fileutil"
|
||||
"go.step.sm/cli-utils/ui"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/linkedca"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -176,6 +177,10 @@ type options struct {
|
|||
noDB bool
|
||||
isHelm bool
|
||||
deploymentType DeploymentType
|
||||
rootKeyURI string
|
||||
intermediateKeyURI string
|
||||
hostKeyURI string
|
||||
userKeyURI string
|
||||
}
|
||||
|
||||
// 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.
|
||||
type PKI struct {
|
||||
linkedca.Configuration
|
||||
|
@ -265,6 +290,7 @@ type PKI struct {
|
|||
casOptions apiv1.Options
|
||||
caService apiv1.CertificateAuthorityService
|
||||
caCreator apiv1.CertificateAuthorityCreator
|
||||
keyManager kmsapi.KeyManager
|
||||
config string
|
||||
defaults string
|
||||
ottPublicKey *jose.JSONWebKey
|
||||
|
@ -303,8 +329,9 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
|
|||
Files: make(map[string][]byte),
|
||||
},
|
||||
casOptions: o,
|
||||
caCreator: caCreator,
|
||||
caService: caService,
|
||||
caCreator: caCreator,
|
||||
keyManager: o.KeyManager,
|
||||
options: &options{
|
||||
provisioner: "step-cli",
|
||||
},
|
||||
|
@ -313,6 +340,11 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) {
|
|||
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 the current step path when creating pki in files.
|
||||
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
|
||||
// and using the default key type.
|
||||
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{
|
||||
Name: resource + "-Root-CA",
|
||||
Type: apiv1.RootCA,
|
||||
Lifetime: 10 * 365 * 24 * time.Hour,
|
||||
CreateKey: nil, // use default
|
||||
CreateKey: &apiv1.CreateKeyRequest{
|
||||
Name: p.RootKey[0],
|
||||
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||
},
|
||||
Template: &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: name + " Root CA",
|
||||
|
@ -469,6 +508,13 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
|
|||
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).
|
||||
if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil {
|
||||
return nil, err
|
||||
|
@ -495,11 +541,18 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
|
|||
// GenerateIntermediateCertificate generates an intermediate certificate with
|
||||
// the given name and using the default key type.
|
||||
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{
|
||||
Name: resource + "-Intermediate-CA",
|
||||
Type: apiv1.IntermediateCA,
|
||||
Lifetime: 10 * 365 * 24 * time.Hour,
|
||||
CreateKey: nil, // use default
|
||||
CreateKey: &apiv1.CreateKeyRequest{
|
||||
Name: p.IntermediateKey,
|
||||
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||
},
|
||||
Template: &x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: name + " Intermediate CA",
|
||||
|
@ -519,7 +572,19 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
|
|||
|
||||
p.casOptions.CertificateAuthority = resp.Name
|
||||
p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -564,27 +629,63 @@ func (p *PKI) GetCertificateAuthority() error {
|
|||
// GenerateSSHSigningKeys generates and encrypts a private key used for signing
|
||||
// SSH user certificates and a private key used for signing host certificates.
|
||||
func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
|
||||
var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey}
|
||||
var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey}
|
||||
for i := 0; i < 2; i++ {
|
||||
pub, priv, err := keyutil.GenerateDefaultKeyPair()
|
||||
// Enable SSH
|
||||
p.options.enableSSH = true
|
||||
|
||||
// Create SSH key used to sign host certificates. Using
|
||||
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
|
||||
name := p.Ssh.HostPublicKey
|
||||
if uri := p.options.hostKeyURI; uri != "" {
|
||||
name = uri
|
||||
}
|
||||
resp, err := p.keyManager.CreateKey(&kmsapi.CreateKeyRequest{
|
||||
Name: name,
|
||||
SignatureAlgorithm: kmsapi.UnspecifiedSignAlgorithm,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := priv.(crypto.Signer); !ok {
|
||||
return errors.Errorf("key of type %T is not a crypto.Signer", priv)
|
||||
}
|
||||
sshKey, err := ssh.NewPublicKey(pub)
|
||||
sshKey, err := ssh.NewPublicKey(resp.PublicKey)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error converting public key")
|
||||
}
|
||||
p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey)
|
||||
p.Files[privNames[i]], err = encodePrivateKey(priv, password)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -684,6 +785,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
|
|||
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
|
||||
// the database.
|
||||
var provisioners []provisioner.Interface
|
||||
|
|
Loading…
Reference in a new issue