Skip to content

Commit

Permalink
[PLAT-13957] Update RBAC wrapper for xCluster DR
Browse files Browse the repository at this point in the history
Summary:
7fef119 / D36651 adds a new
action `XCLUSTER` and modifies the permissions for xCluster replication /
xCluster DR APIs.

```
  {
    "requestType": "DELETE",
    "endpoint": "/api/v1/customers/$cUUID<[^/]+>/dr_configs/$drUUID<[^/]+>",
    "rbacPermissionDefinitions": {
      "operator": "OR",
      "rbacPermissionDefinitionList": [
        {
          "operator": "AND",
          "rbacPermissionList": [
            {
              "resourceType": "UNIVERSE",
              "action": "XCLUSTER"
            },
            {
              "resourceType": "UNIVERSE",
              "action": "XCLUSTER"
            }
          ]
        }
      ]
    }
  },
```

This diff updates the RBAC wrapper on the UI to support the change
in permission requirements. We now pass the source and target universe uuid to validate the
user has permission to perform `XCLUSTER` action on those resources.

Test Plan:
- Create readonly user. Verify that the readonly user can't
perform any xCluster/xCluster DR actions.
{F274627}
- Create a user with xCluster permissions on both source and target. Verify the user can
perform xCluster/xCluster DR actions.
{F274634}
- Create a user with xCluster permission only on source universe and read only permission on target universe.
Verify the user is not able to perform xCluster replication/xCluster DR actions.
{F274629}
{F274631}

{F274655}
{F274663}
   - The user can try to create an xCluster config on the source universe, but they won't be able to select any
      target universe that they don't have xCluster permission for.

Reviewers: vbansal, kkannan, hzare, cwang

Reviewed By: kkannan

Subscribers: yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D37175
  • Loading branch information
Jethro-M committed Aug 12, 2024
1 parent b1a90b9 commit 581648f
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
* You may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://github.com/YugaByte/yugabyte-db/blob/master/licenses/POLYFORM-FREE-TRIAL-LICENSE-1.0.0.txt
*/
import Select, { Styles } from 'react-select';
import { Box, FormHelperText, useTheme } from '@material-ui/core';
import Select, { components, OptionProps, Styles } from 'react-select';
import { Box, FormHelperText, Typography, useTheme } from '@material-ui/core';
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';

import { SelectComponents } from 'react-select/src/components';
import { YBTooltip } from '../../../../../redesign/components';

