mirror of https://github.com/slackhq/nebula.git
Add support for SSH CAs (#1098)
- Accept certs signed by trusted CAs - Username must match the cert principal if set - Any username can be used if cert principal is empty - Don't allow removed pubkeys/CAs to be used after reload
This commit is contained in:
parent
9cd944d320
commit
f31bab5f1a
|
@ -181,12 +181,15 @@ punchy:
|
|||
# A file containing the ssh host private key to use
|
||||
# A decent way to generate one: ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" < /dev/null
|
||||
#host_key: ./ssh_host_ed25519_key
|
||||
# A file containing a list of authorized public keys
|
||||
# Authorized users and their public keys
|
||||
#authorized_users:
|
||||
#- user: steeeeve
|
||||
# keys can be an array of strings or single string
|
||||
#keys:
|
||||
#- "ssh public key string"
|
||||
# Trusted SSH CA public keys. These are the public keys of the CAs that are allowed to sign SSH keys for access.
|
||||
#trusted_cas:
|
||||
#- "ssh public key string"
|
||||
|
||||
# EXPERIMENTAL: relay support for networks that can't establish direct connections.
|
||||
relay:
|
||||
|
|
13
ssh.go
13
ssh.go
|
@ -115,6 +115,19 @@ func configSSH(l *logrus.Logger, ssh *sshd.SSHServer, c *config.C) (func(), erro
|
|||
return nil, fmt.Errorf("error while adding sshd.host_key: %s", err)
|
||||
}
|
||||
|
||||
// Clear existing trusted CAs and authorized keys
|
||||
ssh.ClearTrustedCAs()
|
||||
ssh.ClearAuthorizedKeys()
|
||||
|
||||
rawCAs := c.GetStringSlice("sshd.trusted_cas", []string{})
|
||||
for _, caAuthorizedKey := range rawCAs {
|
||||
err := ssh.AddTrustedCA(caAuthorizedKey)
|
||||
if err != nil {
|
||||
l.WithError(err).WithField("sshCA", caAuthorizedKey).Warn("SSH CA had an error, ignoring")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rawKeys := c.Get("sshd.authorized_users")
|
||||
keys, ok := rawKeys.([]interface{})
|
||||
if ok {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package sshd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
@ -15,8 +16,11 @@ type SSHServer struct {
|
|||
config *ssh.ServerConfig
|
||||
l *logrus.Entry
|
||||
|
||||
certChecker *ssh.CertChecker
|
||||
|
||||
// Map of user -> authorized keys
|
||||
trustedKeys map[string]map[string]bool
|
||||
trustedCAs []ssh.PublicKey
|
||||
|
||||
// List of available commands
|
||||
helpCommand *Command
|
||||
|
@ -31,6 +35,7 @@ type SSHServer struct {
|
|||
|
||||
// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen
|
||||
func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
||||
|
||||
s := &SSHServer{
|
||||
trustedKeys: make(map[string]map[string]bool),
|
||||
l: l,
|
||||
|
@ -38,8 +43,43 @@ func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
|||
conns: make(map[int]*session),
|
||||
}
|
||||
|
||||
cc := ssh.CertChecker{
|
||||
IsUserAuthority: func(auth ssh.PublicKey) bool {
|
||||
for _, ca := range s.trustedCAs {
|
||||
if bytes.Equal(ca.Marshal(), auth.Marshal()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
UserKeyFallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
pk := string(pubKey.Marshal())
|
||||
fp := ssh.FingerprintSHA256(pubKey)
|
||||
|
||||
tk, ok := s.trustedKeys[c.User()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown user %s", c.User())
|
||||
}
|
||||
|
||||
_, ok = tk[pk]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown public key for %s (%s)", c.User(), fp)
|
||||
}
|
||||
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
Extensions: map[string]string{
|
||||
"fp": fp,
|
||||
"user": c.User(),
|
||||
},
|
||||
}, nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
s.config = &ssh.ServerConfig{
|
||||
PublicKeyCallback: s.matchPubKey,
|
||||
PublicKeyCallback: cc.Authenticate,
|
||||
//TODO: AuthLogCallback: s.authAttempt,
|
||||
//TODO: version string
|
||||
ServerVersion: fmt.Sprintf("SSH-2.0-Nebula???"),
|
||||
|
@ -66,10 +106,26 @@ func (s *SSHServer) SetHostKey(hostPrivateKey []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *SSHServer) ClearTrustedCAs() {
|
||||
s.trustedCAs = []ssh.PublicKey{}
|
||||
}
|
||||
|
||||
func (s *SSHServer) ClearAuthorizedKeys() {
|
||||
s.trustedKeys = make(map[string]map[string]bool)
|
||||
}
|
||||
|
||||
// AddTrustedCA adds a trusted CA for user certificates
|
||||
func (s *SSHServer) AddTrustedCA(pubKey string) error {
|
||||
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.trustedCAs = append(s.trustedCAs, pk)
|
||||
s.l.WithField("sshKey", pubKey).Info("Trusted CA key")
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAuthorizedKey adds an ssh public key for a user
|
||||
func (s *SSHServer) AddAuthorizedKey(user, pubKey string) error {
|
||||
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
|
||||
|
@ -178,26 +234,3 @@ func (s *SSHServer) closeSessions() {
|
|||
}
|
||||
s.connsLock.Unlock()
|
||||
}
|
||||
|
||||
func (s *SSHServer) matchPubKey(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
pk := string(pubKey.Marshal())
|
||||
fp := ssh.FingerprintSHA256(pubKey)
|
||||
|
||||
tk, ok := s.trustedKeys[c.User()]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown user %s", c.User())
|
||||
}
|
||||
|
||||
_, ok = tk[pk]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown public key for %s (%s)", c.User(), fp)
|
||||
}
|
||||
|
||||
return &ssh.Permissions{
|
||||
// Record the public key used for authentication.
|
||||
Extensions: map[string]string{
|
||||
"fp": fp,
|
||||
"user": c.User(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue