Merge pull request #633 from smallstep/linkedca

Linkedca
This commit is contained in:
Mariano Cano 2021-08-25 16:06:57 -07:00 committed by GitHub
commit 9e57e4db2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 2252 additions and 505 deletions

View file

@ -7,39 +7,40 @@ import (
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"log" "log"
"strings"
"sync" "sync"
"time" "time"
"github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/scep"
"go.step.sm/linkedca"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/admin"
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql" adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
"github.com/smallstep/certificates/authority/administrator" "github.com/smallstep/certificates/authority/administrator"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/cas"
casapi "github.com/smallstep/certificates/cas/apiv1" casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/kms/sshagentkms"
"github.com/smallstep/certificates/scep"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/nosql" "github.com/smallstep/nosql"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// Authority implements the Certificate Authority internal interface. // Authority implements the Certificate Authority internal interface.
type Authority struct { type Authority struct {
config *config.Config config *config.Config
keyManager kms.KeyManager keyManager kms.KeyManager
provisioners *provisioner.Collection provisioners *provisioner.Collection
admins *administrator.Collection admins *administrator.Collection
db db.AuthDB db db.AuthDB
adminDB admin.DB adminDB admin.DB
templates *templates.Templates templates *templates.Templates
linkedCAToken string
// X509 CA // X509 CA
x509CAService cas.CertificateAuthorityService x509CAService cas.CertificateAuthorityService
@ -205,6 +206,11 @@ func (a *Authority) init() error {
var err error var err error
// Automatically enable admin for all linked cas.
if a.linkedCAToken != "" {
a.config.AuthorityConfig.EnableAdmin = true
}
// Initialize step-ca Database if it's not already initialized with WithDB. // Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used. // If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil { if a.db == nil {
@ -442,10 +448,24 @@ func (a *Authority) init() error {
// Initialize step-ca Admin Database if it's not already initialized using // Initialize step-ca Admin Database if it's not already initialized using
// WithAdminDB. // WithAdminDB.
if a.adminDB == nil { if a.adminDB == nil {
// Check if AuthConfig already exists if a.linkedCAToken == "" {
a.adminDB, err = adminDBNosql.New(a.db.(nosql.DB), admin.DefaultAuthorityID) // Check if AuthConfig already exists
if err != nil { a.adminDB, err = adminDBNosql.New(a.db.(nosql.DB), admin.DefaultAuthorityID)
return err if err != nil {
return err
}
} else {
// Use the linkedca client as the admindb.
client, err := newLinkedCAClient(a.linkedCAToken)
if err != nil {
return err
}
// If authorityId is configured make sure it matches the one in the token
if id := a.config.AuthorityConfig.AuthorityID; id != "" && !strings.EqualFold(id, client.authorityID) {
return errors.New("error initializing linkedca: token authority and configured authority do not match")
}
client.Run()
a.adminDB = client
} }
} }
@ -453,7 +473,7 @@ func (a *Authority) init() error {
if err != nil { if err != nil {
return admin.WrapErrorISE(err, "error loading provisioners to initialize authority") return admin.WrapErrorISE(err, "error loading provisioners to initialize authority")
} }
if len(provs) == 0 { if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") {
// Create First Provisioner // Create First Provisioner
prov, err := CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password) prov, err := CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password)
if err != nil { if err != nil {
@ -527,6 +547,9 @@ func (a *Authority) CloseForReload() {
if err := a.keyManager.Close(); err != nil { if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err) log.Printf("error closing the key manager: %v", err)
} }
if client, ok := a.adminDB.(*linkedCaClient); ok {
client.Stop()
}
} }
// requiresDecrypter returns whether the Authority // requiresDecrypter returns whether the Authority

View file

@ -6,6 +6,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@ -273,10 +274,19 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error {
// //
// TODO(mariano): should we authorize by default? // TODO(mariano): should we authorize by default?
func (a *Authority) authorizeRenew(cert *x509.Certificate) error { func (a *Authority) authorizeRenew(cert *x509.Certificate) error {
var err error
var isRevoked bool
var opts = []interface{}{errs.WithKeyVal("serialNumber", cert.SerialNumber.String())} var opts = []interface{}{errs.WithKeyVal("serialNumber", cert.SerialNumber.String())}
// Check the passive revocation table. // Check the passive revocation table.
isRevoked, err := a.db.IsRevoked(cert.SerialNumber.String()) serial := cert.SerialNumber.String()
if lca, ok := a.adminDB.(interface {
IsRevoked(string) (bool, error)
}); ok {
isRevoked, err = lca.IsRevoked(serial)
} else {
isRevoked, err = a.db.IsRevoked(serial)
}
if err != nil { if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...)
} }
@ -294,6 +304,28 @@ func (a *Authority) authorizeRenew(cert *x509.Certificate) error {
return nil return nil
} }
// authorizeSSHCertificate returns an error if the given certificate is revoked.
func (a *Authority) authorizeSSHCertificate(ctx context.Context, cert *ssh.Certificate) error {
var err error
var isRevoked bool
serial := strconv.FormatUint(cert.Serial, 10)
if lca, ok := a.adminDB.(interface {
IsSSHRevoked(string) (bool, error)
}); ok {
isRevoked, err = lca.IsSSHRevoked(serial)
} else {
isRevoked, err = a.db.IsSSHRevoked(serial)
}
if err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeSSHCertificate", errs.WithKeyVal("serialNumber", serial))
}
if isRevoked {
return errs.Unauthorized("authority.authorizeSSHCertificate: certificate has been revoked", errs.WithKeyVal("serialNumber", serial))
}
return nil
}
// authorizeSSHSign loads the provisioner from the token, checks that it has not // authorizeSSHSign loads the provisioner from the token, checks that it has not
// been used again and calls the provisioner AuthorizeSSHSign method. Returns a // been used again and calls the provisioner AuthorizeSSHSign method. Returns a
// list of methods to apply to the signing flow. // list of methods to apply to the signing flow.

View file

@ -75,6 +75,7 @@ type ASN1DN struct {
Locality string `json:"locality,omitempty"` Locality string `json:"locality,omitempty"`
Province string `json:"province,omitempty"` Province string `json:"province,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"` StreetAddress string `json:"streetAddress,omitempty"`
SerialNumber string `json:"serialNumber,omitempty"`
CommonName string `json:"commonName,omitempty"` CommonName string `json:"commonName,omitempty"`
} }
@ -83,8 +84,9 @@ type ASN1DN struct {
// cas.Options. // cas.Options.
type AuthConfig struct { type AuthConfig struct {
*cas.Options *cas.Options
AuthorityID string `json:"authorityID,omitempty"` AuthorityID string `json:"authorityId,omitempty"`
Provisioners provisioner.List `json:"provisioners"` DeploymentType string `json:"deploymentType,omitempty"`
Provisioners provisioner.List `json:"provisioners,omitempty"`
Admins []*linkedca.Admin `json:"-"` Admins []*linkedca.Admin `json:"-"`
Template *ASN1DN `json:"template,omitempty"` Template *ASN1DN `json:"template,omitempty"`
Claims *provisioner.Claims `json:"claims,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"`
@ -188,9 +190,10 @@ func (c *Config) Validate() error {
switch { switch {
case c.Address == "": case c.Address == "":
return errors.New("address cannot be empty") return errors.New("address cannot be empty")
case len(c.DNSNames) == 0: case len(c.DNSNames) == 0:
return errors.New("dnsNames cannot be empty") return errors.New("dnsNames cannot be empty")
case c.AuthorityConfig == nil:
return errors.New("authority cannot be nil")
} }
// Options holds the RA/CAS configuration. // Options holds the RA/CAS configuration.
@ -222,7 +225,7 @@ func (c *Config) Validate() error {
c.TLS.MaxVersion = DefaultTLSOptions.MaxVersion c.TLS.MaxVersion = DefaultTLSOptions.MaxVersion
} }
if c.TLS.MinVersion == 0 { if c.TLS.MinVersion == 0 {
c.TLS.MinVersion = c.TLS.MaxVersion c.TLS.MinVersion = DefaultTLSOptions.MinVersion
} }
if c.TLS.MinVersion > c.TLS.MaxVersion { if c.TLS.MinVersion > c.TLS.MaxVersion {
return errors.New("tls minVersion cannot exceed tls maxVersion") return errors.New("tls minVersion cannot exceed tls maxVersion")

View file

@ -15,8 +15,9 @@ var (
// DefaultTLSRenegotiation default TLS connection renegotiation policy. // DefaultTLSRenegotiation default TLS connection renegotiation policy.
DefaultTLSRenegotiation = false // Never regnegotiate. DefaultTLSRenegotiation = false // Never regnegotiate.
// DefaultTLSCipherSuites specifies default step ciphersuite(s). // DefaultTLSCipherSuites specifies default step ciphersuite(s).
// These are TLS 1.0 - 1.2 cipher suites.
DefaultTLSCipherSuites = CipherSuites{ DefaultTLSCipherSuites = CipherSuites{
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
} }
// ApprovedTLSCipherSuites smallstep approved ciphersuites. // ApprovedTLSCipherSuites smallstep approved ciphersuites.
@ -26,25 +27,21 @@ var (
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
} }
// DefaultTLSOptions represents the default TLS version as well as the cipher // DefaultTLSOptions represents the default TLS version as well as the cipher
// suites used in the TLS certificates. // suites used in the TLS certificates.
DefaultTLSOptions = TLSOptions{ DefaultTLSOptions = TLSOptions{
CipherSuites: CipherSuites{ CipherSuites: DefaultTLSCipherSuites,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", MinVersion: DefaultTLSMinVersion,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", MaxVersion: DefaultTLSMaxVersion,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", Renegotiation: DefaultTLSRenegotiation,
},
MinVersion: 1.2,
MaxVersion: 1.2,
Renegotiation: false,
} }
) )
@ -119,27 +116,38 @@ func (c CipherSuites) Value() []uint16 {
// cipherSuites has the list of supported cipher suites. // cipherSuites has the list of supported cipher suites.
var cipherSuites = map[string]uint16{ var cipherSuites = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, // TLS 1.0 - 1.2 cipher suites.
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
// TLS 1.3 cipher sutes.
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
// Legacy names.
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
} }
// TLSOptions represents the TLS options that can be specified on *tls.Config // TLSOptions represents the TLS options that can be specified on *tls.Config

284
authority/export.go Normal file
View file

@ -0,0 +1,284 @@
package authority
import (
"encoding/json"
"io/ioutil"
"net/url"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/cli-utils/config"
"go.step.sm/linkedca"
"google.golang.org/protobuf/types/known/structpb"
)
// Export creates a linkedca configuration form the current ca.json and loaded
// authorities.
//
// Note that export will not export neither the pki password nor the certificate
// issuer password.
func (a *Authority) Export() (c *linkedca.Configuration, err error) {
// Recover from panics
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
files := make(map[string][]byte)
// The exported configuration should not include the password in it.
c = &linkedca.Configuration{
Version: "1.0",
Root: mustReadFilesOrURIs(a.config.Root, files),
FederatedRoots: mustReadFilesOrURIs(a.config.FederatedRoots, files),
Intermediate: mustReadFileOrURI(a.config.IntermediateCert, files),
IntermediateKey: mustReadFileOrURI(a.config.IntermediateKey, files),
Address: a.config.Address,
InsecureAddress: a.config.InsecureAddress,
DnsNames: a.config.DNSNames,
Db: mustMarshalToStruct(a.config.DB),
Logger: mustMarshalToStruct(a.config.Logger),
Monitoring: mustMarshalToStruct(a.config.Monitoring),
Authority: &linkedca.Authority{
Id: a.config.AuthorityConfig.AuthorityID,
EnableAdmin: a.config.AuthorityConfig.EnableAdmin,
DisableIssuedAtCheck: a.config.AuthorityConfig.DisableIssuedAtCheck,
Backdate: mustDuration(a.config.AuthorityConfig.Backdate),
DeploymentType: a.config.AuthorityConfig.DeploymentType,
},
Files: files,
}
// SSH
if v := a.config.SSH; v != nil {
c.Ssh = &linkedca.SSH{
HostKey: mustReadFileOrURI(v.HostKey, files),
UserKey: mustReadFileOrURI(v.UserKey, files),
AddUserPrincipal: v.AddUserPrincipal,
AddUserCommand: v.AddUserCommand,
}
for _, k := range v.Keys {
typ, ok := linkedca.SSHPublicKey_Type_value[strings.ToUpper(k.Type)]
if !ok {
return nil, errors.Errorf("unsupported ssh key type %s", k.Type)
}
c.Ssh.Keys = append(c.Ssh.Keys, &linkedca.SSHPublicKey{
Type: linkedca.SSHPublicKey_Type(typ),
Federated: k.Federated,
Key: mustMarshalToStruct(k),
})
}
if b := v.Bastion; b != nil {
c.Ssh.Bastion = &linkedca.Bastion{
Hostname: b.Hostname,
User: b.User,
Port: b.Port,
Command: b.Command,
Flags: b.Flags,
}
}
}
// KMS
if v := a.config.KMS; v != nil {
var typ int32
var ok bool
if v.Type == "" {
typ = int32(linkedca.KMS_SOFTKMS)
} else {
typ, ok = linkedca.KMS_Type_value[strings.ToUpper(v.Type)]
if !ok {
return nil, errors.Errorf("unsupported kms type %s", v.Type)
}
}
c.Kms = &linkedca.KMS{
Type: linkedca.KMS_Type(typ),
CredentialsFile: v.CredentialsFile,
Uri: v.URI,
Pin: v.Pin,
ManagementKey: v.ManagementKey,
Region: v.Region,
Profile: v.Profile,
}
}
// Authority
// cas options
if v := a.config.AuthorityConfig.Options; v != nil {
c.Authority.Type = 0
c.Authority.CertificateAuthority = v.CertificateAuthority
c.Authority.CertificateAuthorityFingerprint = v.CertificateAuthorityFingerprint
c.Authority.CredentialsFile = v.CredentialsFile
if iss := v.CertificateIssuer; iss != nil {
typ, ok := linkedca.CertificateIssuer_Type_value[strings.ToUpper(iss.Type)]
if !ok {
return nil, errors.Errorf("unknown certificate issuer type %s", iss.Type)
}
// The exported certificate issuer should not include the password.
c.Authority.CertificateIssuer = &linkedca.CertificateIssuer{
Type: linkedca.CertificateIssuer_Type(typ),
Provisioner: iss.Provisioner,
Certificate: mustReadFileOrURI(iss.Certificate, files),
Key: mustReadFileOrURI(iss.Key, files),
}
}
}
// admins
for {
list, cursor := a.admins.Find("", 100)
c.Authority.Admins = append(c.Authority.Admins, list...)
if cursor == "" {
break
}
}
// provisioners
for {
list, cursor := a.provisioners.Find("", 100)
for _, p := range list {
lp, err := ProvisionerToLinkedca(p)
if err != nil {
return nil, err
}
c.Authority.Provisioners = append(c.Authority.Provisioners, lp)
}
if cursor == "" {
break
}
}
// global claims
c.Authority.Claims = claimsToLinkedca(a.config.AuthorityConfig.Claims)
// Distinguished names template
if v := a.config.AuthorityConfig.Template; v != nil {
c.Authority.Template = &linkedca.DistinguishedName{
Country: v.Country,
Organization: v.Organization,
OrganizationalUnit: v.OrganizationalUnit,
Locality: v.Locality,
Province: v.Province,
StreetAddress: v.StreetAddress,
SerialNumber: v.SerialNumber,
CommonName: v.CommonName,
}
}
// TLS
if v := a.config.TLS; v != nil {
c.Tls = &linkedca.TLS{
MinVersion: v.MinVersion.String(),
MaxVersion: v.MaxVersion.String(),
Renegotiation: v.Renegotiation,
}
for _, cs := range v.CipherSuites.Value() {
c.Tls.CipherSuites = append(c.Tls.CipherSuites, linkedca.TLS_CiperSuite(cs))
}
}
// Templates
if v := a.config.Templates; v != nil {
c.Templates = &linkedca.ConfigTemplates{
Ssh: &linkedca.SSHConfigTemplate{},
Data: mustMarshalToStruct(v.Data),
}
// Remove automatically loaded vars
if c.Templates.Data != nil && c.Templates.Data.Fields != nil {
delete(c.Templates.Data.Fields, "Step")
}
for _, t := range v.SSH.Host {
typ, ok := linkedca.ConfigTemplate_Type_value[strings.ToUpper(string(t.Type))]
if !ok {
return nil, errors.Errorf("unsupported template type %s", t.Type)
}
c.Templates.Ssh.Hosts = append(c.Templates.Ssh.Hosts, &linkedca.ConfigTemplate{
Type: linkedca.ConfigTemplate_Type(typ),
Name: t.Name,
Template: mustReadFileOrURI(t.TemplatePath, files),
Path: t.Path,
Comment: t.Comment,
Requires: t.RequiredData,
Content: t.Content,
})
}
for _, t := range v.SSH.User {
typ, ok := linkedca.ConfigTemplate_Type_value[strings.ToUpper(string(t.Type))]
if !ok {
return nil, errors.Errorf("unsupported template type %s", t.Type)
}
c.Templates.Ssh.Users = append(c.Templates.Ssh.Users, &linkedca.ConfigTemplate{
Type: linkedca.ConfigTemplate_Type(typ),
Name: t.Name,
Template: mustReadFileOrURI(t.TemplatePath, files),
Path: t.Path,
Comment: t.Comment,
Requires: t.RequiredData,
Content: t.Content,
})
}
}
return c, nil
}
func mustDuration(d *provisioner.Duration) string {
if d == nil || d.Duration == 0 {
return ""
}
return d.String()
}
func mustMarshalToStruct(v interface{}) *structpb.Struct {
b, err := json.Marshal(v)
if err != nil {
panic(errors.Wrapf(err, "error marshaling %T", v))
}
var r *structpb.Struct
if err := json.Unmarshal(b, &r); err != nil {
panic(errors.Wrapf(err, "error unmarshaling %T", v))
}
return r
}
func mustReadFileOrURI(fn string, m map[string][]byte) string {
if fn == "" {
return ""
}
stepPath := filepath.ToSlash(config.StepPath())
if !strings.HasSuffix(stepPath, "/") {
stepPath += "/"
}
fn = strings.TrimPrefix(filepath.ToSlash(fn), stepPath)
ok, err := isFilename(fn)
if err != nil {
panic(err)
}
if ok {
b, err := ioutil.ReadFile(config.StepAbs(fn))
if err != nil {
panic(errors.Wrapf(err, "error reading %s", fn))
}
m[fn] = b
return fn
}
return fn
}
func mustReadFilesOrURIs(fns []string, m map[string][]byte) []string {
var result []string
for _, fn := range fns {
result = append(result, mustReadFileOrURI(fn, m))
}
return result
}
func isFilename(fn string) (bool, error) {
u, err := url.Parse(fn)
if err != nil {
return false, errors.Wrapf(err, "error parsing %s", fn)
}
return u.Scheme == "" || u.Scheme == "file", nil
}

490
authority/linkedca.go Normal file
View file

@ -0,0 +1,490 @@
package authority
import (
"context"
"crypto"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"net/url"
"regexp"
"strings"
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/db"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/tlsutil"
"go.step.sm/crypto/x509util"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const uuidPattern = "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
type linkedCaClient struct {
renewer *tlsutil.Renewer
client linkedca.MajordomoClient
authorityID string
}
type linkedCAClaims struct {
jose.Claims
SANs []string `json:"sans"`
SHA string `json:"sha"`
}
func newLinkedCAClient(token string) (*linkedCaClient, error) {
tok, err := jose.ParseSigned(token)
if err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
var claims linkedCAClaims
if err := tok.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil, errors.Wrap(err, "error parsing token")
}
// Validate claims
if len(claims.Audience) != 1 {
return nil, errors.New("error parsing token: invalid aud claim")
}
if claims.SHA == "" {
return nil, errors.New("error parsing token: invalid sha claim")
}
// Get linkedCA endpoint from audience.
u, err := url.Parse(claims.Audience[0])
if err != nil {
return nil, errors.New("error parsing token: invalid aud claim")
}
// Get authority from SANs
authority, err := getAuthority(claims.SANs)
if err != nil {
return nil, err
}
// Create csr to login with
signer, err := keyutil.GenerateDefaultSigner()
if err != nil {
return nil, err
}
csr, err := x509util.CreateCertificateRequest(claims.Subject, claims.SANs, signer)
if err != nil {
return nil, err
}
// Get and verify root certificate
root, err := getRootCertificate(u.Host, claims.SHA)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
pool.AddCert(root)
// Login with majordomo and get certificates
cert, tlsConfig, err := login(authority, token, csr, signer, u.Host, pool)
if err != nil {
return nil, err
}
// Start TLS renewer and set the GetClientCertificate callback to it.
renewer, err := tlsutil.NewRenewer(cert, tlsConfig, func() (*tls.Certificate, *tls.Config, error) {
return login(authority, token, csr, signer, u.Host, pool)
})
if err != nil {
return nil, err
}
tlsConfig.GetClientCertificate = renewer.GetClientCertificate
// Start mTLS client
conn, err := grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
return nil, errors.Wrapf(err, "error connecting %s", u.Host)
}
return &linkedCaClient{
renewer: renewer,
client: linkedca.NewMajordomoClient(conn),
authorityID: authority,
}, nil
}
func (c *linkedCaClient) Run() {
c.renewer.Run()
}
func (c *linkedCaClient) Stop() {
c.renewer.Stop()
}
func (c *linkedCaClient) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
resp, err := c.client.CreateProvisioner(ctx, &linkedca.CreateProvisionerRequest{
Type: prov.Type,
Name: prov.Name,
Details: prov.Details,
Claims: prov.Claims,
X509Template: prov.X509Template,
SshTemplate: prov.SshTemplate,
})
if err != nil {
return errors.Wrap(err, "error creating provisioner")
}
prov.Id = resp.Id
prov.AuthorityId = resp.AuthorityId
return nil
}
func (c *linkedCaClient) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) {
resp, err := c.client.GetProvisioner(ctx, &linkedca.GetProvisionerRequest{
Id: id,
})
if err != nil {
return nil, errors.Wrap(err, "error getting provisioners")
}
return resp, nil
}
func (c *linkedCaClient) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) {
resp, err := c.client.GetConfiguration(ctx, &linkedca.ConfigurationRequest{
AuthorityId: c.authorityID,
})
if err != nil {
return nil, errors.Wrap(err, "error getting provisioners")
}
return resp.Provisioners, nil
}
func (c *linkedCaClient) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
_, err := c.client.UpdateProvisioner(ctx, &linkedca.UpdateProvisionerRequest{
Id: prov.Id,
Name: prov.Name,
Details: prov.Details,
Claims: prov.Claims,
X509Template: prov.X509Template,
SshTemplate: prov.SshTemplate,
})
return errors.Wrap(err, "error updating provisioner")
}
func (c *linkedCaClient) DeleteProvisioner(ctx context.Context, id string) error {
_, err := c.client.DeleteProvisioner(ctx, &linkedca.DeleteProvisionerRequest{
Id: id,
})
return errors.Wrap(err, "error deleting provisioner")
}
func (c *linkedCaClient) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error {
resp, err := c.client.CreateAdmin(ctx, &linkedca.CreateAdminRequest{
Subject: adm.Subject,
ProvisionerId: adm.ProvisionerId,
Type: adm.Type,
})
if err != nil {
return errors.Wrap(err, "error creating admin")
}
adm.Id = resp.Id
adm.AuthorityId = resp.AuthorityId
return nil
}
func (c *linkedCaClient) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) {
resp, err := c.client.GetAdmin(ctx, &linkedca.GetAdminRequest{
Id: id,
})
if err != nil {
return nil, errors.Wrap(err, "error getting admins")
}
return resp, nil
}
func (c *linkedCaClient) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
resp, err := c.client.GetConfiguration(ctx, &linkedca.ConfigurationRequest{
AuthorityId: c.authorityID,
})
if err != nil {
return nil, errors.Wrap(err, "error getting admins")
}
return resp.Admins, nil
}
func (c *linkedCaClient) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error {
_, err := c.client.UpdateAdmin(ctx, &linkedca.UpdateAdminRequest{
Id: adm.Id,
Type: adm.Type,
})
return errors.Wrap(err, "error updating admin")
}
func (c *linkedCaClient) DeleteAdmin(ctx context.Context, id string) error {
_, err := c.client.DeleteAdmin(ctx, &linkedca.DeleteAdminRequest{
Id: id,
})
return errors.Wrap(err, "error deleting admin")
}
func (c *linkedCaClient) StoreCertificateChain(fullchain ...*x509.Certificate) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := c.client.PostCertificate(ctx, &linkedca.CertificateRequest{
PemCertificate: serializeCertificateChain(fullchain[0]),
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
})
return errors.Wrap(err, "error posting certificate")
}
func (c *linkedCaClient) StoreRenewedCertificate(parent *x509.Certificate, fullchain ...*x509.Certificate) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := c.client.PostCertificate(ctx, &linkedca.CertificateRequest{
PemCertificate: serializeCertificateChain(fullchain[0]),
PemCertificateChain: serializeCertificateChain(fullchain[1:]...),
PemParentCertificate: serializeCertificateChain(parent),
})
return errors.Wrap(err, "error posting certificate")
}
func (c *linkedCaClient) StoreSSHCertificate(crt *ssh.Certificate) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := c.client.PostSSHCertificate(ctx, &linkedca.SSHCertificateRequest{
Certificate: string(ssh.MarshalAuthorizedKey(crt)),
})
return errors.Wrap(err, "error posting ssh certificate")
}
func (c *linkedCaClient) Revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := c.client.RevokeCertificate(ctx, &linkedca.RevokeCertificateRequest{
Serial: rci.Serial,
PemCertificate: serializeCertificate(crt),
Reason: rci.Reason,
ReasonCode: linkedca.RevocationReasonCode(rci.ReasonCode),
Passive: true,
})
return errors.Wrap(err, "error revoking certificate")
}
func (c *linkedCaClient) RevokeSSH(ssh *ssh.Certificate, rci *db.RevokedCertificateInfo) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, err := c.client.RevokeSSHCertificate(ctx, &linkedca.RevokeSSHCertificateRequest{
Serial: rci.Serial,
Certificate: serializeSSHCertificate(ssh),
Reason: rci.Reason,
ReasonCode: linkedca.RevocationReasonCode(rci.ReasonCode),
Passive: true,
})
return errors.Wrap(err, "error revoking ssh certificate")
}
func (c *linkedCaClient) IsRevoked(serial string) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
resp, err := c.client.GetCertificateStatus(ctx, &linkedca.GetCertificateStatusRequest{
Serial: serial,
})
if err != nil {
return false, errors.Wrap(err, "error getting certificate status")
}
return resp.Status != linkedca.RevocationStatus_ACTIVE, nil
}
func (c *linkedCaClient) IsSSHRevoked(serial string) (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
resp, err := c.client.GetSSHCertificateStatus(ctx, &linkedca.GetSSHCertificateStatusRequest{
Serial: serial,
})
if err != nil {
return false, errors.Wrap(err, "error getting certificate status")
}
return resp.Status != linkedca.RevocationStatus_ACTIVE, nil
}
func serializeCertificate(crt *x509.Certificate) string {
if crt == nil {
return ""
}
return string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
}))
}
func serializeCertificateChain(fullchain ...*x509.Certificate) string {
var chain string
for _, crt := range fullchain {
chain += string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
}))
}
return chain
}
func serializeSSHCertificate(crt *ssh.Certificate) string {
if crt == nil {
return ""
}
return string(ssh.MarshalAuthorizedKey(crt))
}
func getAuthority(sans []string) (string, error) {
for _, s := range sans {
if strings.HasPrefix(s, "urn:smallstep:authority:") {
if regexp.MustCompile(uuidPattern).MatchString(s[24:]) {
return s[24:], nil
}
}
}
return "", fmt.Errorf("error parsing token: invalid sans claim")
}
// getRootCertificate creates an insecure majordomo client and returns the
// verified root certificate.
func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})))
if err != nil {
return nil, errors.Wrapf(err, "error connecting %s", endpoint)
}
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)
resp, err := client.GetRootCertificate(ctx, &linkedca.GetRootCertificateRequest{
Fingerprint: fingerprint,
})
if err != nil {
return nil, fmt.Errorf("error getting root certificate: %w", err)
}
var block *pem.Block
b := []byte(resp.PemCertificate)
for len(b) > 0 {
block, b = pem.Decode(b)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing certificate: %w", err)
}
// verify the sha256
sum := sha256.Sum256(cert.Raw)
if !strings.EqualFold(fingerprint, hex.EncodeToString(sum[:])) {
return nil, fmt.Errorf("error verifying certificate: SHA256 fingerprint does not match")
}
return cert, nil
}
return nil, fmt.Errorf("error getting root certificate: certificate not found")
}
// login creates a new majordomo client with just the root ca pool and returns
// the signed certificate and tls configuration.
func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) {
// Connect to majordomo
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
RootCAs: rootCAs,
})))
if err != nil {
return nil, nil, errors.Wrapf(err, "error connecting %s", endpoint)
}
// Login to get the signed certificate
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
client := linkedca.NewMajordomoClient(conn)
resp, err := client.Login(ctx, &linkedca.LoginRequest{
AuthorityId: authority,
Token: token,
PemCertificateRequest: string(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr.Raw,
})),
})
if err != nil {
return nil, nil, errors.Wrapf(err, "error logging in %s", endpoint)
}
// Parse login response
var block *pem.Block
var bundle []*x509.Certificate
rest := []byte(resp.PemCertificateChain)
for {
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
return nil, nil, errors.New("error decoding login response: pemCertificateChain is not a certificate bundle")
}
crt, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, errors.Wrap(err, "error parsing login response")
}
bundle = append(bundle, crt)
}
if len(bundle) == 0 {
return nil, nil, errors.New("error decoding login response: pemCertificateChain should not be empty")
}
// Build tls.Certificate with PemCertificate and intermediates in the
// PemCertificateChain
cert := &tls.Certificate{
PrivateKey: signer,
}
rest = []byte(resp.PemCertificate)
for {
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type == "CERTIFICATE" {
leaf, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, errors.Wrap(err, "error parsing pemCertificate")
}
cert.Certificate = append(cert.Certificate, block.Bytes)
cert.Leaf = leaf
}
}
// Add intermediates to the tls.Certificate
last := len(bundle) - 1
for i := 0; i < last; i++ {
cert.Certificate = append(cert.Certificate, bundle[i].Raw)
}
// Add root to the pool if it's not there yet
rootCAs.AddCert(bundle[last])
return cert, &tls.Config{
RootCAs: rootCAs,
}, nil
}

