package accesscontrol import ( // #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes "crypto/sha1" "encoding/base64" "fmt" "strings" "sync" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" ) const ( BasicRolePrefix = "basic:" BasicRoleUIDPrefix = "basic_" ExternalServiceRolePrefix = "extsvc:" ExternalServiceRoleUIDPrefix = "extsvc_" FixedRolePrefix = "fixed:" FixedRoleUIDPrefix = "fixed_" ManagedRolePrefix = "managed:" PluginRolePrefix = "plugins:" BasicRoleNoneUID = "basic_none" BasicRoleNoneName = "basic:none" FixedCloudRolePrefix = "fixed:cloud:" FixedCloudViewerRole = "fixed:cloud:viewer" FixedCloudEditorRole = "fixed:cloud:editor" FixedCloudAdminRole = "fixed:cloud:admin" ) // Roles definition var ( ldapReaderRole = RoleDTO{ Name: "fixed:ldap:reader", DisplayName: "LDAP reader", Description: "Read LDAP configuration and status.", Group: "LDAP", Permissions: []Permission{ { Action: ActionLDAPUsersRead, }, { Action: ActionLDAPStatusRead, }, }, } ldapWriterRole = RoleDTO{ Name: "fixed:ldap:writer", DisplayName: "LDAP writer", Description: "Read and update LDAP configuration and read LDAP status.", Group: "LDAP", Permissions: ConcatPermissions(ldapReaderRole.Permissions, []Permission{ { Action: ActionLDAPUsersSync, }, { Action: ActionLDAPConfigReload, }, }), } orgUsersWriterRole = RoleDTO{ Name: "fixed:org.users:writer", DisplayName: "Organization user writer", Description: "Within a single organization, add a user, invite a user, read information about a user and their role, remove a user from that organization, or change the role of a user.", Group: "User administration (organizational)", Permissions: ConcatPermissions(orgUsersReaderRole.Permissions, []Permission{ { Action: ActionOrgUsersAdd, Scope: ScopeUsersAll, }, { Action: ActionOrgUsersWrite, Scope: ScopeUsersAll, }, { Action: ActionOrgUsersRemove, Scope: ScopeUsersAll, }, }), } orgUsersReaderRole = RoleDTO{ Name: "fixed:org.users:reader", DisplayName: "Organization user reader", Description: "Read users within a single organization.", Group: "User administration (organizational)", Permissions: []Permission{ { Action: ActionOrgUsersRead, Scope: ScopeUsersAll, }, { Action: ActionUsersPermissionsRead, Scope: ScopeUsersAll, }, }, } SettingsReaderRole = RoleDTO{ Name: "fixed:settings:reader", DisplayName: "Setting reader", Description: "Read Grafana instance settings.", Group: "Settings", Permissions: []Permission{ { Action: ActionSettingsRead, Scope: ScopeSettingsAll, }, }, } statsReaderRole = RoleDTO{ Name: "fixed:stats:reader", DisplayName: "Statistics reader", Description: "Read Grafana instance statistics.", Group: "Statistics", Permissions: []Permission{ { Action: ActionServerStatsRead, }, }, } usersReaderRole = RoleDTO{ Name: "fixed:users:reader", DisplayName: "User reader", Description: "Read all users and their information, such as team memberships, authentication tokens, and quotas.", Group: "User administration (global)", Permissions: []Permission{ { Action: ActionUsersRead, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersAuthTokenList, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersQuotasList, Scope: ScopeGlobalUsersAll, }, }, } usersWriterRole = RoleDTO{ Name: "fixed:users:writer", DisplayName: "User writer", Description: "Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users.", Group: "User administration (global)", Permissions: ConcatPermissions(usersReaderRole.Permissions, []Permission{ { Action: ActionUsersPasswordUpdate, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersCreate, }, { Action: ActionUsersWrite, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersDelete, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersEnable, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersDisable, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersPermissionsUpdate, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersLogout, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersAuthTokenUpdate, Scope: ScopeGlobalUsersAll, }, { Action: ActionUsersQuotasUpdate, Scope: ScopeGlobalUsersAll, }, }), } authenticationConfigWriterRole = RoleDTO{ Name: "fixed:authentication.config:writer", DisplayName: "Authentication config writer", Description: "Read and update authentication configuration and access configuration UI.", Group: "Settings", Permissions: []Permission{ { Action: ActionSettingsRead, Scope: ScopeSettingsSAML, }, { Action: ActionSettingsWrite, Scope: ScopeSettingsSAML, }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("azuread"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("azuread"), }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("okta"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("okta"), }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("github"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("github"), }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("gitlab"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("gitlab"), }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("google"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("google"), }, { Action: ActionSettingsRead, Scope: ScopeSettingsOAuth("generic_oauth"), }, { Action: ActionSettingsWrite, Scope: ScopeSettingsOAuth("generic_oauth"), }, }, } generalAuthConfigWriterRole = RoleDTO{ Name: "fixed:general.auth.config:writer", DisplayName: "General authentication config writer", Description: "Read and update the Grafana instance's general authentication configuration.", Group: "Settings", Permissions: []Permission{ { Action: ActionSettingsRead, Scope: "settings:auth:oauth_allow_insecure_email_lookup", }, { Action: ActionSettingsWrite, Scope: "settings:auth:oauth_allow_insecure_email_lookup", }, }, } usagestatsReaderRole = RoleDTO{ Name: "fixed:usagestats:reader", DisplayName: "Usage stats report reader", Description: "View usage statistics report", Group: "Statistics", Permissions: []Permission{ {Action: ActionUsageStatsRead}, }, } ) // Declare OSS roles to the accesscontrol service func DeclareFixedRoles(service Service, cfg *setting.Cfg) error { ldapReader := RoleRegistration{ Role: ldapReaderRole, Grants: []string{RoleGrafanaAdmin}, } ldapWriter := RoleRegistration{ Role: ldapWriterRole, Grants: []string{RoleGrafanaAdmin}, } orgUsersReader := RoleRegistration{ Role: orgUsersReaderRole, Grants: []string{RoleGrafanaAdmin, string(org.RoleAdmin)}, } orgUsersWriter := RoleRegistration{ Role: orgUsersWriterRole, Grants: []string{RoleGrafanaAdmin, string(org.RoleAdmin)}, } settingsReader := RoleRegistration{ Role: SettingsReaderRole, Grants: []string{RoleGrafanaAdmin}, } statsReader := RoleRegistration{ Role: statsReaderRole, Grants: []string{RoleGrafanaAdmin}, } usersReader := RoleRegistration{ Role: usersReaderRole, Grants: []string{RoleGrafanaAdmin}, } usersWriter := RoleRegistration{ Role: usersWriterRole, Grants: []string{RoleGrafanaAdmin}, } generalAuthConfigWriter := RoleRegistration{ Role: generalAuthConfigWriterRole, Grants: []string{RoleGrafanaAdmin}, } // TODO: Move to own service when implemented authenticationConfigWriter := RoleRegistration{ Role: authenticationConfigWriterRole, Grants: []string{RoleGrafanaAdmin}, } usageStatsReader := RoleRegistration{ Role: usagestatsReaderRole, Grants: []string{RoleGrafanaAdmin}, } return service.DeclareFixedRoles( ldapReader, ldapWriter, orgUsersReader, orgUsersWriter, settingsReader, statsReader, usersReader, usersWriter, authenticationConfigWriter, generalAuthConfigWriter, usageStatsReader, ) } func ConcatPermissions(permissions ...[]Permission) []Permission { if permissions == nil { return nil } perms := make([]Permission, 0) for _, p := range permissions { pCopy := make([]Permission, 0, len(p)) copy(pCopy, p) perms = append(perms, p...) } return perms } // PrefixedRoleUID generates a uid from name with the same prefix. // Generated uid is 28 bytes + length of prefix: _base64(sha1(roleName)) func PrefixedRoleUID(roleName string) string { prefix := strings.Split(roleName, ":")[0] + "_" // #nosec G505 Used only for generating a 160 bit hash, it's not used for security purposes hasher := sha1.New() hasher.Write([]byte(roleName)) return fmt.Sprintf("%s%s", prefix, base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))) } // ValidateFixedRole errors when a fixed role does not match expected pattern func ValidateFixedRole(role RoleDTO) error { if !strings.HasPrefix(role.Name, FixedRolePrefix) { return ErrFixedRolePrefixMissing } return nil } // ValidateBuiltInRoles errors when a built-in role does not match expected pattern func ValidateBuiltInRoles(builtInRoles []string) error { for _, br := range builtInRoles { if org.RoleType(br) == org.RoleNone { return ErrNoneRoleAssignment } if !org.RoleType(br).IsValid() && br != RoleGrafanaAdmin { return fmt.Errorf("'%s' %w", br, ErrInvalidBuiltinRole) } } return nil } type RegistrationList struct { mx sync.RWMutex registrations []RoleRegistration } func (m *RegistrationList) Append(regs ...RoleRegistration) { m.mx.Lock() defer m.mx.Unlock() m.registrations = append(m.registrations, regs...) } func (m *RegistrationList) Range(f func(registration RoleRegistration) bool) { m.mx.RLock() defer m.mx.RUnlock() for _, registration := range m.registrations { if ok := f(registration); !ok { return } } } func (m *RegistrationList) Slice() []RoleRegistration { m.mx.RLock() defer m.mx.RUnlock() out := make([]RoleRegistration, len(m.registrations)) copy(out, m.registrations) return out } func BuildBasicRoleDefinitions() map[string]*RoleDTO { return map[string]*RoleDTO{ string(org.RoleAdmin): { Name: BasicRolePrefix + "admin", UID: BasicRoleUIDPrefix + "admin", OrgID: GlobalOrgID, Version: 1, DisplayName: string(org.RoleAdmin), Description: "Admin role", Group: "Basic", Permissions: []Permission{}, Hidden: true, }, string(org.RoleEditor): { Name: BasicRolePrefix + "editor", UID: BasicRoleUIDPrefix + "editor", OrgID: GlobalOrgID, Version: 1, DisplayName: string(org.RoleEditor), Description: "Editor role", Group: "Basic", Permissions: []Permission{}, Hidden: true, }, string(org.RoleViewer): { Name: BasicRolePrefix + "viewer", UID: BasicRoleUIDPrefix + "viewer", OrgID: GlobalOrgID, Version: 1, DisplayName: string(org.RoleViewer), Description: "Viewer role", Group: "Basic", Permissions: []Permission{}, Hidden: true, }, string(org.RoleNone): { Name: BasicRolePrefix + "none", UID: BasicRoleUIDPrefix + "none", OrgID: GlobalOrgID, Version: 1, DisplayName: string(org.RoleNone), Description: "None role", Group: "Basic", Permissions: []Permission{}, Hidden: true, }, RoleGrafanaAdmin: { Name: BasicRolePrefix + "grafana_admin", UID: BasicRoleUIDPrefix + "grafana_admin", OrgID: GlobalOrgID, Version: 1, DisplayName: RoleGrafanaAdmin, Description: "Grafana Admin role", Group: "Basic", Permissions: []Permission{}, Hidden: true, }, } }