From a232e7ceca994bfd84cb031571d969c833a66c09 Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Mon, 30 Jan 2023 10:54:14 +0000 Subject: [PATCH] Auth: Add skip_org_role_sync for Okta (#62106) * WIP * Update pkg/services/login/authinfo.go * fix: merge * change order to internal last * adds: docs * add: configuration for defaults and sample * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Jo * Update docs/sources/setup-grafana/configure-grafana/_index.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --------- Co-authored-by: Jo Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --- conf/defaults.ini | 1 + conf/sample.ini | 1 + .../setup-grafana/configure-grafana/_index.md | 15 +++++++++++ .../configure-authentication/okta/index.md | 11 ++++++++ packages/grafana-data/src/types/config.ts | 1 + pkg/api/frontendsettings.go | 1 + pkg/login/social/okta_oauth.go | 27 ++++++++++++------- pkg/login/social/social.go | 7 ++--- pkg/services/login/authinfo.go | 2 ++ pkg/setting/setting.go | 11 ++++++++ public/app/features/admin/UserAdminPage.tsx | 3 +++ 11 files changed, 68 insertions(+), 12 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index d4028ef65ca..539e1bee548 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -621,6 +621,7 @@ allowed_groups = role_attribute_path = role_attribute_strict = false allow_assign_grafana_admin = false +skip_org_role_sync = false #################################### Generic OAuth ####################### [auth.generic_oauth] diff --git a/conf/sample.ini b/conf/sample.ini index 546f828ac45..250205753bf 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -616,6 +616,7 @@ ;role_attribute_path = ;role_attribute_strict = false ;allow_assign_grafana_admin = false +;skip_org_role_sync = false #################################### Generic OAuth ########################## [auth.generic_oauth] diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index dc04190ddd1..bc213e0bd06 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -940,6 +940,21 @@ The following table shows the OAuth provider's setting with the default value an | GitLab | false | true | User organization roles are set with `defaultRole`, and the organization role can be changed for GitLab synced users. | | GitLab | true | true | User organization roles are set with `defaultRole` for GitLab. For other providers, the synchronization is skipped, and the org role can be changed, along with other OAuth provider users' org roles. | +### [auth.okta] skip_org_role_sync + +When a user logs in the first time, Grafana sets the organization role based on the value specified in `AutoAssignOrgRole`. If you want to manage organization roles through Grafana's UI, set the `skip_org_role_sync` option to `true`. +This also impacts `allow_assign_grafana_admin` setting, by not syncing the grafana admin role from GitLab. + +> **Note:** There is a separate setting called `oauth_skip_org_role_update_sync` which has a different scope. While `skip_org_role_sync` only applies to the specific OAuth provider, `oauth_skip_org_role_update_sync` is a generic setting that affects all configured OAuth providers. + +The following table shows the OAuth provider's setting with the default value and the skip org role sync setting. +| OAuth Provider | `oauth_skip_org_role_sync_update` | `skip_org_role_sync` | Behavior | +| --- | --- | --- | --- | +| Okta | false | false | User organization roles are set with `defaultRole` and cannot be changed. | +| Github | true | false | User organization roles are set with `defaultRole` for Okta, and Grafana Admins are set. For other providers, the synchronization is skipped, and the org role can be changed, along with other OAuth provider users' org roles. | +| Okta | false | true | User organization roles are set with `defaultRole`, and the organization role can be changed for Okta synced users. | +| Okta | true | true | User organization roles are set with `defaultRole` for Okta. For other providers, the synchronization is skipped, and the org role can be changed, along with other OAuth provider users' org roles. | + ### api_key_max_seconds_to_live Limit of API key seconds to live before expiration. Default is -1 (unlimited). diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md index 4b3834cf72e..143d7eb0861 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/okta/index.md @@ -130,6 +130,17 @@ Example: role_attribute_path = contains(groups[*], 'admin') && 'GrafanaAdmin' || contains(groups[*], 'editor') && 'Editor' || 'Viewer' ``` +## Skip organization role sync + +To prevent the sync of org roles from Okta, set `skip_org_role_sync` to `true`. This is useful if you want to manage the organization roles for your users from within Grafana. + +```ini +[auth.okta] +# .. +# prevents the sync of org roles from Okta +skip_org_role_sync = true +``` + ### Team Sync (Enterprise only) Map your Okta groups to teams in Grafana so that your users will automatically be added to diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 155c1acc198..d4c9381f6f7 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -232,6 +232,7 @@ export interface AuthSettings { GrafanaComSkipOrgRoleSync?: boolean; GithubSkipOrgRoleSync?: boolean; GitLabSkipOrgRoleSync?: boolean; + OktaSkipOrgRoleSync?: boolean; AzureADSkipOrgRoleSync?: boolean; GoogleSkipOrgRoleSync?: boolean; DisableSyncLock?: boolean; diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 00c05cf7e3d..ea05fdfdbaa 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -155,6 +155,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *contextmodel.ReqContext) (map[st "GrafanaComSkipOrgRoleSync": hs.Cfg.GrafanaComSkipOrgRoleSync, "GitLabSkipOrgRoleSync": hs.Cfg.GitLabSkipOrgRoleSync, "AzureADSkipOrgRoleSync": hs.Cfg.AzureADSkipOrgRoleSync, + "OktaSkipOrgRoleSync": hs.Cfg.OktaSkipOrgRoleSync, "DisableSyncLock": hs.Cfg.DisableSyncLock, }, "buildInfo": map[string]interface{}{ diff --git a/pkg/login/social/okta_oauth.go b/pkg/login/social/okta_oauth.go index 6eda8afab24..0cfe488e85c 100644 --- a/pkg/login/social/okta_oauth.go +++ b/pkg/login/social/okta_oauth.go @@ -8,12 +8,15 @@ import ( "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2/jwt" + + "github.com/grafana/grafana/pkg/models/roletype" ) type SocialOkta struct { *SocialBase - apiUrl string - allowedGroups []string + apiUrl string + allowedGroups []string + skipOrgRoleSync bool } type OktaUserInfoJson struct { @@ -75,14 +78,20 @@ func (s *SocialOkta) UserInfo(client *http.Client, token *oauth2.Token) (*BasicU return nil, errMissingGroupMembership } - role, grafanaAdmin := s.extractRoleAndAdmin(data.rawJSON, groups, true) - if s.roleAttributeStrict && !role.IsValid() { - return nil, &InvalidBasicRoleError{idP: "Okta", assignedRole: string(role)} + var role roletype.RoleType + var isGrafanaAdmin *bool + if !s.skipOrgRoleSync { + var grafanaAdmin bool + role, grafanaAdmin = s.extractRoleAndAdmin(data.rawJSON, groups, true) + if s.roleAttributeStrict && !role.IsValid() { + return nil, &InvalidBasicRoleError{idP: "Okta", assignedRole: string(role)} + } + if s.allowAssignGrafanaAdmin { + isGrafanaAdmin = &grafanaAdmin + } } - - var isGrafanaAdmin *bool = nil - if s.allowAssignGrafanaAdmin { - isGrafanaAdmin = &grafanaAdmin + if s.allowAssignGrafanaAdmin && s.skipOrgRoleSync { + s.log.Debug("allowAssignGrafanaAdmin and skipOrgRoleSync are both set, Grafana Admin role will not be synced, consider setting one or the other") } return &BasicUserInfo{ diff --git a/pkg/login/social/social.go b/pkg/login/social/social.go index 9781bd832f9..5ff006a5827 100644 --- a/pkg/login/social/social.go +++ b/pkg/login/social/social.go @@ -183,9 +183,10 @@ func ProvideService(cfg *setting.Cfg, // Okta if name == "okta" { ss.socialMap["okta"] = &SocialOkta{ - SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole, cfg.OAuthSkipOrgRoleUpdateSync, *features), - apiUrl: info.ApiUrl, - allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), + SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole, cfg.OAuthSkipOrgRoleUpdateSync, *features), + apiUrl: info.ApiUrl, + allowedGroups: util.SplitString(sec.Key("allowed_groups").String()), + skipOrgRoleSync: cfg.OktaSkipOrgRoleSync, } } diff --git a/pkg/services/login/authinfo.go b/pkg/services/login/authinfo.go index 9f7e8908533..ee0146cd5d4 100644 --- a/pkg/services/login/authinfo.go +++ b/pkg/services/login/authinfo.go @@ -34,6 +34,8 @@ func GetAuthProviderLabel(authModule string) string { return "AzureAD" case "oauth_gitlab": return "GitLab" + case "oauth_okta": + return "Okta" case "oauth_grafana_com", "oauth_grafananet": return "grafana.com" case SAMLAuthModule: diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index dbcb416f317..4734f5c12c1 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -500,6 +500,9 @@ type Cfg struct { SecureSocksDSProxy SecureSocksDSProxySettings + // Okta OAuth + OktaSkipOrgRoleSync bool + // Access Control RBACEnabled bool RBACPermissionCache bool @@ -1394,6 +1397,11 @@ func readAuthGitlabSettings(iniFile *ini.File, cfg *Cfg) { cfg.GitLabSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false) } +func readAuthOktaSettings(iniFile *ini.File, cfg *Cfg) { + sec := iniFile.Section("auth.okta") + cfg.OktaSkipOrgRoleSync = sec.Key("skip_org_role_sync").MustBool(false) +} + func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { auth := iniFile.Section("auth") @@ -1454,6 +1462,9 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { // GitLab Auth readAuthGitlabSettings(iniFile, cfg) + // Okta Auth + readAuthOktaSettings(iniFile, cfg) + // anonymous access AnonymousEnabled = iniFile.Section("auth.anonymous").Key("enabled").MustBool(false) cfg.AnonymousEnabled = AnonymousEnabled diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index dc84ebc0f84..dcf0ba5dee7 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -117,6 +117,7 @@ export class UserAdminPage extends PureComponent { const isGitLabUser = user?.isExternal && user?.authLabels?.includes('GitLab'); const isAuthProxyUser = user?.isExternal && user?.authLabels?.includes('Auth Proxy'); const isAzureADUser = user?.isExternal && user?.authLabels?.includes('AzureAD'); + const isOktaUser = user?.isExternal && user?.authLabels?.includes('Okta'); const isGrafanaComUser = user?.isExternal && user?.authLabels?.includes('grafana.com'); const isUserSynced = !config.auth.DisableSyncLock && @@ -127,6 +128,7 @@ export class UserAdminPage extends PureComponent { isGitLabUser || isOAuthUserWithSkippableSync || isSAMLUser || + isOktaUser || isLDAPUser || isGithubUser || isAzureADUser || @@ -139,6 +141,7 @@ export class UserAdminPage extends PureComponent { (!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.OktaSkipOrgRoleSync && isOktaUser) || (!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GithubSkipOrgRoleSync && isGithubUser) || (!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.AzureADSkipOrgRoleSync && isAzureADUser) || (!config.auth.OAuthSkipOrgRoleUpdateSync && !config.auth.GitLabSkipOrgRoleSync && isGitLabUser) ||