Auth: id response header (#77871)

* Add config options for identity id response header

* Add feature to add identity id response header to all responses

* Use util.SplitString
pull/78830/head
Karl Persson 2 years ago committed by GitHub
parent 27b7d1de6f
commit 21f94c5b78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      conf/defaults.ini
  2. 11
      conf/sample.ini
  3. 43
      pkg/services/contexthandler/contexthandler.go
  4. 14
      pkg/setting/setting.go

@ -565,6 +565,17 @@ azure_auth_enabled = false
# Use email lookup in addition to the unique ID provided by the IdP # Use email lookup in addition to the unique ID provided by the IdP
oauth_allow_insecure_email_lookup = false oauth_allow_insecure_email_lookup = false
# Set to true to include id of identity as a response header
id_response_header_enabled = false
# Prefix used for the id response header, X-Grafana-Identity-Id
id_response_header_prefix = X-Grafana
# List of identity namespaces to add id response headers for, separated by space.
# Available namespaces are user, api-key and service-account.
# The header value will encode the namespace ("user:<id>", "api-key:<id>", "service-account:<id>")
id_response_header_namespaces = user api-key service-account
#################################### Anonymous Auth ###################### #################################### Anonymous Auth ######################
[auth.anonymous] [auth.anonymous]
# enable anonymous access # enable anonymous access

@ -554,6 +554,17 @@
# Use email lookup in addition to the unique ID provided by the IdP # Use email lookup in addition to the unique ID provided by the IdP
;oauth_allow_insecure_email_lookup = false ;oauth_allow_insecure_email_lookup = false
# Set to true to include id of identity as a response header
;id_response_header_enabled = false
# Prefix used for the id response header, X-Grafana-Identity-Id
;id_response_header_prefix = X-Grafana
# List of identity namespaces to add id response headers for, separated by space.
# Available namespaces are user, api-key and service-account.
# The header value will encode the namespace ("user:<id>", "api-key:<id>", "service-account:<id>")
;id_response_header_namespaces = user api-key service-account
#################################### Anonymous Auth ###################### #################################### Anonymous Auth ######################
[auth.anonymous] [auth.anonymous]
# enable anonymous access # enable anonymous access

@ -4,7 +4,9 @@ package contexthandler
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/http" "net/http"
"strconv"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -135,10 +137,51 @@ func (h *ContextHandler) Middleware(next http.Handler) http.Handler {
attribute.Int64("userId", reqContext.UserID), attribute.Int64("userId", reqContext.UserID),
)) ))
if h.Cfg.IDResponseHeaderEnabled && reqContext.SignedInUser != nil {
namespace, id := getNamespaceAndID(reqContext.SignedInUser)
reqContext.Resp.Before(h.addIDHeaderEndOfRequestFunc(namespace, id))
}
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
// TODO(kalleep): Refactor to user identity.Requester interface and methods after we have backported this
func getNamespaceAndID(user *user.SignedInUser) (string, string) {
var namespace, id string
if user.UserID > 0 && user.IsServiceAccount {
id = strconv.Itoa(int(user.UserID))
namespace = "service-account"
} else if user.UserID > 0 {
id = strconv.Itoa(int(user.UserID))
namespace = "user"
} else if user.ApiKeyID > 0 {
id = strconv.Itoa(int(user.ApiKeyID))
namespace = "api-key"
}
return namespace, id
}
func (h *ContextHandler) addIDHeaderEndOfRequestFunc(namespace, id string) web.BeforeFunc {
return func(w web.ResponseWriter) {
if w.Written() {
return
}
if namespace == "" || id == "" {
return
}
if _, ok := h.Cfg.IDResponseHeaderNamespaces[namespace]; !ok {
return
}
headerName := fmt.Sprintf("%s-Identity-Id", h.Cfg.IDResponseHeaderPrefix)
w.Header().Add(headerName, fmt.Sprintf("%s:%s", namespace, id))
}
}
func (h *ContextHandler) deleteInvalidCookieEndOfRequestFunc(reqContext *contextmodel.ReqContext) web.BeforeFunc { func (h *ContextHandler) deleteInvalidCookieEndOfRequestFunc(reqContext *contextmodel.ReqContext) web.BeforeFunc {
return func(w web.ResponseWriter) { return func(w web.ResponseWriter) {
if h.features.IsEnabled(reqContext.Req.Context(), featuremgmt.FlagClientTokenRotation) { if h.features.IsEnabled(reqContext.Req.Context(), featuremgmt.FlagClientTokenRotation) {

@ -285,6 +285,9 @@ type Cfg struct {
AdminEmail string AdminEmail string
DisableLoginForm bool DisableLoginForm bool
SignoutRedirectUrl string SignoutRedirectUrl string
IDResponseHeaderEnabled bool
IDResponseHeaderPrefix string
IDResponseHeaderNamespaces map[string]struct{}
// Not documented & not supported // Not documented & not supported
// stand in until a more complete solution is implemented // stand in until a more complete solution is implemented
AuthConfigUIAdminAccess bool AuthConfigUIAdminAccess bool
@ -1607,6 +1610,17 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
// Azure Auth // Azure Auth
AzureAuthEnabled = auth.Key("azure_auth_enabled").MustBool(false) AzureAuthEnabled = auth.Key("azure_auth_enabled").MustBool(false)
cfg.AzureAuthEnabled = AzureAuthEnabled cfg.AzureAuthEnabled = AzureAuthEnabled
// ID response header
cfg.IDResponseHeaderEnabled = auth.Key("id_response_header_enabled").MustBool(false)
cfg.IDResponseHeaderPrefix = auth.Key("id_response_header_prefix").MustString("X-Grafana-")
idHeaderNamespaces := util.SplitString(auth.Key("id_response_header_namespaces").MustString(""))
cfg.IDResponseHeaderNamespaces = make(map[string]struct{}, len(idHeaderNamespaces))
for _, namespace := range idHeaderNamespaces {
cfg.IDResponseHeaderNamespaces[namespace] = struct{}{}
}
readAuthAzureADSettings(cfg) readAuthAzureADSettings(cfg)
// Google Auth // Google Auth

Loading…
Cancel
Save