Merge pull request #707 from smallstep/password-flags

Add support for setting individual password for ssh and tls keys
This commit is contained in:
Mariano Cano 2021-09-16 13:50:03 -07:00 committed by GitHub
commit ebf1afa96e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 26 deletions

View file

@ -43,6 +43,8 @@ type Authority struct {
linkedCAToken string linkedCAToken string
// X509 CA // X509 CA
password []byte
issuerPassword []byte
x509CAService cas.CertificateAuthorityService x509CAService cas.CertificateAuthorityService
rootX509Certs []*x509.Certificate rootX509Certs []*x509.Certificate
rootX509CertPool *x509.CertPool rootX509CertPool *x509.CertPool
@ -53,6 +55,8 @@ type Authority struct {
scepService *scep.Service scepService *scep.Service
// SSH CA // SSH CA
sshHostPassword []byte
sshUserPassword []byte
sshCAUserCertSignKey ssh.Signer sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer
sshCAUserCerts []ssh.PublicKey sshCAUserCerts []ssh.PublicKey
@ -206,6 +210,21 @@ func (a *Authority) init() error {
var err error var err error
// Set password if they are not set.
var configPassword []byte
if a.config.Password != "" {
configPassword = []byte(a.config.Password)
}
if configPassword != nil && a.password == nil {
a.password = configPassword
}
if a.sshHostPassword == nil {
a.sshHostPassword = a.password
}
if a.sshUserPassword == nil {
a.sshUserPassword = a.password
}
// Automatically enable admin for all linked cas. // Automatically enable admin for all linked cas.
if a.linkedCAToken != "" { if a.linkedCAToken != "" {
a.config.AuthorityConfig.EnableAdmin = true a.config.AuthorityConfig.EnableAdmin = true
@ -238,6 +257,11 @@ func (a *Authority) init() error {
options = *a.config.AuthorityConfig.Options options = *a.config.AuthorityConfig.Options
} }
// Set the issuer password if passed in the flags.
if options.CertificateIssuer != nil && a.issuerPassword != nil {
options.CertificateIssuer.Password = string(a.issuerPassword)
}
// Read intermediate and create X509 signer for default CAS. // Read intermediate and create X509 signer for default CAS.
if options.Is(casapi.SoftCAS) { if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
@ -246,7 +270,7 @@ func (a *Authority) init() error {
} }
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey, SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password), Password: []byte(a.password),
}) })
if err != nil { if err != nil {
return err return err
@ -315,7 +339,7 @@ func (a *Authority) init() error {
if a.config.SSH.HostKey != "" { if a.config.SSH.HostKey != "" {
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.HostKey, SigningKey: a.config.SSH.HostKey,
Password: []byte(a.config.Password), Password: []byte(a.sshHostPassword),
}) })
if err != nil { if err != nil {
return err return err
@ -341,7 +365,7 @@ func (a *Authority) init() error {
if a.config.SSH.UserKey != "" { if a.config.SSH.UserKey != "" {
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.SSH.UserKey, SigningKey: a.config.SSH.UserKey,
Password: []byte(a.config.Password), Password: []byte(a.sshUserPassword),
}) })
if err != nil { if err != nil {
return err return err
@ -420,7 +444,7 @@ func (a *Authority) init() error {
} }
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey, SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password), Password: []byte(a.password),
}) })
if err != nil { if err != nil {
return err return err
@ -429,7 +453,7 @@ func (a *Authority) init() error {
if km, ok := a.keyManager.(kmsapi.Decrypter); ok { if km, ok := a.keyManager.(kmsapi.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey, DecryptionKey: a.config.IntermediateKey,
Password: []byte(a.config.Password), Password: []byte(a.password),
}) })
if err != nil { if err != nil {
return err return err
@ -475,7 +499,7 @@ func (a *Authority) init() error {
} }
if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") { 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, string(a.password))
if err != nil { if err != nil {
return admin.WrapErrorISE(err, "error creating first provisioner") return admin.WrapErrorISE(err, "error creating first provisioner")
} }

View file

@ -38,6 +38,42 @@ func WithConfigFile(filename string) Option {
} }
} }
// WithPassword set the password to decrypt the intermediate key as well as the
// ssh host and user keys if they are not overridden by other options.
func WithPassword(password []byte) Option {
return func(a *Authority) (err error) {
a.password = password
return
}
}
// WithSSHHostPassword set the password to decrypt the key used to sign SSH host
// certificates.
func WithSSHHostPassword(password []byte) Option {
return func(a *Authority) (err error) {
a.sshHostPassword = password
return
}
}
// WithSSHUserPassword set the password to decrypt the key used to sign SSH user
// certificates.
func WithSSHUserPassword(password []byte) Option {
return func(a *Authority) (err error) {
a.sshUserPassword = password
return
}
}
// WithIssuerPassword set the password to decrypt the certificate issuer private
// key used in RA mode.
func WithIssuerPassword(password []byte) Option {
return func(a *Authority) (err error) {
a.issuerPassword = password
return
}
}
// WithDatabase sets an already initialized authority database to a new // WithDatabase sets an already initialized authority database to a new
// authority. This option is intended to be use on graceful reloads. // authority. This option is intended to be use on graceful reloads.
func WithDatabase(db db.AuthDB) Option { func WithDatabase(db db.AuthDB) Option {

View file

@ -29,11 +29,13 @@ import (
) )
type options struct { type options struct {
configFile string configFile string
linkedCAToken string linkedCAToken string
password []byte password []byte
issuerPassword []byte issuerPassword []byte
database db.AuthDB sshHostPassword []byte
sshUserPassword []byte
database db.AuthDB
} }
func (o *options) apply(opts []Option) { func (o *options) apply(opts []Option) {
@ -61,6 +63,22 @@ func WithPassword(password []byte) Option {
} }
} }
// WithSSHHostPassword sets the given password to decrypt the key used to sign
// ssh host certificates.
func WithSSHHostPassword(password []byte) Option {
return func(o *options) {
o.sshHostPassword = password
}
}
// WithSSHUserPassword sets the given password to decrypt the key used to sign
// ssh user certificates.
func WithSSHUserPassword(password []byte) Option {
return func(o *options) {
o.sshUserPassword = password
}
}
// WithIssuerPassword sets the given password as the configured certificate // WithIssuerPassword sets the given password as the configured certificate
// issuer password in the CA options. // issuer password in the CA options.
func WithIssuerPassword(password []byte) Option { func WithIssuerPassword(password []byte) Option {
@ -106,19 +124,14 @@ func New(config *config.Config, opts ...Option) (*CA, error) {
// Init initializes the CA with the given configuration. // Init initializes the CA with the given configuration.
func (ca *CA) Init(config *config.Config) (*CA, error) { func (ca *CA) Init(config *config.Config) (*CA, error) {
// Intermediate Password. // Set password, it's ok to set nil password, the ca will prompt for them if
if len(ca.opts.password) > 0 { // they are required.
ca.config.Password = string(ca.opts.password) opts := []authority.Option{
authority.WithPassword(ca.opts.password),
authority.WithSSHHostPassword(ca.opts.sshHostPassword),
authority.WithSSHUserPassword(ca.opts.sshUserPassword),
authority.WithIssuerPassword(ca.opts.issuerPassword),
} }
// Certificate issuer password for RA mode.
if len(ca.opts.issuerPassword) > 0 {
if ca.config.AuthorityConfig != nil && ca.config.AuthorityConfig.CertificateIssuer != nil {
ca.config.AuthorityConfig.CertificateIssuer.Password = string(ca.opts.issuerPassword)
}
}
var opts []authority.Option
if ca.opts.linkedCAToken != "" { if ca.opts.linkedCAToken != "" {
opts = append(opts, authority.WithLinkedCAToken(ca.opts.linkedCAToken)) opts = append(opts, authority.WithLinkedCAToken(ca.opts.linkedCAToken))
} }
@ -337,6 +350,8 @@ func (ca *CA) Reload() error {
newCA, err := New(config, newCA, err := New(config,
WithPassword(ca.opts.password), WithPassword(ca.opts.password),
WithSSHHostPassword(ca.opts.sshHostPassword),
WithSSHUserPassword(ca.opts.sshUserPassword),
WithIssuerPassword(ca.opts.issuerPassword), WithIssuerPassword(ca.opts.issuerPassword),
WithLinkedCAToken(ca.opts.linkedCAToken), WithLinkedCAToken(ca.opts.linkedCAToken),
WithConfigFile(ca.opts.configFile), WithConfigFile(ca.opts.configFile),

View file

@ -107,7 +107,9 @@ func main() {
app.HelpName = "step-ca" app.HelpName = "step-ca"
app.Version = config.Version() app.Version = config.Version()
app.Usage = "an online certificate authority for secure automated certificate management" app.Usage = "an online certificate authority for secure automated certificate management"
app.UsageText = `**step-ca** <config> [**--password-file**=<file>] [**--issuer-password-file**=<file>] [**--resolver**=<addr>] [**--help**] [**--version**]` app.UsageText = `**step-ca** <config> [**--password-file**=<file>]
[**--ssh-host-password-file**=<file>] [**--ssh-user-password-file**=<file>]
[**--issuer-password-file**=<file>] [**--resolver**=<addr>] [**--help**] [**--version**]`
app.Description = `**step-ca** runs the Step Online Certificate Authority app.Description = `**step-ca** runs the Step Online Certificate Authority
(Step CA) using the given configuration. (Step CA) using the given configuration.
See the README.md for more detailed configuration documentation. See the README.md for more detailed configuration documentation.

View file

@ -23,13 +23,26 @@ import (
var AppCommand = cli.Command{ var AppCommand = cli.Command{
Name: "start", Name: "start",
Action: appAction, Action: appAction,
UsageText: `**step-ca** <config> UsageText: `**step-ca** <config> [**--password-file**=<file>]
[**--password-file**=<file>] [**--issuer-password-file**=<file>] [**--resolver**=<addr>]`, [**--ssh-host-password-file**=<file>] [**--ssh-user-password-file**=<file>]
[**--issuer-password-file**=<file>] [**--resolver**=<addr>]`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "password-file", Name: "password-file",
Usage: `path to the <file> containing the password to decrypt the Usage: `path to the <file> containing the password to decrypt the
intermediate private key.`, intermediate private key.`,
},
cli.StringFlag{
Name: "ssh-host-password-file",
Usage: `path to the <file> containing the password to decrypt the
private key used to sign SSH host certificates. If the flag is not passed it
will default to --password-file.`,
},
cli.StringFlag{
Name: "ssh-user-password-file",
Usage: `path to the <file> containing the password to decrypt the
private key used to sign SSH user certificates. If the flag is not passed it
will default to --password-file.`,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "issuer-password-file", Name: "issuer-password-file",
@ -51,6 +64,8 @@ certificate issuer private key used in the RA mode.`,
// AppAction is the action used when the top command runs. // AppAction is the action used when the top command runs.
func appAction(ctx *cli.Context) error { func appAction(ctx *cli.Context) error {
passFile := ctx.String("password-file") passFile := ctx.String("password-file")
sshHostPassFile := ctx.String("ssh-host-password-file")
sshUserPassFile := ctx.String("ssh-user-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") token := ctx.String("token")
@ -89,6 +104,22 @@ To get a linked authority token:
password = bytes.TrimRightFunc(password, unicode.IsSpace) password = bytes.TrimRightFunc(password, unicode.IsSpace)
} }
var sshHostPassword []byte
if sshHostPassFile != "" {
if sshHostPassword, err = ioutil.ReadFile(sshHostPassFile); err != nil {
fatal(errors.Wrapf(err, "error reading %s", sshHostPassFile))
}
sshHostPassword = bytes.TrimRightFunc(sshHostPassword, unicode.IsSpace)
}
var sshUserPassword []byte
if sshUserPassFile != "" {
if sshUserPassword, err = ioutil.ReadFile(sshUserPassFile); err != nil {
fatal(errors.Wrapf(err, "error reading %s", sshUserPassFile))
}
sshUserPassword = bytes.TrimRightFunc(sshUserPassword, unicode.IsSpace)
}
var issuerPassword []byte var issuerPassword []byte
if issuerPassFile != "" { if issuerPassFile != "" {
if issuerPassword, err = ioutil.ReadFile(issuerPassFile); err != nil { if issuerPassword, err = ioutil.ReadFile(issuerPassFile); err != nil {
@ -108,6 +139,8 @@ To get a linked authority token:
srv, err := ca.New(config, srv, err := ca.New(config,
ca.WithConfigFile(configFile), ca.WithConfigFile(configFile),
ca.WithPassword(password), ca.WithPassword(password),
ca.WithSSHHostPassword(sshHostPassword),
ca.WithSSHUserPassword(sshUserPassword),
ca.WithIssuerPassword(issuerPassword), ca.WithIssuerPassword(issuerPassword),
ca.WithLinkedCAToken(token)) ca.WithLinkedCAToken(token))
if err != nil { if err != nil {