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 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
|
# A decent way to generate one: ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" < /dev/null
|
||||||
#host_key: ./ssh_host_ed25519_key
|
#host_key: ./ssh_host_ed25519_key
|
||||||
# A file containing a list of authorized public keys
|
# Authorized users and their public keys
|
||||||
#authorized_users:
|
#authorized_users:
|
||||||
#- user: steeeeve
|
#- user: steeeeve
|
||||||
# keys can be an array of strings or single string
|
# keys can be an array of strings or single string
|
||||||
#keys:
|
#keys:
|
||||||
#- "ssh public key string"
|
#- "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.
|
# EXPERIMENTAL: relay support for networks that can't establish direct connections.
|
||||||
relay:
|
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)
|
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")
|
rawKeys := c.Get("sshd.authorized_users")
|
||||||
keys, ok := rawKeys.([]interface{})
|
keys, ok := rawKeys.([]interface{})
|
||||||
if ok {
|
if ok {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package sshd
|
package sshd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
@ -15,8 +16,11 @@ type SSHServer struct {
|
||||||
config *ssh.ServerConfig
|
config *ssh.ServerConfig
|
||||||
l *logrus.Entry
|
l *logrus.Entry
|
||||||
|
|
||||||
|
certChecker *ssh.CertChecker
|
||||||
|
|
||||||
// Map of user -> authorized keys
|
// Map of user -> authorized keys
|
||||||
trustedKeys map[string]map[string]bool
|
trustedKeys map[string]map[string]bool
|
||||||
|
trustedCAs []ssh.PublicKey
|
||||||
|
|
||||||
// List of available commands
|
// List of available commands
|
||||||
helpCommand *Command
|
helpCommand *Command
|
||||||
|
@ -31,6 +35,7 @@ type SSHServer struct {
|
||||||
|
|
||||||
// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen
|
// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen
|
||||||
func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
||||||
|
|
||||||
s := &SSHServer{
|
s := &SSHServer{
|
||||||
trustedKeys: make(map[string]map[string]bool),
|
trustedKeys: make(map[string]map[string]bool),
|
||||||
l: l,
|
l: l,
|
||||||
|
@ -38,8 +43,43 @@ func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
||||||
conns: make(map[int]*session),
|
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{
|
s.config = &ssh.ServerConfig{
|
||||||
PublicKeyCallback: s.matchPubKey,
|
PublicKeyCallback: cc.Authenticate,
|
||||||
//TODO: AuthLogCallback: s.authAttempt,
|
//TODO: AuthLogCallback: s.authAttempt,
|
||||||
//TODO: version string
|
//TODO: version string
|
||||||
ServerVersion: fmt.Sprintf("SSH-2.0-Nebula???"),
|
ServerVersion: fmt.Sprintf("SSH-2.0-Nebula???"),
|
||||||
|
@ -66,10 +106,26 @@ func (s *SSHServer) SetHostKey(hostPrivateKey []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SSHServer) ClearTrustedCAs() {
|
||||||
|
s.trustedCAs = []ssh.PublicKey{}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SSHServer) ClearAuthorizedKeys() {
|
func (s *SSHServer) ClearAuthorizedKeys() {
|
||||||
s.trustedKeys = make(map[string]map[string]bool)
|
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
|
// AddAuthorizedKey adds an ssh public key for a user
|
||||||
func (s *SSHServer) AddAuthorizedKey(user, pubKey string) error {
|
func (s *SSHServer) AddAuthorizedKey(user, pubKey string) error {
|
||||||
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
|
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKey))
|
||||||
|
@ -178,26 +234,3 @@ func (s *SSHServer) closeSessions() {
|
||||||
}
|
}
|
||||||
s.connsLock.Unlock()
|
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