145 строки
4.4 KiB
Go
145 строки
4.4 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Tests for ssh client multi-auth
|
|
//
|
|
// These tests run a simple go ssh client against OpenSSH server
|
|
// over unix domain sockets. The tests use multiple combinations
|
|
// of password, keyboard-interactive and publickey authentication
|
|
// methods.
|
|
//
|
|
// A wrapper library for making sshd PAM authentication use test
|
|
// passwords is required in ./sshd_test_pw.so. If the library does
|
|
// not exist these tests will be skipped. See compile instructions
|
|
// (for linux) in file ./sshd_test_pw.c.
|
|
|
|
// +build linux
|
|
|
|
package test
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// test cases
|
|
type multiAuthTestCase struct {
|
|
authMethods []string
|
|
expectedPasswordCbs int
|
|
expectedKbdIntCbs int
|
|
}
|
|
|
|
// test context
|
|
type multiAuthTestCtx struct {
|
|
password string
|
|
numPasswordCbs int
|
|
numKbdIntCbs int
|
|
}
|
|
|
|
// create test context
|
|
func newMultiAuthTestCtx(t *testing.T) *multiAuthTestCtx {
|
|
password, err := randomPassword()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate random test password: %s", err.Error())
|
|
}
|
|
|
|
return &multiAuthTestCtx{
|
|
password: password,
|
|
}
|
|
}
|
|
|
|
// password callback
|
|
func (ctx *multiAuthTestCtx) passwordCb() (secret string, err error) {
|
|
ctx.numPasswordCbs++
|
|
return ctx.password, nil
|
|
}
|
|
|
|
// keyboard-interactive callback
|
|
func (ctx *multiAuthTestCtx) kbdIntCb(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
|
if len(questions) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
ctx.numKbdIntCbs++
|
|
if len(questions) == 1 {
|
|
return []string{ctx.password}, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unsupported keyboard-interactive flow")
|
|
}
|
|
|
|
// TestMultiAuth runs several subtests for different combinations of password, keyboard-interactive and publickey authentication methods
|
|
func TestMultiAuth(t *testing.T) {
|
|
testCases := []multiAuthTestCase{
|
|
// Test password,publickey authentication, assert that password callback is called 1 time
|
|
multiAuthTestCase{
|
|
authMethods: []string{"password", "publickey"},
|
|
expectedPasswordCbs: 1,
|
|
},
|
|
// Test keyboard-interactive,publickey authentication, assert that keyboard-interactive callback is called 1 time
|
|
multiAuthTestCase{
|
|
authMethods: []string{"keyboard-interactive", "publickey"},
|
|
expectedKbdIntCbs: 1,
|
|
},
|
|
// Test publickey,password authentication, assert that password callback is called 1 time
|
|
multiAuthTestCase{
|
|
authMethods: []string{"publickey", "password"},
|
|
expectedPasswordCbs: 1,
|
|
},
|
|
// Test publickey,keyboard-interactive authentication, assert that keyboard-interactive callback is called 1 time
|
|
multiAuthTestCase{
|
|
authMethods: []string{"publickey", "keyboard-interactive"},
|
|
expectedKbdIntCbs: 1,
|
|
},
|
|
// Test password,password authentication, assert that password callback is called 2 times
|
|
multiAuthTestCase{
|
|
authMethods: []string{"password", "password"},
|
|
expectedPasswordCbs: 2,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(strings.Join(testCase.authMethods, ","), func(t *testing.T) {
|
|
ctx := newMultiAuthTestCtx(t)
|
|
|
|
server := newServerForConfig(t, "MultiAuth", map[string]string{"AuthMethods": strings.Join(testCase.authMethods, ",")})
|
|
defer server.Shutdown()
|
|
|
|
clientConfig := clientConfig()
|
|
server.setTestPassword(clientConfig.User, ctx.password)
|
|
|
|
publicKeyAuthMethod := clientConfig.Auth[0]
|
|
clientConfig.Auth = nil
|
|
for _, authMethod := range testCase.authMethods {
|
|
switch authMethod {
|
|
case "publickey":
|
|
clientConfig.Auth = append(clientConfig.Auth, publicKeyAuthMethod)
|
|
case "password":
|
|
clientConfig.Auth = append(clientConfig.Auth,
|
|
ssh.RetryableAuthMethod(ssh.PasswordCallback(ctx.passwordCb), 5))
|
|
case "keyboard-interactive":
|
|
clientConfig.Auth = append(clientConfig.Auth,
|
|
ssh.RetryableAuthMethod(ssh.KeyboardInteractive(ctx.kbdIntCb), 5))
|
|
default:
|
|
t.Fatalf("Unknown authentication method %s", authMethod)
|
|
}
|
|
}
|
|
|
|
conn := server.Dial(clientConfig)
|
|
defer conn.Close()
|
|
|
|
if ctx.numPasswordCbs != testCase.expectedPasswordCbs {
|
|
t.Fatalf("passwordCallback was called %d times, expected %d times", ctx.numPasswordCbs, testCase.expectedPasswordCbs)
|
|
}
|
|
|
|
if ctx.numKbdIntCbs != testCase.expectedKbdIntCbs {
|
|
t.Fatalf("keyboardInteractiveCallback was called %d times, expected %d times", ctx.numKbdIntCbs, testCase.expectedKbdIntCbs)
|
|
}
|
|
})
|
|
}
|
|
}
|