Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/testutils/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ func SleepMultiplier() float64 {

return sleepMultiplier
}

// IsCI returns whether the test is running in CI environment.
var IsCI = sync.OnceValue(func() bool {
_, ok := os.LookupEnv("GITHUB_ACTIONS")
return ok
})
38 changes: 35 additions & 3 deletions internal/users/db/bbolt/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package bbolt
// They are not exported, and guarded by testing assertions.

import (
"encoding/json"
"os"
"path/filepath"
"strings"
Expand All @@ -14,14 +15,26 @@ import (
"gopkg.in/yaml.v3"
)

// We need to replace the current time by a deterministic time in the golden files to be able to compare them.
// We use the first second of the year 2020 as a recognizable value (which is not the zero value).
var redactedCurrentTime = "2020-01-01T00:00:00Z"
const (
// We need to replace the current time by a deterministic time in the golden files to be able to compare them.
// We use the first second of the year 2020 as a recognizable value (which is not the zero value).
redactedCurrentTime = "2020-01-01T00:00:00Z"

// User homes can have an invalid prefix that we can adjust during tests.
redactedUserHome = "@HOME_BASE@"
)

// Z_ForTests_CreateDBFromYAML creates the database inside destDir and loads the src file content into it.
//
// nolint:revive,nolintlint // We want to use underscores in the function name here.
func Z_ForTests_CreateDBFromYAML(src, destDir string) (err error) {
return Z_ForTests_CreateDBFromYAMLWithBaseHome(src, destDir, "")
}

// Z_ForTests_CreateDBFromYAMLWithBaseHome creates the database inside destDir and loads the src file content into it.
//
// nolint:revive,nolintlint // We want to use underscores in the function name here.
func Z_ForTests_CreateDBFromYAMLWithBaseHome(src, destDir, baseHomeDir string) (err error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in the old bbolt package?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that's a function to generate a bolt db, no?
I just added another function similar to where the one we had was...

I initially was just doing this as part of the integration tests helpers, but it felt something to do in its own spot (also to be able to access to private stuff).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the integration tests should not use a bbolt database anymore, except for test cases which test migration from bbolt to sqlite. I see useOldDatabaseEnv is used in many other test cases as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah but it just returns if oldDB is empty, which it is for all test cases except for the ones which test migration

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that means that Z_ForTests_CreateDBFromYAMLWithBaseHome is also only used for those test cases which test migration. is that expected?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not need to create the home directory in the other cases, where we use a SQLite db?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not need to create the home directory in the other cases, where we use a SQLite db?

Oh, we should, but IIRC we don't have such tests yet... I need to double check it.

testsdetection.MustBeTesting()

src, err = filepath.Abs(src)
Expand Down Expand Up @@ -64,7 +77,26 @@ func Z_ForTests_CreateDBFromYAML(src, destDir string) (err error) {
if bucketName == userByIDBucketName || bucketName == userByNameBucketName {
// Replace the redacted time in the json value by a valid time.
val = strings.Replace(val, redactedCurrentTime, time.Now().Format(time.RFC3339), 1)

if baseHomeDir != "" {
var u UserDB
if err := json.Unmarshal([]byte(val), &u); err != nil {
return err
}

u.Dir = strings.ReplaceAll(u.Dir, redactedUserHome, baseHomeDir)
if err := os.MkdirAll(u.Dir, 0700); err != nil {
return err
}

v, err := json.Marshal(u)
if err != nil {
return err
}
val = string(v)
}
}

if err := bucket.Put([]byte(key), []byte(val)); err != nil {
panic("programming error: put called in a RO transaction")
}
Expand Down
3 changes: 2 additions & 1 deletion pam/integration-tests/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ func useOldDatabaseEnv(t *testing.T, oldDB string) []string {
oldDBDir, err := os.MkdirTemp(tempDir, "old-db-path")
require.NoError(t, err, "Cannot create db directory in %q", tempDir)

err = bbolt.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", oldDB+".db.yaml"), oldDBDir)
err = bbolt.Z_ForTests_CreateDBFromYAMLWithBaseHome(
filepath.Join("testdata", "db", oldDB+".db.yaml"), oldDBDir, t.TempDir())
require.NoError(t, err, "Setup: creating old database")

return []string{fmt.Sprintf("AUTHD_INTEGRATIONTESTS_OLD_DB_DIR=%s", oldDBDir)}
Expand Down
108 changes: 76 additions & 32 deletions pam/integration-tests/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) {
execModule := buildExecModuleWithCFlags(t, []string{"-std=c11"}, true)
execChild := buildPAMExecChild(t)

specialUserAcceptAll := "authd-test-user-sshd-accept-all"

mkHomeDirHelper, err := exec.LookPath("mkhomedir_helper")
require.NoError(t, err, "Setup: mkhomedir_helper not found")
pamMkHomeDirModule := buildCPAMModule(t,
Expand All @@ -94,7 +96,8 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) {
nssLibrary, nssEnv = testutils.BuildRustNSSLib(t, true)
sshdPreloadLibraries = append(sshdPreloadLibraries, nssLibrary)
sshdPreloaderCFlags = append(sshdPreloaderCFlags,
"-DAUTHD_TESTS_SSH_USE_AUTHD_NSS")
"-DAUTHD_TESTS_SSH_USE_AUTHD_NSS",
fmt.Sprintf("-DAUTHD_SPECIAL_USER_ACCEPT_ALL=%q", specialUserAcceptAll))
nssEnv = append(nssEnv, nssTestEnvBase(t, nssLibrary)...)
} else if err != nil {
t.Logf("Using the dummy library to implement NSS: %v", err)
Expand Down Expand Up @@ -127,8 +130,9 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) {
serviceFile := createSshdServiceFile(t, execModule, execChild, pamMkHomeDirModule, defaultSocketPath)
sshdEnv = append(sshdEnv, nssEnv...)
sshdEnv = append(sshdEnv, fmt.Sprintf("AUTHD_NSS_SOCKET=%s", defaultSocketPath))
defaultSSHDPort, defaultUserHome = startSSHdForTest(t, serviceFile, sshdHostKey,
"authd-test-user-sshd-accept-all", sshdPreloadLibraries, sshdEnv, true, false)
defaultUserHome = expectedUserHome(t, specialUserAcceptAll)
defaultSSHDPort = startSSHdForTest(t, serviceFile, sshdHostKey,
defaultUserHome, specialUserAcceptAll, sshdPreloadLibraries, sshdEnv, true, false)
}

sshEnvVariablesRegex = regexp.MustCompile(`(?m) (PATH|HOME|PWD|SSH_[A-Z]+)=.*(\n*)($[^ ]{2}.*)?$`)
Expand All @@ -146,6 +150,7 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) {
socketPath string
daemonizeSSHd bool
interactiveShell bool
command []string
oldDB string

wantUserAlreadyExist bool
Expand All @@ -163,6 +168,10 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) {
tape: "simple_auth_with_shell",
interactiveShell: true,
},
"Authenticate_user_successfully_launching_command": {
tape: "simple_auth",
command: []string{"true"},
},
"Authenticate_user_successfully_with_upper_case": {
tape: "simple_auth",
user: strings.ToUpper(vhsTestUserNameFull(t,
Expand Down Expand Up @@ -405,6 +414,9 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
user = vhsTestUserNameFull(t, tc.userPrefix, "ssh")
}

sshdPort := defaultSSHDPort
userHome := defaultUserHome

var userClient authd.UserServiceClient
if tc.socketPath == "" {
conn, err := grpc.NewClient("unix://"+socketPath,
Expand All @@ -419,14 +431,17 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
userClient = authd.NewUserServiceClient(conn)

if tc.wantUserAlreadyExist {
requireAuthdUser(t, userClient, user)
authdUser := requireAuthdUser(t, userClient, user)
userHome = authdUser.Homedir
} else {
requireNoAuthdUser(t, userClient, user)
}
}

sshdPort := defaultSSHDPort
userHome := defaultUserHome
if userHome == "" {
userHome = expectedUserHome(t, user)
}

if !sharedSSHd || tc.wantLocalGroups || tc.oldDB != "" ||
tc.interactiveShell || tc.socketPath != "" {
sshdEnv := sshdEnv
Expand All @@ -441,13 +456,20 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
}
serviceFile := createSshdServiceFile(t, execModule, execChild,
pamMkHomeDirModule, socketPath)
sshdPort, userHome = startSSHdForTest(t, serviceFile, sshdHostKey, user,
sshdPreloadLibraries, sshdEnv, tc.daemonizeSSHd, tc.interactiveShell)
sshdPort = startSSHdForTest(t, serviceFile, sshdHostKey,
userHome, user, sshdPreloadLibraries, sshdEnv, tc.daemonizeSSHd,
tc.interactiveShell)
}

if !sharedSSHd {
_, err := os.Stat(userHome)
require.ErrorIs(t, err, os.ErrNotExist, "Unexpected error checking for %q", userHome)
if tc.wantUserAlreadyExist {
require.NoError(t, err, os.ErrNotExist,
"User home %q must already exist", userHome)
} else {
require.ErrorIs(t, err, os.ErrNotExist,
"Unexpected error checking for %q", userHome)
}
}

knownHost := filepath.Join(t.TempDir(), "known_hosts")
Expand All @@ -456,21 +478,28 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
), 0600)
require.NoError(t, err, "Setup: can't create known hosts file")

outDir := t.TempDir()
td := newTapeData(tc.tape, append(defaultTapeSettings, tc.tapeSettings...)...)
td.Command = tapeCommand
td.Env[pam_test.RunnerEnvSupportsConversation] = "1"
td.Env["HOME"] = t.TempDir()
td.Env[pamSSHUserEnv] = user
td.Env["AUTHD_PAM_SSH_ARGS"] = strings.Join([]string{
sshArgs := []string{
"-p", sshdPort,
"-F", os.DevNull,
"-i", os.DevNull,
"-o", "ServerAliveInterval=300",
"-o", "PasswordAuthentication=no",
"-o", "PubkeyAuthentication=no",
"-o", "UserKnownHostsFile=" + knownHost,
}, " ")
}

if tc.interactiveShell {
require.Nil(t, tc.command, "Setup: Interactive shell and commands are incompatible")
}
sshArgs = append(sshArgs, tc.command...)

outDir := t.TempDir()
td := newTapeData(tc.tape, append(defaultTapeSettings, tc.tapeSettings...)...)
td.Command = tapeCommand
td.Env[pam_test.RunnerEnvSupportsConversation] = "1"
td.Env["HOME"] = t.TempDir()
td.Env[pamSSHUserEnv] = user
td.Env["AUTHD_PAM_SSH_ARGS"] = strings.Join(sshArgs, " ")
td.Variables = tc.tapeVariables
td.RunVhs(t, vhsTestTypeSSH, outDir, nil)
got := sanitizeGoldenFile(t, td, outDir)
Expand All @@ -486,6 +515,9 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
if nssLibrary != "" {
requireGetEntExists(t, nssLibrary, socketPath, user, tc.isLocalUser)
}

_, err := os.Stat(userHome)
require.Error(t, err, "User %q home %q must not exist", user, userHome)
} else {
require.Contains(t, got, userEnv, "Logged in user does not matches")

Expand All @@ -503,12 +535,10 @@ Wait@%dms`, sshDefaultFinalWaitTimeout),
}
}

if !tc.wantUserAlreadyExist {
// Check if user home has been created, but only if the user is a new one.
stat, err := os.Stat(userHome)
require.NoError(t, err, "Error checking for %q", userHome)
require.True(t, stat.IsDir(), "%q is not a directory", userHome)
}
// Check if user home has been created.
stat, err := os.Stat(userHome)
require.NoError(t, err, "Error checking for %q", userHome)
require.True(t, stat.IsDir(), "User %q home %q is not a directory", user, userHome)
}

if tc.wantLocalGroups || tc.oldDB != "" {
Expand Down Expand Up @@ -590,12 +620,18 @@ func createSshdServiceFile(t *testing.T, module, execChild, mkHomeModule, socket
return serviceFile
}

func startSSHdForTest(t *testing.T, serviceFile, hostKey, user string, preloadLibraries []string, env []string, daemonize bool, interactiveShell bool) (string, string) {
func expectedUserHome(t *testing.T, userName string) string {
t.Helper()

return filepath.Join(t.TempDir(), strings.ToLower(userName))
}

func startSSHdForTest(t *testing.T, serviceFile, hostKey, userHome, user string, preloadLibraries []string, env []string, daemonize bool, interactiveShell bool) string {
t.Helper()

sshdConnectCommand := fmt.Sprintf(
"/usr/bin/echo ' SSHD: Connected to ssh via authd module! [%s]'",
t.Name())
"/usr/bin/sleep %.2f && /usr/bin/echo ' SSHD: Connected to ssh via authd module! [%s]'",
sleepDuration(1*time.Second).Seconds(), t.Name())
if daemonize {
// When in daemon mode SSH doesn't show debug infos, so let's
// handle this manually.
Expand All @@ -605,17 +641,18 @@ func startSSHdForTest(t *testing.T, serviceFile, hostKey, user string, preloadLi
sshdConnectCommand = "/bin/sh"
}

homeBase := t.TempDir()
userHome := filepath.Join(homeBase, user)
require.NotEmpty(t, user, "Setup: User name is unset")
require.NotEmpty(t, userHome, "Setup: User HOME for %q is unset", user)

sshdPort := startSSHd(t, hostKey, sshdConnectCommand, append([]string{
fmt.Sprintf("HOME=%s", homeBase),
fmt.Sprintf("HOME=%s", t.TempDir()),
fmt.Sprintf("LD_PRELOAD=%s", strings.Join(preloadLibraries, ":")),
fmt.Sprintf("AUTHD_TEST_SSH_USER=%s", user),
fmt.Sprintf("AUTHD_TEST_SSH_HOME=%s", userHome),
fmt.Sprintf("AUTHD_TEST_SSH_PAM_SERVICE=%s", serviceFile),
}, env...), daemonize)

return sshdPort, userHome
return sshdPort
}

func sshdCommand(t *testing.T, port, hostKey, forcedCommand string, env []string, daemonize bool) (*exec.Cmd, string, string) {
Expand Down Expand Up @@ -674,7 +711,9 @@ func startSSHd(t *testing.T, hostKey, forcedCommand string, env []string, daemon
sshd, sshdPidFile, sshdLogFile := sshdCommand(t, sshdPort, hostKey, forcedCommand, env, daemonize)
sshdStderr := bytes.Buffer{}
sshd.Stderr = &sshdStderr
if testing.Verbose() {

isCIVerbose := testutils.IsCI() && testing.Verbose()
if isCIVerbose {
sshd.Stdout = os.Stdout
sshd.Stderr = os.Stderr
}
Expand All @@ -685,9 +724,14 @@ func startSSHd(t *testing.T, hostKey, forcedCommand string, env []string, daemon
sshdPid := sshd.Process.Pid

t.Cleanup(func() {
if testing.Verbose() || !t.Failed() {
if isCIVerbose || !t.Failed() {
return
}

if testing.Verbose() {
t.Logf("SSHd log:\n%s", sshdStderr.Bytes())
}

sshdLog := filepath.Join(t.TempDir(), "sshd.log")
require.NoError(t, os.WriteFile(sshdLog, sshdStderr.Bytes(), 0600),
"TearDown: Saving sshd log")
Expand Down
1 change: 0 additions & 1 deletion pam/integration-tests/sshd_preloader/sshd_preloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#define AUTHD_TEST_SHELL "/bin/sh"
#define AUTHD_TEST_GECOS ""
#define AUTHD_DEFAULT_SSH_PAM_SERVICE_NAME "sshd"
#define AUTHD_SPECIAL_USER_ACCEPT_ALL "authd-test-user-sshd-accept-all"

#define SIZE_OF_ARRAY(a) (sizeof ((a)) / sizeof (*(a)))

Expand Down
Loading
Loading