Skip to content

Commit 1b959dd

Browse files
Jethro-Mavoidik
authored andcommitted
[PLAT-18841][PLAT-18818] Add stricter RBAC to continuous backup actions
Summary: Prior to this diff, all continuous/isolated YBA backup API requests only required `READ` access on `OTHER` resource. We want to be stricter here. In particular, restoring from a YBA backup is an extremely destructive operation that can overwrite existing data. To prevent accidental or unauthorized use, this change restricts all create/edit backup & restore actions to users with SUPER_ADMIN_ACTION permissions only. On the UI side, this diff adds logic to read and verify that the user has sufficient permissions for performing continuous/isolated backup actions. Test Plan: Test continuous/isolated backup workflow with users who have different access levels. Users who don't have SUPER_ADMIN_ACTION permission should not be allowed to restore YBA backups. Verify the restrictions put in place by our RBAC implementation are as expected. {F409070} {F409071} Read-only User {F409072} {F409073} Reviewers: muthu, rmadhavan Reviewed By: muthu Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D47813
1 parent 60abc22 commit 1b959dd

File tree

10 files changed

+139
-67
lines changed

10 files changed

+139
-67
lines changed

managed/src/main/resources/openapi/paths/_index.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@
203203
x-yba-api-authz:
204204
- requiredPermission:
205205
resourceType: other
206-
action: read
206+
action: SUPER_ADMIN_ACTIONS
207207
resourceLocation:
208208
path: customers
209209
sourceType: endpoint
@@ -244,7 +244,7 @@
244244
x-yba-api-authz:
245245
- requiredPermission:
246246
resourceType: other
247-
action: read
247+
action: SUPER_ADMIN_ACTIONS
248248
resourceLocation:
249249
path: customers
250250
sourceType: endpoint
@@ -295,7 +295,7 @@
295295
x-yba-api-authz:
296296
- requiredPermission:
297297
resourceType: other
298-
action: read
298+
action: SUPER_ADMIN_ACTIONS
299299
resourceLocation:
300300
path: customers
301301
sourceType: endpoint
@@ -324,7 +324,7 @@
324324
x-yba-api-authz:
325325
- requiredPermission:
326326
resourceType: other
327-
action: read
327+
action: SUPER_ADMIN_ACTIONS
328328
resourceLocation:
329329
path: customers
330330
sourceType: endpoint
@@ -365,7 +365,7 @@
365365
x-yba-api-authz:
366366
- requiredPermission:
367367
resourceType: other
368-
action: read
368+
action: SUPER_ADMIN_ACTIONS
369369
resourceLocation:
370370
path: customers
371371
sourceType: endpoint
@@ -406,7 +406,7 @@
406406
x-yba-api-authz:
407407
- requiredPermission:
408408
resourceType: other
409-
action: read
409+
action: SUPER_ADMIN_ACTIONS
410410
resourceLocation:
411411
path: customers
412412
sourceType: endpoint

managed/src/main/resources/openapi/paths/continuousbackups.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
x-yba-api-authz:
6262
- requiredPermission:
6363
resourceType: other
64-
action: read
64+
action: SUPER_ADMIN_ACTIONS
6565
resourceLocation:
6666
path: customers
6767
sourceType: endpoint
@@ -102,7 +102,7 @@
102102
x-yba-api-authz:
103103
- requiredPermission:
104104
resourceType: other
105-
action: read
105+
action: SUPER_ADMIN_ACTIONS
106106
resourceLocation:
107107
path: customers
108108
sourceType: endpoint
@@ -153,7 +153,7 @@
153153
x-yba-api-authz:
154154
- requiredPermission:
155155
resourceType: other
156-
action: read
156+
action: SUPER_ADMIN_ACTIONS
157157
resourceLocation:
158158
path: customers
159159
sourceType: endpoint
@@ -182,7 +182,7 @@
182182
x-yba-api-authz:
183183
- requiredPermission:
184184
resourceType: other
185-
action: read
185+
action: SUPER_ADMIN_ACTIONS
186186
resourceLocation:
187187
path: customers
188188
sourceType: endpoint

