refactor(handlers): utilize referer for auth logging rm/rd (#2655)

This utilizes the referrer query parameters instead of current request query parameters for logging the requested URI and method. Minor performance improvements to header peek/sets.
pull/2658/head
James Elliott 2021-12-02 13:21:46 +11:00 committed by GitHub
parent f3f3b31b12
commit bf9ab360bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 72 additions and 56 deletions

View File

@ -1,5 +1,9 @@
package handlers
import (
"github.com/valyala/fasthttp"
)
const (
// ActionTOTPRegistration is the string representation of the action for which the token has been produced.
ActionTOTPRegistration = "RegisterTOTPDevice"
@ -11,20 +15,15 @@ const (
ActionResetPassword = "ResetPassword"
)
const (
// HeaderProxyAuthorization is the basic-auth HTTP header Authelia utilises.
HeaderProxyAuthorization = "Proxy-Authorization"
var (
headerAuthorization = []byte(fasthttp.HeaderAuthorization)
headerProxyAuthorization = []byte(fasthttp.HeaderProxyAuthorization)
// HeaderAuthorization is the basic-auth HTTP header Authelia utilises with "auth=basic" query param.
HeaderAuthorization = "Authorization"
// HeaderSessionUsername is used as additional protection to validate a user for things like pam_exec.
HeaderSessionUsername = "Session-Username"
headerRemoteUser = "Remote-User"
headerRemoteName = "Remote-Name"
headerRemoteEmail = "Remote-Email"
headerRemoteGroups = "Remote-Groups"
headerSessionUsername = []byte("Session-Username")
headerRemoteUser = []byte("Remote-User")
headerRemoteGroups = []byte("Remote-Groups")
headerRemoteName = []byte("Remote-Name")
headerRemoteEmail = []byte("Remote-Email")
)
const (

View File

@ -33,7 +33,7 @@ func isSchemeWSS(url *url.URL) bool {
// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
func parseBasicAuth(header, auth string) (username, password string, err error) {
func parseBasicAuth(header []byte, auth string) (username, password string, err error) {
if !strings.HasPrefix(auth, authPrefix) {
return "", "", fmt.Errorf("%s prefix not found in %s header", strings.Trim(authPrefix, " "), header)
}
@ -85,7 +85,7 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
// verifyBasicAuth verify that the provided username and password are correct and
// that the user is authorized to target the resource.
func verifyBasicAuth(header string, auth []byte, ctx *middlewares.AutheliaCtx) (username, name string, groups, emails []string, authLevel authentication.Level, err error) {
func verifyBasicAuth(ctx *middlewares.AutheliaCtx, header, auth []byte) (username, name string, groups, emails []string, authLevel authentication.Level, err error) {
username, password, err := parseBasicAuth(header, string(auth))
if err != nil {
@ -116,14 +116,14 @@ func verifyBasicAuth(header string, auth []byte, ctx *middlewares.AutheliaCtx) (
// setForwardedHeaders set the forwarded User, Groups, Name and Email headers.
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username, name string, groups, emails []string) {
if username != "" {
headers.Set(headerRemoteUser, username)
headers.Set(headerRemoteGroups, strings.Join(groups, ","))
headers.Set(headerRemoteName, name)
headers.SetBytesK(headerRemoteUser, username)
headers.SetBytesK(headerRemoteGroups, strings.Join(groups, ","))
headers.SetBytesK(headerRemoteName, name)
if emails != nil {
headers.Set(headerRemoteEmail, emails[0])
headers.SetBytesK(headerRemoteEmail, emails[0])
} else {
headers.Set(headerRemoteEmail, "")
headers.SetBytesK(headerRemoteEmail, "")
}
}
}
@ -403,13 +403,13 @@ func getProfileRefreshSettings(cfg schema.AuthenticationBackendConfiguration) (r
}
func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile bool, refreshProfileInterval time.Duration) (isBasicAuth bool, username, name string, groups, emails []string, authLevel authentication.Level, err error) {
authHeader := HeaderProxyAuthorization
authHeader := headerProxyAuthorization
if bytes.Equal(ctx.QueryArgs().Peek("auth"), []byte("basic")) {
authHeader = HeaderAuthorization
authHeader = headerAuthorization
isBasicAuth = true
}
authValue := ctx.Request.Header.Peek(authHeader)
authValue := ctx.Request.Header.PeekBytes(authHeader)
if authValue != nil {
isBasicAuth = true
} else if isBasicAuth {
@ -418,23 +418,23 @@ func verifyAuth(ctx *middlewares.AutheliaCtx, targetURL *url.URL, refreshProfile
}
if isBasicAuth {
username, name, groups, emails, authLevel, err = verifyBasicAuth(authHeader, authValue, ctx)
username, name, groups, emails, authLevel, err = verifyBasicAuth(ctx, authHeader, authValue)
return
}
userSession := ctx.GetSession()
username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, refreshProfile, refreshProfileInterval)
sessionUsername := ctx.Request.Header.Peek(HeaderSessionUsername)
sessionUsername := ctx.Request.Header.PeekBytes(headerSessionUsername)
if sessionUsername != nil && !strings.EqualFold(string(sessionUsername), username) {
ctx.Logger.Warnf("Possible cookie hijack or attempt to bypass security detected destroying the session and sending 401 response")
err = ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
if err != nil {
ctx.Logger.Errorf("Unable to destroy user session after handler could not match them to their %s header: %s", HeaderSessionUsername, err)
ctx.Logger.Errorf("Unable to destroy user session after handler could not match them to their %s header: %s", headerSessionUsername, err)
}
err = fmt.Errorf("could not match user %s to their %s header with a value of %s when visiting %s", username, HeaderSessionUsername, sessionUsername, targetURL.String())
err = fmt.Errorf("could not match user %s to their %s header with a value of %s when visiting %s", username, headerSessionUsername, sessionUsername, targetURL.String())
}
return

View File

@ -85,34 +85,34 @@ func TestShouldRaiseWhenXForwardedURIIsNotParsable(t *testing.T) {
// Test parseBasicAuth.
func TestShouldRaiseWhenHeaderDoesNotContainBasicPrefix(t *testing.T) {
_, _, err := parseBasicAuth(HeaderProxyAuthorization, "alzefzlfzemjfej==")
_, _, err := parseBasicAuth(headerProxyAuthorization, "alzefzlfzemjfej==")
assert.Error(t, err)
assert.Equal(t, "Basic prefix not found in Proxy-Authorization header", err.Error())
}
func TestShouldRaiseWhenCredentialsAreNotInBase64(t *testing.T) {
_, _, err := parseBasicAuth(HeaderProxyAuthorization, "Basic alzefzlfzemjfej==")
_, _, err := parseBasicAuth(headerProxyAuthorization, "Basic alzefzlfzemjfej==")
assert.Error(t, err)
assert.Equal(t, "illegal base64 data at input byte 16", err.Error())
}
func TestShouldRaiseWhenCredentialsAreNotInCorrectForm(t *testing.T) {
// The decoded format should be user:password.
_, _, err := parseBasicAuth(HeaderProxyAuthorization, "Basic am9obiBwYXNzd29yZA==")
_, _, err := parseBasicAuth(headerProxyAuthorization, "Basic am9obiBwYXNzd29yZA==")
assert.Error(t, err)
assert.Equal(t, "format of Proxy-Authorization header must be user:password", err.Error())
}
func TestShouldUseProvidedHeaderName(t *testing.T) {
// The decoded format should be user:password.
_, _, err := parseBasicAuth("HeaderName", "")
_, _, err := parseBasicAuth([]byte("HeaderName"), "")
assert.Error(t, err)
assert.Equal(t, "Basic prefix not found in HeaderName header", err.Error())
}
func TestShouldReturnUsernameAndPassword(t *testing.T) {
// the decoded format should be user:password.
user, password, err := parseBasicAuth(HeaderProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
user, password, err := parseBasicAuth(headerProxyAuthorization, "Basic am9objpwYXNzd29yZA==")
assert.NoError(t, err)
assert.Equal(t, "john", user)
assert.Equal(t, "password", password)
@ -176,7 +176,7 @@ func TestShouldVerifyWrongCredentials(t *testing.T) {
CheckUserPassword(gomock.Eq("john"), gomock.Eq("password")).
Return(false, nil)
_, _, _, _, _, err := verifyBasicAuth(HeaderProxyAuthorization, []byte("Basic am9objpwYXNzd29yZA=="), mock.Ctx)
_, _, _, _, _, err := verifyBasicAuth(mock.Ctx, headerProxyAuthorization, []byte("Basic am9objpwYXNzd29yZA=="))
assert.Error(t, err)
}
@ -1211,7 +1211,7 @@ func TestShouldCheckValidSessionUsernameHeaderAndReturn200(t *testing.T) {
require.NoError(t, err)
mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com")
mock.Ctx.Request.Header.Set(HeaderSessionUsername, testUsername)
mock.Ctx.Request.Header.SetBytesK(headerSessionUsername, testUsername)
VerifyGet(verifyGetCfg)(mock.Ctx)
assert.Equal(t, expectedStatusCode, mock.Ctx.Response.StatusCode())
@ -1235,7 +1235,7 @@ func TestShouldCheckInvalidSessionUsernameHeaderAndReturn401(t *testing.T) {
require.NoError(t, err)
mock.Ctx.Request.Header.Set("X-Original-URL", "https://one-factor.example.com")
mock.Ctx.Request.Header.Set(HeaderSessionUsername, "root")
mock.Ctx.Request.Header.SetBytesK(headerSessionUsername, "root")
VerifyGet(verifyGetCfg)(mock.Ctx)
assert.Equal(t, expectedStatusCode, mock.Ctx.Response.StatusCode())

View File

@ -150,7 +150,20 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
// We only Mark if there was no underlying error.
ctx.Logger.Debugf("Mark %s authentication attempt made by user '%s'", authType, username)
if err = ctx.Providers.Regulator.Mark(ctx, successful, bannedUntil != nil, username, string(ctx.RequestCtx.QueryArgs().Peek("rd")), string(ctx.RequestCtx.QueryArgs().Peek("rm")), authType, ctx.RemoteIP()); err != nil {
var (
requestURI, requestMethod string
)
referer := ctx.Request.Header.Referer()
if referer != nil {
refererURL, err := url.Parse(string(referer))
if err == nil {
requestURI = refererURL.Query().Get("rd")
requestMethod = refererURL.Query().Get("rm")
}
}
if err = ctx.Providers.Regulator.Mark(ctx, successful, bannedUntil != nil, username, requestURI, requestMethod, authType, ctx.RemoteIP()); err != nil {
ctx.Logger.Errorf("Unable to mark %s authentication attempt by user '%s': %+v", authType, username, err)
return err

View File

@ -102,22 +102,22 @@ func (c *AutheliaCtx) ReplyBadRequest() {
// XForwardedProto return the content of the X-Forwarded-Proto header.
func (c *AutheliaCtx) XForwardedProto() []byte {
return c.RequestCtx.Request.Header.Peek(headerXForwardedProto)
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedProto)
}
// XForwardedMethod return the content of the X-Forwarded-Method header.
func (c *AutheliaCtx) XForwardedMethod() []byte {
return c.RequestCtx.Request.Header.Peek(headerXForwardedMethod)
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedMethod)
}
// XForwardedHost return the content of the X-Forwarded-Host header.
func (c *AutheliaCtx) XForwardedHost() []byte {
return c.RequestCtx.Request.Header.Peek(headerXForwardedHost)
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedHost)
}
// XForwardedURI return the content of the X-Forwarded-URI header.
func (c *AutheliaCtx) XForwardedURI() []byte {
return c.RequestCtx.Request.Header.Peek(headerXForwardedURI)
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedURI)
}
// BasePath returns the base_url as per the path visited by the client.
@ -159,7 +159,7 @@ func (c *AutheliaCtx) ExternalRootURL() (string, error) {
// XOriginalURL return the content of the X-Original-URL header.
func (c *AutheliaCtx) XOriginalURL() []byte {
return c.RequestCtx.Request.Header.Peek(headerXOriginalURL)
return c.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
}
// GetSession return the user session. Any update will be saved in cache.
@ -220,7 +220,7 @@ func (c *AutheliaCtx) SetJSONBody(value interface{}) error {
// RemoteIP return the remote IP taking X-Forwarded-For header into account if provided.
func (c *AutheliaCtx) RemoteIP() net.IP {
XForwardedFor := c.Request.Header.Peek("X-Forwarded-For")
XForwardedFor := c.Request.Header.PeekBytes(headerXForwardedFor)
if XForwardedFor != nil {
ips := strings.Split(string(XForwardedFor), ",")
@ -278,14 +278,14 @@ func (c *AutheliaCtx) GetOriginalURL() (*url.URL, error) {
// IsXHR returns true if the request is a XMLHttpRequest.
func (c AutheliaCtx) IsXHR() (xhr bool) {
requestedWith := c.Request.Header.Peek(headerXRequestedWith)
requestedWith := c.Request.Header.PeekBytes(headerXRequestedWith)
return requestedWith != nil && string(requestedWith) == headerValueXRequestedWithXHR
}
// AcceptsMIME takes a mime type and returns true if the request accepts that type or the wildcard type.
func (c AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) {
accepts := strings.Split(string(c.Request.Header.Peek("Accept")), ",")
accepts := strings.Split(string(c.Request.Header.PeekBytes(headerAccept)), ",")
for i, accept := range accepts {
mimeType := strings.Trim(strings.SplitN(accept, ";", 2)[0], " ")

View File

@ -1,21 +1,25 @@
package middlewares
const (
headerXForwardedProto = "X-Forwarded-Proto"
headerXForwardedMethod = "X-Forwarded-Method"
headerXForwardedHost = "X-Forwarded-Host"
headerXForwardedURI = "X-Forwarded-URI"
headerXOriginalURL = "X-Original-URL"
headerXRequestedWith = "X-Requested-With"
import (
"github.com/valyala/fasthttp"
)
var (
headerXForwardedProto = []byte(fasthttp.HeaderXForwardedProto)
headerXForwardedHost = []byte(fasthttp.HeaderXForwardedHost)
headerXForwardedFor = []byte(fasthttp.HeaderXForwardedFor)
headerXRequestedWith = []byte(fasthttp.HeaderXRequestedWith)
headerAccept = []byte(fasthttp.HeaderAccept)
headerXForwardedURI = []byte("X-Forwarded-URI")
headerXOriginalURL = []byte("X-Original-URL")
headerXForwardedMethod = []byte("X-Forwarded-Method")
)
const (
headerValueXRequestedWithXHR = "XMLHttpRequest"
)
const (
contentTypeApplicationJSON = "application/json"
contentTypeTextHTML = "text/html"
contentTypeApplicationJSON = "application/json"
contentTypeTextHTML = "text/html"
)
var okMessageBytes = []byte("{\"status\":\"OK\"}")