Access control: expose SA frontend to users with the right permissions (#47727)

* expose frontend to users with permissions

* cover the ui endpoints

* fix permissions
pull/47768/head
Ieva 3 years ago committed by GitHub
parent 6f31a69bfd
commit e50bd5cac8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      pkg/api/accesscontrol.go
  2. 7
      pkg/api/api.go
  3. 9
      pkg/api/index.go
  4. 37
      public/app/features/serviceaccounts/ServiceAccountsListItem.tsx
  5. 18
      public/app/features/serviceaccounts/state/actions.ts
  6. 4
      public/app/routes/routes.tsx
  7. 3
      public/app/types/accessControl.ts

@ -5,6 +5,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/setting"
)
@ -462,6 +463,12 @@ var teamsEditAccessEvaluator = ac.EvalAll(
),
)
// apiKeyAccessEvaluator is used to protect the "Configuration > API keys" page access
var apiKeyAccessEvaluator = ac.EvalPermission(ac.ActionAPIKeyRead)
// serviceAccountAccessEvaluator is used to protect the "Configuration > Service accounts" page access
var serviceAccountAccessEvaluator = ac.EvalPermission(serviceaccounts.ActionRead)
// Metadata helpers
// getAccessControlMetadata returns the accesscontrol metadata associated with a given resource
func (hs *HTTPServer) getAccessControlMetadata(c *models.ReqContext,

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/web"
)
@ -62,9 +63,9 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index)
r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index)
r.Get("/org/serviceaccounts", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/serviceaccounts/:serviceAccountId", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
r.Get("/org/serviceaccounts", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index)
r.Get("/org/serviceaccounts/:serviceAccountId", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index)
r.Get("/org/apikeys/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyRead)), hs.Index)
r.Get("/dashboard/import/", reqSignedIn, hs.Index)
r.Get("/configuration", reqGrafanaAdmin, hs.Index)
r.Get("/admin", reqGrafanaAdmin, hs.Index)

@ -149,8 +149,11 @@ func (hs *HTTPServer) getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error)
}
func enableServiceAccount(hs *HTTPServer, c *models.ReqContext) bool {
return (c.OrgRole == models.ROLE_ADMIN || (hs.Cfg.EditorsCanAdmin && c.OrgRole == models.ROLE_EDITOR)) &&
hs.Features.IsEnabled(featuremgmt.FlagServiceAccounts)
if !hs.Features.IsEnabled(featuremgmt.FlagServiceAccounts) {
return false
}
hasAccess := ac.HasAccess(hs.AccessControl, c)
return hasAccess(ac.ReqOrgAdmin, serviceAccountAccessEvaluator)
}
func (hs *HTTPServer) ReqCanAdminTeams(c *models.ReqContext) bool {
@ -291,7 +294,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
})
}
if c.OrgRole == models.ROLE_ADMIN {
if hasAccess(ac.ReqOrgAdmin, apiKeyAccessEvaluator) {
configNodes = append(configNodes, &dtos.NavLink{
Text: "API keys",
Id: "apikeys",

@ -28,7 +28,10 @@ const ServiceAccountListItem = memo(
const editUrl = `org/serviceaccounts/${serviceAccount.id}`;
const styles = useStyles2(getStyles);
const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount);
const rolePickerDisabled = !canUpdateRole;
const displayRolePicker =
contextSrv.hasPermission(AccessControlAction.ActionRolesList) &&
contextSrv.hasPermission(AccessControlAction.ActionUserRolesList);
const enableRolePicker = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate) && canUpdateRole;
return (
<tr key={serviceAccount.id}>
@ -61,26 +64,30 @@ const ServiceAccountListItem = memo(
{serviceAccount.login}
</a>
</td>
<td className={cx('link-td', styles.iconRow)}>
{contextSrv.licensedAccessControlEnabled() ? (
<UserRolePicker
userId={serviceAccount.id}
orgId={serviceAccount.orgId}
builtInRole={serviceAccount.role}
onBuiltinRoleChange={(newRole) => onRoleChange(newRole, serviceAccount)}
roleOptions={roleOptions}
builtInRoles={builtInRoles}
disabled={rolePickerDisabled}
/>
) : (
{contextSrv.licensedAccessControlEnabled() ? (
displayRolePicker && (
<td className={cx('link-td', styles.iconRow)}>
<UserRolePicker
userId={serviceAccount.id}
orgId={serviceAccount.orgId}
builtInRole={serviceAccount.role}
onBuiltinRoleChange={(newRole) => onRoleChange(newRole, serviceAccount)}
roleOptions={roleOptions}
builtInRoles={builtInRoles}
disabled={!enableRolePicker}
/>
</td>
)
) : (
<td className={cx('link-td', styles.iconRow)}>
<OrgRolePicker
aria-label="Role"
value={serviceAccount.role}
disabled={!canUpdateRole}
onChange={(newRole) => onRoleChange(newRole, serviceAccount)}
/>
)}
</td>
</td>
)}
<td className="link-td max-width-10">
<a
className="ellipsis"

@ -1,4 +1,4 @@
import { ServiceAccountDTO, ThunkResult, ServiceAccountFilter } from '../../../types';
import { ServiceAccountDTO, ThunkResult, ServiceAccountFilter, AccessControlAction } from '../../../types';
import { getBackendSrv, locationService } from '@grafana/runtime';
import {
acOptionsLoaded,
@ -16,6 +16,7 @@ import {
import { accessControlQueryParam } from 'app/core/utils/accessControl';
import { fetchBuiltinRoles, fetchRoleOptions } from 'app/core/components/RolePicker/api';
import { debounce } from 'lodash';
import { contextSrv } from '../../../core/services/context_srv';
import { ServiceAccountToken } from '../CreateServiceAccountTokenModal';
const BASE_URL = `/api/serviceaccounts`;
@ -23,10 +24,17 @@ const BASE_URL = `/api/serviceaccounts`;
export function fetchACOptions(): ThunkResult<void> {
return async (dispatch) => {
try {
const options = await fetchRoleOptions();
dispatch(acOptionsLoaded(options));
const builtInRoles = await fetchBuiltinRoles();
dispatch(builtInRolesLoaded(builtInRoles));
if (contextSrv.licensedAccessControlEnabled() && contextSrv.hasPermission(AccessControlAction.ActionRolesList)) {
const options = await fetchRoleOptions();
dispatch(acOptionsLoaded(options));
}
if (
contextSrv.licensedAccessControlEnabled() &&
contextSrv.hasPermission(AccessControlAction.ActionBuiltinRolesList)
) {
const builtInRoles = await fetchBuiltinRoles();
dispatch(builtInRolesLoaded(builtInRoles));
}
} catch (error) {
console.error(error);
}

@ -195,14 +195,14 @@ export function getAppRoutes(): RouteDescriptor[] {
},
{
path: '/org/apikeys',
roles: () => ['Editor', 'Admin'],
roles: () => contextSrv.evaluatePermission(() => ['Admin'], [AccessControlAction.ActionAPIKeysRead]),
component: SafeDynamicImport(
() => import(/* webpackChunkName: "ApiKeysPage" */ 'app/features/api-keys/ApiKeysPage')
),
},
{
path: '/org/serviceaccounts',
roles: () => ['Editor', 'Admin'],
roles: () => contextSrv.evaluatePermission(() => ['Admin'], [AccessControlAction.ServiceAccountsRead]),
component: SafeDynamicImport(
() =>
import(/* webpackChunkName: "ServiceAccountsPage" */ 'app/features/serviceaccounts/ServiceAccountsListPage')

@ -23,6 +23,7 @@ export enum AccessControlAction {
UsersQuotasList = 'users.quotas:list',
UsersQuotasUpdate = 'users.quotas:update',
ServiceAccountsRead = 'serviceaccounts:read',
ServiceAccountsCreate = 'serviceaccounts:create',
ServiceAccountsWrite = 'serviceaccounts:write',
ServiceAccountsDelete = 'serviceaccounts:delete',
@ -107,6 +108,8 @@ export enum AccessControlAction {
// External alerting notifications actions.
AlertingNotificationsExternalWrite = 'alert.notifications.external:write',
AlertingNotificationsExternalRead = 'alert.notifications.external:read',
ActionAPIKeysRead = 'apikeys:read',
}
export interface Role {

Loading…
Cancel
Save