From 8c30b5946a89f6aab8641d0d11748568c715a39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 25 Sep 2023 23:13:34 +0200 Subject: [PATCH] pam-moduler: Add test that generates a new debug module and verify it works We mimic what pam_debug.so does by default, by implementing a similar module fully in go, generated using pam-moduler. This requires various utilities to generate the module and run the tests that are in a separate internal modules so that it can be shared between multiple implementations --- .gitignore | 2 + .../tests/debug-module/debug-module.go | 119 ++++++++++++ .../tests/debug-module/debug-module_test.go | 120 ++++++++++++ .../tests/debug-module/pam_module.go | 96 ++++++++++ .../tests/internal/utils/base-module.go | 38 ++++ .../tests/internal/utils/base-module_test.go | 35 ++++ .../tests/internal/utils/test-setup.go | 135 +++++++++++++ .../tests/internal/utils/test-setup_test.go | 180 ++++++++++++++++++ .../tests/internal/utils/test-utils.go | 99 ++++++++++ 9 files changed, 824 insertions(+) create mode 100644 cmd/pam-moduler/tests/debug-module/debug-module.go create mode 100644 cmd/pam-moduler/tests/debug-module/debug-module_test.go create mode 100644 cmd/pam-moduler/tests/debug-module/pam_module.go create mode 100644 cmd/pam-moduler/tests/internal/utils/base-module.go create mode 100644 cmd/pam-moduler/tests/internal/utils/base-module_test.go create mode 100644 cmd/pam-moduler/tests/internal/utils/test-setup.go create mode 100644 cmd/pam-moduler/tests/internal/utils/test-setup_test.go create mode 100644 cmd/pam-moduler/tests/internal/utils/test-utils.go diff --git a/.gitignore b/.gitignore index a2f238d..0700a89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ coverage.out example-module/*.so example-module/*.h +cmd/pam-moduler/tests/*/*.so +cmd/pam-moduler/tests/*/*.h diff --git a/cmd/pam-moduler/tests/debug-module/debug-module.go b/cmd/pam-moduler/tests/debug-module/debug-module.go new file mode 100644 index 0000000..843b329 --- /dev/null +++ b/cmd/pam-moduler/tests/debug-module/debug-module.go @@ -0,0 +1,119 @@ +//go:generate go run github.com/msteinert/pam/v2/cmd/pam-moduler -libname "pam_godebug.so" +//go:generate go generate --skip="pam_module.go" + +// This is a similar implementation of pam_debug.so + +// Package main is the package for the debug PAM module library +package main + +import ( + "fmt" + "strings" + + "github.com/msteinert/pam/v2" + "github.com/msteinert/pam/v2/cmd/pam-moduler/tests/internal/utils" +) + +var pamModuleHandler pam.ModuleHandler = &DebugModule{} +var _ = pamModuleHandler + +var moduleArgsRetTypes = map[string]error{ + "success": nil, + "open_err": pam.ErrOpen, + "symbol_err": pam.ErrSymbol, + "service_err": pam.ErrService, + "system_err": pam.ErrSystem, + "buf_err": pam.ErrBuf, + "perm_denied": pam.ErrPermDenied, + "auth_err": pam.ErrAuth, + "cred_insufficient": pam.ErrCredInsufficient, + "authinfo_unavail": pam.ErrAuthinfoUnavail, + "user_unknown": pam.ErrUserUnknown, + "maxtries": pam.ErrMaxtries, + "new_authtok_reqd": pam.ErrNewAuthtokReqd, + "acct_expired": pam.ErrAcctExpired, + "session_err": pam.ErrSession, + "cred_unavail": pam.ErrCredUnavail, + "cred_expired": pam.ErrCredExpired, + "cred_err": pam.ErrCred, + "no_module_data": pam.ErrNoModuleData, + "conv_err": pam.ErrConv, + "authtok_err": pam.ErrAuthtok, + "authtok_recover_err": pam.ErrAuthtokRecovery, + "authtok_lock_busy": pam.ErrAuthtokLockBusy, + "authtok_disable_aging": pam.ErrAuthtokDisableAging, + "try_again": pam.ErrTryAgain, + "ignore": pam.ErrIgnore, + "abort": pam.ErrAbort, + "authtok_expired": pam.ErrAuthtokExpired, + "module_unknown": pam.ErrModuleUnknown, + "bad_item": pam.ErrBadItem, + "conv_again": pam.ErrConvAgain, + "incomplete": pam.ErrIncomplete, +} + +var debugModuleArgs = []string{"auth", "cred", "acct", "prechauthtok", + "chauthtok", "open_session", "close_session"} + +// DebugModule is the PAM module structure. +type DebugModule struct { + utils.BaseModule +} + +func (dm *DebugModule) getReturnType(args []string, key string) error { + var value string + for _, a := range args { + v, found := strings.CutPrefix(a, key+"=") + if found { + value = v + } + } + + if value == "" { + return fmt.Errorf("Value not found") + } + + if ret, found := moduleArgsRetTypes[value]; found { + return ret + } + return fmt.Errorf("Parameter %s not known", value) +} + +func (dm *DebugModule) handleCall(args []string, action string) error { + err := dm.getReturnType(args, action) + if err == nil { + return nil + } + + return fmt.Errorf("error %w", err) +} + +// AcctMgmt is a PAM handler. +func (dm *DebugModule) AcctMgmt(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "acct") +} + +// Authenticate is a PAM handler. +func (dm *DebugModule) Authenticate(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "auth") +} + +// ChangeAuthTok is a PAM handler. +func (dm *DebugModule) ChangeAuthTok(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "chauthtok") +} + +// OpenSession is a PAM handler. +func (dm *DebugModule) OpenSession(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "open_session") +} + +// CloseSession is a PAM handler. +func (dm *DebugModule) CloseSession(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "close_session") +} + +// SetCred is a PAM handler. +func (dm *DebugModule) SetCred(mt pam.ModuleTransaction, flags pam.Flags, args []string) error { + return dm.handleCall(args, "cred") +} diff --git a/cmd/pam-moduler/tests/debug-module/debug-module_test.go b/cmd/pam-moduler/tests/debug-module/debug-module_test.go new file mode 100644 index 0000000..8a5d58d --- /dev/null +++ b/cmd/pam-moduler/tests/debug-module/debug-module_test.go @@ -0,0 +1,120 @@ +package main + +import ( + "errors" + "fmt" + "testing" + + "github.com/msteinert/pam/v2" + "github.com/msteinert/pam/v2/cmd/pam-moduler/tests/internal/utils" +) + +func Test_DebugModule_ActionStatus(t *testing.T) { + t.Parallel() + + module := DebugModule{} + + for ret, expected := range moduleArgsRetTypes { + ret := ret + expected := expected + for actionName, action := range utils.Actions { + actionName := actionName + action := action + t.Run(fmt.Sprintf("%s %s", ret, actionName), func(t *testing.T) { + t.Parallel() + moduleArgs := make([]string, 0) + for _, a := range debugModuleArgs { + moduleArgs = append(moduleArgs, fmt.Sprintf("%s=%s", a, ret)) + } + + mt := pam.ModuleTransactionInvoker(nil) + var err error + + switch action { + case utils.Account: + err = module.AcctMgmt(mt, 0, moduleArgs) + case utils.Auth: + err = module.Authenticate(mt, 0, moduleArgs) + case utils.Password: + err = module.ChangeAuthTok(mt, 0, moduleArgs) + case utils.Session: + err = module.OpenSession(mt, 0, moduleArgs) + } + + if !errors.Is(err, expected) { + t.Fatalf("error #unexpected %#v vs %#v", expected, err) + } + }) + } + } +} + +func Test_DebugModuleTransaction_ActionStatus(t *testing.T) { + t.Parallel() + if !pam.CheckPamHasStartConfdir() { + t.Skip("this requires PAM with Conf dir support") + } + + ts := utils.NewTestSetup(t, utils.WithWorkDir()) + modulePath := ts.GenerateModule(".", "pam_godebug.so") + + for ret, expected := range moduleArgsRetTypes { + ret := ret + expected := expected + for actionName, action := range utils.Actions { + ret := ret + expected := expected + actionName := actionName + action := action + t.Run(fmt.Sprintf("%s %s", ret, actionName), func(t *testing.T) { + t.Parallel() + serviceName := ret + "-" + actionName + moduleArgs := make([]string, 0) + for _, a := range debugModuleArgs { + moduleArgs = append(moduleArgs, fmt.Sprintf("%s=%s", a, ret)) + } + control := utils.Requisite + fallbackModule := utils.Permit + if ret == "success" { + fallbackModule = utils.Deny + control = utils.Sufficient + } + ts.CreateService(serviceName, []utils.ServiceLine{ + {Action: action, Control: control, Module: modulePath, Args: moduleArgs}, + {Action: action, Control: control, Module: fallbackModule.String(), Args: []string{}}, + }) + + tx, err := pam.StartConfDir(serviceName, "user", nil, ts.WorkDir()) + if err != nil { + t.Fatalf("start #error: %v", err) + } + defer func() { + err := tx.End() + if err != nil { + t.Fatalf("end #error: %v", err) + } + }() + + switch action { + case utils.Account: + err = tx.AcctMgmt(pam.Silent) + case utils.Auth: + err = tx.Authenticate(pam.Silent) + case utils.Password: + err = tx.ChangeAuthTok(pam.Silent) + case utils.Session: + err = tx.OpenSession(pam.Silent) + } + + if errors.Is(expected, pam.ErrIgnore) { + // Ignore can't be returned + expected = nil + } + + if !errors.Is(err, expected) { + t.Fatalf("error #unexpected %#v vs %#v", expected, err) + } + }) + } + } +} diff --git a/cmd/pam-moduler/tests/debug-module/pam_module.go b/cmd/pam-moduler/tests/debug-module/pam_module.go new file mode 100644 index 0000000..837842e --- /dev/null +++ b/cmd/pam-moduler/tests/debug-module/pam_module.go @@ -0,0 +1,96 @@ +// Code generated by "pam-moduler -libname pam_godebug.so"; DO NOT EDIT. + +//go:generate go build "-ldflags=-extldflags -Wl,-soname,pam_godebug.so" -buildmode=c-shared -o pam_godebug.so -tags go_pam_module + +// Package main is the package for the PAM module library. +package main + +/* +#cgo LDFLAGS: -lpam -fPIC +#include + +typedef const char _const_char_t; +*/ +import "C" + +import ( + "errors" + "fmt" + "github.com/msteinert/pam/v2" + "os" + "unsafe" +) + +// Do a typecheck at compile time +var _ pam.ModuleHandler = pamModuleHandler + +// sliceFromArgv returns a slice of strings given to the PAM module. +func sliceFromArgv(argc C.int, argv **C._const_char_t) []string { + r := make([]string, 0, argc) + for _, s := range unsafe.Slice(argv, argc) { + r = append(r, C.GoString(s)) + } + return r +} + +// handlePamCall is the function that translates C pam requests to Go. +func handlePamCall(pamh *C.pam_handle_t, flags C.int, argc C.int, + argv **C._const_char_t, moduleFunc pam.ModuleHandlerFunc) C.int { + if pamModuleHandler == nil { + return C.int(pam.ErrNoModuleData) + } + + if moduleFunc == nil { + return C.int(pam.ErrIgnore) + } + + mt := pam.NewModuleTransactionInvoker(pam.NativeHandle(pamh)) + err := mt.InvokeHandler(moduleFunc, pam.Flags(flags), + sliceFromArgv(argc, argv)) + if err == nil { + return 0 + } + + if (pam.Flags(flags)&pam.Silent) == 0 && !errors.Is(err, pam.ErrIgnore) { + fmt.Fprintf(os.Stderr, "module returned error: %v\n", err) + } + + var pamErr pam.Error + if errors.As(err, &pamErr) { + return C.int(pamErr) + } + + return C.int(pam.ErrSystem) +} + +//export pam_sm_authenticate +func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.Authenticate) +} + +//export pam_sm_setcred +func pam_sm_setcred(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.SetCred) +} + +//export pam_sm_acct_mgmt +func pam_sm_acct_mgmt(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.AcctMgmt) +} + +//export pam_sm_open_session +func pam_sm_open_session(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.OpenSession) +} + +//export pam_sm_close_session +func pam_sm_close_session(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.CloseSession) +} + +//export pam_sm_chauthtok +func pam_sm_chauthtok(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C._const_char_t) C.int { + return handlePamCall(pamh, flags, argc, argv, pamModuleHandler.ChangeAuthTok) +} + +func main() {} diff --git a/cmd/pam-moduler/tests/internal/utils/base-module.go b/cmd/pam-moduler/tests/internal/utils/base-module.go new file mode 100644 index 0000000..494b077 --- /dev/null +++ b/cmd/pam-moduler/tests/internal/utils/base-module.go @@ -0,0 +1,38 @@ +package utils + +import "github.com/msteinert/pam/v2" + +// BaseModule is the type for a base PAM module. +type BaseModule struct{} + +// AcctMgmt is the handler function for PAM AcctMgmt. +func (h *BaseModule) AcctMgmt(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +// Authenticate is the handler function for PAM Authenticate. +func (h *BaseModule) Authenticate(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +// ChangeAuthTok is the handler function for PAM ChangeAuthTok. +func (h *BaseModule) ChangeAuthTok(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +// OpenSession is the handler function for PAM OpenSession. +func (h *BaseModule) OpenSession(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +// CloseSession is the handler function for PAM CloseSession. +func (h *BaseModule) CloseSession(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +// SetCred is the handler function for PAM SetCred. +func (h *BaseModule) SetCred(pam.ModuleTransaction, pam.Flags, []string) error { + return nil +} + +var _ pam.ModuleHandler = &BaseModule{} diff --git a/cmd/pam-moduler/tests/internal/utils/base-module_test.go b/cmd/pam-moduler/tests/internal/utils/base-module_test.go new file mode 100644 index 0000000..461d90f --- /dev/null +++ b/cmd/pam-moduler/tests/internal/utils/base-module_test.go @@ -0,0 +1,35 @@ +package utils + +import ( + "testing" + + "github.com/msteinert/pam/v2" +) + +func TestMain(t *testing.T) { + bm := BaseModule{} + + if bm.AcctMgmt(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } + + if bm.Authenticate(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } + + if bm.ChangeAuthTok(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } + + if bm.OpenSession(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } + + if bm.CloseSession(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } + + if bm.SetCred(nil, pam.Flags(0), nil) != nil { + t.Fatalf("Unexpected non-nil value") + } +} diff --git a/cmd/pam-moduler/tests/internal/utils/test-setup.go b/cmd/pam-moduler/tests/internal/utils/test-setup.go new file mode 100644 index 0000000..77fc71d --- /dev/null +++ b/cmd/pam-moduler/tests/internal/utils/test-setup.go @@ -0,0 +1,135 @@ +// Package utils contains the internal test utils +package utils + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/msteinert/pam/v2" +) + +// TestSetup is an utility type for having a playground for test PAM modules. +type TestSetup struct { + t *testing.T + workDir string +} + +type withWorkDir struct{} + +//nolint:revive +func WithWorkDir() withWorkDir { + return withWorkDir{} +} + +// NewTestSetup creates a new TestSetup. +func NewTestSetup(t *testing.T, args ...interface{}) *TestSetup { + t.Helper() + + ts := &TestSetup{t: t} + for _, arg := range args { + switch argType := arg.(type) { + case withWorkDir: + ts.ensureWorkDir() + default: + t.Fatalf("Unknown parameter of type %v", argType) + } + } + + return ts +} + +// CreateTemporaryDir creates a temporary directory with provided basename. +func (ts *TestSetup) CreateTemporaryDir(basename string) string { + tmpDir, err := os.MkdirTemp(os.TempDir(), basename) + if err != nil { + ts.t.Fatalf("can't create service path %v", err) + } + + ts.t.Cleanup(func() { os.RemoveAll(tmpDir) }) + return tmpDir +} + +func (ts *TestSetup) ensureWorkDir() string { + if ts.workDir != "" { + return ts.workDir + } + + ts.workDir = ts.CreateTemporaryDir("go-pam-*") + return ts.workDir +} + +// WorkDir returns the test setup work directory. +func (ts TestSetup) WorkDir() string { + return ts.workDir +} + +// GenerateModule generates a PAM module for the provided path and name. +func (ts *TestSetup) GenerateModule(testModulePath string, moduleName string) string { + cmd := exec.Command("go", "generate", "-C", testModulePath) + out, err := cmd.CombinedOutput() + if err != nil { + ts.t.Fatalf("can't build pam module %v: %s", err, out) + } + + builtFile := filepath.Join(cmd.Dir, testModulePath, moduleName) + modulePath := filepath.Join(ts.ensureWorkDir(), filepath.Base(builtFile)) + if err = os.Rename(builtFile, modulePath); err != nil { + ts.t.Fatalf("can't move module: %v", err) + os.Remove(builtFile) + } + + return modulePath +} + +func (ts TestSetup) currentFile(skip int) string { + _, currentFile, _, ok := runtime.Caller(skip) + if !ok { + ts.t.Fatalf("can't get current binary path") + } + return currentFile +} + +// GetCurrentFile returns the current file path. +func (ts TestSetup) GetCurrentFile() string { + // This is a library so we care about the caller location + return ts.currentFile(2) +} + +// GetCurrentFileDir returns the current file directory. +func (ts TestSetup) GetCurrentFileDir() string { + return filepath.Dir(ts.currentFile(2)) +} + +// GenerateModuleDefault generates a default module. +func (ts *TestSetup) GenerateModuleDefault(testModulePath string) string { + return ts.GenerateModule(testModulePath, "pam_go.so") +} + +// CreateService creates a service file. +func (ts *TestSetup) CreateService(serviceName string, services []ServiceLine) string { + if !pam.CheckPamHasStartConfdir() { + ts.t.Skip("PAM has no support for custom service paths") + return "" + } + + serviceName = strings.ToLower(serviceName) + serviceFile := filepath.Join(ts.ensureWorkDir(), serviceName) + var contents = []string{} + + for _, s := range services { + contents = append(contents, strings.TrimRight(strings.Join([]string{ + s.Action.String(), s.Control.String(), s.Module, strings.Join(s.Args, " "), + }, "\t"), "\t")) + } + + if err := os.WriteFile(serviceFile, + []byte(strings.Join(contents, "\n")), 0600); err != nil { + ts.t.Fatalf("can't create service file %v: %v", serviceFile, err) + } + + return serviceFile +} diff --git a/cmd/pam-moduler/tests/internal/utils/test-setup_test.go b/cmd/pam-moduler/tests/internal/utils/test-setup_test.go new file mode 100644 index 0000000..f8a17a6 --- /dev/null +++ b/cmd/pam-moduler/tests/internal/utils/test-setup_test.go @@ -0,0 +1,180 @@ +package utils + +import ( + "fmt" + "math/rand" + "os" + "path/filepath" + "strings" + "testing" +) + +func isDir(t *testing.T, path string) bool { + t.Helper() + if file, err := os.Open(path); err == nil { + if fileInfo, err := file.Stat(); err == nil { + return fileInfo.IsDir() + } + t.Fatalf("error: %v", err) + } else { + t.Fatalf("error: %v", err) + } + return false +} + +func Test_CreateTemporaryDir(t *testing.T) { + t.Parallel() + ts := NewTestSetup(t) + dir := ts.CreateTemporaryDir("") + if !isDir(t, dir) { + t.Fatalf("%s not a directory", dir) + } + + dir = ts.CreateTemporaryDir("foo-prefix-*") + if !isDir(t, dir) { + t.Fatalf("%s not a directory", dir) + } +} + +func Test_TestSetupWithWorkDir(t *testing.T) { + t.Parallel() + ts := NewTestSetup(t, WithWorkDir()) + if !isDir(t, ts.WorkDir()) { + t.Fatalf("%s not a directory", ts.WorkDir()) + } +} + +func Test_CreateService(t *testing.T) { + t.Parallel() + ts := NewTestSetup(t) + + tests := map[string]struct { + services []ServiceLine + expectedContent string + }{ + "empty": {}, + "CApital-Empty": {}, + "auth-sufficient-permit": { + services: []ServiceLine{ + {Auth, Sufficient, Permit.String(), []string{}}, + }, + expectedContent: "auth sufficient pam_permit.so", + }, + "auth-sufficient-permit-args": { + services: []ServiceLine{ + {Auth, Required, Deny.String(), []string{"a b c [d e]"}}, + }, + expectedContent: "auth required pam_deny.so a b c [d e]", + }, + "complete-custom": { + services: []ServiceLine{ + {Account, Required, "pam_account_module.so", []string{"a", "b", "c", "[d e]"}}, + {Account, Required, Deny.String(), []string{}}, + {Auth, Requisite, "pam_auth_module.so", []string{}}, + {Auth, Requisite, Deny.String(), []string{}}, + {Password, Sufficient, "pam_password_module.so", []string{"arg"}}, + {Password, Sufficient, Deny.String(), []string{}}, + {Session, Optional, "pam_session_module.so", []string{""}}, + {Session, Optional, Deny.String(), []string{}}, + }, + expectedContent: `account required pam_account_module.so a b c [d e] +account required pam_deny.so +auth requisite pam_auth_module.so +auth requisite pam_deny.so +password sufficient pam_password_module.so arg +password sufficient pam_deny.so +session optional pam_session_module.so +session optional pam_deny.so`, + }, + } + + for name, tc := range tests { + tc := tc + name := name + t.Run(name, func(t *testing.T) { + t.Parallel() + service := ts.CreateService(name, tc.services) + + if filepath.Base(service) != strings.ToLower(name) { + t.Fatalf("Invalid service name %s", service) + } + + if bytes, err := os.ReadFile(service); err != nil { + t.Fatalf("Failed reading %s: %v", service, err) + } else { + if string(bytes) != tc.expectedContent { + t.Fatalf("Unexpected file content:\n%s\n---\n%s", + tc.expectedContent, string(bytes)) + } + } + }) + } +} + +func Test_GenerateModule(t *testing.T) { + ts := NewTestSetup(t) + dir := ts.CreateTemporaryDir("") + if !isDir(t, dir) { + t.Fatalf("%s not a directory", dir) + } + + f, err := os.Create(filepath.Join(dir, "test-generate.go")) + if err != nil { + t.Fatalf("can't create file %v", err) + } + defer f.Close() + + randomName := "" + for i := 0; i < 10; i++ { + // #nosec:G404 - it's a test, we don't care. + randomName += string(byte('a' + rand.Intn('z'-'a'))) + } + + wantFile := randomName + ".so" + fmt.Fprintf(f, `//go:generate touch %s +package generate_file +`, wantFile) + + mod, err := os.Create(filepath.Join(dir, "go.mod")) + if err != nil { + t.Fatalf("can't create file %v", err) + } + defer mod.Close() + + fmt.Fprintf(mod, `module example.com/greetings + +go 1.20 +`) + + fakeModule := ts.GenerateModule(dir, wantFile) + if _, err := os.Stat(fakeModule); err != nil { + t.Fatalf("module not generated %v", err) + } + + fmt.Fprint(f, `//go:generate touch pam_go.so +package generate_file +`, wantFile) + + fakeModule = ts.GenerateModuleDefault(dir) + if _, err := os.Stat(fakeModule); err != nil { + t.Fatalf("module not generated %v", err) + } +} + +func Test_GetCurrentFileDir(t *testing.T) { + t.Parallel() + + ts := NewTestSetup(t) + if !strings.HasSuffix(ts.GetCurrentFileDir(), filepath.Join("internal", "utils")) { + t.Fatalf("unexpected file %v", ts.GetCurrentFileDir()) + } +} + +func Test_GetCurrentFile(t *testing.T) { + t.Parallel() + + ts := NewTestSetup(t) + if !strings.HasSuffix(ts.GetCurrentFile(), filepath.Join("utils", "test-setup_test.go")) { + t.Fatalf("unexpected file %v", ts.GetCurrentFile()) + } +} diff --git a/cmd/pam-moduler/tests/internal/utils/test-utils.go b/cmd/pam-moduler/tests/internal/utils/test-utils.go new file mode 100644 index 0000000..556f160 --- /dev/null +++ b/cmd/pam-moduler/tests/internal/utils/test-utils.go @@ -0,0 +1,99 @@ +// Package utils contains the internal test utils +package utils + +// Action represents a PAM action to perform. +type Action int + +const ( + // Account is the account. + Account Action = iota + 1 + // Auth is the auth. + Auth + // Password is the password. + Password + // Session is the session. + Session +) + +func (a Action) String() string { + switch a { + case Account: + return "account" + case Auth: + return "auth" + case Password: + return "password" + case Session: + return "session" + default: + return "" + } +} + +// Actions is a map with all the available Actions by their name. +var Actions = map[string]Action{ + Account.String(): Account, + Auth.String(): Auth, + Password.String(): Password, + Session.String(): Session, +} + +// Control represents how a PAM module should controlled in PAM service file. +type Control int + +const ( + // Required implies that the module is required. + Required Control = iota + 1 + // Requisite implies that the module is requisite. + Requisite + // Sufficient implies that the module is sufficient. + Sufficient + // Optional implies that the module is optional. + Optional +) + +func (c Control) String() string { + switch c { + case Required: + return "required" + case Requisite: + return "requisite" + case Sufficient: + return "sufficient" + case Optional: + return "optional" + default: + return "" + } +} + +// ServiceLine is the representation of a PAM module service file line. +type ServiceLine struct { + Action Action + Control Control + Module string + Args []string +} + +// FallBackModule is a type to represent the module that should be used as fallback. +type FallBackModule int + +const ( + // NoFallback add no fallback module. + NoFallback FallBackModule = iota + 1 + // Permit uses a module that always permits. + Permit + // Deny uses a module that always denys. + Deny +) + +func (a FallBackModule) String() string { + switch a { + case Permit: + return "pam_permit.so" + case Deny: + return "pam_deny.so" + default: + return "" + } +}