mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
IAM: Protect external service accounts frontend list page (#77834)
* Add `isExternal` property to frontend model * Remove enabled and token buttons for external SA * Replace trash icon for lock icon for external SA * Block the role picker for external SA * Filter SA list using the external filter * Add only external filter at backend --------- Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
@@ -6767,7 +6767,7 @@
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
@@ -6822,7 +6822,7 @@
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
|
||||
@@ -18652,7 +18652,7 @@
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
@@ -18707,7 +18707,7 @@
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
||||
import config from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { StoreState, ServiceAccountDTO, AccessControlAction, ServiceAccountStateFilter } from 'app/types';
|
||||
|
||||
@@ -56,6 +57,16 @@ const mapDispatchToProps = {
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const availableFilters = [
|
||||
{ label: 'All', value: ServiceAccountStateFilter.All },
|
||||
{ label: 'With expired tokens', value: ServiceAccountStateFilter.WithExpiredTokens },
|
||||
{ label: 'Disabled', value: ServiceAccountStateFilter.Disabled },
|
||||
];
|
||||
|
||||
if (config.featureToggles.externalServiceAccounts || config.featureToggles.externalServiceAuth) {
|
||||
availableFilters.push({ label: 'Managed', value: ServiceAccountStateFilter.External });
|
||||
}
|
||||
|
||||
export const ServiceAccountsListPageUnconnected = ({
|
||||
page,
|
||||
changePage,
|
||||
@@ -191,11 +202,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
/>
|
||||
</InlineField>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'All', value: ServiceAccountStateFilter.All },
|
||||
{ label: 'With expired tokens', value: ServiceAccountStateFilter.WithExpiredTokens },
|
||||
{ label: 'Disabled', value: ServiceAccountStateFilter.Disabled },
|
||||
]}
|
||||
options={availableFilters}
|
||||
onChange={onStateFilterChange}
|
||||
value={serviceAccountStateFilter}
|
||||
className={styles.filter}
|
||||
|
||||
@@ -91,7 +91,7 @@ const ServiceAccountListItem = memo(
|
||||
<OrgRolePicker
|
||||
aria-label="Role"
|
||||
value={serviceAccount.role}
|
||||
disabled={!canUpdateRole || serviceAccount.isDisabled}
|
||||
disabled={serviceAccount.isExternal || !canUpdateRole || serviceAccount.isDisabled}
|
||||
onChange={(newRole) => onRoleChange(newRole, serviceAccount)}
|
||||
/>
|
||||
</td>
|
||||
@@ -112,32 +112,48 @@ const ServiceAccountListItem = memo(
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<HorizontalGroup justify="flex-end">
|
||||
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsWrite) && !serviceAccount.tokens && (
|
||||
<Button onClick={() => onAddTokenClick(serviceAccount)} disabled={serviceAccount.isDisabled}>
|
||||
Add token
|
||||
</Button>
|
||||
)}
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount) &&
|
||||
(serviceAccount.isDisabled ? (
|
||||
<Button variant="primary" onClick={() => onEnable(serviceAccount)}>
|
||||
Enable
|
||||
{!serviceAccount.isExternal && (
|
||||
<HorizontalGroup justify="flex-end">
|
||||
{contextSrv.hasPermission(AccessControlAction.ServiceAccountsWrite) && !serviceAccount.tokens && (
|
||||
<Button
|
||||
onClick={() => onAddTokenClick(serviceAccount)}
|
||||
disabled={serviceAccount.isDisabled}
|
||||
className={styles.actionButton}
|
||||
>
|
||||
Add token
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="secondary" onClick={() => onDisable(serviceAccount)}>
|
||||
Disable
|
||||
</Button>
|
||||
))}
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsDelete, serviceAccount) && (
|
||||
)}
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount) &&
|
||||
(serviceAccount.isDisabled ? (
|
||||
<Button variant="primary" onClick={() => onEnable(serviceAccount)} className={styles.actionButton}>
|
||||
Enable
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="secondary" onClick={() => onDisable(serviceAccount)} className={styles.actionButton}>
|
||||
Disable
|
||||
</Button>
|
||||
))}
|
||||
{contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsDelete, serviceAccount) && (
|
||||
<IconButton
|
||||
className={styles.deleteButton}
|
||||
name="trash-alt"
|
||||
size="md"
|
||||
onClick={() => onRemoveButtonClick(serviceAccount)}
|
||||
tooltip={`Delete service account ${serviceAccount.name}`}
|
||||
/>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
{serviceAccount.isExternal && (
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<IconButton
|
||||
className={styles.deleteButton}
|
||||
name="trash-alt"
|
||||
disabled={true}
|
||||
name="lock"
|
||||
size="md"
|
||||
onClick={() => onRemoveButtonClick(serviceAccount)}
|
||||
tooltip={`Delete service account ${serviceAccount.name}`}
|
||||
tooltip={`This is a managed service account and cannot be modified.`}
|
||||
/>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -174,6 +190,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
color: ${theme.colors.text.secondary};
|
||||
}
|
||||
`,
|
||||
actionButton: css({
|
||||
minWidth: 85,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ const getStateFilter = (value: ServiceAccountStateFilter) => {
|
||||
return '&expiredTokens=true';
|
||||
case ServiceAccountStateFilter.Disabled:
|
||||
return '&disabled=true';
|
||||
case ServiceAccountStateFilter.External:
|
||||
return '&external=true';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface ServiceAccountDTO extends WithAccessControlMetadata {
|
||||
avatarUrl?: string;
|
||||
createdAt: string;
|
||||
isDisabled: boolean;
|
||||
isExternal?: boolean;
|
||||
teams: string[];
|
||||
role: OrgRole;
|
||||
roles?: Role[];
|
||||
@@ -60,6 +61,7 @@ export interface ServiceAccountProfileState {
|
||||
export enum ServiceAccountStateFilter {
|
||||
All = 'All',
|
||||
WithExpiredTokens = 'WithExpiredTokens',
|
||||
External = 'External',
|
||||
Disabled = 'Disabled',
|
||||
}
|
||||
|
||||
|
||||
@@ -9554,7 +9554,7 @@
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -9609,7 +9609,7 @@
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"isManaged": {
|
||||
"isExternal": {
|
||||
"example": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user