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
This commit is contained in:
119
cmd/pam-moduler/tests/debug-module/debug-module.go
Normal file
119
cmd/pam-moduler/tests/debug-module/debug-module.go
Normal file
@@ -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")
|
||||
}
|
||||
120
cmd/pam-moduler/tests/debug-module/debug-module_test.go
Normal file
120
cmd/pam-moduler/tests/debug-module/debug-module_test.go
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
96
cmd/pam-moduler/tests/debug-module/pam_module.go
Normal file
96
cmd/pam-moduler/tests/debug-module/pam_module.go
Normal file
@@ -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 <security/pam_modules.h>
|
||||
|
||||
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() {}
|
||||
Reference in New Issue
Block a user