diff --git a/authority/authority.go b/authority/authority.go index 1b060ef8..16968d9d 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -43,6 +43,8 @@ type Authority struct { linkedCAToken string // X509 CA + password []byte + issuerPassword []byte x509CAService cas.CertificateAuthorityService rootX509Certs []*x509.Certificate rootX509CertPool *x509.CertPool @@ -53,6 +55,8 @@ type Authority struct { scepService *scep.Service // SSH CA + sshHostPassword []byte + sshUserPassword []byte sshCAUserCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer sshCAUserCerts []ssh.PublicKey @@ -206,6 +210,21 @@ func (a *Authority) init() 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. if a.linkedCAToken != "" { a.config.AuthorityConfig.EnableAdmin = true @@ -238,6 +257,11 @@ func (a *Authority) init() error { 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. if options.Is(casapi.SoftCAS) { 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{ SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), + Password: []byte(a.password), }) if err != nil { return err @@ -315,7 +339,7 @@ func (a *Authority) init() error { if a.config.SSH.HostKey != "" { signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.SSH.HostKey, - Password: []byte(a.config.Password), + Password: []byte(a.sshHostPassword), }) if err != nil { return err @@ -341,7 +365,7 @@ func (a *Authority) init() error { if a.config.SSH.UserKey != "" { signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.SSH.UserKey, - Password: []byte(a.config.Password), + Password: []byte(a.sshUserPassword), }) if err != nil { return err @@ -420,7 +444,7 @@ func (a *Authority) init() error { } options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), + Password: []byte(a.password), }) if err != nil { return err @@ -429,7 +453,7 @@ func (a *Authority) init() error { if km, ok := a.keyManager.(kmsapi.Decrypter); ok { options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), + Password: []byte(a.password), }) if err != nil { return err @@ -475,7 +499,7 @@ func (a *Authority) init() error { } if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") { // 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 { return admin.WrapErrorISE(err, "error creating first provisioner") } diff --git a/authority/options.go b/authority/options.go index 6baeb2bc..5c8a6e66 100644 --- a/authority/options.go +++ b/authority/options.go @@ -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 // authority. This option is intended to be use on graceful reloads. func WithDatabase(db db.AuthDB) Option { diff --git a/ca/ca.go b/ca/ca.go index 51d15bec..00a5970a 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -29,11 +29,13 @@ import ( ) type options struct { - configFile string - linkedCAToken string - password []byte - issuerPassword []byte - database db.AuthDB + configFile string + linkedCAToken string + password []byte + issuerPassword []byte + sshHostPassword []byte + sshUserPassword []byte + database db.AuthDB } 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 // issuer password in the CA options. 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. func (ca *CA) Init(config *config.Config) (*CA, error) { - // Intermediate Password. - if len(ca.opts.password) > 0 { - ca.config.Password = string(ca.opts.password) + // Set password, it's ok to set nil password, the ca will prompt for them if + // they are required. + 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 != "" { opts = append(opts, authority.WithLinkedCAToken(ca.opts.linkedCAToken)) } @@ -337,6 +350,8 @@ func (ca *CA) Reload() error { newCA, err := New(config, WithPassword(ca.opts.password), + WithSSHHostPassword(ca.opts.sshHostPassword), + WithSSHUserPassword(ca.opts.sshUserPassword), WithIssuerPassword(ca.opts.issuerPassword), WithLinkedCAToken(ca.opts.linkedCAToken), WithConfigFile(ca.opts.configFile), diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 4396e028..aaf37df2 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -107,7 +107,9 @@ func main() { app.HelpName = "step-ca" app.Version = config.Version() app.Usage = "an online certificate authority for secure automated certificate management" - app.UsageText = `**step-ca** [**--password-file**=] [**--issuer-password-file**=] [**--resolver**=] [**--help**] [**--version**]` + app.UsageText = `**step-ca** [**--password-file**=] +[**--ssh-host-password-file**=] [**--ssh-user-password-file**=] +[**--issuer-password-file**=] [**--resolver**=] [**--help**] [**--version**]` app.Description = `**step-ca** runs the Step Online Certificate Authority (Step CA) using the given configuration. See the README.md for more detailed configuration documentation. diff --git a/commands/app.go b/commands/app.go index aa7b43d4..3aaee0f5 100644 --- a/commands/app.go +++ b/commands/app.go @@ -23,13 +23,26 @@ import ( var AppCommand = cli.Command{ Name: "start", Action: appAction, - UsageText: `**step-ca** -[**--password-file**=] [**--issuer-password-file**=] [**--resolver**=]`, + UsageText: `**step-ca** [**--password-file**=] +[**--ssh-host-password-file**=] [**--ssh-user-password-file**=] +[**--issuer-password-file**=] [**--resolver**=]`, Flags: []cli.Flag{ cli.StringFlag{ Name: "password-file", Usage: `path to the containing the password to decrypt the intermediate private key.`, + }, + cli.StringFlag{ + Name: "ssh-host-password-file", + Usage: `path to the 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 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{ 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. func appAction(ctx *cli.Context) error { 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") resolver := ctx.String("resolver") token := ctx.String("token") @@ -89,6 +104,22 @@ To get a linked authority token: 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 if issuerPassFile != "" { if issuerPassword, err = ioutil.ReadFile(issuerPassFile); err != nil { @@ -108,6 +139,8 @@ To get a linked authority token: srv, err := ca.New(config, ca.WithConfigFile(configFile), ca.WithPassword(password), + ca.WithSSHHostPassword(sshHostPassword), + ca.WithSSHUserPassword(sshUserPassword), ca.WithIssuerPassword(issuerPassword), ca.WithLinkedCAToken(token)) if err != nil {