Files
llmgw/internal/slurm/pam_test.go
T
Ilker ManapandClaude Opus 5.5 4b4306bb6a
CI / test (push) Successful in 9m41s
Slurm users reach a node only through a job of theirs
llmgw's Slurm users log in to the controller freely and to another Slurm
machine only while a job of theirs runs there: the agent installs
pam_slurm_adopt and puts it in sshd's account stack for the users it made
alone, so root and the machine's own users are never locked out. Their
session is put in the job's extern step (PrologFlags=Contain), and
pam_systemd, which would move it out, is skipped for them by taking the
session file into sshd's stack with a guard in front of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 20:30:49 +01:00

114 lines
4.1 KiB
Go

package slurm
import (
"errors"
"strings"
"testing"
)
const ubuntuSSHD = `# PAM configuration for the Secure Shell service
@include common-auth
account required pam_nologin.so
@include common-account
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
session required pam_loginuid.so
@include common-session
session required pam_limits.so
@include common-password
`
const ubuntuSession = `# here are the per-package modules (the "Primary" block)
session [default=1] pam_permit.so
session requisite pam_deny.so
session required pam_permit.so
session optional pam_umask.so
session required pam_unix.so
session optional pam_systemd.so
# end of pam-auth-update config
`
const redhatSSHD = `#%PAM-1.0
auth substack password-auth
account required pam_sepermit.so
account required pam_nologin.so
account include password-auth
password include password-auth
session required pam_loginuid.so
session include password-auth
session include postlogin
`
const redhatPasswordAuth = `auth required pam_env.so
account required pam_unix.so
-session optional pam_keyinit.so revoke
session required pam_limits.so
-session optional pam_systemd.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required pam_unix.so
`
func files(m map[string]string) func(string) (string, error) {
return func(n string) (string, error) {
if s, ok := m[n]; ok {
return s, nil
}
return "", errors.New("no such file")
}
}
func TestPAMAdopt(t *testing.T) {
read := files(map[string]string{"common-session": ubuntuSession, "password-auth": redhatPasswordAuth, "postlogin": "session optional pam_lastlog.so\n"})
for name, stack := range map[string]string{"ubuntu": ubuntuSSHD, "redhat": redhatSSHD} {
on, contained, err := PAMAdopt(stack, "/var/lib/x-users", read, true)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
if !contained {
t.Errorf("%s: pam_systemd not skipped:\n%s", name, on)
}
lines := strings.Split(on, "\n")
if !strings.Contains(on, "account [success=ignore default=1] pam_listfile.so item=user sense=allow file=/var/lib/x-users onerr=fail") {
t.Fatalf("%s: lines not put in:\n%s", name, on)
}
for j, l := range lines {
if l == pamBegin && !strings.Contains(lines[j-1], "account") {
t.Errorf("%s: put after %q", name, lines[j-1])
}
if strings.Contains(l, "pam_systemd.so") &&
lines[j-1] != "session [success=1 default=ignore] pam_listfile.so item=user sense=allow file=/var/lib/x-users onerr=fail" {
t.Errorf("%s: pam_systemd not guarded, after %q", name, lines[j-1])
}
}
again, _, _ := PAMAdopt(on, "/var/lib/x-users", read, true)
if again != on {
t.Errorf("%s: put in twice:\n%s", name, again)
}
off, _, err := PAMAdopt(on, "", read, false)
if err != nil || off != stack {
t.Errorf("%s: taken out, not as it was (%v):\n%s", name, err, off)
}
}
// The jump over pam_deny lands where it did; the one past crond's line too.
on, _, _ := PAMAdopt(ubuntuSSHD, "/l", read, true)
if !strings.Contains(on, "[default=1]\t\t\tpam_permit.so") {
t.Errorf("a jump that does not cross the guard was changed:\n%s", on)
}
body := []string{"session [default=2] pam_permit.so", "session required pam_unix.so", "session optional pam_systemd.so", "session required pam_x.so"}
got, _ := pamInsert(body, 2, "G")
if got[0] != "session [default=3] pam_permit.so" || got[2] != "G" {
t.Errorf("a jump across the guard not made longer: %q", got)
}
if _, _, err := PAMAdopt("auth required pam_unix.so\n", "/x", read, true); err == nil {
t.Error("a stack with no account lines taken")
}
if _, _, err := PAMAdopt(pamBegin+"\naccount required x\n", "/x", read, false); err == nil {
t.Error("lines with no end taken")
}
// The session file unreadable: held to their jobs all the same.
on, contained, err := PAMAdopt(ubuntuSSHD, "/l", files(nil), true)
if err != nil || contained || !strings.Contains(on, AdoptModule) {
t.Errorf("contained %v, err %v:\n%s", contained, err, on)
}
}