managed/src/main/resources/openapi/paths/isolatedbackups.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
x-yba-api-authz:
3434
- requiredPermission:
3535
resourceType: other
36-
action: read
36+
action: SUPER_ADMIN_ACTIONS
3737
resourceLocation:
3838
path: customers
3939
sourceType: endpoint
@@ -74,7 +74,7 @@
7474
x-yba-api-authz:
7575
- requiredPermission:
7676
resourceType: other
77-
action: read
77+
action: SUPER_ADMIN_ACTIONS
7878
resourceLocation:
7979
path: customers
8080
sourceType: endpoint

managed/ui/src/components/panels/UniverseDisplayPanel/OnboardingPanel.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { makeStyles } from '@material-ui/core/styles';
33
import { Typography } from '@material-ui/core';
44
import { useTranslation } from 'react-i18next';
55
import { Link } from 'react-router';
6+
67
import { YBButton } from '../../../redesign/components/YBButton/YBButton';
7-
import { RestoreYbaBackupFormModal } from '../../../redesign/features/continuous-backup/RestoreYbaBackupFormModal';
88
import { ReactComponent as BriefcaseIcon } from '@app/redesign/assets/briefcase.svg';
99
import { ReactComponent as BulbIcon } from '@app/redesign/assets/bulb2.svg';
10+
import { RestoreYbaBackupModal } from '@app/redesign/features/continuous-backup/RestoreYbaBackupModal';
11+
import {
12+
hasNecessaryPerm,
13+
RbacValidator
14+
} from '@app/redesign/features/rbac/common/RbacApiPermValidator';
15+
import { ApiPermissionMap } from '@app/redesign/features/rbac/ApiAndUserPermMapping';
1016

