Fix pam
This commit is contained in:
@@ -3,8 +3,9 @@ module shell2
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/msteinert/pam/v2 v2.1.0
|
||||
github.com/openwall/yescrypt-go v1.0.0
|
||||
github.com/xtaci/kcp-go/v5 v5.6.61
|
||||
github.com/xtaci/smux v1.5.50
|
||||
golang.org/x/crypto v0.45.0
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
|
||||
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
@@ -36,15 +38,15 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidGY=
|
||||
github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/openwall/yescrypt-go v1.0.0 h1:jsGk48zkFvtUjGVOhYPGh+CS595JmTRcKnpggK2AON4=
|
||||
github.com/openwall/yescrypt-go v1.0.0/go.mod h1:e6CWtFizUEOUttaOjeVMiv1lJaJie3mfOtLJ9CCD6sA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.61 h1:ajm12pGuWO+GWQNusPyPESC7Rq0yTC2rEXVYkM8ExOg=
|
||||
|
||||
+5
-6
@@ -61,24 +61,23 @@ detect_pkg_manager() {
|
||||
set_package_deps() {
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
# libpam0g-dev: PAM headers for the optional PAM auth build (cgo).
|
||||
PKG_DEPS=(curl wget git rsync build-essential libpam0g-dev postgresql ca-certificates unzip openssh-client openssl python3 tar gzip)
|
||||
PKG_DEPS=(curl wget git rsync build-essential postgresql ca-certificates unzip openssh-client openssl python3 tar gzip)
|
||||
PKG_OPTIONAL_DEPS=(postgresql-contrib iptables nftables)
|
||||
;;
|
||||
dnf|yum)
|
||||
PKG_DEPS=(curl wget git rsync gcc make pam-devel postgresql-server ca-certificates unzip openssh-clients openssl python3 tar gzip)
|
||||
PKG_DEPS=(curl wget git rsync gcc make postgresql-server ca-certificates unzip openssh-clients openssl python3 tar gzip)
|
||||
PKG_OPTIONAL_DEPS=(postgresql-contrib iptables nftables)
|
||||
;;
|
||||
zypper)
|
||||
PKG_DEPS=(curl wget git rsync gcc make pam-devel postgresql-server ca-certificates unzip openssh openssl python3 tar gzip)
|
||||
PKG_DEPS=(curl wget git rsync gcc make postgresql-server ca-certificates unzip openssh openssl python3 tar gzip)
|
||||
PKG_OPTIONAL_DEPS=(postgresql-contrib iptables nftables)
|
||||
;;
|
||||
pacman)
|
||||
PKG_DEPS=(curl wget git rsync base-devel pam postgresql ca-certificates unzip openssh openssl python tar gzip)
|
||||
PKG_DEPS=(curl wget git rsync base-devel postgresql ca-certificates unzip openssh openssl python tar gzip)
|
||||
PKG_OPTIONAL_DEPS=(iptables-nft nftables)
|
||||
;;
|
||||
apk)
|
||||
PKG_DEPS=(curl wget git rsync build-base linux-pam-dev postgresql ca-certificates unzip openssh-client openssl python3 tar gzip)
|
||||
PKG_DEPS=(curl wget git rsync build-base postgresql ca-certificates unzip openssh-client openssl python3 tar gzip)
|
||||
PKG_OPTIONAL_DEPS=(postgresql-contrib iptables nftables)
|
||||
;;
|
||||
esac
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/GehirnInc/crypt"
|
||||
_ "github.com/GehirnInc/crypt/apr1_crypt"
|
||||
_ "github.com/GehirnInc/crypt/md5_crypt"
|
||||
_ "github.com/GehirnInc/crypt/sha256_crypt"
|
||||
_ "github.com/GehirnInc/crypt/sha512_crypt"
|
||||
"github.com/openwall/yescrypt-go"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// pamAuthAvailable reports that system-password ("PAM") auth is compiled in.
|
||||
// The implementation is pure Go (no cgo, no libpam), so it is always available;
|
||||
// on non-Linux builds /etc/shadow simply does not exist and auth fails closed.
|
||||
const pamAuthAvailable = true
|
||||
|
||||
const shadowFile = "/etc/shadow"
|
||||
|
||||
var errNoSystemPassword = errors.New("account has no usable password")
|
||||
|
||||
// authenticatePAM verifies password against the Linux system account matching
|
||||
// username. It reads the account's hash from /etc/shadow (the panel runs as
|
||||
// root) and recomputes it with the same algorithm — this is the "just the auth"
|
||||
// behaviour: the supplied password is checked exactly as the system would,
|
||||
// with no account/session management and nothing to do with the SSH daemon.
|
||||
//
|
||||
// It is called "PAM" for continuity with the user-facing flag, but it does not
|
||||
// link libpam; it verifies the crypt(3) hash directly. Supported hash formats:
|
||||
// yescrypt ($y$), sha512-crypt ($6$), sha256-crypt ($5$), md5-crypt ($1$),
|
||||
// apr1 ($apr1$) and bcrypt ($2a$/$2b$/$2y$). Returns nil on success.
|
||||
func authenticatePAM(username, password string) error {
|
||||
if username == "" {
|
||||
return errors.New("shadow: empty username")
|
||||
}
|
||||
hash, err := lookupShadowHash(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyCryptHash(hash, password)
|
||||
}
|
||||
|
||||
// lookupShadowHash returns the password hash field for username from /etc/shadow.
|
||||
func lookupShadowHash(username string) (string, error) {
|
||||
data, err := os.ReadFile(shadowFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read %s: %w", shadowFile, err)
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) < 2 || fields[0] != username {
|
||||
continue
|
||||
}
|
||||
hash := fields[1]
|
||||
// Empty, or locked/disabled accounts (! or * in the hash field) have no
|
||||
// password that any input can match — reject rather than risk a match.
|
||||
if hash == "" || strings.HasPrefix(hash, "!") || strings.HasPrefix(hash, "*") {
|
||||
return "", errNoSystemPassword
|
||||
}
|
||||
return hash, nil
|
||||
}
|
||||
return "", fmt.Errorf("shadow: user %q not found", username)
|
||||
}
|
||||
|
||||
// verifyCryptHash checks password against a crypt(3)-style hash string,
|
||||
// dispatching on the hash prefix. Returns nil only on an exact match.
|
||||
func verifyCryptHash(hash, password string) error {
|
||||
switch {
|
||||
case strings.HasPrefix(hash, "$y$"):
|
||||
computed, err := yescrypt.Hash([]byte(password), []byte(hash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("yescrypt: %w", err)
|
||||
}
|
||||
if subtle.ConstantTimeCompare(computed, []byte(hash)) == 1 {
|
||||
return nil
|
||||
}
|
||||
return errors.New("password mismatch")
|
||||
case strings.HasPrefix(hash, "$2a$"), strings.HasPrefix(hash, "$2b$"), strings.HasPrefix(hash, "$2y$"):
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
case crypt.IsHashSupported(hash):
|
||||
return crypt.NewFromHash(hash).Verify(hash, []byte(password))
|
||||
default:
|
||||
return fmt.Errorf("shadow: unsupported hash format")
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//go:build linux && cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/msteinert/pam/v2"
|
||||
)
|
||||
|
||||
// pamAuthAvailable reports that PAM authentication is compiled into this build.
|
||||
const pamAuthAvailable = true
|
||||
|
||||
// pamServiceName returns the PAM service to use for the auth-only check. This
|
||||
// is the name of a file under /etc/pam.d. Override with SSHPANEL_PAM_SERVICE;
|
||||
// it defaults to "login", which exists on every mainstream distro and runs the
|
||||
// standard unix auth stack (common-auth / system-auth).
|
||||
func pamServiceName() string {
|
||||
if s := strings.TrimSpace(os.Getenv("SSHPANEL_PAM_SERVICE")); s != "" {
|
||||
return s
|
||||
}
|
||||
return "login"
|
||||
}
|
||||
|
||||
// authenticatePAM verifies password against the Linux PAM stack for username.
|
||||
//
|
||||
// It runs ONLY the auth phase (pam_authenticate) — no account management
|
||||
// (pam_acct_mgmt), no session, no credential setup, and nothing to do with the
|
||||
// system SSH daemon. This is the "just the auth" behaviour: the supplied
|
||||
// password is checked against the system account exactly as PAM's auth modules
|
||||
// would, and nothing else. Returns nil on success, an error on failure.
|
||||
//
|
||||
// The panel runs as root, so pam_unix can read /etc/shadow to verify the hash
|
||||
// (including yescrypt/sha512crypt) for any local account.
|
||||
func authenticatePAM(username, password string) error {
|
||||
if username == "" {
|
||||
return errors.New("pam: empty username")
|
||||
}
|
||||
t, err := pam.StartFunc(pamServiceName(), username, func(s pam.Style, msg string) (string, error) {
|
||||
switch s {
|
||||
case pam.PromptEchoOff, pam.PromptEchoOn:
|
||||
return password, nil
|
||||
case pam.ErrorMsg, pam.TextInfo:
|
||||
// Informational messages from modules; nothing to return.
|
||||
return "", nil
|
||||
default:
|
||||
return "", fmt.Errorf("pam: unsupported conversation style %v", s)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("pam start: %w", err)
|
||||
}
|
||||
defer func() { _ = t.End() }()
|
||||
|
||||
// Silent keeps modules from writing to stdout/syslog noise; auth phase only.
|
||||
if err := t.Authenticate(pam.Silent); err != nil {
|
||||
return fmt.Errorf("pam auth: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
//go:build !linux || !cgo
|
||||
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
||||
// pamAuthAvailable is false when the binary is built without cgo/PAM support
|
||||
// (for example the Windows development build, or any CGO_ENABLED=0 build).
|
||||
// In that case a user flagged for PAM auth can never authenticate.
|
||||
const pamAuthAvailable = false
|
||||
|
||||
func pamServiceName() string { return "" }
|
||||
|
||||
func authenticatePAM(username, password string) error {
|
||||
return errors.New("pam authentication is not supported in this build")
|
||||
}
|
||||
@@ -79,29 +79,21 @@ detect_pkg_manager() {
|
||||
}
|
||||
|
||||
set_update_deps() {
|
||||
# PAM_DEV_PKG provides <security/pam_appl.h>, needed to build the optional
|
||||
# PAM auth support (cgo). Tracked separately because it is a dev header, not
|
||||
# a command, so the command-based dependency check cannot detect it.
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
PAM_DEV_PKG="libpam0g-dev"
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip "$PAM_DEV_PKG")
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip)
|
||||
;;
|
||||
dnf|yum)
|
||||
PAM_DEV_PKG="pam-devel"
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip "$PAM_DEV_PKG")
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip)
|
||||
;;
|
||||
zypper)
|
||||
PAM_DEV_PKG="pam-devel"
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip "$PAM_DEV_PKG")
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip)
|
||||
;;
|
||||
pacman)
|
||||
PAM_DEV_PKG="pam"
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python gcc make tar gzip "$PAM_DEV_PKG")
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python gcc make tar gzip)
|
||||
;;
|
||||
apk)
|
||||
PAM_DEV_PKG="linux-pam-dev"
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip "$PAM_DEV_PKG")
|
||||
UPDATE_DEPS=(git rsync wget ca-certificates python3 gcc make tar gzip)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -138,11 +130,6 @@ ensure_update_dependencies() {
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
missing=true
|
||||
fi
|
||||
# The PAM auth build needs <security/pam_appl.h>; a dev header install, not a
|
||||
# command, so check for the header file directly.
|
||||
if [ ! -e /usr/include/security/pam_appl.h ]; then
|
||||
missing=true
|
||||
fi
|
||||
if $missing; then
|
||||
warn "One or more updater dependencies are missing. Installing them with $PKG_MANAGER..."
|
||||
pkg_update
|
||||
|
||||
Reference in New Issue
Block a user