Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

maint(pam/integration-tests): Run all the tests with a shared authd when possible #593

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0ffebe6
examplebroker: Add support for local groups integration users
3v1n0 Nov 22, 2024
160fb40
examplebroker: Move users to another file
3v1n0 Nov 22, 2024
4355edc
examplebroker: Expose user integration prefixes and use them in tests
3v1n0 Nov 22, 2024
667b7b9
pam/integration-tests/vhs: Add function to define integration test us…
3v1n0 Nov 21, 2024
d3afdb2
pam/integration-tests/gdm: Use unique test user names
3v1n0 Oct 17, 2024
5b21a31
pam/integration-tests/native: Ensure user names are unique for all tests
3v1n0 Oct 16, 2024
dc0bf21
examplebroker: Fix support for integration tests users in passwd mode
3v1n0 Oct 17, 2024
3ebc681
pam/integration-tests/native-passwd: Use unique user names in all the…
3v1n0 Oct 17, 2024
f56b066
pam/integration-tests/cli-passwd: Ensure user names are unique for al…
3v1n0 Oct 16, 2024
ff6b703
testutils/daemon: Remove temporary dir when process has been stopped
3v1n0 Oct 17, 2024
30b0361
examplebroker: Use simpler check for "pre-check" users
3v1n0 Nov 22, 2024
f31ab3f
pam/integration-tests/ssh: Use unique names for SSH tests
3v1n0 Nov 22, 2024
79ddfae
pam/integration-tests/helpers: Add support for running a shared authd…
3v1n0 Oct 17, 2024
2927d66
pam/integration-tests: Run all the tests that we can with a shared authd
3v1n0 Oct 17, 2024
023a127
pam/integration-tests: Allow to run tests with individual authd insta…
3v1n0 Oct 17, 2024
975fd89
pam/integration-tests: Allow single authd instances for each test
3v1n0 Oct 17, 2024
1493b08
brokers/manager: Run cleanup function, if any, on errors
3v1n0 Oct 17, 2024
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
59 changes: 23 additions & 36 deletions examplebroker/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,28 +88,6 @@ type Broker struct {
sleepMultiplier float64
}

type userInfoBroker struct {
Password string
}

var (
exampleUsersMu = sync.RWMutex{}
exampleUsers = map[string]userInfoBroker{
"user1": {Password: "goodpass"},
"user2": {Password: "goodpass"},
"user3": {Password: "goodpass"},
"user-mfa": {Password: "goodpass"},
"user-mfa-with-reset": {Password: "goodpass"},
"user-needs-reset": {Password: "goodpass"},
"user-needs-reset2": {Password: "goodpass"},
"user-can-reset": {Password: "goodpass"},
"user-can-reset2": {Password: "goodpass"},
"user-local-groups": {Password: "goodpass"},
"user-pre-check": {Password: "goodpass"},
"user-sudo": {Password: "goodpass"},
}
)

// New creates a new examplebroker object.
func New(name string) (b *Broker, fullName, brandIcon string) {
// Generate a new private key for the broker.
Expand Down Expand Up @@ -173,50 +151,54 @@ func (b *Broker) NewSession(ctx context.Context, username, lang, mode string) (s
case "user-mfa-with-reset":
info.neededAuthSteps = 3
info.pwdChange = canReset
case "user-unexistent":
case UserIntegrationUnexistent:
return "", "", fmt.Errorf("user %q does not exist", username)
}

if info.sessionMode == "passwd" {
info.neededAuthSteps++
info.pwdChange = mustReset
}

exampleUsersMu.Lock()
defer exampleUsersMu.Unlock()
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-mfa-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationMfaPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
info.neededAuthSteps = 3
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-mfa-needs-reset-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationMfaNeedsResetPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
info.neededAuthSteps = 3
info.pwdChange = mustReset
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-mfa-with-reset-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationMfaWithResetPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
info.neededAuthSteps = 3
info.pwdChange = canReset
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-needs-reset-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationNeedsResetPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
info.neededAuthSteps = 2
info.pwdChange = mustReset
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, "user-can-reset-integration") {
if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationCanResetPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
info.neededAuthSteps = 2
info.pwdChange = canReset
}

if _, ok := exampleUsers[username]; !ok && strings.HasPrefix(username, UserIntegrationLocalGroupsPrefix) {
exampleUsers[username] = userInfoBroker{Password: "goodpass"}
}