1117
const useStyles = makeStyles((theme) => ({
1218
container: {
@@ -97,14 +103,22 @@ export const OnboardingPanel = () => {
97103
<div className={classes.card}>
98104
<BriefcaseIcon width={62} height={62} />
99105
<Typography className={classes.cardText}>{t('restoreBackupCard.title')}</Typography>
100-
<YBButton variant="primary" onClick={openRestoreYbaBackupModal}>
101-
{t('restoreBackupCard.buttonText')}
102-
</YBButton>
106+
<RbacValidator
107+
customValidateFunction={() =>
108+
hasNecessaryPerm(ApiPermissionMap.RESTORE_CONTINUOUS_YBA_BACKUP) ||
109+
hasNecessaryPerm(ApiPermissionMap.RESTORE_ISOLATED_YBA_BACKUP)
110+
}
111+
isControl
112+
>
113+
<YBButton variant="primary" onClick={openRestoreYbaBackupModal}>
114+
{t('restoreBackupCard.buttonText')}
115+
</YBButton>
116+
</RbacValidator>
103117
</div>
104118
</div>
105119

106120
{isRestoreYbaBackupModalOpen && (
107-
<RestoreYbaBackupFormModal
121+
<RestoreYbaBackupModal
108122
modalProps={{ open: isRestoreYbaBackupModalOpen, onClose: closeRestoreYbaBackupModal }}
109123
/>
110124
)}

managed/ui/src/redesign/features/continuous-backup/ContinuousBackupActionBar.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Box, useTheme } from '@material-ui/core';
22
import { useState } from 'react';
33
import { useTranslation } from 'react-i18next';
4+
45
import { YBButton } from '../../components';
6+
import { ApiPermissionMap } from '../rbac/ApiAndUserPermMapping';
7+
import { hasNecessaryPerm, RbacValidator } from '../rbac/common/RbacApiPermValidator';
58
import { CreateYbaBackupModal } from './CreateYbaBackupModal';
6-
import { RestoreYbaBackupFormModal } from './RestoreYbaBackupFormModal';
79
import { RestoreYbaBackupModal } from './RestoreYbaBackupModal';
810

911
const TRANSLATION_KEY_PREFIX = 'continuousBackup.actionBar';
@@ -21,20 +23,30 @@ export const ContinuousBackupActionBar = () => {
2123

2224
return (
2325
<Box display="flex" gridGap={theme.spacing(1)}>
24-
<YBButton
25-
variant="secondary"
26-
onClick={openCreateYbaBackupModal}
27-
data-testid="ContinuousBackupActionBar-OneTimeExportButton"
28-
>
29-
{t('oneTimeBackup')}
30-
</YBButton>
31-
<YBButton
32-
variant="secondary"
33-
onClick={openRestoreYbaBackupModal}
34-
data-testid="ContinuousBackupActionBar-AdvancedRestoreButton"
26+
<RbacValidator accessRequiredOn={ApiPermissionMap.CREATE_ISOLATED_YBA_BACKUP} isControl>
27+
<YBButton
28+
variant="secondary"
29+
onClick={openCreateYbaBackupModal}
30+
data-testid="ContinuousBackupActionBar-OneTimeExportButton"
31+
>
32+
{t('oneTimeBackup')}
33+
</YBButton>
34+
</RbacValidator>
35+
<RbacValidator
36+
customValidateFunction={() =>
37+
hasNecessaryPerm(ApiPermissionMap.RESTORE_CONTINUOUS_YBA_BACKUP) ||
38+
hasNecessaryPerm(ApiPermissionMap.RESTORE_ISOLATED_YBA_BACKUP)
39+
}
40+
isControl
3541
>
36-
{t('advancedRestore')}
37-
</YBButton>
42+
<YBButton
43+
variant="secondary"
44+
onClick={openRestoreYbaBackupModal}
45+
data-testid="ContinuousBackupActionBar-AdvancedRestoreButton"
46+
>
47+
{t('advancedRestore')}
48+
</YBButton>
49+
</RbacValidator>
3850
{isCreateYbaBackupModalOpen && (
3951
<CreateYbaBackupModal
4052
modalProps={{ open: isCreateYbaBackupModalOpen, onClose: closeCreateYbaBackupModal }}

managed/ui/src/redesign/features/continuous-backup/ContinuousBackupCard.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
import { useFormatDatetime } from '../../helpers/DateUtils';
1818
import { DeleteContinuousBackupConfigModal } from './DeleteContinuousBackupConfigModal';
1919
import { getIsLastPlatformBackupOld } from './utils';
20+
import { ApiPermissionMap } from '../rbac/ApiAndUserPermMapping';
21+
import { RbacValidator } from '../rbac/common/RbacApiPermValidator';
2022

2123
interface ContinuousBackupCardProps {
2224
continuousBackupConfig: ContinuousBackup;
@@ -127,14 +129,18 @@ export const ContinuousBackupCard = ({ continuousBackupConfig }: ContinuousBacku
127129
<div className={classes.cardHeader}>
128130
<Typography variant="h5">{t('title')}</Typography>
129131
<div className={classes.cardActionsContainer}>
130-
<YBButton variant="secondary" onClick={openConfigureContinuousBackupModal}>
131-
<PenIcon className={classes.icon} />
132-
<Typography variant="body2">{t('edit', { keyPrefix: 'common' })}</Typography>
133-
</YBButton>
134-
<YBButton variant="secondary" onClick={openDeleteContinuousBackupModal}>
135-
<TrashIcon className={classes.icon} />
136-
<Typography variant="body2">{t('button.remove')}</Typography>
137-
</YBButton>
132+
<RbacValidator accessRequiredOn={ApiPermissionMap.EDIT_CONTINUOUS_YBA_BACKUP} isControl>
133+
<YBButton variant="secondary" onClick={openConfigureContinuousBackupModal}>
134+
<PenIcon className={classes.icon} />
135+
<Typography variant="body2">{t('edit', { keyPrefix: 'common' })}</Typography>
136+
</YBButton>
137+
</RbacValidator>
138+
<RbacValidator accessRequiredOn={ApiPermissionMap.DELETE_CONTINUOUS_YBA_BACKUP} isControl>
139+
<YBButton variant="secondary" onClick={openDeleteContinuousBackupModal}>
140+
<TrashIcon className={classes.icon} />
141+
<Typography variant="body2">{t('button.remove')}</Typography>
142+
</YBButton>
143+
</RbacValidator>
138144
</div>
139145
</div>
140146
<div className={classes.cardBody}>

managed/ui/src/redesign/features/continuous-backup/EnableContinuousBackupPrompt.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const EnableContinuousBackupPrompt = ({
7979
<Typography className={classes.promptPrimaryText} variant="body2">
8080
{t('featureDescription')}
8181
</Typography>
82-
<RbacValidator accessRequiredOn={ApiPermissionMap.CREATE_CONTINUOUS_BACKUP} isControl>
82+
<RbacValidator accessRequiredOn={ApiPermissionMap.CREATE_CONTINUOUS_YBA_BACKUP} isControl>
8383
<YBButton
8484
style={{ minWidth: '200px' }}
8585
variant="primary"

managed/ui/src/redesign/features/continuous-backup/RestoreYbaBackupFormModal.tsx

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { api, CUSTOMER_CONFIG_QUERY_KEY } from '@app/redesign/helpers/api';
2020
import { ReactComponent as SelectedIcon } from '@app/redesign/assets/circle-selected.svg';
2121
import { ReactComponent as UnselectedIcon } from '@app/redesign/assets/circle-empty.svg';
2222
import { ReactComponent as WarningIcon } from '@app/redesign/assets/alert.svg';
23+
import { RbacValidator } from '../rbac/common/RbacApiPermValidator';
24+
import { ApiPermissionMap } from '../rbac/ApiAndUserPermMapping';
2325

2426
import toastStyles from '../../../redesign/styles/toastStyles.module.scss';
2527

@@ -278,32 +280,44 @@ export const RestoreYbaBackupFormModal = ({ modalProps }: RestoreYbaBackupFormMo
278280
render={({ field: { onChange } }) => (
279281
<Box display="flex" gridGap={theme.spacing(1)}>
280282
<Box width="50%">
281-
<div
282-
className={clsx(
283-
classes.optionCard,
284-
backupType === BackupType.CLOUD && classes.selected
285-
)}
286-
onClick={() => handleOptionCardClick(BackupType.CLOUD, onChange)}
283+
<RbacValidator
284+
accessRequiredOn={ApiPermissionMap.RESTORE_CONTINUOUS_YBA_BACKUP}
285+
isControl
286+
overrideStyle={{ display: 'unset' }}
287287
>
288-
<Typography variant="body1">{t('option.cloudBackup')}</Typography>
289-
<Box display="flex" alignItems="center" marginLeft="auto">
290-
{backupType === BackupType.CLOUD ? <SelectedIcon /> : <UnselectedIcon />}
291-
</Box>
292-
</div>
288+
<div
289+
className={clsx(
290+
classes.optionCard,
291+
backupType === BackupType.CLOUD && classes.selected
292+
)}
293+
onClick={() => handleOptionCardClick(BackupType.CLOUD, onChange)}
294+
>
295+
<Typography variant="body1">{t('option.cloudBackup')}</Typography>
296+
<Box display="flex" alignItems="center" marginLeft="auto">
297+
{backupType === BackupType.CLOUD ? <SelectedIcon /> : <UnselectedIcon />}
298+
</Box>
299+
</div>
300+
</RbacValidator>
293301
</Box>
294302
<Box width="50%">
295-
<div
296-
className={clsx(
297-
classes.optionCard,
298-
backupType === BackupType.LOCAL && classes.selected
299-
)}
300-
onClick={() => handleOptionCardClick(BackupType.LOCAL, onChange)}
303+
<RbacValidator
304+
accessRequiredOn={ApiPermissionMap.RESTORE_ISOLATED_YBA_BACKUP}
305+
isControl
306+
overrideStyle={{ display: 'unset' }}
301307
>
302-
<Typography variant="body1">{t('option.localBackup')}</Typography>
303-
<Box display="flex" alignItems="center" marginLeft="auto">
304-
{backupType === BackupType.LOCAL ? <SelectedIcon /> : <UnselectedIcon />}
305-
</Box>
306-
</div>
308+
<div
309+
className={clsx(
310+
classes.optionCard,
311+
backupType === BackupType.LOCAL && classes.selected
312+
)}
313+
onClick={() => handleOptionCardClick(BackupType.LOCAL, onChange)}
314+
>
315+
<Typography variant="body1">{t('option.localBackup')}</Typography>
316+
<Box display="flex" alignItems="center" marginLeft="auto">
317+
{backupType === BackupType.LOCAL ? <SelectedIcon /> : <UnselectedIcon />}
318+
</Box>
319+
</div>
320+
</RbacValidator>
307321
</Box>
308322
</Box>
309323
)}

managed/ui/src/redesign/features/rbac/ApiAndUserPermMapping.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,18 +1085,30 @@ export const ApiPermissionMap = {
10851085
requestType: ApiRequestType.POST,
10861086
endpoint: '/settings/ha/internal/upload'
10871087
},
1088-
CREATE_CONTINUOUS_BACKUP: {
1088+
CREATE_CONTINUOUS_YBA_BACKUP: {
10891089
requestType: ApiRequestType.POST,
10901090
endpoint: '/auto-yba-backups'
10911091
},
1092-
DELETE_CONTINUOUS_BACKUP: {
1092+
DELETE_CONTINUOUS_YBA_BACKUP: {
10931093
requestType: ApiRequestType.DELETE,
10941094
endpoint: '/auto-yba-backups/$bUUID<[^/]+>'
10951095
},
1096-
EDIT_CONTINUOUS_BACKUP: {
1096+
EDIT_CONTINUOUS_YBA_BACKUP: {
10971097
requestType: ApiRequestType.DELETE,
10981098
endpoint: '/auto-yba-backups/$bUUID<[^/]+>'
10991099
},
1100+
RESTORE_CONTINUOUS_YBA_BACKUP: {
1101+
requestType: ApiRequestType.POST,
1102+
endpoint: '/auto-yba-backups/restore'
1103+
},
1104+
CREATE_ISOLATED_YBA_BACKUP: {
1105+
requestType: ApiRequestType.POST,
1106+
endpoint: '/yba-backups'
1107+
},
1108+
RESTORE_ISOLATED_YBA_BACKUP: {
1109+
requestType: ApiRequestType.POST,
1110+
endpoint: '/yba-backups/restore'
1111+
},
11001112
GET_UNIVERSE_PROXY: {
11011113
requestType: ApiRequestType.GET,
11021114
endpoint: '/universes/$uniUUID<[^/]+>/proxy/[^/]+'

managed/ui/src/redesign/features/rbac/common/RbacUtils.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,25 @@ export const setIsRbacEnabled = (flag: boolean) => {
5050
};
5151

5252
export const isRbacEnabled = () => {
53-
return localStorage.getItem(rbac_identifier) === 'true';
53+
try {
54+
if (typeof localStorage === 'undefined' || localStorage === null) {
55+
return false;
56+
}
57+
return localStorage.getItem(rbac_identifier) === 'true';
58+
} catch {
59+
return false;
60+
}
5461
};
5562

5663
export const getRbacEnabledVal = () => {
57-
return localStorage.getItem(rbac_identifier);
64+
try {
65+
if (typeof localStorage === 'undefined' || localStorage === null) {
66+
return null;
67+
}
68+
return localStorage.getItem(rbac_identifier);
69+
} catch {
70+
return null;
71+
}
5872
};
5973

6074
export const clearRbacCreds = () => {

0 commit comments

Comments
 (0)