Add initial implementation of ssh config.

This commit is contained in:
Mariano Cano 2019-09-24 19:12:13 -07:00 committed by max furman
parent 9ec2fe74b4
commit dc6ffb7670
3 changed files with 87 additions and 24 deletions

View file

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -15,6 +16,7 @@ import (
type SSHAuthority interface { type SSHAuthority interface {
SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) SignSSHAddUser(key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
SSHConfig() (*authority.SSHConfiguration, error)
} }
// SignSSHRequest is the request body of an SSH certificate request. // SignSSHRequest is the request body of an SSH certificate request.
@ -34,6 +36,13 @@ type SignSSHResponse struct {
AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"` AddUserCertificate *SSHCertificate `json:"addUserCrt,omitempty"`
} }
// SSHConfigResponse represents the response object that returns the SSH user
// and host keys.
type SSHConfigResponse struct {
UserKey *SSHPublicKey `json:"userKey,omitempty"`
HostKey *SSHPublicKey `json:"hostKey,omitempty"`
}
// SSHCertificate represents the response SSH certificate. // SSHCertificate represents the response SSH certificate.
type SSHCertificate struct { type SSHCertificate struct {
*ssh.Certificate `json:"omitempty"` *ssh.Certificate `json:"omitempty"`
@ -49,6 +58,21 @@ func (c SSHCertificate) MarshalJSON() ([]byte, error) {
return []byte(`"` + s + `"`), nil return []byte(`"` + s + `"`), nil
} }
// SSHPublicKey represents a public key in a response object.
type SSHPublicKey struct {
ssh.PublicKey
}
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
// base64 encoded, openssh wire format version of the public key.
func (p *SSHPublicKey) MarshalJSON() ([]byte, error) {
if p == nil || p.PublicKey == nil {
return []byte("null"), nil
}
s := base64.StdEncoding.EncodeToString(p.PublicKey.Marshal())
return []byte(`"` + s + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface. The certificate is // UnmarshalJSON implements the json.Unmarshaler interface. The certificate is
// expected to be a quoted, base64 encoded, openssh wire formatted block of bytes. // expected to be a quoted, base64 encoded, openssh wire formatted block of bytes.
func (c *SSHCertificate) UnmarshalJSON(data []byte) error { func (c *SSHCertificate) UnmarshalJSON(data []byte) error {
@ -157,3 +181,26 @@ func (h *caHandler) SignSSH(w http.ResponseWriter, r *http.Request) {
AddUserCertificate: addUserCertificate, AddUserCertificate: addUserCertificate,
}) })
} }
// SSHConfig is an HTTP handler that returns the SSH public keys for user and
// host certificates.
func (h *caHandler) SSHConfig(w http.ResponseWriter, r *http.Request) {
config, err := h.Authority.SSHConfig()
if err != nil {
WriteError(w, NotFound(err))
return
}
var host, user *SSHPublicKey
if config.HostKey != nil {
host = &SSHPublicKey{config.HostKey}
}
if config.UserKey != nil {
user = &SSHPublicKey{config.UserKey}
}
JSON(w, &SSHConfigResponse{
HostKey: host,
UserKey: user,
})
}

View file

