package accesscontrol import ( "encoding/json" "errors" "fmt" "strings" "time" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/slugify" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/org" ) const ( CacheHit = "hit" CacheMiss = "miss" ) var ( ErrInternal = errutil.Internal("accesscontrol.internal") CacheUsageStatuses = []string{CacheHit, CacheMiss} ) // RoleRegistration stores a role and its assignments to built-in roles // (Viewer, Editor, Admin, Grafana Admin) type RoleRegistration struct { Role RoleDTO Grants []string Exclude []string } // Role is the model for Role in RBAC. type Role struct { ID int64 `json:"-" xorm:"pk autoincr 'id'"` OrgID int64 `json:"-" xorm:"org_id"` Version int64 `json:"version"` UID string `xorm:"uid" json:"uid"` Name string `json:"name"` DisplayName string `json:"displayName,omitempty"` Group string `xorm:"group_name" json:"group"` Description string `json:"description"` Hidden bool `json:"hidden"` Updated time.Time `json:"updated"` Created time.Time `json:"created"` } func (r *Role) Global() bool { return r.OrgID == GlobalOrgID } func (r *Role) IsFixed() bool { return strings.HasPrefix(r.Name, FixedRolePrefix) } func (r *Role) IsBasic() bool { return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix) } func (r Role) MarshalJSON() ([]byte, error) { type Alias Role return json.Marshal(&struct { Alias Global bool `json:"global" xorm:"-"` }{ Alias: (Alias)(r), Global: r.Global(), }) } // swagger:ignore type RoleDTO struct { Version int64 `json:"version"` UID string `xorm:"uid" json:"uid"` Name string `json:"name"` DisplayName string `json:"displayName,omitempty"` Description string `json:"description"` Group string `xorm:"group_name" json:"group"` Permissions []Permission `json:"permissions,omitempty"` Delegatable *bool `json:"delegatable,omitempty"` Mapped bool `json:"mapped,omitempty"` Hidden bool `json:"hidden,omitempty"` ID int64 `json:"-" xorm:"pk autoincr 'id'"` OrgID int64 `json:"-" xorm:"org_id"` Updated time.Time `json:"updated"` Created time.Time `json:"created"` } func (r *RoleDTO) LogID() string { var org string if r.Global() { org = "Global" } else { org = fmt.Sprintf("OrgId:%v", r.OrgID) } if r.UID != "" { return fmt.Sprintf("[%s RoleUID:%v]", org, r.UID) } return fmt.Sprintf("[%s Role:%v]", org, r.Name) } func (r *RoleDTO) Role() Role { return Role{ ID: r.ID, OrgID: r.OrgID, UID: r.UID, Version: r.Version, Name: r.Name, DisplayName: r.DisplayName, Group: r.Group, Description: r.Description, Hidden: r.Hidden, Updated: r.Updated, Created: r.Created, } } func (r *RoleDTO) Global() bool { return r.OrgID == GlobalOrgID } func (r *RoleDTO) IsManaged() bool { return strings.HasPrefix(r.Name, ManagedRolePrefix) } func (r *RoleDTO) IsFixed() bool { return strings.HasPrefix(r.Name, FixedRolePrefix) } func (r *RoleDTO) IsPlugin() bool { return strings.HasPrefix(r.Name, PluginRolePrefix) } func (r *RoleDTO) IsBasic() bool { return strings.HasPrefix(r.Name, BasicRolePrefix) || strings.HasPrefix(r.UID, BasicRoleUIDPrefix) } func (r *RoleDTO) IsExternalService() bool { return strings.HasPrefix(r.Name, ExternalServiceRolePrefix) || strings.HasPrefix(r.UID, ExternalServiceRoleUIDPrefix) } // swagger:model RoleDTO type RoleDTOStatic struct { RoleDTO Global bool `json:"global" xorm:"-"` } func (r RoleDTO) MarshalJSON() ([]byte, error) { type Alias RoleDTO return json.Marshal(&struct { Alias Global bool `json:"global" xorm:"-"` }{ Alias: (Alias)(r), Global: r.Global(), }) } type TeamRole struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` OrgID int64 `json:"orgId" xorm:"org_id"` RoleID int64 `json:"roleId" xorm:"role_id"` TeamID int64 `json:"teamId" xorm:"team_id"` Created time.Time } type UserRole struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` OrgID int64 `json:"orgId" xorm:"org_id"` RoleID int64 `json:"roleId" xorm:"role_id"` UserID int64 `json:"userId" xorm:"user_id"` GroupMappingUID string `json:"groupMappingUID" xorm:"group_mapping_uid"` Created time.Time } type BuiltinRole struct { ID int64 `json:"id" xorm:"pk autoincr 'id'"` RoleID int64 `json:"roleId" xorm:"role_id"` OrgID int64 `json:"orgId" xorm:"org_id"` Role string Updated time.Time Created time.Time } // Permission is the model for access control permissions. type Permission struct { ID int64 `json:"-" xorm:"pk autoincr 'id'"` RoleID int64 `json:"-" xorm:"role_id"` Action string `json:"action"` Scope string `json:"scope"` Kind string `json:"-"` Attribute string `json:"-"` Identifier string `json:"-"` Updated time.Time `json:"updated"` Created time.Time `json:"created"` } func (p Permission) OSSPermission() Permission { return Permission{ Action: p.Action, Scope: p.Scope, } } // SplitScope returns kind, attribute and Identifier func (p Permission) SplitScope() (string, string, string) { return SplitScope(p.Scope) } type GetUserPermissionsQuery struct { OrgID int64 UserID int64 Roles []string TeamIDs []int64 RolePrefixes []string } // ResourcePermission is structure that holds all actions that either a team / user / builtin-role // can perform against specific resource. type ResourcePermission struct { ID int64 RoleName string Actions []string Scope string UserID int64 UserUID string UserLogin string UserEmail string TeamID int64 TeamUID string TeamEmail string Team string BuiltInRole string IsManaged bool IsInherited bool IsServiceAccount bool Created time.Time Updated time.Time } func (p *ResourcePermission) Contains(targetActions []string) bool { if len(p.Actions) < len(targetActions) { return false } var contain = func(arr []string, s string) bool { for _, item := range arr { if item == s { return true } } return false } for _, a := range targetActions { if !contain(p.Actions, a) { return false } } return true } type SetResourcePermissionCommand struct { UserID int64 `json:"userId,omitempty"` TeamID int64 `json:"teamId,omitempty"` BuiltinRole string `json:"builtInRole,omitempty"` Permission string `json:"permission"` } type SaveExternalServiceRoleCommand struct { AssignmentOrgID int64 ExternalServiceID string ServiceAccountID int64 Permissions []Permission } func (cmd *SaveExternalServiceRoleCommand) Validate() error { if cmd.ExternalServiceID == "" { return errors.New("external service id not specified") } // slugify the external service id ID for the role to have correct name and uid cmd.ExternalServiceID = slugify.Slugify(cmd.ExternalServiceID) // Check and deduplicate permissions if len(cmd.Permissions) == 0 { return errors.New("no permissions provided") } dedupMap := map[Permission]bool{} dedup := make([]Permission, 0, len(cmd.Permissions)) for i := range cmd.Permissions { if len(cmd.Permissions[i].Action) == 0 { return fmt.Errorf("external service %v requests a permission with no Action", cmd.ExternalServiceID) } if dedupMap[cmd.Permissions[i]] { continue } dedupMap[cmd.Permissions[i]] = true dedup = append(dedup, cmd.Permissions[i]) } cmd.Permissions = dedup if cmd.ServiceAccountID <= 0 { return fmt.Errorf("invalid service account id %d", cmd.ServiceAccountID) } return nil } const ( GlobalOrgID = 0 NoOrgID = int64(-1) GeneralFolderUID = "general" K6FolderUID = "k6-app" RoleGrafanaAdmin = "Grafana Admin" // Permission actions ActionAPIKeyRead = "apikeys:read" ActionAPIKeyCreate = "apikeys:create" ActionAPIKeyDelete = "apikeys:delete" // Users actions ActionUsersRead = "users:read" ActionUsersWrite = "users:write" // We can ignore gosec G101 since this does not contain any credentials. // nolint:gosec ActionUsersAuthTokenList = "users.authtoken:read" // We can ignore gosec G101 since this does not contain any credentials. // nolint:gosec ActionUsersAuthTokenUpdate = "users.authtoken:write" // We can ignore gosec G101 since this does not contain any credentials. // nolint:gosec ActionUsersPasswordUpdate = "users.password:write" ActionUsersDelete = "users:delete" ActionUsersCreate = "users:create" ActionUsersEnable = "users:enable" ActionUsersDisable = "users:disable" ActionUsersPermissionsUpdate = "users.permissions:write" ActionUsersLogout = "users:logout" ActionUsersQuotasList = "users.quotas:read" ActionUsersQuotasUpdate = "users.quotas:write" ActionUsersPermissionsRead = "users.permissions:read" // Org actions ActionOrgsRead = "orgs:read" ActionOrgsPreferencesRead = "orgs.preferences:read" ActionOrgsQuotasRead = "orgs.quotas:read" ActionOrgsWrite = "orgs:write" ActionOrgsPreferencesWrite = "orgs.preferences:write" ActionOrgsQuotasWrite = "orgs.quotas:write" ActionOrgsDelete = "orgs:delete" ActionOrgsCreate = "orgs:create" ActionOrgUsersRead = "org.users:read" ActionOrgUsersAdd = "org.users:add" ActionOrgUsersRemove = "org.users:remove" ActionOrgUsersWrite = "org.users:write" // LDAP actions ActionLDAPUsersRead = "ldap.user:read" ActionLDAPUsersSync = "ldap.user:sync" ActionLDAPStatusRead = "ldap.status:read" ActionLDAPConfigReload = "ldap.config:reload" // Server actions ActionServerStatsRead = "server.stats:read" // Settings actions ActionSettingsRead = "settings:read" ActionSettingsWrite = "settings:write" // Datasources actions ActionDatasourcesExplore = "datasources:explore" // Global Scopes ScopeGlobalUsersAll = "global.users:*" // APIKeys scope ScopeAPIKeysAll = "apikeys:*" // Users scope ScopeUsersAll = "users:*" ScopeUsersPrefix = "users:id:" // Settings scope ScopeSettingsAll = "settings:*" ScopeSettingsSAML = "settings:auth.saml:*" // Team related actions ActionTeamsCreate = "teams:create" ActionTeamsDelete = "teams:delete" ActionTeamsRead = "teams:read" ActionTeamsWrite = "teams:write" ActionTeamsPermissionsRead = "teams.permissions:read" ActionTeamsPermissionsWrite = "teams.permissions:write" // Team related scopes ScopeTeamsAll = "teams:*" // Annotations related actions ActionAnnotationsCreate = "annotations:create" ActionAnnotationsDelete = "annotations:delete" ActionAnnotationsRead = "annotations:read" ActionAnnotationsWrite = "annotations:write" // Alert scopes are divided into two groups. The internal (to Grafana) and the external ones. // For the Grafana ones, given we have ACID control we're able to provide better granularity by defining CRUD options. // For the external ones, we only have read and write permissions due to the lack of atomicity control of the external system. // Alerting rules actions ActionAlertingRuleCreate = "alert.rules:create" ActionAlertingRuleRead = "alert.rules:read" ActionAlertingRuleUpdate = "alert.rules:write" ActionAlertingRuleDelete = "alert.rules:delete" // Alerting instances (+silences) actions ActionAlertingInstanceCreate = "alert.instances:create" ActionAlertingInstanceUpdate = "alert.instances:write" ActionAlertingInstanceRead = "alert.instances:read" ActionAlertingSilencesRead = "alert.silences:read" ActionAlertingSilencesCreate = "alert.silences:create" ActionAlertingSilencesWrite = "alert.silences:write" // Alerting Notification actions (legacy) ActionAlertingNotificationsRead = "alert.notifications:read" ActionAlertingNotificationsWrite = "alert.notifications:write" // Alerting notifications template actions ActionAlertingNotificationsTemplatesRead = "alert.notifications.templates:read" ActionAlertingNotificationsTemplatesWrite = "alert.notifications.templates:write" ActionAlertingNotificationsTemplatesDelete = "alert.notifications.templates:delete" // Alerting notifications time interval actions ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read" ActionAlertingNotificationsTimeIntervalsWrite = "alert.notifications.time-intervals:write" ActionAlertingNotificationsTimeIntervalsDelete = "alert.notifications.time-intervals:delete" // Alerting receiver actions ActionAlertingReceiversList = "alert.notifications.receivers:list" ActionAlertingReceiversRead = "alert.notifications.receivers:read" ActionAlertingReceiversReadSecrets = "alert.notifications.receivers.secrets:read" ActionAlertingReceiversCreate = "alert.notifications.receivers:create" ActionAlertingReceiversUpdate = "alert.notifications.receivers:write" ActionAlertingReceiversDelete = "alert.notifications.receivers:delete" ActionAlertingReceiversTest = "alert.notifications.receivers:test" ActionAlertingReceiversPermissionsRead = "receivers.permissions:read" ActionAlertingReceiversPermissionsWrite = "receivers.permissions:write" // Alerting routes policies actions ActionAlertingRoutesRead = "alert.notifications.routes:read" ActionAlertingRoutesWrite = "alert.notifications.routes:write" // External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system. ActionAlertingRuleExternalWrite = "alert.rules.external:write" ActionAlertingRuleExternalRead = "alert.rules.external:read" // External alerting instances actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system. ActionAlertingInstancesExternalWrite = "alert.instances.external:write" ActionAlertingInstancesExternalRead = "alert.instances.external:read" // External alerting notifications actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system. ActionAlertingNotificationsExternalWrite = "alert.notifications.external:write" ActionAlertingNotificationsExternalRead = "alert.notifications.external:read" // Alerting provisioning actions ActionAlertingProvisioningRead = "alert.provisioning:read" ActionAlertingProvisioningReadSecrets = "alert.provisioning.secrets:read" ActionAlertingProvisioningWrite = "alert.provisioning:write" ActionAlertingRulesProvisioningRead = "alert.rules.provisioning:read" ActionAlertingRulesProvisioningWrite = "alert.rules.provisioning:write" ActionAlertingNotificationsProvisioningRead = "alert.notifications.provisioning:read" ActionAlertingNotificationsProvisioningWrite = "alert.notifications.provisioning:write" // ActionAlertingProvisioningSetStatus Gives access to set provisioning status to alerting resources. Cannot be used alone. Only in conjunction with other permissions. ActionAlertingProvisioningSetStatus = "alert.provisioning.provenance:write" // Feature Management actions ActionFeatureManagementRead = "featuremgmt.read" ActionFeatureManagementWrite = "featuremgmt.write" // Library Panel actions ActionLibraryPanelsCreate = "library.panels:create" ActionLibraryPanelsRead = "library.panels:read" ActionLibraryPanelsWrite = "library.panels:write" ActionLibraryPanelsDelete = "library.panels:delete" // Usage stats actions ActionUsageStatsRead = "server.usagestats.report:read" ) var ( // Team scope ScopeTeamsID = Scope("teams", "id", Parameter(":teamId")) ScopeSettingsOAuth = func(provider string) string { return Scope("settings", "auth."+provider, "*") } ScopeSettingsLDAP = Scope("settings", "auth.ldap", "*") // Annotation scopes ScopeAnnotationsRoot = "annotations" ScopeAnnotationsProvider = NewScopeProvider(ScopeAnnotationsRoot) ScopeAnnotationsAll = ScopeAnnotationsProvider.GetResourceAllScope() ScopeAnnotationsID = Scope(ScopeAnnotationsRoot, "id", Parameter(":annotationId")) ScopeAnnotationsTypeDashboard = ScopeAnnotationsProvider.GetResourceScopeType(annotations.Dashboard.String()) ScopeAnnotationsTypeOrganization = ScopeAnnotationsProvider.GetResourceScopeType(annotations.Organization.String()) ) func BuiltInRolesWithParents(builtInRoles []string) map[string]struct{} { res := map[string]struct{}{} for _, br := range builtInRoles { res[br] = struct{}{} if br != RoleGrafanaAdmin { for _, parent := range org.RoleType(br).Parents() { res[string(parent)] = struct{}{} } } } return res } // Evaluators // TeamsAccessEvaluator is used to protect the "Configuration > Teams" page access // grants access to a user when they can either create teams or can read and update a team var TeamsAccessEvaluator = EvalAny( EvalPermission(ActionTeamsCreate), EvalAll( EvalPermission(ActionTeamsRead), EvalAny( EvalPermission(ActionTeamsWrite), EvalPermission(ActionTeamsPermissionsWrite), EvalPermission(ActionTeamsPermissionsRead), ), ), ) // TeamsEditAccessEvaluator is used to protect the "Configuration > Teams > edit" page access var TeamsEditAccessEvaluator = EvalAll( EvalPermission(ActionTeamsRead), EvalAny( EvalPermission(ActionTeamsCreate), EvalPermission(ActionTeamsWrite), EvalPermission(ActionTeamsPermissionsWrite), ), ) // OrgPreferencesAccessEvaluator is used to protect the "Configure > Preferences" page access var OrgPreferencesAccessEvaluator = EvalAny( EvalAll( EvalPermission(ActionOrgsRead), EvalPermission(ActionOrgsWrite), ), EvalAll( EvalPermission(ActionOrgsPreferencesRead), EvalPermission(ActionOrgsPreferencesWrite), ), ) // OrgsAccessEvaluator is used to protect the "Server Admin > Orgs" page access // (you need to have read access to update or delete orgs; read is the minimum) var OrgsAccessEvaluator = EvalPermission(ActionOrgsRead) // OrgsCreateAccessEvaluator is used to protect the "Server Admin > Orgs > New Org" page access var OrgsCreateAccessEvaluator = EvalAll( EvalPermission(ActionOrgsRead), EvalPermission(ActionOrgsCreate), ) // ApiKeyAccessEvaluator is used to protect the "Configuration > API keys" page access var ApiKeyAccessEvaluator = EvalPermission(ActionAPIKeyRead) type QueryWithOrg struct { OrgId *int64 `json:"orgId"` Global bool `json:"global"` }