//go:build linux package auth import ( "bytes" "errors" "os" "os/user" "path/filepath" "github.com/msteinert/pam" "golang.org/x/crypto/ssh" ) type system struct{} type systemPrincipal struct { *user.User attributes Attributes } func newSystemPrincipal(name string) (*systemPrincipal, error) { u, err := user.Lookup(name) if err != nil { return nil, err } a := Attributes{ Custom: make(map[string]any), } if gids, err := u.GroupIds(); err == nil { a.Custom["groups"] = gids for _, gid := range gids { if g, err := user.LookupGroupId(gid); err == nil { a.Groups = append(a.Groups, g.Name) } } } return &systemPrincipal{ User: u, attributes: a, }, nil } func (u systemPrincipal) Type() string { return "user" } func (u systemPrincipal) Identity() string { return u.Name } func (u systemPrincipal) Attributes() Attributes { return u.attributes } func SystemPassword() Password { return system{} } func (system) VerifyPassword(username, password string) (Principal, error) { t, err := pam.StartFunc("sshd", username, func(s pam.Style, msg string) (string, error) { switch s { case pam.PromptEchoOff: return password, nil case pam.PromptEchoOn: return username, nil default: return "", errors.New("unrecognized message style") } }) if err != nil { return nil, err } if err = t.Authenticate(0); err != nil { return nil, err } return newSystemPrincipal(username) } func (system) VerifyPublicKey(username string, key ssh.PublicKey) (Principal, error) { p, err := newSystemPrincipal(username) if err != nil { return nil, err } rest, err := os.ReadFile(filepath.Join(p.HomeDir, ".ssh", "authorized_keys")) if err != nil { return nil, err } for len(rest) > 0 { var out ssh.PublicKey if out, _, _, rest, err = ssh.ParseAuthorizedKey(rest); err != nil { return nil, err } if bytes.Equal(out.Marshal(), key.Marshal()) { return p, nil } } return nil, ErrAuthorized }