View file

@ -196,6 +196,15 @@ func WithAdminDB(db admin.DB) Option {
} }
} }
// WithLinkedCAToken is an option to set the authentication token used to enable
// linked ca.
func WithLinkedCAToken(token string) Option {
return func(a *Authority) error {
a.linkedCAToken = token
return nil
}
}
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
var block *pem.Block var block *pem.Block
var certs []*x509.Certificate var certs []*x509.Certificate

View file

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -30,7 +29,6 @@ type SSHPOP struct {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
db db.AuthDB
claimer *Claimer claimer *Claimer
audiences Audiences audiences Audiences
sshPubKeys *SSHKeys sshPubKeys *SSHKeys
@ -102,7 +100,6 @@ func (p *SSHPOP) Init(config Config) error {
} }
p.audiences = config.Audiences.WithFragment(p.GetIDForToken()) p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
p.db = config.DB
p.sshPubKeys = config.SSHKeys p.sshPubKeys = config.SSHKeys
return nil return nil
} }
@ -110,6 +107,8 @@ func (p *SSHPOP) Init(config Config) error {
// authorizeToken performs common jwt authorization actions and returns the // authorizeToken performs common jwt authorization actions and returns the
// claims for case specific downstream parsing. // claims for case specific downstream parsing.
// e.g. a Sign request will auth/validate different fields than a Revoke request. // e.g. a Sign request will auth/validate different fields than a Revoke request.
//
// Checking for certificate revocation has been moved to the authority package.
func (p *SSHPOP) authorizeToken(token string, audiences []string) (*sshPOPPayload, error) { func (p *SSHPOP) authorizeToken(token string, audiences []string) (*sshPOPPayload, error) {
sshCert, jwt, err := ExtractSSHPOPCert(token) sshCert, jwt, err := ExtractSSHPOPCert(token)
if err != nil { if err != nil {
@ -117,14 +116,6 @@ func (p *SSHPOP) authorizeToken(token string, audiences []string) (*sshPOPPayloa
"sshpop.authorizeToken; error extracting sshpop header from token") "sshpop.authorizeToken; error extracting sshpop header from token")
} }
// Check for revocation.
if isRevoked, err := p.db.IsSSHRevoked(strconv.FormatUint(sshCert.Serial, 10)); err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"sshpop.authorizeToken; error checking checking sshpop cert revocation")
} else if isRevoked {
return nil, errs.Unauthorized("sshpop.authorizeToken; sshpop certificate is revoked")
}
// Check validity period of the certificate. // Check validity period of the certificate.
n := time.Now() n := time.Now()
if sshCert.ValidAfter != 0 && time.Unix(int64(sshCert.ValidAfter), 0).After(n) { if sshCert.ValidAfter != 0 && time.Unix(int64(sshCert.ValidAfter), 0).After(n) {

View file

@ -11,7 +11,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
@ -83,52 +82,9 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
err: errors.New("sshpop.authorizeToken; error extracting sshpop header from token: extractSSHPOPCert; error parsing token: "), err: errors.New("sshpop.authorizeToken; error extracting sshpop header from token: extractSSHPOPCert; error parsing token: "),
} }
}, },
"fail/error-revoked-db-check": func(t *testing.T) test {
p, err := generateSSHPOP()
assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, errors.New("force")
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err)
tok, err := generateSSHPOPToken(p, cert, jwk)
assert.FatalError(t, err)
return test{
p: p,
token: tok,
code: http.StatusInternalServerError,
err: errors.New("sshpop.authorizeToken; error checking checking sshpop cert revocation: force"),
}
},
"fail/cert-already-revoked": func(t *testing.T) test {
p, err := generateSSHPOP()
assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return true, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err)
tok, err := generateSSHPOPToken(p, cert, jwk)
assert.FatalError(t, err)
return test{
p: p,
token: tok,
code: http.StatusUnauthorized,
err: errors.New("sshpop.authorizeToken; sshpop certificate is revoked"),
}
},
"fail/cert-not-yet-valid": func(t *testing.T) test { "fail/cert-not-yet-valid": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{ cert, jwk, err := createSSHCert(&ssh.Certificate{
CertType: ssh.UserCert, CertType: ssh.UserCert,
ValidAfter: uint64(time.Now().Add(time.Minute).Unix()), ValidAfter: uint64(time.Now().Add(time.Minute).Unix()),
@ -146,11 +102,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/cert-past-validity": func(t *testing.T) test { "fail/cert-past-validity": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{ cert, jwk, err := createSSHCert(&ssh.Certificate{
CertType: ssh.UserCert, CertType: ssh.UserCert,
ValidBefore: uint64(time.Now().Add(-time.Minute).Unix()), ValidBefore: uint64(time.Now().Add(-time.Minute).Unix()),
@ -168,11 +119,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/no-signer-found": func(t *testing.T) test { "fail/no-signer-found": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.HostCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.HostCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateSSHPOPToken(p, cert, jwk) tok, err := generateSSHPOPToken(p, cert, jwk)
@ -187,11 +133,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/error-parsing-claims-bad-sig": func(t *testing.T) test { "fail/error-parsing-claims-bad-sig": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, _, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, _, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
otherJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) otherJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
@ -208,11 +149,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/invalid-claims-issuer": func(t *testing.T) test { "fail/invalid-claims-issuer": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("foo", "bar", testAudiences.Sign[0], "", tok, err := generateToken("foo", "bar", testAudiences.Sign[0], "",
@ -228,11 +164,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/invalid-audience": func(t *testing.T) test { "fail/invalid-audience": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("foo", p.GetName(), "invalid-aud", "", tok, err := generateToken("foo", p.GetName(), "invalid-aud", "",
@ -248,11 +179,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"fail/empty-subject": func(t *testing.T) test { "fail/empty-subject": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "", tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "",
@ -268,11 +194,6 @@ func TestSSHPOP_authorizeToken(t *testing.T) {
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateSSHPOPToken(p, cert, jwk) tok, err := generateSSHPOPToken(p, cert, jwk)
@ -330,11 +251,6 @@ func TestSSHPOP_AuthorizeSSHRevoke(t *testing.T) {
"fail/subject-not-equal-serial": func(t *testing.T) test { "fail/subject-not-equal-serial": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRevoke[0], "", tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRevoke[0], "",
@ -350,11 +266,6 @@ func TestSSHPOP_AuthorizeSSHRevoke(t *testing.T) {
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.UserCert}, sshSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.UserCert}, sshSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRevoke[0], "", tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRevoke[0], "",
@ -419,11 +330,6 @@ func TestSSHPOP_AuthorizeSSHRenew(t *testing.T) {
"fail/not-host-cert": func(t *testing.T) test { "fail/not-host-cert": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshUserSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshUserSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRenew[0], "", tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRenew[0], "",
@ -439,11 +345,6 @@ func TestSSHPOP_AuthorizeSSHRenew(t *testing.T) {
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.HostCert}, sshHostSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.HostCert}, sshHostSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRenew[0], "", tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRenew[0], "",
@ -511,11 +412,6 @@ func TestSSHPOP_AuthorizeSSHRekey(t *testing.T) {
"fail/not-host-cert": func(t *testing.T) test { "fail/not-host-cert": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshUserSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{CertType: ssh.UserCert}, sshUserSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRekey[0], "", tok, err := generateToken("foo", p.GetName(), testAudiences.SSHRekey[0], "",
@ -531,11 +427,6 @@ func TestSSHPOP_AuthorizeSSHRekey(t *testing.T) {
"ok": func(t *testing.T) test { "ok": func(t *testing.T) test {
p, err := generateSSHPOP() p, err := generateSSHPOP()
assert.FatalError(t, err) assert.FatalError(t, err)
p.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.HostCert}, sshHostSigner) cert, jwk, err := createSSHCert(&ssh.Certificate{Serial: 123455, CertType: ssh.HostCert}, sshHostSigner)
assert.FatalError(t, err) assert.FatalError(t, err)
tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRekey[0], "", tok, err := generateToken("123455", p.GetName(), testAudiences.SSHRekey[0], "",

View file

@ -4,12 +4,17 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"encoding/pem"
"fmt" "fmt"
"io/ioutil"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
step "go.step.sm/cli-utils/config"
"go.step.sm/cli-utils/ui"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/linkedca" "go.step.sm/linkedca"
"gopkg.in/square/go-jose.v2/jwt" "gopkg.in/square/go-jose.v2/jwt"
@ -234,6 +239,14 @@ func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error {
} }
func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (*linkedca.Provisioner, error) { func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (*linkedca.Provisioner, error) {
if password == "" {
pass, err := ui.PromptPasswordGenerate("Please enter the password to encrypt your first provisioner, leave empty and we'll generate one")
if err != nil {
return nil, err
}
password = string(pass)
}
jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password)) jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password))
if err != nil { if err != nil {
return nil, admin.WrapErrorISE(err, "error generating JWK key pair") return nil, admin.WrapErrorISE(err, "error generating JWK key pair")
@ -398,6 +411,13 @@ func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.
return return
} }
func durationsToLinkedca(d *provisioner.Duration) string {
if d == nil {
return ""
}
return d.Duration.String()
}
// claimsToCertificates converts the linkedca provisioner claims type to the // claimsToCertificates converts the linkedca provisioner claims type to the
// certifictes claims type. // certifictes claims type.
func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
@ -438,6 +458,109 @@ func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
return pc, nil return pc, nil
} }
func claimsToLinkedca(c *provisioner.Claims) *linkedca.Claims {
if c == nil {
return nil
}
disableRenewal := config.DefaultDisableRenewal
if c.DisableRenewal != nil {
disableRenewal = *c.DisableRenewal
}
lc := &linkedca.Claims{
DisableRenewal: disableRenewal,
}
if c.DefaultTLSDur != nil || c.MinTLSDur != nil || c.MaxTLSDur != nil {
lc.X509 = &linkedca.X509Claims{
Enabled: true,
Durations: &linkedca.Durations{
Default: durationsToLinkedca(c.DefaultTLSDur),
Min: durationsToLinkedca(c.MinTLSDur),
Max: durationsToLinkedca(c.MaxTLSDur),
},
}
}
if c.EnableSSHCA != nil && *c.EnableSSHCA {
lc.Ssh = &linkedca.SSHClaims{
Enabled: true,
}
if c.DefaultUserSSHDur != nil || c.MinUserSSHDur != nil || c.MaxUserSSHDur != nil {
lc.Ssh.UserDurations = &linkedca.Durations{
Default: durationsToLinkedca(c.DefaultUserSSHDur),
Min: durationsToLinkedca(c.MinUserSSHDur),
Max: durationsToLinkedca(c.MaxUserSSHDur),
}
}
if c.DefaultHostSSHDur != nil || c.MinHostSSHDur != nil || c.MaxHostSSHDur != nil {
lc.Ssh.HostDurations = &linkedca.Durations{
Default: durationsToLinkedca(c.DefaultHostSSHDur),
Min: durationsToLinkedca(c.MinHostSSHDur),
Max: durationsToLinkedca(c.MaxHostSSHDur),
}
}
}
return lc
}
func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, *linkedca.Template, error) {
var err error
var x509Template, sshTemplate *linkedca.Template
if p == nil {
return nil, nil, nil
}
if p.X509 != nil && p.X509.HasTemplate() {
x509Template = &linkedca.Template{
Template: nil,
Data: nil,
}
if p.X509.Template != "" {
x509Template.Template = []byte(p.SSH.Template)
} else if p.X509.TemplateFile != "" {
filename := step.StepAbs(p.X509.TemplateFile)
if x509Template.Template, err = ioutil.ReadFile(filename); err != nil {
return nil, nil, errors.Wrap(err, "error reading x509 template")
}
}
}
if p.SSH != nil && p.SSH.HasTemplate() {
sshTemplate = &linkedca.Template{
Template: nil,
Data: nil,
}
if p.SSH.Template != "" {
sshTemplate.Template = []byte(p.SSH.Template)
} else if p.SSH.TemplateFile != "" {
filename := step.StepAbs(p.SSH.TemplateFile)
if sshTemplate.Template, err = ioutil.ReadFile(filename); err != nil {
return nil, nil, errors.Wrap(err, "error reading ssh template")
}
}
}
return x509Template, sshTemplate, nil
}
func provisionerPEMToLinkedca(b []byte) [][]byte {
var roots [][]byte
var block *pem.Block
for {
if block, b = pem.Decode(b); block == nil {
break
}
roots = append(roots, pem.EncodeToMemory(block))
}
return roots
}
// ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner // ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner
// interface. // interface.
func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) {
@ -448,7 +571,7 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
details := p.Details.GetData() details := p.Details.GetData()
if details == nil { if details == nil {
return nil, fmt.Errorf("provisioner does not have any details") return nil, errors.New("provisioner does not have any details")
} }
options := optionsToCertificates(p) options := optionsToCertificates(p)
@ -457,7 +580,7 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
case *linkedca.ProvisionerDetails_JWK: case *linkedca.ProvisionerDetails_JWK:
jwk := new(jose.JSONWebKey) jwk := new(jose.JSONWebKey)
if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil { if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil {
return nil, err return nil, errors.Wrap(err, "error unmarshaling public key")
} }
return &provisioner.JWK{ return &provisioner.JWK{
ID: p.Id, ID: p.Id,
@ -588,6 +711,233 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
} }
} }
// ProvisionerToLinkedca converts a provisioner.Interface to a
// linkedca.Provisioner type.
func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, error) {
switch p := p.(type) {
case *provisioner.JWK:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
publicKey, err := json.Marshal(p.Key)
if err != nil {
return nil, errors.Wrap(err, "error marshaling key")
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_JWK,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_JWK{
JWK: &linkedca.JWKProvisioner{
PublicKey: publicKey,
EncryptedPrivateKey: []byte(p.EncryptedKey),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.OIDC:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_OIDC,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_OIDC{
OIDC: &linkedca.OIDCProvisioner{
ClientId: p.ClientID,
ClientSecret: p.ClientSecret,
ConfigurationEndpoint: p.ConfigurationEndpoint,
Admins: p.Admins,
Domains: p.Domains,
Groups: p.Groups,
ListenAddress: p.ListenAddress,
TenantId: p.TenantID,
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.GCP:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_GCP,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_GCP{
GCP: &linkedca.GCPProvisioner{
ServiceAccounts: p.ServiceAccounts,
ProjectIds: p.ProjectIDs,
DisableCustomSans: p.DisableCustomSANs,
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
InstanceAge: p.InstanceAge.String(),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.AWS:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_AWS,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_AWS{
AWS: &linkedca.AWSProvisioner{
Accounts: p.Accounts,
DisableCustomSans: p.DisableCustomSANs,
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
InstanceAge: p.InstanceAge.String(),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.Azure:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_AZURE,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_Azure{
Azure: &linkedca.AzureProvisioner{
TenantId: p.TenantID,
ResourceGroups: p.ResourceGroups,
Audience: p.Audience,
DisableCustomSans: p.DisableCustomSANs,
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.ACME:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_ACME,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_ACME{
ACME: &linkedca.ACMEProvisioner{
ForceCn: p.ForceCN,
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.X5C:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_X5C,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_X5C{
X5C: &linkedca.X5CProvisioner{
Roots: provisionerPEMToLinkedca(p.Roots),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.K8sSA:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_K8SSA,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_K8SSA{
K8SSA: &linkedca.K8SSAProvisioner{
PublicKeys: provisionerPEMToLinkedca(p.PubKeys),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
case *provisioner.SSHPOP:
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_SSHPOP,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_SSHPOP{
SSHPOP: &linkedca.SSHPOPProvisioner{},
},
},
Claims: claimsToLinkedca(p.Claims),
}, nil
case *provisioner.SCEP:
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
if err != nil {
return nil, err
}
return &linkedca.Provisioner{
Id: p.ID,
Type: linkedca.Provisioner_SCEP,
Name: p.GetName(),
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_SCEP{
SCEP: &linkedca.SCEPProvisioner{
ForceCn: p.ForceCN,
Challenge: p.GetChallengePassword(),
Capabilities: p.Capabilities,
MinimumPublicKeyLength: int32(p.MinimumPublicKeyLength),
},
},
},
Claims: claimsToLinkedca(p.Claims),
X509Template: x509Template,
SshTemplate: sshTemplate,
}, nil
default:
return nil, fmt.Errorf("provisioner %s not implemented", p.GetType())
}
}
func parseInstanceAge(age string) (provisioner.Duration, error) { func parseInstanceAge(age string) (provisioner.Duration, error) {
var instanceAge provisioner.Duration var instanceAge provisioner.Duration
if age != "" { if age != "" {

View file

@ -239,7 +239,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
} }
} }
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented { if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db")
} }
@ -249,7 +249,11 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template. // RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) { func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) {
if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 {
return nil, errs.BadRequest("rewnewSSH: cannot renew certificate without validity period") return nil, errs.BadRequest("renewSSH: cannot renew certificate without validity period")
}
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err
} }
backdate := a.config.AuthorityConfig.Backdate.Duration backdate := a.config.AuthorityConfig.Backdate.Duration
@ -294,7 +298,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate")
} }
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented { if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db")
} }
@ -319,6 +323,10 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
return nil, errs.BadRequest("rekeySSH; cannot rekey certificate without validity period") return nil, errs.BadRequest("rekeySSH; cannot rekey certificate without validity period")
} }
if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil {
return nil, err
}
backdate := a.config.AuthorityConfig.Backdate.Duration backdate := a.config.AuthorityConfig.Backdate.Duration
duration := time.Duration(oldCert.ValidBefore-oldCert.ValidAfter) * time.Second duration := time.Duration(oldCert.ValidBefore-oldCert.ValidAfter) * time.Second
now := time.Now() now := time.Now()
@ -369,13 +377,23 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub
} }
} }
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented { if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db")
} }
return cert, nil return cert, nil
} }
func (a *Authority) storeSSHCertificate(cert *ssh.Certificate) error {
type sshCertificateStorer interface {
StoreSSHCertificate(crt *ssh.Certificate) error
}
if s, ok := a.adminDB.(sshCertificateStorer); ok {
return s.StoreSSHCertificate(cert)
}
return a.db.StoreSSHCertificate(cert)
}
// IsValidForAddUser checks if a user provisioner certificate can be issued to // IsValidForAddUser checks if a user provisioner certificate can be issued to
// the given certificate. // the given certificate.
func IsValidForAddUser(cert *ssh.Certificate) error { func IsValidForAddUser(cert *ssh.Certificate) error {
@ -451,7 +469,7 @@ func (a *Authority) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, subje
} }
cert.Signature = sig cert.Signature = sig
if err = a.db.StoreSSHCertificate(cert); err != nil && err != db.ErrNotImplemented { if err = a.storeSSHCertificate(cert); err != nil && err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSHAddUser: error storing certificate in db") return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSHAddUser: error storing certificate in db")
} }

