Skip to content
10 changes: 10 additions & 0 deletions central/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@ func main() {
devmode.StartOnDevBuilds("central")

log.Infof("Running StackRox Version: %s", pkgVersion.GetMainVersion())
// TODO: ROX-12750 update with new list of replaced/deprecated resources
log.Warn("The following permission resources have been replaced:\n" +
" Access replaces AuthProvider, Group, Licenses, and User\n" +
" DeploymentExtension replaces Indicator, NetworkBaseline, ProcessWhitelist, and Risk\n" +
" Integration replaces APIToken, BackupPlugins, ImageIntegration, Notifier, and SignatureIntegration\n" +
" Image now also covers ImageComponent\n" +
"The following permission resources will be replaced in the upcoming versions:\n" +
" Administration will replace AllComments, Config, DebugLogs, NetworkGraphConfig, ProbeUpload, ScannerBundle, ScannerDefinitions, SensorUpgradeConfig, and ServiceIdentity\n" +
" Compliance will replace ComplianceRuns\n" +
" Cluster will cover ClusterCVE.")
ensureDB(ctx)

// Need to remove the backup clone and set the current version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('Access Control Permission sets', () => {
$tds.get().forEach((td) => {
const resource = td.textContent;
// TODO: ROX-12750 Rename DebugLogs to Administration
if (resource === 'DebugLogs') {
if (resource.includes('DebugLogs')) {
cy.get(getReadAccessIconForResource(resource)).should(
'have.attr',
'aria-label',
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('Access Control Permission sets', () => {

$tds.get().forEach((td) => {
const resource = td.textContent;
if (!resourcesLimited.includes(resource)) {
if (!resourcesLimited.some((v) => resource.includes(v))) {
cy.get(getReadAccessIconForResource(resource)).should(
'have.attr',
'aria-label',
Expand Down Expand Up @@ -315,7 +315,7 @@ describe('Access Control Permission sets', () => {

$tds.get().forEach((td) => {
const resource = td.textContent;
if (!resourcesLimited.includes(resource)) {
if (!resourcesLimited.some((v) => resource.includes(v))) {
cy.get(getReadAccessIconForResource(resource)).should(
'have.attr',
'aria-label',
Expand Down
61 changes: 55 additions & 6 deletions ui/apps/platform/src/Containers/AccessControl/AccessControl.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,77 @@
import React, { ReactElement } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';

import usePermissions from 'hooks/usePermissions';

import { Alert, List, ListItem } from '@patternfly/react-core';
import { accessControlBasePath, accessControlPath, getEntityPath } from './accessControlPaths';

import AccessControlNoPermission from './AccessControlNoPermission';
import AccessControlRouteNotFound from './AccessControlRouteNotFound';
import AccessScopes from './AccessScopes/AccessScopes';
import AuthProviders from './AuthProviders/AuthProviders';
import PermissionSets from './PermissionSets/PermissionSets';
import Roles from './Roles/Roles';
import usePermissions from '../../hooks/usePermissions';
import AccessControlNoPermission from './AccessControlNoPermission';

const paramId = ':entityId?';

function AccessControl(): ReactElement {
// TODO is read access required for all routes in improved Access Control?
// TODO Is write access required anywhere in classic Access Control?
const { hasReadAccess } = usePermissions();
const hasReadAccessForAuthProvider = hasReadAccess('Access');

return (
<>
{hasReadAccessForAuthProvider ? (
<Alert
isInline
variant="warning"
title={
<>
<p>The following permission resources have been replaced:</p>
<List>
<ListItem>
<b>Access</b> replaces{' '}
<b>AuthProvider, Group, Licenses, and User</b>
</ListItem>
<ListItem>
<b>DeploymentExtension</b> replaces{' '}
<b>Indicator, NetworkBaseline, ProcessWhitelist, and Risk</b>
</ListItem>
<ListItem>
<b>Integration</b> replaces{' '}
<b>
APIToken, BackupPlugins, ImageIntegration, Notifier, and
SignatureIntegration
</b>
</ListItem>
<ListItem>
<b>Image</b> now also covers <b>ImageComponent</b>
</ListItem>
</List>

<p>
The following permission resources will be replaced in the upcoming
versions:
</p>
<List>
<ListItem>
<b>Administration</b> will replace{' '}
<b>
AllComments, Config, DebugLogs, NetworkGraphConfig, ProbeUpload,
ScannerBundle, ScannerDefinitions, SensorUpgradeConfig, and
ServiceIdentity
</b>
</ListItem>
<ListItem>
<b>Compliance</b> will replace <b>ComplianceRuns</b>
</ListItem>
<ListItem>
<b>Cluster</b> will cover <b>ClusterCVE</b>
</ListItem>
</List>
</>
}
/>
{hasReadAccess('Access') || hasReadAccess('Role') ? (
<Switch>
<Route exact path={accessControlBasePath}>
<Redirect to={getEntityPath('AUTH_PROVIDER')} />
Expand All @@ -48,7 +97,7 @@ function AccessControl(): ReactElement {
</Route>
</Switch>
) : (
<AccessControlNoPermission />
<AccessControlNoPermission subPage="Access Control" isNavHidden />
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ import React, { ReactElement } from 'react';
import { Alert, AlertVariant, PageSection, PageSectionVariants } from '@patternfly/react-core';

import AccessControlHeading from './AccessControlHeading';
import { AccessControlEntityType } from '../../constants/entityTypes';

function AccessControlNoPermission(): ReactElement {
type AccessControlNoPermissionProps = {
subPage: string;
entityType?: AccessControlEntityType;
isNavHidden?: boolean;
};

function AccessControlNoPermission({
subPage,
entityType,
isNavHidden = false,
}: AccessControlNoPermissionProps): ReactElement {
return (
<>
<AccessControlHeading isNavHidden />
<AccessControlHeading isNavHidden={isNavHidden} entityType={entityType} />
<PageSection variant={PageSectionVariants.light}>
<Alert
className="pf-u-mt-md"
title="You do not have permission to view Access Control"
title={`You do not have permission to view ${subPage}`}
variant={AlertVariant.info}
isInline
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ import './AccessScopes.css';
import AccessControlHeading from '../AccessControlHeading';
import AccessControlBreadcrumbs from '../AccessControlBreadcrumbs';
import AccessControlHeaderActionBar from '../AccessControlHeaderActionBar';
import AccessControlNoPermission from '../AccessControlNoPermission';
import usePermissions from '../../../hooks/usePermissions';

const entityType = 'ACCESS_SCOPE';

function AccessScopes(): ReactElement {
const { hasReadAccess } = usePermissions();
const hasReadAccessForAccessScopes = hasReadAccess('Role');
const history = useHistory();
const { search } = useLocation();
const queryObject = getQueryObject(search);
Expand Down Expand Up @@ -149,6 +153,14 @@ function AccessScopes(): ReactElement {
const hasAction = Boolean(action);
const isList = typeof entityId !== 'string' && !hasAction;

if (!hasReadAccessForAccessScopes) {
return (
<>
<AccessControlNoPermission subPage="access scopes" entityType={entityType} />
</>
);
}

return (
<>
<AccessControlPageTitle entityType={entityType} isList={isList} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import AuthProvidersList from './AuthProvidersList';
import AccessControlBreadcrumbs from '../AccessControlBreadcrumbs';
import AccessControlHeading from '../AccessControlHeading';
import AccessControlHeaderActionBar from '../AccessControlHeaderActionBar';
import usePermissions from '../../../hooks/usePermissions';
import AccessControlNoPermission from '../AccessControlNoPermission';

const entityType = 'AUTH_PROVIDER';

Expand All @@ -65,6 +67,9 @@ function getNewAuthProviderObj(type) {
}

function AuthProviders(): ReactElement {
const { hasReadAccess } = usePermissions();
const hasReadAccessForAuthProviders = hasReadAccess('Access');
const hasReadAccessForRoles = hasReadAccess('Role');
const history = useHistory();
const { search } = useLocation();
const queryObject = getQueryObject(search);
Expand Down Expand Up @@ -138,6 +143,14 @@ function AuthProviders(): ReactElement {
</DropdownItem>
));

if (!hasReadAccessForAuthProviders) {
return (
<>
<AccessControlNoPermission subPage="auth providers" entityType={entityType} />
</>
);
}

return (
<>
<AccessControlPageTitle entityType={entityType} isList={isList} />
Expand Down Expand Up @@ -184,7 +197,7 @@ function AuthProviders(): ReactElement {
/>
)}
<PageSection variant={isList ? 'default' : 'light'}>
{isFetchingAuthProviders || isFetchingRoles ? (
{isFetchingAuthProviders || (isFetchingRoles && hasReadAccessForRoles) ? (
<Bullseye>
<Spinner isSVG />
</Bullseye>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ import { getNewPermissionSet, getCompletePermissionSet } from './permissionSets.
import AccessControlHeaderActionBar from '../AccessControlHeaderActionBar';
import AccessControlBreadcrumbs from '../AccessControlBreadcrumbs';
import AccessControlHeading from '../AccessControlHeading';
import AccessControlNoPermission from '../AccessControlNoPermission';
import usePermissions from '../../../hooks/usePermissions';

const entityType = 'PERMISSION_SET';

function PermissionSets(): ReactElement {
const { hasReadAccess } = usePermissions();
const hasReadAccessForPermissionSets = hasReadAccess('Role');
const history = useHistory();
const { search } = useLocation();
const queryObject = getQueryObject(search);
Expand Down Expand Up @@ -176,6 +180,14 @@ function PermissionSets(): ReactElement {
const hasAction = Boolean(action);
const isList = typeof entityId !== 'string' && !hasAction;

if (!hasReadAccessForPermissionSets) {
return (
<>
<AccessControlNoPermission subPage="permission sets" entityType={entityType} />
</>
);
}

return (
<>
<AccessControlPageTitle entityType={entityType} isList={isList} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import { PermissionsMap } from 'services/RolesService';

import { ReadAccessIcon, WriteAccessIcon } from './AccessIcons';
import { getReadAccessCount, getWriteAccessCount } from './permissionSets.utils';
import ResourceDescription from './ResourceDescription';
import { ResourceDescription } from './ResourceDescription';
import {
replacedResourceMapping,
resourceRemovalReleaseVersions,
resourceSubstitutions,
deprecatedResourceRowStyle,
} from '../../../constants/accessControl';
import { ResourceName } from '../../../types/roleResources';

export type PermissionsTableProps = {
resourceToAccess: PermissionsMap;
Expand Down Expand Up @@ -51,8 +58,41 @@ function PermissionsTable({
</Thead>
<Tbody>
{resourceToAccessEntries.map(([resource, accessLevel]) => (
<Tr key={resource}>
<Td dataLabel="Resource">{resource}</Td>
<Tr
key={resource}
style={
resourceRemovalReleaseVersions.has(resource as ResourceName)
? deprecatedResourceRowStyle
: {}
}
>
<Td dataLabel="Resource">
<p>{resource}</p>
<p style={{ fontStyle: 'italic' }}>
{resourceSubstitutions[resource] && (
<>Replaces {resourceSubstitutions[resource].join(', ')}</>
)}
</p>
<p style={{ fontStyle: 'italic' }}>
{resourceRemovalReleaseVersions.has(resource as ResourceName) && (
<>
Will be removed in{' '}
{resourceRemovalReleaseVersions.get(
resource as ResourceName
)}
.
</>
)}
</p>
<p style={{ fontStyle: 'italic' }}>
{replacedResourceMapping.has(resource as ResourceName) && (
<>
Will be replaced by{' '}
{replacedResourceMapping.get(resource as ResourceName)}.
</>
)}
</p>
</Td>
<Td dataLabel="Description">
<ResourceDescription resource={resource} />
</Td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const resourceDescriptions: Record<ResourceName, string> = {
Alert: 'Read: View policy violations. Write: Resolve or edit policy violations.',
CVE: 'Internal use only',
Cluster: 'Read: View secured clusters. Write: Add, modify, or delete secured clusters.',
ClusterCVE: 'Internal use only',
Compliance:
'Read: View compliance standards, results, and runs. Write: Add, modify, or delete scheduled compliance runs.',
Deployment: 'Read: View deployments (workloads) in secured clusters. Write: N/A',
Expand All @@ -32,8 +33,8 @@ const resourceDescriptions: Record<ResourceName, string> = {
NetworkPolicy:
'Read: View network policies in secured clusters and simulate changes. Write: Apply network policy changes in secured clusters.',
Node: 'Read: View Kubernetes nodes in secured clusters. Write: N/A',
Policy: 'Read: View system policies. Write: Add, modify, or delete system policies.',
Role: 'Read: View roles, permision sets and access scopes. Write: Add, modify or delete roles, permission sets and access scopes.',
Policy: 'Read: View system policies. Write: Add, modify, or delete system policies.',
Secret: 'Read: View metadata about secrets in secured clusters. Write: N/A',
ServiceAccount: 'Read: List Kubernetes service accounts in secured clusters. Write: N/A',
VulnerabilityManagementApprovals:
Expand Down Expand Up @@ -72,7 +73,7 @@ export type ResourceDescriptionProps = {
resource: string;
};

function ResourceDescription({ resource }: ResourceDescriptionProps): ReactElement {
export function ResourceDescription({ resource }: ResourceDescriptionProps): ReactElement {
// The description becomes the prop for possible future request from backend.
const description = resourceDescriptions[resource] ?? '';
const readIndex = description.indexOf('Read: ');
Expand Down
12 changes: 12 additions & 0 deletions ui/apps/platform/src/Containers/AccessControl/Roles/Roles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import RolesList from './RolesList';
import AccessControlBreadcrumbs from '../AccessControlBreadcrumbs';
import AccessControlHeaderActionBar from '../AccessControlHeaderActionBar';
import AccessControlHeading from '../AccessControlHeading';
import usePermissions from '../../../hooks/usePermissions';
import AccessControlNoPermission from '../AccessControlNoPermission';

const entityType = 'ROLE';

Expand All @@ -52,6 +54,8 @@ const roleNew: Role = {
};

function Roles(): ReactElement {
const { hasReadAccess } = usePermissions();
const hasReadAccessForRoles = hasReadAccess('Role');
const history = useHistory();
const { search } = useLocation();
const queryObject = getQueryObject(search);
Expand Down Expand Up @@ -224,6 +228,14 @@ function Roles(): ReactElement {
const hasAction = Boolean(action);
const isList = typeof entityName !== 'string' && !hasAction;

if (!hasReadAccessForRoles) {
return (
<>
<AccessControlNoPermission subPage="roles" entityType={entityType} />
</>
);
}

return (
<>
<AccessControlPageTitle entityType={entityType} isList={isList} />
Expand Down
Loading