export type ReactSelectOption = { value: any; label: string; isDisabled?: boolean };
export type ReactSelectGroupedOption = { label: string; options: ReactSelectOption[] };
Expand Down Expand Up @@ -84,7 +86,7 @@ export const YBReactSelectField = <T extends FieldValues>({
onChange={handleChange}
onBlur={field.onBlur}
value={field.value}
components={components}
components={{ Option: Option, ...components }}
options={options}
isDisabled={isDisabled}
placeholder={placeholder}
Expand All @@ -97,3 +99,22 @@ export const YBReactSelectField = <T extends FieldValues>({
</Box>
);
};

/**
* Customized React-Select Option which adds support for disabled option tooltip.
*/
export const Option = <T extends FieldValues>({ children, ...props }: OptionProps<T>) => (
<components.Option {...props}>
<YBTooltip
title={
props.isDisabled && props.data?.disabledReason ? (
<Typography variant="body2">{props.data.disabledReason}</Typography>
) : (
''
)
}
>
<div>{children}</div>
</YBTooltip>
</components.Option>
);
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ export function ReplicationDetails({
</MenuItem>
<RbacValidator
accessRequiredOn={{
...ApiPermissionMap.SYNC_XCLUSTER_REQUIREMENT,
...ApiPermissionMap.SYNC_XCLUSTER,
onResource: xClusterConfig.targetUniverseUUID
}}
isControl
Expand Down Expand Up @@ -532,17 +532,33 @@ export function ReplicationDetails({
numTablesRequiringBootstrap > 1 ? 'tables' : 'table'
} and replication restart is
required.`}
<YBButton
className="restart-replication-button"
btnIcon="fa fa-refresh"
btnText="Restart Replication"
onClick={() => {
if (_.includes(enabledConfigActions, XClusterConfigAction.RESTART)) {
dispatch(openDialog(XClusterModalName.RESTART_CONFIG));
}
<RbacValidator
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.MODIFY_XCLUSTER_REPLICATION,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.MODIFY_XCLUSTER_REPLICATION,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
disabled={!_.includes(enabledConfigActions, XClusterConfigAction.RESTART)}
/>
isControl
>
<YBButton
className="restart-replication-button"
btnIcon="fa fa-refresh"
btnText="Restart Replication"
onClick={() => {
if (_.includes(enabledConfigActions, XClusterConfigAction.RESTART)) {
dispatch(openDialog(XClusterModalName.RESTART_CONFIG));
}
}}
disabled={!_.includes(enabledConfigActions, XClusterConfigAction.RESTART)}
/>
</RbacValidator>
</div>
</YBBanner>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ import {
import { RuntimeConfigKey } from '../../../redesign/helpers/constants';

import { XClusterTableType } from '../XClusterTypes';
import {
TableType,
TableTypeLabel,
Universe,
UniverseNamespace,
YBTable
} from '../../../redesign/helpers/dtos';
import { TableType, TableTypeLabel, Universe, YBTable } from '../../../redesign/helpers/dtos';

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

Expand All @@ -51,7 +45,7 @@ interface CreateConfigModalProps {

export interface CreateXClusterConfigFormValues {
configName: string;
targetUniverse: { label: string; value: Universe };
targetUniverse: { label: string; value: Universe; isDisabled: boolean; disabledReason?: string };
tableType: { label: string; value: XClusterTableType };
isTransactionalConfig: boolean;
namespaceUuids: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
import { getIsTransactionalAtomicityEnabled } from '../ReplicationUtils';
import { YBBanner, YBBannerVariant } from '../../common/descriptors';
import InfoMessageIcon from '../../../redesign/assets/info-message.svg';
import { hasNecessaryPerm } from '../../../redesign/features/rbac/common/RbacApiPermValidator';
import { ApiPermissionMap } from '../../../redesign/features/rbac/ApiAndUserPermMapping';

import { TableType, TableTypeLabel, Universe } from '../../../redesign/helpers/dtos';

Expand Down Expand Up @@ -133,9 +135,15 @@ export const SelectTargetUniverseStep = ({
!UnavailableUniverseStates.includes(getUniverseStatus(universe).state)
)
.map((universe) => {
const isDisabled = !hasNecessaryPerm({
...ApiPermissionMap.CREATE_XCLUSTER_REPLICATION,
onResource: universe.universeUUID
});
return {
label: universe.name,
value: universe
value: universe,
isDisabled: isDisabled,
disabledReason: isDisabled ? t('missingPermissionOnUniverse') : ''
};
});

Expand Down
141 changes: 130 additions & 11 deletions managed/ui/src/components/xcluster/disasterRecovery/DrPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ import { FailoverIcon } from '../icons/FailoverIcon';
import { RepairDrConfigModal } from './repairConfig/RepairDrConfigModal';
import { DrConfigOverview } from './drConfig/DrConfigOverview';
import { DrBannerSection } from './DrBannerSection';
import { RbacValidator } from '../../../redesign/features/rbac/common/RbacApiPermValidator';
import {
hasNecessaryPerm,
RbacValidator
} from '../../../redesign/features/rbac/common/RbacApiPermValidator';
import { ApiPermissionMap } from '../../../redesign/features/rbac/ApiAndUserPermMapping';
import { getUniverseStatus, UniverseState } from '../../universes/helpers/universeHelpers';
import { EditConfigModal } from './editConfig/EditConfigModal';
Expand Down Expand Up @@ -289,9 +292,23 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
targetUniverse,
drConfig.state
);

return (
<>
<RbacValidator accessRequiredOn={ApiPermissionMap.GET_DR_CONFIG}>
<RbacValidator
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.GET_DR_CONFIG,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.GET_DR_CONFIG,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
>
<DrBannerSection
drConfig={drConfig}
openRepairConfigModal={openRepairConfigModal}
Expand All @@ -300,7 +317,21 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
<div className={classes.header}>
<Typography variant="h3">{t('heading')}</Typography>
<div className={classes.actionButtonContainer}>
<RbacValidator accessRequiredOn={ApiPermissionMap.DR_CONFIG_SWITCHOVER} isControl>
<RbacValidator
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SWITCHOVER,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SWITCHOVER,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
isControl
>
<YBButton
variant="primary"
size="large"
Expand All @@ -325,7 +356,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
mainMenu={(showSubmenu) => (
<>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_SET_TABLES}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SET_TABLES,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SET_TABLES,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand All @@ -343,7 +385,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</MenuItem>
</RbacValidator>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_EDIT}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_EDIT,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_EDIT,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand All @@ -359,7 +412,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</MenuItem>
</RbacValidator>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_REPLACE_REPLICA}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_REPLACE_REPLICA,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_REPLACE_REPLICA,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand Down Expand Up @@ -390,7 +454,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</MenuItem>
<MenuItem divider />
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_SWITCHOVER}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SWITCHOVER,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SWITCHOVER,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand All @@ -412,7 +487,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</MenuItem>
</RbacValidator>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_FAILOVER}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_FAILOVER,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_FAILOVER,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand All @@ -435,7 +521,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</RbacValidator>
<MenuItem divider />
<RbacValidator
accessRequiredOn={ApiPermissionMap.DELETE_DR_CONFIG}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DELETE_DR_CONFIG,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DELETE_DR_CONFIG,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand Down Expand Up @@ -463,7 +560,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
/>
</MenuItem>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_RESTART}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_RESTART,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_RESTART,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand All @@ -481,7 +589,18 @@ export const DrPanel = ({ currentUniverseUuid }: DrPanelProps) => {
</MenuItem>
</RbacValidator>
<RbacValidator
accessRequiredOn={ApiPermissionMap.DR_CONFIG_SYNC}
customValidateFunction={() => {
return (
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SYNC,
onResource: xClusterConfig.sourceUniverseUUID
}) &&
hasNecessaryPerm({
...ApiPermissionMap.DR_CONFIG_SYNC,
onResource: xClusterConfig.targetUniverseUUID
})
);
}}
overrideStyle={{ display: 'block' }}
isControl
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { parseDurationToSeconds } from '../../../../utils/parsers';
import toastStyles from '../../../../redesign/styles/toastStyles.module.scss';

export interface CreateDrConfigFormValues {
targetUniverse: { label: string; value: Universe };
targetUniverse: { label: string; value: Universe; isDisabled: boolean; disabledReason?: string };
namespaceUuids: string[];
tableUuids: string[];
storageConfig: StorageConfigOption;
Expand Down
Loading

0 comments on commit 581648f

Please sign in to comment.