Auth: Implement Token URL JWT Auth (#52662)

* Auth: check of auth_token in url and resolve user if present

* check if auth_token is passed in url

* Auth: Pass auth_token for request if present in path

* no need to decode token in index

* temp

* use loadURLToken and set authorization header

* cache token in memory and strip it from url

* Use loadURLToken

* Keep token in url

* strip sensitive query strings from url used by context logger

* adapt login by url to jwt token

* add jwt iframe devenv

* add jwt iframe devenv instructions

* add access note

* add test for cleaning request

* ensure jwt token is not carried into handlers

* do not reshuffle queries, might be important

* add correct db dump location

* prefer set token instead of cached token

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

Co-authored-by: Karl Persson <kalle.persson@grafana.com>
Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Jo
2022-07-27 16:10:47 +02:00
committed by GitHub
co-authored by Ieva Karl Persson
parent 7ba076de10
commit c2d3c90bc8
12 changed files with 138 additions and 5 deletions
+14 -2
View File
@@ -57,7 +57,7 @@ func Logger(cfg *setting.Cfg) web.Handler {
"time_ms", int64(timeTaken),
"duration", duration,
"size", rw.Size(),
"referer", sanitizeURL(ctx, req.Referer()),
"referer", SanitizeURL(ctx, req.Referer()),
}
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
@@ -74,7 +74,11 @@ func Logger(cfg *setting.Cfg) web.Handler {
}
}
func sanitizeURL(ctx *models.ReqContext, s string) string {
var sensitiveQueryStrings = [...]string{
"auth_token",
}
func SanitizeURL(ctx *models.ReqContext, s string) string {
if s == "" {
return s
}
@@ -84,5 +88,13 @@ func sanitizeURL(ctx *models.ReqContext, s string) string {
ctx.Logger.Warn("Received invalid referer in request headers, removed for log forgery prevention")
return ""
}
// strip out sensitive query strings
values := u.Query()
for _, query := range sensitiveQueryStrings {
values.Del(query)
}
u.RawQuery = values.Encode()
return u.String()
}
+1 -1
View File
@@ -51,7 +51,7 @@ func Test_sanitizeURL(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, sanitizeURL(tt.args.ctx, tt.args.s), "sanitizeURL(%v, %v)", tt.args.ctx, tt.args.s)
assert.Equalf(t, tt.want, SanitizeURL(tt.args.ctx, tt.args.s), "sanitizeURL(%v, %v)", tt.args.ctx, tt.args.s)
})
}
}
+30
View File
@@ -493,6 +493,36 @@ func TestMiddlewareContext(t *testing.T) {
sc.exec()
})
middlewareScenario(t, "Request body should not be read in default context handler, but query should be altered - jwt", func(t *testing.T, sc *scenarioContext) {
sc.fakeReq("POST", "/?targetOrgId=123&auth_token=token")
body := "key=value"
sc.req.Body = io.NopCloser(strings.NewReader(body))
sc.handlerFunc = func(c *models.ReqContext) {
t.Log("Handler called")
defer func() {
err := c.Req.Body.Close()
require.NoError(t, err)
}()
require.Equal(t, "", c.Req.URL.Query().Get("auth_token"))
bodyAfterHandler, e := io.ReadAll(c.Req.Body)
require.NoError(t, e)
require.Equal(t, body, string(bodyAfterHandler))
}
sc.req.Header.Set(sc.cfg.AuthProxyHeaderName, hdrName)
sc.req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
sc.req.Header.Set("Content-Length", strconv.Itoa(len(body)))
sc.m.Post("/", sc.defaultHandler)
sc.exec()
}, func(cfg *setting.Cfg) {
cfg.JWTAuthEnabled = true
cfg.JWTAuthURLLogin = true
cfg.JWTAuthHeaderName = "X-WEBAUTH-TOKEN"
})
middlewareScenario(t, "Should get an existing user from header", func(t *testing.T, sc *scenarioContext) {
const userID int64 = 12
const orgID int64 = 2