From 004ea12212588acdce971a182c785d1868719da0 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 1 Aug 2019 15:04:56 -0700 Subject: [PATCH] Allow to use custom SSH user/host key files. --- authority/authority.go | 34 ++++++++++++++++++++++++++++++++-- authority/authorize.go | 35 ++++++++++++++++++++++------------- authority/config.go | 7 +++++++ authority/ssh.go | 12 ++++++++++++ 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index c4d9d1cd..848a4f63 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/cli/crypto/pemutil" @@ -120,8 +121,21 @@ func (a *Authority) init() error { } } - a.sshCAHostCertSignKey = a.intermediateIdentity.Key.(crypto.Signer) - a.sshCAUserCertSignKey = a.intermediateIdentity.Key.(crypto.Signer) + // Decrypt and load SSH keys + if a.config.SSH != nil { + if a.config.SSH.HostKey != "" { + a.sshCAHostCertSignKey, err = parseCryptoSigner(a.config.SSH.HostKey, a.config.Password) + if err != nil { + return err + } + } + if a.config.SSH.UserKey != "" { + a.sshCAUserCertSignKey, err = parseCryptoSigner(a.config.SSH.UserKey, a.config.Password) + if err != nil { + return err + } + } + } // Store all the provisioners for _, p := range a.config.AuthorityConfig.Provisioners { @@ -149,3 +163,19 @@ func (a *Authority) GetDatabase() db.AuthDB { func (a *Authority) Shutdown() error { return a.db.Shutdown() } + +func parseCryptoSigner(filename, password string) (crypto.Signer, error) { + var opts []pemutil.Options + if password != "" { + opts = append(opts, pemutil.WithPassword([]byte(password))) + } + key, err := pemutil.Read(filename, opts...) + if err != nil { + return nil, err + } + signer, ok := key.(crypto.Signer) + if !ok { + return nil, errors.Errorf("key %s of type %T cannot be used for signing operations", filename, key) + } + return signer, nil +} diff --git a/authority/authorize.go b/authority/authorize.go index 6ce2d5bd..e514681a 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -78,20 +78,13 @@ func (a *Authority) authorizeToken(ott string) (provisioner.Interface, error) { func (a *Authority) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) { var errContext = apiCtx{"ott": ott} switch m := provisioner.MethodFromContext(ctx); m { - case provisioner.SignMethod, provisioner.SignSSHMethod: - p, err := a.authorizeToken(ott) - if err != nil { - return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext} - } - - // Call the provisioner AuthorizeSign method to apply provisioner specific - // auth claims and get the signing options. - opts, err := p.AuthorizeSign(ctx, ott) - if err != nil { - return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext} + case provisioner.SignMethod: + return a.authorizeSign(ctx, ott) + case provisioner.SignSSHMethod: + if a.sshCAHostCertSignKey == nil && a.sshCAUserCertSignKey == nil { + return nil, &apiError{errors.New("authorize: ssh signing is not enabled"), http.StatusNotImplemented, errContext} } - - return opts, nil + return a.authorizeSign(ctx, ott) case provisioner.RevokeMethod: return nil, &apiError{errors.New("authorize: revoke method is not supported"), http.StatusInternalServerError, errContext} default: @@ -99,6 +92,22 @@ func (a *Authority) Authorize(ctx context.Context, ott string) ([]provisioner.Si } } +// authorizeSign loads the provisioner from the token, checks that it has not +// been used again and calls the provisioner AuthorizeSign method. returns a +// list of methods to apply to the signing flow. +func (a *Authority) authorizeSign(ctx context.Context, ott string) ([]provisioner.SignOption, error) { + var errContext = apiCtx{"ott": ott} + p, err := a.authorizeToken(ott) + if err != nil { + return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext} + } + opts, err := p.AuthorizeSign(ctx, ott) + if err != nil { + return nil, &apiError{errors.Wrap(err, "authorizeSign"), http.StatusUnauthorized, errContext} + } + return opts, nil +} + // AuthorizeSign authorizes a signature request by validating and authenticating // a OTT that must be sent w/ the request. func (a *Authority) AuthorizeSign(ott string) ([]provisioner.SignOption, error) { diff --git a/authority/config.go b/authority/config.go index 7cfdf744..4117d279 100644 --- a/authority/config.go +++ b/authority/config.go @@ -50,6 +50,7 @@ type Config struct { IntermediateKey string `json:"key"` Address string `json:"address"` DNSNames []string `json:"dnsNames"` + SSH *SSHConfig `json:"ssh,omitempty"` Logger json.RawMessage `json:"logger,omitempty"` DB *db.Config `json:"db,omitempty"` Monitoring json.RawMessage `json:"monitoring,omitempty"` @@ -98,6 +99,12 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error { return nil } +// SSHConfig contains the user and host keys. +type SSHConfig struct { + HostKey string `json:"hostKey"` + UserKey string `json:"userKey"` +} + // LoadConfiguration parses the given filename in JSON format and returns the // configuration struct. func LoadConfiguration(filename string) (*Config, error) { diff --git a/authority/ssh.go b/authority/ssh.go index 952811ec..e3201683 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -81,8 +81,20 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign var signer ssh.Signer switch cert.CertType { case ssh.UserCert: + if a.sshCAUserCertSignKey == nil { + return nil, &apiError{ + err: errors.New("signSSH: user certificate signing is not enabled"), + code: http.StatusNotImplemented, + } + } signer, err = ssh.NewSignerFromSigner(a.sshCAUserCertSignKey) case ssh.HostCert: + if a.sshCAHostCertSignKey == nil { + return nil, &apiError{ + err: errors.New("signSSH: host certificate signing is not enabled"), + code: http.StatusNotImplemented, + } + } signer, err = ssh.NewSignerFromSigner(a.sshCAHostCertSignKey) default: return nil, &apiError{