forked from TrueCloudLab/certificates
Add initial implementation of ssh config.
This commit is contained in:
parent
9ec2fe74b4
commit
dc6ffb7670
3 changed files with 87 additions and 24 deletions
47
api/ssh.go
47
api/ssh.go
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue