CI / test (push) Successful in 9m41s
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>
114 lines
4.1 KiB
Go
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)
|
|
}
|
|
}
|