mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Implement skip org role sync for jwt (#61647)
* Add new config option * Add frontend control * Condition new auth broker with config option * Condition old auth broker with config option Co-authored-by: Jo <joao.guerreiro@grafana.com> Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
parent
3debfd0ca7
commit
4d095547f8
@ -677,6 +677,7 @@ role_attribute_strict = false
|
||||
auto_sign_up = false
|
||||
url_login = false
|
||||
allow_assign_grafana_admin = false
|
||||
skip_org_role_sync = false
|
||||
|
||||
#################################### Auth LDAP ###########################
|
||||
[auth.ldap]
|
||||
|
@ -498,6 +498,9 @@
|
||||
# Set to true to enable Azure authentication option for HTTP-based datasources.
|
||||
;azure_auth_enabled = false
|
||||
|
||||
# Set to skip the organization role from JWT login and use system's role assignment instead.
|
||||
; skip_org_role_sync = false
|
||||
|
||||
#################################### Anonymous Auth ######################
|
||||
[auth.anonymous]
|
||||
# enable anonymous access
|
||||
|
@ -73,6 +73,17 @@ Grafana instance to include the JWT in the request's headers.
|
||||
In a scenario where it is not possible to rewrite the request headers you
|
||||
can use URL login instead.
|
||||
|
||||
## Skip organization role
|
||||
|
||||
To skip the assignment of roles and permissions upon login via JWT and handle them via other mechanisms like the user interface, we can skip the organization role synchronization with the following configuration.
|
||||
|
||||
```ini
|
||||
[auth.jwt]
|
||||
# ...
|
||||
|
||||
skip_org_role_sync = true
|
||||
```
|
||||
|
||||
### URL login
|
||||
|
||||
`url_login` allows grafana to search for a JWT in the URL query parameter
|
||||
|
@ -223,6 +223,7 @@ export interface AuthSettings {
|
||||
OAuthSkipOrgRoleUpdateSync?: boolean;
|
||||
SAMLSkipOrgRoleSync?: boolean;
|
||||
LDAPSkipOrgRoleSync?: boolean;
|
||||
JWTAuthSkipOrgRoleSync?: boolean;
|
||||
GrafanaComSkipOrgRoleSync?: boolean;
|
||||
AzureADSkipOrgRoleSync?: boolean;
|
||||
DisableSyncLock?: boolean;
|
||||
|
@ -148,6 +148,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
||||
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
|
||||
"LDAPSkipOrgRoleSync": hs.Cfg.LDAPSkipOrgRoleSync,
|
||||
"JWTAuthSkipOrgRoleSync": hs.Cfg.JWTAuthSkipOrgRoleSync,
|
||||
"GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync,
|
||||
"AzureADSkipOrgRoleSync": hs.Cfg.AzureADSkipOrgRoleSync,
|
||||
"DisableSyncLock": hs.Cfg.DisableSyncLock,
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/jmespath/go-jmespath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
authJWT "github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
@ -83,30 +84,34 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
id.Name = name
|
||||
}
|
||||
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(claims)
|
||||
if s.cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
|
||||
s.log.Warn("extracted Role is invalid", "role", role, "auth_id", id.AuthID)
|
||||
return nil, ErrJWTInvalidRole.Errorf("invalid role claim in JWT: %s", role)
|
||||
}
|
||||
|
||||
if role.IsValid() {
|
||||
var orgID int64
|
||||
// FIXME (jguer): GetIDForNewUser already has the auto assign information
|
||||
// just neeeds the org role. Find a meaningful way to pass this default
|
||||
// role to it (that doesn't involve id.OrgRoles[0] = role)
|
||||
if s.cfg.AutoAssignOrg && s.cfg.AutoAssignOrgId > 0 {
|
||||
orgID = int64(s.cfg.AutoAssignOrgId)
|
||||
s.log.Debug("The user has a role assignment and organization membership is auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
} else {
|
||||
orgID = int64(1)
|
||||
s.log.Debug("The user has a role assignment and organization membership is not auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
var role roletype.RoleType
|
||||
var grafanaAdmin bool
|
||||
if !s.cfg.JWTAuthSkipOrgRoleSync {
|
||||
role, grafanaAdmin = s.extractRoleAndAdmin(claims)
|
||||
if s.cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
|
||||
s.log.Warn("extracted Role is invalid", "role", role, "auth_id", id.AuthID)
|
||||
return nil, ErrJWTInvalidRole.Errorf("invalid role claim in JWT: %s", role)
|
||||
}
|
||||
|
||||
id.OrgRoles[orgID] = role
|
||||
if s.cfg.JWTAuthAllowAssignGrafanaAdmin {
|
||||
id.IsGrafanaAdmin = &grafanaAdmin
|
||||
if role.IsValid() {
|
||||
var orgID int64
|
||||
// FIXME (jguer): GetIDForNewUser already has the auto assign information
|
||||
// just needs the org role. Find a meaningful way to pass this default
|
||||
// role to it (that doesn't involve id.OrgRoles[0] = role)
|
||||
if s.cfg.AutoAssignOrg && s.cfg.AutoAssignOrgId > 0 {
|
||||
orgID = int64(s.cfg.AutoAssignOrgId)
|
||||
s.log.Debug("The user has a role assignment and organization membership is auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
} else {
|
||||
orgID = int64(1)
|
||||
s.log.Debug("The user has a role assignment and organization membership is not auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
}
|
||||
|
||||
id.OrgRoles[orgID] = role
|
||||
if s.cfg.JWTAuthAllowAssignGrafanaAdmin {
|
||||
id.IsGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
authJWT "github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@ -101,28 +102,31 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
|
||||
extUser.Name = name
|
||||
}
|
||||
|
||||
role, grafanaAdmin := h.extractJWTRoleAndAdmin(claims)
|
||||
if h.Cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
|
||||
ctx.Logger.Debug("Extracted Role is invalid")
|
||||
ctx.JsonApiErr(http.StatusForbidden, InvalidRole, nil)
|
||||
return true
|
||||
}
|
||||
|
||||
if role.IsValid() {
|
||||
var orgID int64
|
||||
if h.Cfg.AutoAssignOrg && h.Cfg.AutoAssignOrgId > 0 {
|
||||
orgID = int64(h.Cfg.AutoAssignOrgId)
|
||||
ctx.Logger.Debug("The user has a role assignment and organization membership is auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
} else {
|
||||
orgID = int64(1)
|
||||
ctx.Logger.Debug("The user has a role assignment and organization membership is not auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
var role roletype.RoleType
|
||||
var grafanaAdmin bool
|
||||
if !h.Cfg.JWTAuthSkipOrgRoleSync {
|
||||
role, grafanaAdmin = h.extractJWTRoleAndAdmin(claims)
|
||||
if h.Cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
|
||||
ctx.Logger.Debug("Extracted Role is invalid")
|
||||
ctx.JsonApiErr(http.StatusForbidden, InvalidRole, nil)
|
||||
return true
|
||||
}
|
||||
if role.IsValid() {
|
||||
var orgID int64
|
||||
if h.Cfg.AutoAssignOrg && h.Cfg.AutoAssignOrgId > 0 {
|
||||
orgID = int64(h.Cfg.AutoAssignOrgId)
|
||||
ctx.Logger.Debug("The user has a role assignment and organization membership is auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
} else {
|
||||
orgID = int64(1)
|
||||
ctx.Logger.Debug("The user has a role assignment and organization membership is not auto-assigned",
|
||||
"role", role, "orgId", orgID)
|
||||
}
|
||||
|
||||
extUser.OrgRoles[orgID] = role
|
||||
if h.Cfg.JWTAuthAllowAssignGrafanaAdmin {
|
||||
extUser.IsGrafanaAdmin = &grafanaAdmin
|
||||
extUser.OrgRoles[orgID] = role
|
||||
if h.Cfg.JWTAuthAllowAssignGrafanaAdmin {
|
||||
extUser.IsGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,6 +348,7 @@ type Cfg struct {
|
||||
JWTAuthRoleAttributePath string
|
||||
JWTAuthRoleAttributeStrict bool
|
||||
JWTAuthAllowAssignGrafanaAdmin bool
|
||||
JWTAuthSkipOrgRoleSync bool
|
||||
|
||||
// Dataproxy
|
||||
SendUserHeader bool
|
||||
@ -1109,7 +1110,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
||||
cfg.Logger.Warn("require_email_validation is enabled but smtp is disabled")
|
||||
}
|
||||
|
||||
// check old key name
|
||||
// check old key name
|
||||
GrafanaComUrl = valueAsString(iniFile.Section("grafana_net"), "url", "")
|
||||
if GrafanaComUrl == "" {
|
||||
GrafanaComUrl = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com")
|
||||
@ -1444,6 +1445,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
||||
cfg.JWTAuthRoleAttributePath = valueAsString(authJWT, "role_attribute_path", "")
|
||||
cfg.JWTAuthRoleAttributeStrict = authJWT.Key("role_attribute_strict").MustBool(false)
|
||||
cfg.JWTAuthAllowAssignGrafanaAdmin = authJWT.Key("allow_assign_grafana_admin").MustBool(false)
|
||||
cfg.JWTAuthSkipOrgRoleSync = authJWT.Key("skip_org_role_sync").MustBool(false)
|
||||
|
||||
authProxy := iniFile.Section("auth.proxy")
|
||||
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
|
||||
|
@ -106,6 +106,7 @@ export class UserAdminPage extends PureComponent<Props> {
|
||||
render() {
|
||||
const { user, orgs, sessions, ldapSyncInfo, isLoading } = this.props;
|
||||
const isLDAPUser = user?.isExternal && user?.authLabels?.includes('LDAP');
|
||||
const isJWTUser = user?.authLabels?.includes('JWT');
|
||||
const canReadSessions = contextSrv.hasPermission(AccessControlAction.UsersAuthTokenList);
|
||||
const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead);
|
||||
const isOAuthUserWithSkippableSync =
|
||||
@ -125,11 +126,13 @@ export class UserAdminPage extends PureComponent<Props> {
|
||||
isSAMLUser ||
|
||||
isLDAPUser ||
|
||||
isAzureADUser ||
|
||||
isJWTUser ||
|
||||
isGrafanaComUser
|
||||
)) ||
|
||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync) ||
|
||||
(!config.auth.SAMLSkipOrgRoleSync && isSAMLUser) ||
|
||||
(!config.auth.LDAPSkipOrgRoleSync && isLDAPUser) ||
|
||||
(!config.auth.JWTAuthSkipOrgRoleSync && isJWTUser) ||
|
||||
// both OAuthSkipOrgRoleUpdateSync and specific provider settings needs to be false for a user to be synced
|
||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GrafanaComSkipOrgRoleSync && isGrafanaComUser) ||
|
||||
(!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.AzureADSkipOrgRoleSync && isAzureADUser));
|
||||
|
Loading…
Reference in New Issue
Block a user