Covered host authentication with tests

This commit is contained in:
Ev Kontsevoy 2017-06-11 19:22:23 -07:00
parent ef790eefa6
commit 3ff5820b67
2 changed files with 76 additions and 15 deletions

View file

@ -40,6 +40,9 @@ type LocalKeyAgent struct {
// map of "no hosts". these are hosts that user manually (via keyboard
// input) refused connecting to.
noHosts map[string]bool
// function which asks a user to trust host/key combination (during host auth)
hostPromptFunc func(host string, k ssh.PublicKey) error
}
// NewLocalAgent reads all Teleport certificates from disk (using FSLocalKeyStore),
@ -212,40 +215,44 @@ func (a *LocalKeyAgent) UserRefusedHosts() bool {
// CheckHostSignature checks if the given host key was signed by one of the trusted
// certificaate authorities (CAs)
func (a *LocalKeyAgent) CheckHostSignature(hostId string, remote net.Addr, key ssh.PublicKey) error {
promptUser := func() error {
func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh.PublicKey) error {
hostPromptFunc := func(host string, key ssh.PublicKey) error {
userAnswer := "no"
if !a.noHosts[hostId] {
if !a.noHosts[host] {
fmt.Printf("The authenticity of host '%s' can't be established. "+
"Its public key is:\n%s\nAre you sure you want to continue (yes/no)? ",
hostId, ssh.MarshalAuthorizedKey(key))
host, ssh.MarshalAuthorizedKey(key))
bytes := make([]byte, 12)
os.Stdin.Read(bytes)
userAnswer = strings.TrimSpace(strings.ToLower(string(bytes)))
}
if !strings.HasPrefix(userAnswer, "y") {
a.noHosts[hostId] = true
return trace.AccessDenied("untrusted host %v", hostId)
return trace.AccessDenied("untrusted host %v", host)
}
// success
return nil
}
// overwritten host prompt func? (probably for tests)
if a.hostPromptFunc != nil {
hostPromptFunc = a.hostPromptFunc
}
cert, ok := key.(*ssh.Certificate)
if !ok {
// not a signed cert? perhaps we're given a host public key (happens when the host is running
// sshd instead of teleport daemon
keys, _ := a.keyStore.GetKnownHostKeys(hostId)
keys, _ := a.keyStore.GetKnownHostKeys(host)
if len(keys) > 0 && sshutils.KeysEqual(key, keys[0]) {
log.Debugf("[KEY AGENT] verified host %s", hostId)
log.Debugf("[KEY AGENT] verified host %s", host)
return nil
}
// ask user:
if err := promptUser(); err != nil {
if err := hostPromptFunc(host, key); err != nil {
a.noHosts[host] = true
return trace.Wrap(err)
}
// remember the host key (put it into 'known_hosts')
if err := a.keyStore.AddKnownHostKeys(hostId, []ssh.PublicKey{key}); err != nil {
if err := a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key}); err != nil {
log.Warnf("error saving the host key: %v", err)
}
return nil
@ -260,15 +267,16 @@ func (a *LocalKeyAgent) CheckHostSignature(hostId string, remote net.Addr, key s
log.Debugf("[KEY AGENT] got %d known hosts", len(keys))
for i := range keys {
if sshutils.KeysEqual(cert.SignatureKey, keys[i]) {
log.Debugf("[KEY AGENT] verified host %s", hostId)
log.Debugf("[KEY AGENT] verified host %s", host)
return nil
}
}
// final step: lets ask user:
if err = promptUser(); err != nil {
if err = hostPromptFunc(host, key); err != nil {
a.noHosts[host] = true
return trace.Wrap(err)
}
err = a.keyStore.AddKnownHostKeys(hostId, []ssh.PublicKey{key})
err = a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key})
if err != nil {
log.Warn(err)
}

View file

@ -28,7 +28,7 @@ import (
"golang.org/x/crypto/ssh/agent"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
@ -202,8 +202,61 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) {
c.Assert(err, check.IsNil)
}
func (s *KeyAgentTestSuite) TestHostVerification(c *check.C) {
// make a new local agent
lka, err := NewLocalAgent(s.keyDir, s.username)
c.Assert(err, check.IsNil)
// by default user has not refused any hosts:
c.Assert(lka.UserRefusedHosts(), check.Equals, false)
// make a fake host key:
keygen := testauthority.New()
_, pub, err := keygen.GenerateKeyPair("")
c.Assert(err, check.IsNil)
pk, _, _, _, err := ssh.ParseAuthorizedKey(pub)
c.Assert(err, check.IsNil)
// test user refusing connection:
fakeErr := trace.Errorf("luna cannot be trusted!")
lka.hostPromptFunc = func(host string, k ssh.PublicKey) error {
c.Assert(host, check.Equals, "luna")
c.Assert(k, check.Equals, pk)
return fakeErr
}
var a net.TCPAddr
err = lka.CheckHostSignature("luna", &a, pk)
c.Assert(err, check.NotNil)
c.Assert(err.Error(), check.Equals, "luna cannot be trusted!")
c.Assert(lka.UserRefusedHosts(), check.Equals, true)
// clean user answer:
delete(lka.noHosts, "luna")
c.Assert(lka.UserRefusedHosts(), check.Equals, false)
// now lets simulate user being asked:
userWasAsked := false
lka.hostPromptFunc = func(host string, k ssh.PublicKey) error {
// user answered "yes"
userWasAsked = true
return nil
}
c.Assert(lka.UserRefusedHosts(), check.Equals, false)
err = lka.CheckHostSignature("luna", &a, pk)
c.Assert(err, check.IsNil)
c.Assert(userWasAsked, check.Equals, true)
// now lets simulate automatic host verification (no need to ask user, he
// just said "yes")
userWasAsked = false
c.Assert(lka.UserRefusedHosts(), check.Equals, false)
err = lka.CheckHostSignature("luna", &a, pk)
c.Assert(err, check.IsNil)
c.Assert(userWasAsked, check.Equals, false)
}
func makeKey(username string, allowedLogins []string, ttl time.Duration) (*Key, error) {
keygen := native.New()
keygen := testauthority.New()
privateKey, publicKey, err := keygen.GenerateKeyPair("")
if err != nil {