View file

@ -750,6 +750,11 @@ func TestAuthority_RekeySSH(t *testing.T) {
now := time.Now().UTC() now := time.Now().UTC()
a := testAuthority(t) a := testAuthority(t)
a.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
}
type test struct { type test struct {
auth *Authority auth *Authority
@ -763,6 +768,56 @@ func TestAuthority_RekeySSH(t *testing.T) {
code int code int
} }
tests := map[string]func(t *testing.T) *test{ tests := map[string]func(t *testing.T) *test{
"fail/is-revoked": func(t *testing.T) *test {
auth := testAuthority(t)
auth.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return true, nil
},
}
return &test{
auth: auth,
userSigner: signer,
hostSigner: signer,
cert: &ssh.Certificate{
Serial: 1234567890,
ValidAfter: uint64(now.Unix()),
ValidBefore: uint64(now.Add(time.Hour).Unix()),
CertType: ssh.UserCert,
ValidPrincipals: []string{"foo", "bar"},
KeyId: "foo",
},
key: pub,
signOpts: []provisioner.SignOption{},
err: errors.New("authority.authorizeSSHCertificate: certificate has been revoked"),
code: http.StatusUnauthorized,
}
},
"fail/is-revoked-error": func(t *testing.T) *test {
auth := testAuthority(t)
auth.db = &db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, errors.New("an error")
},
}
return &test{
auth: auth,
userSigner: signer,
hostSigner: signer,
cert: &ssh.Certificate{
Serial: 1234567890,
ValidAfter: uint64(now.Unix()),
ValidBefore: uint64(now.Add(time.Hour).Unix()),
CertType: ssh.UserCert,
ValidPrincipals: []string{"foo", "bar"},
KeyId: "foo",
},
key: pub,
signOpts: []provisioner.SignOption{},
err: errors.New("authority.authorizeSSHCertificate: an error"),
code: http.StatusInternalServerError,
}
},
"fail/opts-type": func(t *testing.T) *test { "fail/opts-type": func(t *testing.T) *test {
return &test{ return &test{
userSigner: signer, userSigner: signer,
@ -831,6 +886,9 @@ func TestAuthority_RekeySSH(t *testing.T) {
"fail/db-store": func(t *testing.T) *test { "fail/db-store": func(t *testing.T) *test {
return &test{ return &test{
auth: testAuthority(t, WithDatabase(&db.MockAuthDB{ auth: testAuthority(t, WithDatabase(&db.MockAuthDB{
MIsSSHRevoked: func(sn string) (bool, error) {
return false, nil
},
MStoreSSHCertificate: func(cert *ssh.Certificate) error { MStoreSSHCertificate: func(cert *ssh.Certificate) error {
return errors.New("force") return errors.New("force")
}, },

View file

@ -21,6 +21,7 @@ import (
"go.step.sm/crypto/keyutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh"
) )
// GetTLSOptions returns the tls options configured. // GetTLSOptions returns the tls options configured.
@ -36,7 +37,6 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
if def == nil { if def == nil {
return errors.New("default ASN1DN template cannot be nil") return errors.New("default ASN1DN template cannot be nil")
} }
if len(crt.Subject.Country) == 0 && def.Country != "" { if len(crt.Subject.Country) == 0 && def.Country != "" {
crt.Subject.Country = append(crt.Subject.Country, def.Country) crt.Subject.Country = append(crt.Subject.Country, def.Country)
} }
@ -55,7 +55,12 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
if len(crt.Subject.StreetAddress) == 0 && def.StreetAddress != "" { if len(crt.Subject.StreetAddress) == 0 && def.StreetAddress != "" {
crt.Subject.StreetAddress = append(crt.Subject.StreetAddress, def.StreetAddress) crt.Subject.StreetAddress = append(crt.Subject.StreetAddress, def.StreetAddress)
} }
if len(crt.Subject.SerialNumber) == 0 && def.SerialNumber != "" {
crt.Subject.SerialNumber = def.SerialNumber
}
if len(crt.Subject.CommonName) == 0 && def.CommonName != "" {
crt.Subject.CommonName = def.CommonName
}
return nil return nil
} }
} }
@ -280,9 +285,15 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
// `StoreCertificate(...*x509.Certificate) error` instead of just // `StoreCertificate(...*x509.Certificate) error` instead of just
// `StoreCertificate(*x509.Certificate) error`. // `StoreCertificate(*x509.Certificate) error`.
func (a *Authority) storeCertificate(fullchain []*x509.Certificate) error { func (a *Authority) storeCertificate(fullchain []*x509.Certificate) error {
if s, ok := a.db.(interface { type certificateChainStorer interface {
StoreCertificateChain(...*x509.Certificate) error StoreCertificateChain(...*x509.Certificate) error
}); ok { }
// Store certificate in linkedca
if s, ok := a.adminDB.(certificateChainStorer); ok {
return s.StoreCertificateChain(fullchain...)
}
// Store certificate in local db
if s, ok := a.db.(certificateChainStorer); ok {
return s.StoreCertificateChain(fullchain...) return s.StoreCertificateChain(fullchain...)
} }
return a.db.StoreCertificate(fullchain[0]) return a.db.StoreCertificate(fullchain[0])
@ -293,9 +304,15 @@ func (a *Authority) storeCertificate(fullchain []*x509.Certificate) error {
// //
// TODO: at some point we should implement this in the standard implementation. // TODO: at some point we should implement this in the standard implementation.
func (a *Authority) storeRenewedCertificate(oldCert *x509.Certificate, fullchain []*x509.Certificate) error { func (a *Authority) storeRenewedCertificate(oldCert *x509.Certificate, fullchain []*x509.Certificate) error {
if s, ok := a.db.(interface { type renewedCertificateChainStorer interface {
StoreRenewedCertificate(*x509.Certificate, ...*x509.Certificate) error StoreRenewedCertificate(*x509.Certificate, ...*x509.Certificate) error
}); ok { }
// Store certificate in linkedca
if s, ok := a.adminDB.(renewedCertificateChainStorer); ok {
return s.StoreRenewedCertificate(oldCert, fullchain...)
}
// Store certificate in local db
if s, ok := a.db.(renewedCertificateChainStorer); ok {
return s.StoreRenewedCertificate(oldCert, fullchain...) return s.StoreRenewedCertificate(oldCert, fullchain...)
} }
return a.db.StoreCertificate(fullchain[0]) return a.db.StoreCertificate(fullchain[0])
@ -381,7 +398,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
} }
if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod { if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod {
err = a.db.RevokeSSH(rci) err = a.revokeSSH(nil, rci)
} else { } else {
// Revoke an X.509 certificate using CAS. If the certificate is not // Revoke an X.509 certificate using CAS. If the certificate is not
// provided we will try to read it from the db. If the read fails we // provided we will try to read it from the db. If the read fails we
@ -408,7 +425,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
} }
// Save as revoked in the Db. // Save as revoked in the Db.
err = a.db.Revoke(rci) err = a.revoke(revokedCert, rci)
} }
switch err { switch err {
case nil: case nil:
@ -423,6 +440,24 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
} }
} }
func (a *Authority) revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error {
if lca, ok := a.adminDB.(interface {
Revoke(*x509.Certificate, *db.RevokedCertificateInfo) error
}); ok {
return lca.Revoke(crt, rci)
}
return a.db.Revoke(rci)
}
func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateInfo) error {
if lca, ok := a.adminDB.(interface {
RevokeSSH(*ssh.Certificate, *db.RevokedCertificateInfo) error
}); ok {
return lca.RevokeSSH(crt, rci)
}
return a.db.Revoke(rci)
}
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
fatal := func(err error) (*tls.Certificate, error) { fatal := func(err error) (*tls.Certificate, error) {

View file

@ -30,6 +30,7 @@ import (
type options struct { type options struct {
configFile string configFile string
linkedCAToken string
password []byte password []byte
issuerPassword []byte issuerPassword []byte
database db.AuthDB database db.AuthDB
@ -75,6 +76,13 @@ func WithDatabase(db db.AuthDB) Option {
} }
} }
// WithLinkedCAToken sets the token used to authenticate with the linkedca.
func WithLinkedCAToken(token string) Option {
return func(o *options) {
o.linkedCAToken = token
}
}
// CA is the type used to build the complete certificate authority. It builds // CA is the type used to build the complete certificate authority. It builds
// the HTTP server, set ups the middlewares and the HTTP handlers. // the HTTP server, set ups the middlewares and the HTTP handlers.
type CA struct { type CA struct {
@ -111,6 +119,10 @@ func (ca *CA) Init(config *config.Config) (*CA, error) {
} }
var opts []authority.Option var opts []authority.Option
if ca.opts.linkedCAToken != "" {
opts = append(opts, authority.WithLinkedCAToken(ca.opts.linkedCAToken))
}
if ca.opts.database != nil { if ca.opts.database != nil {
opts = append(opts, authority.WithDatabase(ca.opts.database)) opts = append(opts, authority.WithDatabase(ca.opts.database))
} }
@ -326,6 +338,7 @@ func (ca *CA) Reload() error {
newCA, err := New(config, newCA, err := New(config,
WithPassword(ca.opts.password), WithPassword(ca.opts.password),
WithIssuerPassword(ca.opts.issuerPassword), WithIssuerPassword(ca.opts.issuerPassword),
WithLinkedCAToken(ca.opts.linkedCAToken),
WithConfigFile(ca.opts.configFile), WithConfigFile(ca.opts.configFile),
WithDatabase(ca.auth.GetDatabase()), WithDatabase(ca.auth.GetDatabase()),
) )

7
ca/testdata/ca.json vendored
View file

@ -9,12 +9,11 @@
"logger": {"format": "text"}, "logger": {"format": "text"},
"tls": { "tls": {
"minVersion": 1.2, "minVersion": 1.2,
"maxVersion": 1.2, "maxVersion": 1.3,
"renegotiation": false, "renegotiation": false,
"cipherSuites": [ "cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
] ]
}, },
"authority": { "authority": {

View file

@ -38,10 +38,17 @@ type Options struct {
CertificateChain []*x509.Certificate `json:"-"` CertificateChain []*x509.Certificate `json:"-"`
Signer crypto.Signer `json:"-"` Signer crypto.Signer `json:"-"`
// IsCreator is set to true when we're creating a certificate authority. Is // IsCreator is set to true when we're creating a certificate authority. It
// used to skip some validations when initializing a CertificateAuthority. // is used to skip some validations when initializing a
// CertificateAuthority. This option is used on SoftCAS and CloudCAS.
IsCreator bool `json:"-"` IsCreator bool `json:"-"`
// IsCAGetter is set to true when we're just using the
// CertificateAuthorityGetter interface to retrieve the root certificate. It
// is used to skip some validations when initializing a
// CertificateAuthority. This option is used on StepCAS.
IsCAGetter bool `json:"-"`
// KeyManager is the KMS used to generate keys in SoftCAS. // KeyManager is the KMS used to generate keys in SoftCAS.
KeyManager kms.KeyManager `json:"-"` KeyManager kms.KeyManager `json:"-"`

View file

@ -47,10 +47,13 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
return nil, err return nil, err
} }
// Create configured issuer var iss stepIssuer
iss, err := newStepIssuer(caURL, client, opts.CertificateIssuer) // Create configured issuer unless we only want to use GetCertificateAuthority.
if err != nil { // This avoid the request for the password if not provided.
return nil, err if !opts.IsCAGetter {
if iss, err = newStepIssuer(caURL, client, opts.CertificateIssuer); err != nil {
return nil, err
}
} }
return &StepCAS{ return &StepCAS{

View file

@ -411,6 +411,19 @@ func TestNew(t *testing.T) {
client: client, client: client,
fingerprint: testRootFingerprint, fingerprint: testRootFingerprint,
}, false}, }, false},
{"ok ca getter", args{context.TODO(), apiv1.Options{
IsCAGetter: true,
CertificateAuthority: caURL.String(),
CertificateAuthorityFingerprint: testRootFingerprint,
CertificateIssuer: &apiv1.CertificateIssuer{
Type: "jwk",
Provisioner: "ra@doe.org",
},
}}, &StepCAS{
iss: nil,
client: client,
fingerprint: testRootFingerprint,
}, false},
{"fail authority", args{context.TODO(), apiv1.Options{ {"fail authority", args{context.TODO(), apiv1.Options{
CertificateAuthority: "", CertificateAuthority: "",
CertificateAuthorityFingerprint: testRootFingerprint, CertificateAuthorityFingerprint: testRootFingerprint,

View file

@ -8,11 +8,13 @@ import (
"net" "net"
"net/http" "net/http"
"os" "os"
"strings"
"unicode" "unicode"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/pki"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/cli-utils/errs" "go.step.sm/cli-utils/errs"
) )
@ -38,6 +40,11 @@ certificate issuer private key used in the RA mode.`,
Name: "resolver", Name: "resolver",
Usage: "address of a DNS resolver to be used instead of the default.", Usage: "address of a DNS resolver to be used instead of the default.",
}, },
cli.StringFlag{
Name: "token",
Usage: "token used to enable the linked ca.",
EnvVar: "STEP_CA_TOKEN",
},
}, },
} }
@ -46,6 +53,7 @@ func appAction(ctx *cli.Context) error {
passFile := ctx.String("password-file") passFile := ctx.String("password-file")
issuerPassFile := ctx.String("issuer-password-file") issuerPassFile := ctx.String("issuer-password-file")
resolver := ctx.String("resolver") resolver := ctx.String("resolver")
token := ctx.String("token")
// If zero cmd line args show help, if >1 cmd line args show error. // If zero cmd line args show help, if >1 cmd line args show error.
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
@ -61,6 +69,18 @@ func appAction(ctx *cli.Context) error {
fatal(err) fatal(err)
} }
if config.AuthorityConfig != nil {
if token == "" && strings.EqualFold(config.AuthorityConfig.DeploymentType, pki.LinkedDeployment.String()) {
return errors.New(`'step-ca' requires the '--token' flag for linked deploy type.
To get a linked authority token:
1. Log in or create a Certificate Manager account at ` + "\033[1mhttps://u.step.sm/linked\033[0m" + `
2. Add a new authority and select "Link a step-ca instance"
3. Follow instructions in browser to start 'step-ca' using the '--token' flag
`)
}
}
var password []byte var password []byte
if passFile != "" { if passFile != "" {
if password, err = ioutil.ReadFile(passFile); err != nil { if password, err = ioutil.ReadFile(passFile); err != nil {
@ -88,7 +108,8 @@ func appAction(ctx *cli.Context) error {
srv, err := ca.New(config, srv, err := ca.New(config,
ca.WithConfigFile(configFile), ca.WithConfigFile(configFile),
ca.WithPassword(password), ca.WithPassword(password),
ca.WithIssuerPassword(issuerPassword)) ca.WithIssuerPassword(issuerPassword),
ca.WithLinkedCAToken(token))
if err != nil { if err != nil {
fatal(err) fatal(err)
} }

113
commands/export.go Normal file
View file

@ -0,0 +1,113 @@
package commands
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"unicode"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config"
"github.com/urfave/cli"
"google.golang.org/protobuf/encoding/protojson"
"go.step.sm/cli-utils/command"
"go.step.sm/cli-utils/errs"
)
func init() {
command.Register(cli.Command{
Name: "export",
Usage: "export the current configuration of step-ca",
UsageText: "**step-ca export** <config>",
Action: exportAction,
Description: `**step-ca export** exports the current configuration of step-ca.
Note that neither the PKI password nor the certificate issuer password will be
included in the export file.
## POSITIONAL ARGUMENTS
<config>
: The ca.json that contains the step-ca configuration.
## EXAMPLES
Export the current configuration:
'''
$ step-ca export $(step path)/config/ca.json
'''`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "password-file",
Usage: `path to the <file> containing the password to decrypt the
intermediate private key.`,
},
cli.StringFlag{
Name: "issuer-password-file",
Usage: `path to the <file> containing the password to decrypt the
certificate issuer private key used in the RA mode.`,
},
},
})
}
func exportAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 1); err != nil {
return err
}
configFile := ctx.Args().Get(0)
passwordFile := ctx.String("password-file")
issuerPasswordFile := ctx.String("issuer-password-file")
config, err := config.LoadConfiguration(configFile)
if err != nil {
return err
}
if err := config.Validate(); err != nil {
return err
}
if passwordFile != "" {
b, err := ioutil.ReadFile(passwordFile)
if err != nil {
return errors.Wrapf(err, "error reading %s", passwordFile)
}
config.Password = string(bytes.TrimRightFunc(b, unicode.IsSpace))
}
if issuerPasswordFile != "" {
b, err := ioutil.ReadFile(issuerPasswordFile)
if err != nil {
return errors.Wrapf(err, "error reading %s", issuerPasswordFile)
}
if config.AuthorityConfig.CertificateIssuer != nil {
config.AuthorityConfig.CertificateIssuer.Password = string(bytes.TrimRightFunc(b, unicode.IsSpace))
}
}
auth, err := authority.New(config)
if err != nil {
return err
}
export, err := auth.Export()
if err != nil {
return err
}
b, err := protojson.Marshal(export)
if err != nil {
return errors.Wrap(err, "error marshaling export")
}
var buf bytes.Buffer
if err := json.Indent(&buf, b, "", "\t"); err != nil {
return errors.Wrap(err, "error indenting export")
}
fmt.Println(buf.String())
return nil
}

View file

@ -163,17 +163,21 @@ func onboardAction(ctx *cli.Context) error {
} }
func onboardPKI(config onboardingConfiguration) (*config.Config, string, error) { func onboardPKI(config onboardingConfiguration) (*config.Config, string, error) {
var opts = []pki.Option{
pki.WithAddress(config.Address),
pki.WithDNSNames([]string{config.DNS}),
pki.WithProvisioner("admin"),
}
p, err := pki.New(apiv1.Options{ p, err := pki.New(apiv1.Options{
Type: apiv1.SoftCAS, Type: apiv1.SoftCAS,
IsCreator: true, IsCreator: true,
}) }, opts...)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
p.SetAddress(config.Address) // Generate pki
p.SetDNSNames([]string{config.DNS})
ui.Println("Generating root certificate...") ui.Println("Generating root certificate...")
root, err := p.GenerateRootCertificate(config.Name, config.Name, config.Name, config.password) root, err := p.GenerateRootCertificate(config.Name, config.Name, config.Name, config.password)
if err != nil { if err != nil {
@ -186,8 +190,12 @@ func onboardPKI(config onboardingConfiguration) (*config.Config, string, error)
return nil, "", err return nil, "", err
} }
// Write files to disk
if err = p.WriteFiles(); err != nil {
return nil, "", err
}
// Generate provisioner // Generate provisioner
p.SetProvisioner("admin")
ui.Println("Generating admin provisioner...") ui.Println("Generating admin provisioner...")
if err = p.GenerateKeyPairs(config.password); err != nil { if err = p.GenerateKeyPairs(config.password); err != nil {
return nil, "", err return nil, "", err

20
go.mod
View file

@ -29,20 +29,20 @@ require (
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
go.step.sm/cli-utils v0.4.1 go.step.sm/cli-utils v0.4.1
go.step.sm/crypto v0.9.0 go.step.sm/crypto v0.9.0
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 go.step.sm/linkedca v0.5.0
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 golang.org/x/net v0.0.0-20210716203947-853a461950ff
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
google.golang.org/api v0.47.0 google.golang.org/api v0.47.0
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492
google.golang.org/grpc v1.38.0 google.golang.org/grpc v1.39.0
google.golang.org/protobuf v1.26.0 google.golang.org/protobuf v1.27.1
gopkg.in/square/go-jose.v2 v2.5.1 gopkg.in/square/go-jose.v2 v2.5.1
) )
//replace github.com/smallstep/nosql => ../nosql // replace github.com/smallstep/nosql => ../nosql
// replace go.step.sm/crypto => ../crypto
//replace go.step.sm/crypto => ../crypto // replace go.step.sm/cli-utils => ../cli-utils
// replace go.step.sm/linkedca => ../linkedca
//replace go.step.sm/cli-utils => ../cli-utils
replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568

29
go.sum
View file

@ -71,6 +71,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -113,6 +114,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -155,6 +157,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
@ -274,6 +277,7 @@ github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -435,6 +439,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -516,12 +521,13 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q= go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q=
go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA= go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA=
go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA= go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA=
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 h1:ncJqviWswJT19IdnfOYQGKG1zL7IDy4lAJz1PuM3fgw= go.step.sm/linkedca v0.5.0 h1:oZVRSpElM7lAL1XN2YkjdHwI/oIZ+1ULOnuqYPM6xjY=
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo= go.step.sm/linkedca v0.5.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@ -620,8 +626,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -702,8 +709,9 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -772,6 +780,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -834,6 +843,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -851,8 +861,9 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492 h1:7yQQsvnwjfEahbNNEKcBHv3mR+HnB1ctGY/z1JXzx8M=
google.golang.org/genproto v0.0.0-20210719143636-1d5a45f8e492/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -871,6 +882,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
@ -878,8 +890,9 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -892,8 +905,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -911,6 +925,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

154
pki/helm.go Normal file
View file

@ -0,0 +1,154 @@
package pki
import (
"io"
"text/template"
"github.com/Masterminds/sprig/v3"
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
authconfig "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/linkedca"
)
type helmVariables struct {
*linkedca.Configuration
Defaults *linkedca.Defaults
Password string
SSH struct {
Enabled bool
}
TLS authconfig.TLSOptions
Provisioners []provisioner.Interface
}
func (p *PKI) WriteHelmTemplate(w io.Writer) error {
tmpl, err := template.New("helm").Funcs(sprig.TxtFuncMap()).Parse(helmTemplate)
if err != nil {
return errors.Wrap(err, "error writing helm template")
}
// Delete ssh section if it is not enabled
if !p.options.enableSSH {
p.Ssh = nil
}
// Convert provisioner to ca.json
provisioners := make([]provisioner.Interface, len(p.Authority.Provisioners))
for i, p := range p.Authority.Provisioners {
pp, err := authority.ProvisionerToCertificates(p)
if err != nil {
return err
}
provisioners[i] = pp
}
if err := tmpl.Execute(w, helmVariables{
Configuration: &p.Configuration,
Defaults: &p.Defaults,
Password: "",
TLS: authconfig.DefaultTLSOptions,
Provisioners: provisioners,
}); err != nil {
return errors.Wrap(err, "error executing helm template")
}
return nil
}
const helmTemplate = `# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: {{ first .Root }}
federateRoots: []
crt: {{ .Intermediate }}
key: {{ .IntermediateKey }}
{{- if .SSH.Enabled }}
ssh:
hostKey: {{ .Ssh.HostKey }}
userKey: {{ .Ssh.UserKey }}
{{- end }}
address: {{ .Address }}
dnsNames:
{{- range .DnsNames }}
- {{ . }}
{{- end }}
logger:
format: json
db:
type: badger
dataSource: /home/step/db
authority:
provisioners:
{{- range .Provisioners }}
- {{ . | toJson }}
{{- end }}
tls:
cipherSuites:
{{- range .TLS.CipherSuites }}
- {{ . }}
{{- end }}
minVersion: {{ .TLS.MinVersion }}
maxVersion: {{ .TLS.MaxVersion }}
renegotiation: {{ .TLS.Renegotiation }}
defaults.json:
ca-url: {{ .Defaults.CaUrl }}
ca-config: {{ .Defaults.CaConfig }}
fingerprint: {{ .Defaults.Fingerprint }}
root: {{ .Defaults.Root }}
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
{{- index .Files .Intermediate | toString | nindent 6 }}
# root_ca contains the text of the root CA Certificate
root_ca: |
{{- first .Root | index .Files | toString | nindent 6 }}
{{- if .Ssh }}
# ssh_host_ca contains the text of the public ssh key for the SSH root CA
ssh_host_ca: {{ index .Files .Ssh.HostPublicKey | toString }}
# ssh_user_ca contains the text of the public ssh key for the SSH root CA
ssh_user_ca: {{ index .Files .Ssh.UserPublicKey | toString }}
{{- end }}
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password: {{ .Password | b64enc }}
provisioner_password: {{ .Password | b64enc}}
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
{{- index .Files .IntermediateKey | toString | nindent 8 }}
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
{{- first .RootKey | index .Files | toString | nindent 8 }}
{{- if .Ssh }}
ssh:
# ssh_host_ca_key contains the contents of your encrypted SSH Host CA key
host_ca_key: |
{{- index .Files .Ssh.HostKey | toString | nindent 8 }}
# ssh_user_ca_key contains the contents of your encrypted SSH User CA key
user_ca_key: |
{{- index .Files .Ssh.UserKey | toString | nindent 8 }}
{{- end }}
`

View file

@ -10,21 +10,23 @@ import (
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"html"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/admin"
admindb "github.com/smallstep/certificates/authority/admin/db/nosql"
authconfig "github.com/smallstep/certificates/authority/config" authconfig "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"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/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"
@ -32,9 +34,40 @@ import (
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// DeploymentType defines witch type of deployment a user is initializing
type DeploymentType int
const (
// StandaloneDeployment is a deployment where all the components like keys,
// provisioners, admins, certificates and others are managed by the user.
StandaloneDeployment DeploymentType = iota
// LinkedDeployment is a deployment where the keys are managed by the user,
// but provisioners, admins and the record of certificates are managed in
// the cloud.
LinkedDeployment
// HostedDeployment is a deployment where all the components are managed in
// the cloud by smallstep.com/certificate-manager.
HostedDeployment
)
// String returns the string version of the deployment type.
func (d DeploymentType) String() string {
switch d {
case StandaloneDeployment:
return "standalone"
case LinkedDeployment:
return "linked"
case HostedDeployment:
return "hosted"
default:
return "unknown"
}
}
const ( const (
// ConfigPath is the directory name under the step path where the configuration // ConfigPath is the directory name under the step path where the configuration
// files will be stored. // files will be stored.
@ -134,43 +167,125 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
return resp.Key, nil return resp.Key, nil
} }
type options struct {
provisioner string
pkiOnly bool
enableACME bool
enableSSH bool
enableAdmin bool
noDB bool
isHelm bool
deploymentType DeploymentType
}
// Option is the type of a configuration option on the pki constructor.
type Option func(p *PKI)
// WithAddress sets the listen address of step-ca.
func WithAddress(s string) Option {
return func(p *PKI) {
p.Address = s
}
}
// WithCaURL sets the default ca-url of step-ca.
func WithCaURL(s string) Option {
return func(p *PKI) {
p.Defaults.CaUrl = s
}
}
// WithDNSNames sets the SANs of step-ca.
func WithDNSNames(s []string) Option {
return func(p *PKI) {
p.DnsNames = s
}
}
// WithProvisioner defines the name of the default provisioner.
func WithProvisioner(s string) Option {
return func(p *PKI) {
p.options.provisioner = s
}
}
// WithPKIOnly will only generate the PKI without the step-ca config files.
func WithPKIOnly() Option {
return func(p *PKI) {
p.options.pkiOnly = true
}
}
// WithACME enables acme provisioner in step-ca.
func WithACME() Option {
return func(p *PKI) {
p.options.enableACME = true
}
}
// WithSSH enables ssh in step-ca.
func WithSSH() Option {
return func(p *PKI) {
p.options.enableSSH = true
}
}
// WithAdmin enables the admin api in step-ca.
func WithAdmin() Option {
return func(p *PKI) {
p.options.enableAdmin = true
}
}
// WithNoDB disables the db in step-ca.
func WithNoDB() Option {
return func(p *PKI) {
p.options.noDB = true
}
}
// WithHelm configures the pki to create a helm values.yaml.
func WithHelm() Option {
return func(p *PKI) {
p.options.isHelm = true
}
}
// WithDeploymentType defines the deployment type of step-ca.
func WithDeploymentType(dt DeploymentType) Option {
return func(p *PKI) {
p.options.deploymentType = dt
}
}
// 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 {
casOptions apiv1.Options linkedca.Configuration
caCreator apiv1.CertificateAuthorityCreator Defaults linkedca.Defaults
root, rootKey, rootFingerprint string casOptions apiv1.Options
intermediate, intermediateKey string caService apiv1.CertificateAuthorityService
sshHostPubKey, sshHostKey string caCreator apiv1.CertificateAuthorityCreator
sshUserPubKey, sshUserKey string config string
config, defaults string defaults string
ottPublicKey *jose.JSONWebKey ottPublicKey *jose.JSONWebKey
ottPrivateKey *jose.JSONWebEncryption ottPrivateKey *jose.JSONWebEncryption
provisioner string options *options
address string
dnsNames []string
caURL string
enableSSH bool
} }
// New creates a new PKI configuration. // New creates a new PKI configuration.
func New(opts apiv1.Options) (*PKI, error) { func New(o apiv1.Options, opts ...Option) (*PKI, error) {
caCreator, err := cas.NewCreator(context.Background(), opts) caService, err := cas.New(context.Background(), o)
if err != nil { if err != nil {
return nil, err return nil, err
} }
public := GetPublicPath() var caCreator apiv1.CertificateAuthorityCreator
private := GetSecretsPath() if o.IsCreator {
config := GetConfigPath() creator, ok := caService.(apiv1.CertificateAuthorityCreator)
if !ok {
// Create directories return nil, errors.Errorf("cas type '%s' does not implements CertificateAuthorityCreator", o.Type)
dirs := []string{public, private, config, GetTemplatesPath()}
for _, name := range dirs {
if _, err := os.Stat(name); os.IsNotExist(err) {
if err = os.MkdirAll(name, 0700); err != nil {
return nil, errs.FileError(err, name)
}
} }
caCreator = creator
} }
// get absolute path for dir/name // get absolute path for dir/name
@ -180,45 +295,97 @@ func New(opts apiv1.Options) (*PKI, error) {
} }
p := &PKI{ p := &PKI{
casOptions: opts, Configuration: linkedca.Configuration{
caCreator: caCreator, Address: "127.0.0.1:9000",
provisioner: "step-cli", DnsNames: []string{"127.0.0.1"},
address: "127.0.0.1:9000", Ssh: &linkedca.SSH{},
dnsNames: []string{"127.0.0.1"}, Authority: &linkedca.Authority{},
Files: make(map[string][]byte),
},
casOptions: o,
caCreator: caCreator,
caService: caService,
options: &options{
provisioner: "step-cli",
},
} }
if p.root, err = getPath(public, "root_ca.crt"); err != nil { for _, fn := range opts {
return nil, err fn(p)
} }
if p.rootKey, err = getPath(private, "root_ca_key"); err != nil {
return nil, err // Use /home/step as the step path in helm configurations.
} // Use the current step path when creating pki in files.
if p.intermediate, err = getPath(public, "intermediate_ca.crt"); err != nil { var public, private, config string
return nil, err if p.options.isHelm {
} public = "/home/step/certs"
if p.intermediateKey, err = getPath(private, "intermediate_ca_key"); err != nil { private = "/home/step/secrets"
return nil, err config = "/home/step/config"
} } else {
if p.sshHostPubKey, err = getPath(public, "ssh_host_ca_key.pub"); err != nil { public = GetPublicPath()
return nil, err private = GetSecretsPath()
} config = GetConfigPath()
if p.sshUserPubKey, err = getPath(public, "ssh_user_ca_key.pub"); err != nil { // Create directories
return nil, err dirs := []string{public, private, config, GetTemplatesPath()}
} for _, name := range dirs {
if p.sshHostKey, err = getPath(private, "ssh_host_ca_key"); err != nil { if _, err := os.Stat(name); os.IsNotExist(err) {
return nil, err if err = os.MkdirAll(name, 0700); err != nil {
} return nil, errs.FileError(err, name)
if p.sshUserKey, err = getPath(private, "ssh_user_ca_key"); err != nil { }
return nil, err }
}
if len(config) > 0 {
if p.config, err = getPath(config, "ca.json"); err != nil {
return nil, err
}
if p.defaults, err = getPath(config, "defaults.json"); err != nil {
return nil, err
} }
} }
if p.Defaults.CaUrl == "" {
p.Defaults.CaUrl = p.DnsNames[0]
_, port, err := net.SplitHostPort(p.Address)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %s", p.Address)
}
if port == "443" {
p.Defaults.CaUrl = fmt.Sprintf("https://%s", p.Defaults.CaUrl)
} else {
p.Defaults.CaUrl = fmt.Sprintf("https://%s:%s", p.Defaults.CaUrl, port)
}
}
root, err := getPath(public, "root_ca.crt")
if err != nil {
return nil, err
}
rootKey, err := getPath(private, "root_ca_key")
if err != nil {
return nil, err
}
p.Root = []string{root}
p.RootKey = []string{rootKey}
p.Defaults.Root = root
if p.Intermediate, err = getPath(public, "intermediate_ca.crt"); err != nil {
return nil, err
}
if p.IntermediateKey, err = getPath(private, "intermediate_ca_key"); err != nil {
return nil, err
}
if p.Ssh.HostPublicKey, err = getPath(public, "ssh_host_ca_key.pub"); err != nil {
return nil, err
}
if p.Ssh.UserPublicKey, err = getPath(public, "ssh_user_ca_key.pub"); err != nil {
return nil, err
}
if p.Ssh.HostKey, err = getPath(private, "ssh_host_ca_key"); err != nil {
return nil, err
}
if p.Ssh.UserKey, err = getPath(private, "ssh_user_ca_key"); err != nil {
return nil, err
}
if p.defaults, err = getPath(config, "defaults.json"); err != nil {
return nil, err
}
if p.config, err = getPath(config, "ca.json"); err != nil {
return nil, err
}
p.Defaults.CaConfig = p.config
return p, nil return p, nil
} }
@ -229,27 +396,7 @@ func (p *PKI) GetCAConfigPath() string {
// GetRootFingerprint returns the root fingerprint. // GetRootFingerprint returns the root fingerprint.
func (p *PKI) GetRootFingerprint() string { func (p *PKI) GetRootFingerprint() string {
return p.rootFingerprint return p.Defaults.Fingerprint
}
// SetProvisioner sets the provisioner name of the OTT keys.
func (p *PKI) SetProvisioner(s string) {
p.provisioner = s
}
// SetAddress sets the listening address of the CA.
func (p *PKI) SetAddress(s string) {
p.address = s
}
// SetDNSNames sets the dns names of the CA.
func (p *PKI) SetDNSNames(s []string) {
p.dnsNames = s
}
// SetCAURL sets the ca-url to use in the defaults.json.
func (p *PKI) SetCAURL(s string) {
p.caURL = s
} }
// GenerateKeyPairs generates the key pairs used by the certificate authority. // GenerateKeyPairs generates the key pairs used by the certificate authority.
@ -261,6 +408,28 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
return err return err
} }
// Add JWK provisioner to the configuration.
publicKey, err := json.Marshal(p.ottPublicKey)
if err != nil {
return errors.Wrap(err, "error marshaling public key")
}
encryptedKey, err := p.ottPrivateKey.CompactSerialize()
if err != nil {
return errors.Wrap(err, "error serializing private key")
}
p.Authority.Provisioners = append(p.Authority.Provisioners, &linkedca.Provisioner{
Type: linkedca.Provisioner_JWK,
Name: p.options.provisioner,
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_JWK{
JWK: &linkedca.JWKProvisioner{
PublicKey: publicKey,
EncryptedPrivateKey: []byte(encryptedKey),
},
},
},
})
return nil return nil
} }
@ -296,6 +465,21 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (
return resp, nil return resp, nil
} }
// WriteRootCertificate writes to the buffer the given certificate and key if given.
func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
p.Files[p.Root[0]] = encodeCertificate(rootCrt)
if rootKey != nil {
var err error
p.Files[p.RootKey[0]], err = encodePrivateKey(rootKey, pass)
if err != nil {
return err
}
}
sum := sha256.Sum256(rootCrt.Raw)
p.Defaults.Fingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
return nil
}
// 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 {
@ -322,46 +506,9 @@ func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent
} }
p.casOptions.CertificateAuthority = resp.Name p.casOptions.CertificateAuthority = resp.Name
return p.WriteIntermediateCertificate(resp.Certificate, resp.PrivateKey, pass) p.Files[p.Intermediate] = encodeCertificate(resp.Certificate)
} p.Files[p.IntermediateKey], err = encodePrivateKey(resp.PrivateKey, pass)
return err
// WriteRootCertificate writes to disk the given certificate and key.
func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error {
if err := fileutil.WriteFile(p.root, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rootCrt.Raw,
}), 0600); err != nil {
return err
}
if rootKey != nil {
_, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600))
if err != nil {
return err
}
}
sum := sha256.Sum256(rootCrt.Raw)
p.rootFingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
return nil
}
// WriteIntermediateCertificate writes to disk the given certificate and key.
func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error {
if err := fileutil.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
}), 0600); err != nil {
return err
}
if key != nil {
_, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600))
if err != nil {
return err
}
}
return nil
} }
// CreateCertificateAuthorityResponse returns a // CreateCertificateAuthorityResponse returns a
@ -379,7 +526,7 @@ func (p *PKI) CreateCertificateAuthorityResponse(cert *x509.Certificate, key cry
// GetCertificateAuthority attempts to load the certificate authority from the // GetCertificateAuthority attempts to load the certificate authority from the
// RA. // RA.
func (p *PKI) GetCertificateAuthority() error { func (p *PKI) GetCertificateAuthority() error {
srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter) srv, ok := p.caService.(apiv1.CertificateAuthorityGetter)
if !ok { if !ok {
return nil return nil
} }
@ -396,8 +543,8 @@ func (p *PKI) GetCertificateAuthority() error {
} }
// Issuer is in the RA // Issuer is in the RA
p.intermediate = "" p.Intermediate = ""
p.intermediateKey = "" p.IntermediateKey = ""
return nil return nil
} }
@ -405,8 +552,8 @@ 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.sshHostPubKey, p.sshUserPubKey} var pubNames = []string{p.Ssh.HostPublicKey, p.Ssh.UserPublicKey}
var privNames = []string{p.sshHostKey, p.sshUserKey} var privNames = []string{p.Ssh.HostKey, p.Ssh.UserKey}
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
pub, priv, err := keyutil.GenerateDefaultKeyPair() pub, priv, err := keyutil.GenerateDefaultKeyPair()
if err != nil { if err != nil {
@ -419,57 +566,65 @@ func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
if err != nil { if err != nil {
return errors.Wrapf(err, "error converting public key") return errors.Wrapf(err, "error converting public key")
} }
_, err = pemutil.Serialize(priv, pemutil.WithFilename(privNames[i]), pemutil.WithPassword(password)) p.Files[pubNames[i]] = ssh.MarshalAuthorizedKey(sshKey)
p.Files[privNames[i]], err = encodePrivateKey(priv, password)
if err != nil { if err != nil {
return err return err
} }
if err = fileutil.WriteFile(pubNames[i], ssh.MarshalAuthorizedKey(sshKey), 0600); err != nil { }
p.options.enableSSH = true
return nil
}
// WriteFiles writes on disk the previously generated files.
func (p *PKI) WriteFiles() error {
for fn, b := range p.Files {
if err := fileutil.WriteFile(fn, b, 0600); err != nil {
return err return err
} }
} }
p.enableSSH = true
return nil return nil
} }
func (p *PKI) askFeedback() { func (p *PKI) askFeedback() {
ui.Println() ui.Println()
ui.Printf("\033[1mFEEDBACK\033[0m %s %s\n", ui.Println("\033[1mFEEDBACK\033[0m 😍 🍻")
html.UnescapeString("&#"+strconv.Itoa(128525)+";"), ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not phone")
html.UnescapeString("&#"+strconv.Itoa(127867)+";")) ui.Println(" home. But your feedback is extremely valuable. Any information you can provide")
ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not") ui.Println(" regarding how youre using `step` helps. Please send us a sentence or two,")
ui.Println(" phone home. But your feedback is extremely valuable. Any information you") ui.Println(" good or bad at \033[1mfeedback@smallstep.com\033[0m or join GitHub Discussions")
ui.Println(" can provide regarding how youre using `step` helps. Please send us a") ui.Println(" \033[1mhttps://github.com/smallstep/certificates/discussions\033[0m and our Discord ")
ui.Println(" sentence or two, good or bad: \033[1mfeedback@smallstep.com\033[0m or join") ui.Println(" \033[1mhttps://u.step.sm/discord\033[0m.")
ui.Println(" \033[1mhttps://github.com/smallstep/certificates/discussions\033[0m.")
}
// TellPKI outputs the locations of public and private keys generated if p.options.deploymentType == LinkedDeployment {
// generated for a new PKI. Generally this will consist of a root certificate ui.Println()
// and key and an intermediate certificate and key. ui.Println("\033[1mNEXT STEPS\033[0m")
func (p *PKI) TellPKI() { ui.Println(" 1. Log in or create a Certificate Manager account at \033[1mhttps://u.step.sm/linked\033[0m")
p.tellPKI() ui.Println(" 2. Add a new authority and select \"Link a step-ca instance\"")
p.askFeedback() ui.Println(" 3. Follow instructions in browser to start `step-ca` using the `--token` flag")
ui.Println()
}
} }
func (p *PKI) tellPKI() { func (p *PKI) tellPKI() {
ui.Println() ui.Println()
if p.casOptions.Is(apiv1.SoftCAS) { if p.casOptions.Is(apiv1.SoftCAS) {
ui.PrintSelected("Root certificate", p.root) ui.PrintSelected("Root certificate", p.Root[0])
ui.PrintSelected("Root private key", p.rootKey) ui.PrintSelected("Root private key", p.RootKey[0])
ui.PrintSelected("Root fingerprint", p.rootFingerprint) ui.PrintSelected("Root fingerprint", p.Defaults.Fingerprint)
ui.PrintSelected("Intermediate certificate", p.intermediate) ui.PrintSelected("Intermediate certificate", p.Intermediate)
ui.PrintSelected("Intermediate private key", p.intermediateKey) ui.PrintSelected("Intermediate private key", p.IntermediateKey)
} else if p.rootFingerprint != "" { } else if p.Defaults.Fingerprint != "" {
ui.PrintSelected("Root certificate", p.root) ui.PrintSelected("Root certificate", p.Root[0])
ui.PrintSelected("Root fingerprint", p.rootFingerprint) ui.PrintSelected("Root fingerprint", p.Defaults.Fingerprint)
} else { } else {
ui.Printf(`{{ "%s" | red }} {{ "Root certificate:" | bold }} failed to retrieve it from RA`+"\n", ui.IconBad) ui.Printf(`{{ "%s" | red }} {{ "Root certificate:" | bold }} failed to retrieve it from RA`+"\n", ui.IconBad)
} }
if p.enableSSH { if p.options.enableSSH {
ui.PrintSelected("SSH user root certificate", p.sshUserPubKey) ui.PrintSelected("SSH user public key", p.Ssh.UserPublicKey)
ui.PrintSelected("SSH user root private key", p.sshUserKey) ui.PrintSelected("SSH user private key", p.Ssh.UserKey)
ui.PrintSelected("SSH host root certificate", p.sshHostPubKey) ui.PrintSelected("SSH host public key", p.Ssh.HostPublicKey)
ui.PrintSelected("SSH host root private key", p.sshHostKey) ui.PrintSelected("SSH host private key", p.Ssh.HostKey)
} }
} }
@ -480,176 +635,230 @@ type caDefaults struct {
Root string `json:"root"` Root string `json:"root"`
} }
// Option is the type for modifiers over the auth config object. // ConfigOption is the type for modifiers over the auth config object.
type Option func(c *authconfig.Config) error type ConfigOption func(c *authconfig.Config) error
// WithDefaultDB is a configuration modifier that adds a default DB stanza to
// the authority config.
func WithDefaultDB() Option {
return func(c *authconfig.Config) error {
c.DB = &db.Config{
Type: "badger",
DataSource: GetDBPath(),
}
return nil
}
}
// WithoutDB is a configuration modifier that adds a default DB stanza to
// the authority config.
func WithoutDB() Option {
return func(c *authconfig.Config) error {
c.DB = nil
return nil
}
}
// GenerateConfig returns the step certificates configuration. // GenerateConfig returns the step certificates configuration.
func (p *PKI) GenerateConfig(opt ...Option) (*authconfig.Config, error) { func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
key, err := p.ottPrivateKey.CompactSerialize()
if err != nil {
return nil, errors.Wrap(err, "error serializing private key")
}
prov := &provisioner.JWK{
Name: p.provisioner,
Type: "JWK",
Key: p.ottPublicKey,
EncryptedKey: key,
}
var authorityOptions *apiv1.Options var authorityOptions *apiv1.Options
if !p.casOptions.Is(apiv1.SoftCAS) { if !p.casOptions.Is(apiv1.SoftCAS) {
authorityOptions = &p.casOptions authorityOptions = &p.casOptions
} }
config := &authconfig.Config{ config := &authconfig.Config{
Root: []string{p.root}, Root: p.Root,
FederatedRoots: []string{}, FederatedRoots: p.FederatedRoots,
IntermediateCert: p.intermediate, IntermediateCert: p.Intermediate,
IntermediateKey: p.intermediateKey, IntermediateKey: p.IntermediateKey,
Address: p.address, Address: p.Address,
DNSNames: p.dnsNames, DNSNames: p.DnsNames,
Logger: []byte(`{"format": "text"}`), Logger: []byte(`{"format": "text"}`),
DB: &db.Config{ DB: &db.Config{
Type: "badger", Type: "badgerv2",
DataSource: GetDBPath(), DataSource: GetDBPath(),
}, },
AuthorityConfig: &authconfig.AuthConfig{ AuthorityConfig: &authconfig.AuthConfig{
Options: authorityOptions, Options: authorityOptions,
DisableIssuedAtCheck: false, DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov}, EnableAdmin: false,
},
TLS: &authconfig.TLSOptions{
MinVersion: authconfig.DefaultTLSMinVersion,
MaxVersion: authconfig.DefaultTLSMaxVersion,
Renegotiation: authconfig.DefaultTLSRenegotiation,
CipherSuites: authconfig.DefaultTLSCipherSuites,
}, },
TLS: &authconfig.DefaultTLSOptions,
Templates: p.getTemplates(), Templates: p.getTemplates(),
} }
if p.enableSSH {
enableSSHCA := true // Add linked as a deployment type to detect it on start and provide a
config.SSH = &authconfig.SSHConfig{ // message if the token is not given.
HostKey: p.sshHostKey, if p.options.deploymentType == LinkedDeployment {
UserKey: p.sshUserKey, config.AuthorityConfig.DeploymentType = LinkedDeployment.String()
}
// On standalone deployments add the provisioners to either the ca.json or
// the database.
var provisioners []provisioner.Interface
if p.options.deploymentType == StandaloneDeployment {
key, err := p.ottPrivateKey.CompactSerialize()
if err != nil {
return nil, errors.Wrap(err, "error serializing private key")
} }
// Enable SSH authorization for default JWK provisioner
prov.Claims = &provisioner.Claims{ prov := &provisioner.JWK{
EnableSSHCA: &enableSSHCA, Name: p.options.provisioner,
Type: "JWK",
Key: p.ottPublicKey,
EncryptedKey: key,
} }
// Add default SSHPOP provisioner provisioners = append(provisioners, prov)
sshpop := &provisioner.SSHPOP{
Type: "SSHPOP", // Add default ACME provisioner if enabled
Name: "sshpop", if p.options.enableACME {
Claims: &provisioner.Claims{ provisioners = append(provisioners, &provisioner.ACME{
Type: "ACME",
Name: "acme",
})
}
if p.options.enableSSH {
enableSSHCA := true
config.SSH = &authconfig.SSHConfig{
HostKey: p.Ssh.HostKey,
UserKey: p.Ssh.UserKey,
}
// Enable SSH authorization for default JWK provisioner
prov.Claims = &provisioner.Claims{
EnableSSHCA: &enableSSHCA, EnableSSHCA: &enableSSHCA,
}, }
// Add default SSHPOP provisioner
provisioners = append(provisioners, &provisioner.SSHPOP{
Type: "SSHPOP",
Name: "sshpop",
Claims: &provisioner.Claims{
EnableSSHCA: &enableSSHCA,
},
})
} }
config.AuthorityConfig.Provisioners = append(config.AuthorityConfig.Provisioners, sshpop)
} }
// Apply configuration modifiers // Apply configuration modifiers
for _, o := range opt { for _, o := range opt {
if err = o(config); err != nil { if err := o(config); err != nil {
return nil, err return nil, err
} }
} }
// Set authority.enableAdmin to true
if p.options.enableAdmin {
config.AuthorityConfig.EnableAdmin = true
}
if p.options.deploymentType == StandaloneDeployment {
if !config.AuthorityConfig.EnableAdmin {
config.AuthorityConfig.Provisioners = provisioners
} else {
// At this moment this code path is never used because `step ca
// init` will always set enableAdmin to false for a standalone
// deployment. Once we move `step beta` commands out of the beta we
// should probably default to this route.
//
// Note that we might want to be able to define the database as a
// flag in `step ca init` so we can write to the proper place.
db, err := db.New(config.DB)
if err != nil {
return nil, err
}
adminDB, err := admindb.New(db.(nosql.DB), admin.DefaultAuthorityID)
if err != nil {
return nil, err
}
// Add all the provisioners to the db.
var adminID string
for i, p := range provisioners {
prov, err := authority.ProvisionerToLinkedca(p)
if err != nil {
return nil, err
}
if err := adminDB.CreateProvisioner(context.Background(), prov); err != nil {
return nil, err
}
if i == 0 {
adminID = prov.Id
}
}
// Add the first provisioner as an admin.
if err := adminDB.CreateAdmin(context.Background(), &linkedca.Admin{
AuthorityId: admin.DefaultAuthorityID,
Subject: "step",
Type: linkedca.Admin_SUPER_ADMIN,
ProvisionerId: adminID,
}); err != nil {
return nil, err
}
}
}
return config, nil return config, nil
} }
// Save stores the pki on a json file that will be used as the certificate // Save stores the pki on a json file that will be used as the certificate
// authority configuration. // authority configuration.
func (p *PKI) Save(opt ...Option) error { func (p *PKI) Save(opt ...ConfigOption) error {
// Write generated files
if err := p.WriteFiles(); err != nil {
return err
}
// Display the files written
p.tellPKI() p.tellPKI()
// Generate and write ca.json // Generate and write ca.json
config, err := p.GenerateConfig(opt...) if !p.options.pkiOnly {
if err != nil { config, err := p.GenerateConfig(opt...)
return err
}
b, err := json.MarshalIndent(config, "", "\t")
if err != nil {
return errors.Wrapf(err, "error marshaling %s", p.config)
}
if err = fileutil.WriteFile(p.config, b, 0644); err != nil {
return errs.FileError(err, p.config)
}
// Generate the CA URL.
if p.caURL == "" {
p.caURL = p.dnsNames[0]
var port string
_, port, err = net.SplitHostPort(p.address)
if err != nil { if err != nil {
return errors.Wrapf(err, "error parsing %s", p.address) return err
} }
if port == "443" {
p.caURL = fmt.Sprintf("https://%s", p.caURL) b, err := json.MarshalIndent(config, "", "\t")
} else { if err != nil {
p.caURL = fmt.Sprintf("https://%s:%s", p.caURL, port) return errors.Wrapf(err, "error marshaling %s", p.config)
}
if err = fileutil.WriteFile(p.config, b, 0644); err != nil {
return errs.FileError(err, p.config)
} }
}
// Generate and write defaults.json // Generate and write defaults.json
defaults := &caDefaults{ defaults := &caDefaults{
Root: p.root, Root: p.Defaults.Root,
CAConfig: p.config, CAConfig: p.Defaults.CaConfig,
CAUrl: p.caURL, CAUrl: p.Defaults.CaUrl,
Fingerprint: p.rootFingerprint, Fingerprint: p.Defaults.Fingerprint,
} }
b, err = json.MarshalIndent(defaults, "", "\t") b, err = json.MarshalIndent(defaults, "", "\t")
if err != nil { if err != nil {
return errors.Wrapf(err, "error marshaling %s", p.defaults) return errors.Wrapf(err, "error marshaling %s", p.defaults)
} }
if err = fileutil.WriteFile(p.defaults, b, 0644); err != nil { if err = fileutil.WriteFile(p.defaults, b, 0644); err != nil {
return errs.FileError(err, p.defaults) return errs.FileError(err, p.defaults)
} }
// Generate and write templates // Generate and write templates
if err := generateTemplates(config.Templates); err != nil { if err := generateTemplates(config.Templates); err != nil {
return err return err
} }
if config.DB != nil { if config.DB != nil {
ui.PrintSelected("Database folder", config.DB.DataSource) ui.PrintSelected("Database folder", config.DB.DataSource)
} }
if config.Templates != nil { if config.Templates != nil {
ui.PrintSelected("Templates folder", GetTemplatesPath()) ui.PrintSelected("Templates folder", GetTemplatesPath())
} }
ui.PrintSelected("Default configuration", p.defaults) ui.PrintSelected("Default configuration", p.defaults)
ui.PrintSelected("Certificate Authority configuration", p.config) ui.PrintSelected("Certificate Authority configuration", p.config)
ui.Println() if p.options.deploymentType != LinkedDeployment {
if p.casOptions.Is(apiv1.SoftCAS) { ui.Println()
ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") if p.casOptions.Is(apiv1.SoftCAS) {
} else { ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.") } else {
ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.")
}
}
} }
p.askFeedback() p.askFeedback()
return nil return nil
} }
func encodeCertificate(c *x509.Certificate) []byte {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: c.Raw,
})
}
func encodePrivateKey(key crypto.PrivateKey, pass []byte) ([]byte, error) {
block, err := pemutil.Serialize(key, pemutil.WithPassword(pass))
if err != nil {
return nil, err
}
return pem.EncodeToMemory(block), nil
}

View file

@ -13,7 +13,7 @@ import (
// getTemplates returns all the templates enabled // getTemplates returns all the templates enabled
func (p *PKI) getTemplates() *templates.Templates { func (p *PKI) getTemplates() *templates.Templates {
if !p.enableSSH { if !p.options.enableSSH {
return nil return nil
} }
return &templates.Templates{ return &templates.Templates{