2022-09-10 18:13:20 +01:00
|
|
|
//go:build pam
|
|
|
|
|
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/msteinert/pam"
|
2022-12-01 11:00:42 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2022-09-10 18:13:20 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type pamProvider struct{}
|
|
|
|
|
|
|
|
func NewPAM() (AuthProvider, error) {
|
|
|
|
return pamProvider{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pamProvider) Middleware() func(http.Handler) http.Handler {
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
pamAuth(next, w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func pamAuth(next http.Handler, w http.ResponseWriter, r *http.Request) {
|
|
|
|
user, pass, ok := r.BasicAuth()
|
|
|
|
if !ok {
|
|
|
|
w.Header().Add("WWW-Authenticate", `Basic realm="Please provide your system credentials", charset="UTF-8"`)
|
|
|
|
http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t, err := pam.StartFunc("login", user, func(s pam.Style, msg string) (string, error) {
|
2022-12-01 11:00:42 +00:00
|
|
|
log.Debug().Str("style", fmt.Sprintf("%v", s)).Msg(msg)
|
2022-09-10 18:13:20 +01:00
|
|
|
switch s {
|
|
|
|
case pam.PromptEchoOff:
|
|
|
|
return pass, nil
|
|
|
|
case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo:
|
|
|
|
return "", nil
|
|
|
|
default:
|
|
|
|
return "", fmt.Errorf("unsupported PAM conversation style: %v", s)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if err != nil {
|
2024-02-05 21:23:58 +00:00
|
|
|
log.Warn().Err(err).Msg("failed to start PAM conversation")
|
2022-09-10 18:13:20 +01:00
|
|
|
http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.Authenticate(0); err != nil {
|
2024-02-05 21:23:58 +00:00
|
|
|
log.Debug().Str("user", user).Err(err).Msg("auth error")
|
2022-09-10 18:13:20 +01:00
|
|
|
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.AcctMgmt(0); err != nil {
|
2024-02-05 21:23:58 +00:00
|
|
|
log.Debug().Str("user", user).Err(err).Msg("account unavailable")
|
2022-09-10 18:13:20 +01:00
|
|
|
http.Error(w, "Account unavailable", http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err = t.GetItem(pam.User)
|
|
|
|
if err != nil {
|
2024-02-05 21:23:58 +00:00
|
|
|
log.Warn().Str("user", user).Err(err).Msg("failed to get PAM username")
|
2022-09-10 18:13:20 +01:00
|
|
|
http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authCtx := AuthContext{
|
|
|
|
AuthMethod: "pam",
|
|
|
|
UserName: user,
|
|
|
|
}
|
|
|
|
ctx := NewContext(r.Context(), &authCtx)
|
|
|
|
r = r.WithContext(ctx)
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
}
|