From 3d0ce56967b104956bc435fb160997d566e8070c Mon Sep 17 00:00:00 2001 From: Russell Jones Date: Wed, 11 Oct 2017 22:16:08 +0000 Subject: [PATCH] Test coverage for login attempts. --- lib/auth/tun.go | 5 +++- lib/auth/tun_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/auth/tun.go b/lib/auth/tun.go index c5e7fa32411ac..16fcec6af098e 100644 --- a/lib/auth/tun.go +++ b/lib/auth/tun.go @@ -761,7 +761,10 @@ func TunClientStorage(storage utils.AddrStorage) TunClientOption { } // TunDisableRefresh will disable refreshing the list of auth servers. This is -// required when requesting user certificates. +// required when requesting user certificates because we only allow a single +// HTTP request to be made over the tunnel. This is because each request does +// keyAuth, and for situations like password+otp where the OTP token is invalid +// after the first use, that means all other requests would fail. func TunDisableRefresh() TunClientOption { return func(t *TunClient) { t.disableRefresh = true diff --git a/lib/auth/tun_test.go b/lib/auth/tun_test.go index 1cde6cfae6454..cf80d7f25578b 100644 --- a/lib/auth/tun_test.go +++ b/lib/auth/tun_test.go @@ -542,6 +542,76 @@ func (s *TunSuite) TestSessionsBadPassword(c *C) { c.Assert(ws, IsNil) } +// TestLoginAttempts makes sure the login attempt counter is incremented and +// reset correctly. +func (s *TunSuite) TestLoginAttempts(c *C) { + c.Assert(s.a.UpsertCertAuthority( + suite.NewTestCA(services.UserCA, "localhost")), IsNil) + + user := "system-test" + pass := []byte("system-abc123") + rawSecret := "def456" + otpSecret := base32.StdEncoding.EncodeToString([]byte(rawSecret)) + + err := s.a.UpsertPassword(user, pass) + c.Assert(err, IsNil) + + err = s.a.UpsertTOTP(user, otpSecret) + c.Assert(err, IsNil) + + otpURL, _, err := s.a.GetOTPData(user) + c.Assert(err, IsNil) + + // make sure label in url is correct + u, err := url.Parse(otpURL) + c.Assert(err, IsNil) + c.Assert(u.Path, Equals, "/system-test") + + // create a valid otp token + validToken, err := totp.GenerateCode(otpSecret, time.Now()) + c.Assert(err, IsNil) + + // try first to login with an invalid password + authMethod, err := NewWebPasswordAuth(user, []byte("invalid-password"), validToken) + c.Assert(err, IsNil) + + clt, err := NewTunClient("test", + []utils.NetAddr{{AddrNetwork: "tcp", Addr: s.tsrv.Addr()}}, user, authMethod, TunDisableRefresh()) + c.Assert(err, IsNil) + c.Assert(err, IsNil) + + // we can make any request and don't care about the result. the code keyAuth + // code we care about is run during the ssh handshake. + clt.GetUsers() + err = clt.Close() + c.Assert(err, IsNil) + + // should only create a single failed login attempt + loginAttempts, err := s.a.GetUserLoginAttempts(user) + c.Assert(err, IsNil) + c.Assert(loginAttempts, HasLen, 1) + + // try again with the correct password + authMethod, err = NewWebPasswordAuth(user, pass, validToken) + c.Assert(err, IsNil) + + clt, err = NewTunClient("test", + []utils.NetAddr{{AddrNetwork: "tcp", Addr: s.tsrv.Addr()}}, user, authMethod, TunDisableRefresh()) + c.Assert(err, IsNil) + c.Assert(err, IsNil) + + // once again, we can make any request and don't care about the result. the + // code keyAuth code we care about is run during the ssh handshake. + clt.GetUsers() + err = clt.Close() + c.Assert(err, IsNil) + + // login was successful, attempts should be reset back to 0 + loginAttempts, err = s.a.GetUserLoginAttempts(user) + c.Assert(err, IsNil) + c.Assert(loginAttempts, HasLen, 0) +} + func (s *TunSuite) TestFailover(c *C) { node := newServer( services.KindNode,