if info.sessionMode == "passwd" {
info.neededAuthSteps++
info.pwdChange = mustReset
}

pubASN1, err := x509.MarshalPKIXPublicKey(&b.privateKey.PublicKey)
if err != nil {
return "", "", err
Expand Down Expand Up @@ -474,7 +456,7 @@ func qrcodeData(sessionInfo *sessionInfo) (content string, code string) {
"https://www.ubuntu-it.org/",
}

if strings.HasPrefix(sessionInfo.username, "user-integration-qrcode-static") {
if strings.HasPrefix(sessionInfo.username, UserIntegrationQrcodeStaticPrefix) {
return qrcodeURIs[0], fmt.Sprint(baseCode)
}

Expand Down Expand Up @@ -781,7 +763,8 @@ func (b *Broker) cancelIsAuthenticatedUnlocked(_ context.Context, sessionID stri

// UserPreCheck checks if the user is known to the broker.
func (b *Broker) UserPreCheck(ctx context.Context, username string) (string, error) {
if strings.HasPrefix(username, "user-integration-pre-check") {
if strings.HasPrefix(username, "user-") && strings.Contains(username, "integration") &&
strings.Contains(username, fmt.Sprintf("-%s-", UserIntegrationPreCheckValue)) {
return userInfoFromName(username), nil
}
if _, exists := exampleUsers[username]; !exists {
Expand Down Expand Up @@ -904,6 +887,10 @@ func userInfoFromName(name string) string {
user.Groups = append(user.Groups, groupJSONInfo{Name: "sudo", UGID: ""}, groupJSONInfo{Name: "admin", UGID: ""})
}

if strings.HasPrefix(name, "user-local-groups-integration") {
user.Groups = append(user.Groups, groupJSONInfo{Name: "localgroup", UGID: ""})
}

// only used for tests, we can ignore the template execution error as the returned data will be failing.
var buf bytes.Buffer
_ = template.Must(template.New("").Parse(`{
Expand Down
50 changes: 50 additions & 0 deletions examplebroker/users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package examplebroker

import "sync"

type userInfoBroker struct {
Password string
}

var (
exampleUsersMu = sync.RWMutex{}
exampleUsers = map[string]userInfoBroker{
"user1": {Password: "goodpass"},
"user2": {Password: "goodpass"},
"user3": {Password: "goodpass"},
"user-mfa": {Password: "goodpass"},
"user-mfa-with-reset": {Password: "goodpass"},
"user-needs-reset": {Password: "goodpass"},
"user-needs-reset2": {Password: "goodpass"},
"user-can-reset": {Password: "goodpass"},
"user-can-reset2": {Password: "goodpass"},
"user-local-groups": {Password: "goodpass"},
"user-pre-check": {Password: "goodpass"},
"user-sudo": {Password: "goodpass"},
}
)

const (
// UserIntegrationPrefix is the prefix for an user for integration tests.
UserIntegrationPrefix = "user-integration-"
// UserIntegrationMfaPrefix is the prefix for an mfa user for integration tests.
UserIntegrationMfaPrefix = "user-mfa-integration-"
// UserIntegrationMfaNeedsResetPrefix is the prefix for an mfa-needs-reset user for integration tests.
UserIntegrationMfaNeedsResetPrefix = "user-mfa-needs-reset-integration-"
// UserIntegrationMfaWithResetPrefix is the prefix for an mfa-with-reset user for integration tests.
UserIntegrationMfaWithResetPrefix = "user-mfa-with-reset-integration-"
// UserIntegrationNeedsResetPrefix is the prefix for a needs-reset user for integration tests.
UserIntegrationNeedsResetPrefix = "user-needs-reset-integration-"
// UserIntegrationCanResetPrefix is the prefix for a can-reset user for integration tests.
UserIntegrationCanResetPrefix = "user-can-reset-integration-"
// UserIntegrationLocalGroupsPrefix is the prefix for a local-groups user for integration tests.
UserIntegrationLocalGroupsPrefix = "user-local-groups-integration-"
// UserIntegrationQrcodeStaticPrefix is the prefix for a static qrcode user for integration tests.
UserIntegrationQrcodeStaticPrefix = "user-integration-qrcode-static-"
// UserIntegrationPreCheckValue is the value for a pre-check user for integration tests.
UserIntegrationPreCheckValue = "pre-check"
// UserIntegrationPreCheckPrefix is the prefix for a pre-check user for integration tests.
UserIntegrationPreCheckPrefix = UserIntegrationPrefix + UserIntegrationPreCheckValue + "-"
// UserIntegrationUnexistent is an unexistent user leading to a non-existent user error.
UserIntegrationUnexistent = "user-unexistent"
)
1 change: 1 addition & 0 deletions internal/brokers/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func (b Broker) cancelIsAuthenticated(ctx context.Context, sessionID string) {

// UserPreCheck calls the broker corresponding method.
func (b Broker) UserPreCheck(ctx context.Context, username string) (userinfo string, err error) {
log.Debugf(context.TODO(), "Pre-Checking user %q", username)
return b.brokerer.UserPreCheck(ctx, username)
}

Expand Down
9 changes: 8 additions & 1 deletion internal/brokers/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,17 @@ func NewManager(ctx context.Context, brokersConfPath string, configuredBrokers [
brokersConfPathWithExample, cleanup, err := useExampleBrokers()
if err != nil {
return nil, err
} else if brokersConfPathWithExample != "" {
}
if brokersConfPathWithExample != "" {
brokersConfPath = brokersConfPathWithExample
}

defer func() {
if err != nil && cleanup != nil {
cleanup()
}
}()

// Connect to the system bus
// Don't call dbus.SystemBus which caches globally system dbus (issues in tests)
bus, err := dbus.ConnectSystemBus()
Expand Down
2 changes: 1 addition & 1 deletion internal/testutils/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func RunDaemon(ctx context.Context, t *testing.T, execPath string, args ...Daemo
// Socket name has a maximum size, so we can't use t.TempDir() directly.
tempDir, err := os.MkdirTemp("", "authd-daemon4tests")
require.NoError(t, err, "Setup: failed to create temp dir for tests")
t.Cleanup(func() { os.RemoveAll(tempDir) })

if opts.cachePath == "" {
opts.cachePath = filepath.Join(tempDir, "cache")
Expand Down Expand Up @@ -102,6 +101,7 @@ paths:

// This is the function that is called by CommandContext when the context is cancelled.
cmd.Cancel = func() error {
defer os.RemoveAll(tempDir)
return cmd.Process.Signal(os.Signal(syscall.SIGTERM))
}

Expand Down
51 changes: 35 additions & 16 deletions pam/integration-tests/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/msteinert/pam/v2"
"github.com/stretchr/testify/require"
"github.com/ubuntu/authd/examplebroker"
"github.com/ubuntu/authd/internal/testutils"
localgroupstestutils "github.com/ubuntu/authd/internal/users/localgroups/testutils"
"github.com/ubuntu/authd/pam/internal/pam_test"
Expand All @@ -24,9 +25,6 @@ func TestCLIAuthenticate(t *testing.T) {
const socketPathEnv = "AUTHD_TESTS_CLI_AUTHENTICATE_TESTS_SOCK"
tapeCommand := fmt.Sprintf("./pam_authd login socket=${%s}", socketPathEnv)

defaultGPasswdOutput, groupsFile := prepareGPasswdFiles(t)
defaultSocketPath := runAuthd(t, defaultGPasswdOutput, groupsFile, true)

tests := map[string]struct {
tape string
tapeSettings []tapeSetting
Expand All @@ -39,8 +37,10 @@ func TestCLIAuthenticate(t *testing.T) {
tape: "simple_auth",
},
"Authenticate user successfully with preset user": {
tape: "simple_auth_with_preset_user",
clientOptions: clientOptions{PamUser: "user-integration-simple-preset"},
tape: "simple_auth_with_preset_user",
clientOptions: clientOptions{
PamUser: examplebroker.UserIntegrationPrefix + "simple-preset",
},
},
"Authenticate user successfully after trying empty user": {
tape: "simple_auth_empty_user",
Expand All @@ -52,30 +52,32 @@ func TestCLIAuthenticate(t *testing.T) {
tape: "form_with_button",
},
"Authenticate user with qr code": {
tape: "qr_code",
clientOptions: clientOptions{PamUser: "user-integration-qr-code"},
tape: "qr_code",
clientOptions: clientOptions{
PamUser: examplebroker.UserIntegrationPrefix + "qr-code",
},
},
"Authenticate user with qr code in a TTY": {
tape: "qr_code",
tapeSettings: []tapeSetting{{vhsHeight, 650}},
clientOptions: clientOptions{
PamUser: "user-integration-qr-code-tty",
PamUser: examplebroker.UserIntegrationPrefix + "qr-code-tty",
Term: "linux",
},
},
"Authenticate user with qr code in a TTY session": {
tape: "qr_code",
tapeSettings: []tapeSetting{{vhsHeight, 650}},
clientOptions: clientOptions{
PamUser: "user-integration-qr-code-tty-session",
PamUser: examplebroker.UserIntegrationPrefix + "qr-code-tty-session",
Term: "xterm-256color", SessionType: "tty",
},
},
"Authenticate user with qr code in screen": {
tape: "qr_code",
tapeSettings: []tapeSetting{{vhsHeight, 650}},
clientOptions: clientOptions{
PamUser: "user-integration-qr-code-screen",
PamUser: examplebroker.UserIntegrationPrefix + "qr-code-screen",
Term: "screen",
},
},
Expand Down Expand Up @@ -121,8 +123,10 @@ func TestCLIAuthenticate(t *testing.T) {
},

"Prevent user from switching username": {
tape: "switch_preset_username",
clientOptions: clientOptions{PamUser: "user-integration-pam-preset"},
tape: "switch_preset_username",
clientOptions: clientOptions{
PamUser: examplebroker.UserIntegrationPrefix + "pam-preset",
},
},

"Deny authentication if current user is not considered as root": {
Expand Down Expand Up @@ -155,8 +159,7 @@ func TestCLIAuthenticate(t *testing.T) {
filepath.Join(outDir, "pam_authd"))
require.NoError(t, err, "Setup: symlinking the pam client")

socketPath := defaultSocketPath
gpasswdOutput := defaultGPasswdOutput
var socketPath, gpasswdOutput string
if tc.wantLocalGroups || tc.currentUserNotRoot {
// For the local groups tests we need to run authd again so that it has
// special environment that generates a fake gpasswd output for us to test.
Expand All @@ -165,6 +168,8 @@ func TestCLIAuthenticate(t *testing.T) {
var groupsFile string
gpasswdOutput, groupsFile = prepareGPasswdFiles(t)
socketPath = runAuthd(t, gpasswdOutput, groupsFile, !tc.currentUserNotRoot)
} else {
socketPath, gpasswdOutput = sharedAuthd(t)
}

td := newTapeData(tc.tape, tc.tapeSettings...)
Expand All @@ -190,7 +195,6 @@ func TestCLIChangeAuthTok(t *testing.T) {
const socketPathEnv = "AUTHD_TESTS_CLI_AUTHTOK_TESTS_SOCK"
const tapeBaseCommand = "./pam_authd %s socket=${%s}"
tapeCommand := fmt.Sprintf(tapeBaseCommand, "passwd", socketPathEnv)
defaultSocketPath := runAuthd(t, os.DevNull, os.DevNull, true)

tests := map[string]struct {
tape string
Expand All @@ -207,6 +211,9 @@ func TestCLIChangeAuthTok(t *testing.T) {
},
"Change passwd after MFA auth": {
tape: "passwd_mfa",
tapeVariables: map[string]string{
vhsTapeUserVariable: examplebroker.UserIntegrationMfaPrefix + "cli-passwd",
},
},

"Retry if new password is rejected by broker": {
Expand All @@ -227,6 +234,9 @@ func TestCLIChangeAuthTok(t *testing.T) {
},
"Prevent change password if user does not exist": {
tape: "passwd_unexistent_user",
tapeVariables: map[string]string{
vhsTapeUserVariable: examplebroker.UserIntegrationUnexistent,
},
},
"Prevent change password if current user is not root as can't authenticate": {
tape: "passwd_not_root",
Expand All @@ -244,11 +254,20 @@ func TestCLIChangeAuthTok(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

socketPath := defaultSocketPath
var socketPath string
if tc.currentUserNotRoot {
// For the not-root tests authd has to run in a more restricted way.
// In the other cases this is not needed, so we can just use a shared authd.
socketPath = runAuthd(t, os.DevNull, os.DevNull, false)
} else {
socketPath, _ = sharedAuthd(t)
}

if _, ok := tc.tapeVariables[vhsTapeUserVariable]; !ok && !tc.currentUserNotRoot {
if tc.tapeVariables == nil {
tc.tapeVariables = make(map[string]string)
}
tc.tapeVariables[vhsTapeUserVariable] = vhsTestUserName(t, "cli-passwd")
}

td := newTapeData(tc.tape, tc.tapeSettings...)
Expand Down
Loading
Loading