@ -13,6 +13,7 @@ import (
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/pemutil"
"github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/crypto/x509util"
"golang.org/x/crypto/ssh"
) )
const ( const (
@ -24,8 +25,8 @@ type Authority struct {
config *Config config *Config
rootX509Certs []*x509.Certificate rootX509Certs []*x509.Certificate
intermediateIdentity *x509util.Identity intermediateIdentity *x509util.Identity
sshCAUserCertSignKey crypto.Signer sshCAUserCertSignKey ssh.Signer
sshCAHostCertSignKey crypto.Signer sshCAHostCertSignKey ssh.Signer
certificates *sync.Map certificates *sync.Map
startTime time.Time startTime time.Time
provisioners *provisioner.Collection provisioners *provisioner.Collection
@ -125,16 +126,24 @@ func (a *Authority) init() error {
// Decrypt and load SSH keys // Decrypt and load SSH keys
if a.config.SSH != nil { if a.config.SSH != nil {
if a.config.SSH.HostKey != "" { if a.config.SSH.HostKey != "" {
a.sshCAHostCertSignKey, err = parseCryptoSigner(a.config.SSH.HostKey, a.config.Password) signer, err := parseCryptoSigner(a.config.SSH.HostKey, a.config.Password)
if err != nil { if err != nil {
return err return err
} }
a.sshCAHostCertSignKey, err = ssh.NewSignerFromSigner(signer)
if err != nil {
return errors.Wrap(err, "error creating ssh signer")
}
} }
if a.config.SSH.UserKey != "" { if a.config.SSH.UserKey != "" {
a.sshCAUserCertSignKey, err = parseCryptoSigner(a.config.SSH.UserKey, a.config.Password) signer, err := parseCryptoSigner(a.config.SSH.UserKey, a.config.Password)
if err != nil { if err != nil {
return err return err
} }
a.sshCAUserCertSignKey, err = ssh.NewSignerFromSigner(signer)
if err != nil {
return errors.Wrap(err, "error creating ssh signer")
}
} }
} }

View file

@ -24,6 +24,30 @@ const (
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22" SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22"
) )
// SSHConfiguration is the return type for SSHConfig.
type SSHConfiguration struct {
UserKey ssh.PublicKey
HostKey ssh.PublicKey
}
// SSHConfig returns the SSH User and Host public keys.
func (a *Authority) SSHConfig() (*SSHConfiguration, error) {
var config SSHConfiguration
if a.sshCAUserCertSignKey != nil {
config.UserKey = a.sshCAUserCertSignKey.PublicKey()
}
if a.sshCAHostCertSignKey != nil {
config.HostKey = a.sshCAHostCertSignKey.PublicKey()
}
if config.UserKey == nil && config.HostKey == nil {
return nil, &apiError{
err: errors.New("sshConfig: ssh is not configured"),
code: http.StatusNotFound,
}
}
return &config, nil
}
// SignSSH creates a signed SSH certificate with the given public key and options. // SignSSH creates a signed SSH certificate with the given public key and options.
func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) {
var mods []provisioner.SSHCertificateModifier var mods []provisioner.SSHCertificateModifier
@ -95,12 +119,7 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign
code: http.StatusNotImplemented, code: http.StatusNotImplemented,
} }
} }
if signer, err = ssh.NewSignerFromSigner(a.sshCAUserCertSignKey); err != nil { signer = a.sshCAUserCertSignKey
return nil, &apiError{
err: errors.Wrap(err, "signSSH: error creating signer"),
code: http.StatusInternalServerError,
}
}
case ssh.HostCert: case ssh.HostCert:
if a.sshCAHostCertSignKey == nil { if a.sshCAHostCertSignKey == nil {
return nil, &apiError{ return nil, &apiError{
@ -108,12 +127,7 @@ func (a *Authority) SignSSH(key ssh.PublicKey, opts provisioner.SSHOptions, sign
code: http.StatusNotImplemented, code: http.StatusNotImplemented,
} }
} }
if signer, err = ssh.NewSignerFromSigner(a.sshCAHostCertSignKey); err != nil { signer = a.sshCAHostCertSignKey
return nil, &apiError{
err: errors.Wrap(err, "signSSH: error creating signer"),
code: http.StatusInternalServerError,
}
}
default: default:
return nil, &apiError{ return nil, &apiError{
err: errors.Errorf("signSSH: unexpected ssh certificate type: %d", cert.CertType), err: errors.Errorf("signSSH: unexpected ssh certificate type: %d", cert.CertType),
@ -180,14 +194,7 @@ func (a *Authority) SignSSHAddUser(key ssh.PublicKey, subject *ssh.Certificate)
} }
} }
signer, err := ssh.NewSignerFromSigner(a.sshCAUserCertSignKey) signer := a.sshCAUserCertSignKey
if err != nil {
return nil, &apiError{
err: errors.Wrap(err, "signSSHProxy: error creating signer"),
code: http.StatusInternalServerError,
}
}
principal := subject.ValidPrincipals[0] principal := subject.ValidPrincipals[0]
addUserPrincipal := a.getAddUserPrincipal() addUserPrincipal := a.getAddUserPrincipal()