Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(topology): add permissions to topology plugin #1665

Merged
merged 3 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugins/rbac-backend/docs/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ When defining a permission for the RBAC Backend plugin to consume, follow these
| ---------------- | ------------- | ------ | ----------------------------------------------------------------- | ------------ |
| ocm.entity.read | | read | Allows the user to read from the ocm plugin | X |
| ocm.cluster.read | | read | Allows the user to read the cluster information in the ocm plugin | X |

## Topology

| Name | Resource Type | Policy | Description | Requirements |
| ------------------ | ------------- | ------ | ----------------------------------------------------------------------------------------------------------- | ------------------- |
| topology.view.read | | read | Allows the user to view the topology plugin | X |
| kubernetes.proxy | | | Allows the user to access the proxy endpoint (ability to read pod logs and events within Showcase and RHDH) | catalog.entity.read |
1 change: 1 addition & 0 deletions plugins/topology-common/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
5 changes: 5 additions & 0 deletions plugins/topology-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Topology plugin for Backstage

The Topology plugin enables you to visualize the workloads such as Deployment, Job, Daemonset, Statefulset, CronJob, and Pods powering any service on the Kubernetes cluster.

For more information about Topology plugin, see the [Topology plugin documentation](https://github.com/janus-idp/backstage-plugins/tree/main/plugins/topology) on GitHub.
48 changes: 48 additions & 0 deletions plugins/topology-common/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@janus-idp/backstage-plugin-topology-common",
"description": "Common functionalities for the topology plugin",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "common-library"
},
"sideEffects": false,
"scripts": {
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"postpack": "backstage-cli package postpack",
"prepack": "backstage-cli package prepack",
"test": "backstage-cli package test --passWithNoTests --coverage",
"tsc": "tsc"
},
"devDependencies": {
"@backstage/cli": "0.26.4"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/janus-idp/backstage-plugins",
"directory": "plugins/topology"
},
"keywords": [
"backstage",
"plugin"
],
"homepage": "https://janus-idp.io/",
"bugs": "https://github.com/janus-idp/backstage-plugins/issues",
"dependencies": {
"@backstage/plugin-permission-common": "^0.7.13"
}
}
7 changes: 7 additions & 0 deletions plugins/topology-common/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Common functionalities for the topology plugin.
*
* @packageDocumentation
*/

export * from './permissions';
13 changes: 13 additions & 0 deletions plugins/topology-common/src/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createPermission } from '@backstage/plugin-permission-common';

export const topologyViewPermission = createPermission({
name: 'topology.view.read',
attributes: {
action: 'read',
},
});

/**
* List of all permissions on permission polices.
*/
export const topologyPermissions = [topologyViewPermission];
9 changes: 9 additions & 0 deletions plugins/topology-common/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@backstage/cli/config/tsconfig.json",
"include": ["src"],
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "../../dist-types/plugins/topology-common",
"rootDir": "."
}
}
9 changes: 9 additions & 0 deletions plugins/topology-common/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": ["//"],
"pipeline": {
"tsc": {
"outputs": ["../../dist-types/plugins/topology-common/**"],
"dependsOn": ["^tsc"]
}
}
}
10 changes: 10 additions & 0 deletions plugins/topology/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,16 @@ Topology is a front-end plugin that enables you to view the workloads as nodes t

- Your Backstage application is installed and running.
- You have installed the Topology plugin. For the installation process, see [Installation](#installation).
- If RBAC permission framework is enabled, ensure to add the following permission policies in an external permission policies configuration file named `rbac-policy.csv` to allow the rbac admins or your desired user(s)/group(s) to access the topology plugin:

```csv rbac-policy.csv
g, user:default/<YOUR_USERNAME>, role:default/topology-viewer
p, role:default/topology-viewer, topology.view.read, read, allow
p, role:default/topology-viewer, kubernetes.proxy, use, allow
p, role:default/topology-viewer, catalog-entity, read, allow
```

`p, role:default/topology-viewer, topology.view.read, read, allow` grants the user the ability to see the Topology panel. `p, role:default/topology-viewer, kubernetes.proxy, use, allow` grants the user the ability to view the pod logs. `p, role:default/topology-viewer, catalog-entity, read, allow` grants the user the ability to see the catalog item.

#### Procedure

Expand Down
3 changes: 3 additions & 0 deletions plugins/topology/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
"@backstage/catalog-model": "^1.4.5",
"@backstage/core-components": "^0.14.6",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/plugin-catalog-common": "^1.0.23",
"@backstage/plugin-catalog-react": "^1.11.3",
"@backstage/plugin-kubernetes": "^0.11.9",
"@backstage/plugin-kubernetes-common": "^0.7.5",
"@backstage/plugin-permission-react": "^0.4.22",
"@backstage/theme": "^0.5.3",
"@janus-idp/shared-react": "2.6.2",
"@janus-idp/backstage-plugin-topology-common": "0.1.0",
"@kubernetes/client-node": "^0.20.0",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.11.3",
Expand Down
17 changes: 14 additions & 3 deletions plugins/topology/src/components/Topology/TopologyEmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ import React from 'react';

import {
EmptyState,
EmptyStateBody,
EmptyStateHeader,
EmptyStateIcon,
EmptyStateVariant,
} from '@patternfly/react-core';
import { TopologyIcon } from '@patternfly/react-icons';

export const TopologyEmptyState = () => {
type TopologyEmptyStateProps = {
title?: string;
description?: string;
};

export const TopologyEmptyState = ({
title,
description,
}: TopologyEmptyStateProps) => {
return (
<EmptyState
variant={EmptyStateVariant.full}
isFullHeight
className="pf-topology-visualization-surface"
>
<EmptyStateHeader
titleText="No resources found"
titleText={title || 'No resources found'}
icon={<EmptyStateIcon icon={TopologyIcon} />}
headingLevel="h3"
/>
>
<EmptyStateBody>{description}</EmptyStateBody>
</EmptyStateHeader>
</EmptyState>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';

import { usePermission } from '@backstage/plugin-permission-react';

import { render } from '@testing-library/react';

import { K8sResourcesContext } from '../../hooks/K8sResourcesContext';
Expand All @@ -12,6 +14,14 @@ jest.mock('../../hooks/useWorkloadWatcher', () => ({

const mockUseWorkloadsWatcher = useWorkloadsWatcher as jest.Mock;

jest.mock('@backstage/plugin-permission-react', () => ({
usePermission: jest.fn(),
}));

const mockUsePermission = usePermission as jest.MockedFunction<
typeof usePermission
>;

jest.mock('@patternfly/react-topology', () => ({
useVisualizationController: () => ({
getGraph: () => ({
Expand All @@ -35,6 +45,10 @@ jest.mock('@patternfly/react-topology', () => ({
}));

describe('TopologyViewWorkloadComponent', () => {
beforeEach(() => {
mockUsePermission.mockReturnValue({ loading: false, allowed: true });
});

it('should show loading state when loading is true', () => {
mockUseWorkloadsWatcher.mockReturnValue({
loaded: false,
Expand Down Expand Up @@ -71,4 +85,28 @@ describe('TopologyViewWorkloadComponent', () => {
);
expect(getByText(/topologyview/i)).not.toBeNull();
});

it('should not render TopologyView when data is available, loading is false, but user is not allowed', () => {
mockUsePermission.mockReturnValue({ loading: false, allowed: false });

mockUseWorkloadsWatcher.mockReturnValue({
loaded: true,
dataModel: { nodes: [{}] },
});
const { getByText } = render(
<K8sResourcesContext.Provider
value={{ clusters: ['ocp'], setSelectedCluster: () => {} }}
>
<TopologyViewWorkloadComponent />
</K8sResourcesContext.Provider>,
);
expect(getByText(/topologyview/i)).not.toBeNull();

const { getByRole } = render(<TopologyViewWorkloadComponent />);
expect(
getByRole('heading', {
name: /no resources found/i,
}),
).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import TopologyToolbar from './TopologyToolbar';

import './TopologyToolbar.css';

import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import { usePermission } from '@backstage/plugin-permission-react';

import { topologyViewPermission } from '@janus-idp/backstage-plugin-topology-common';

type TopologyViewWorkloadComponentProps = {
useToolbar?: boolean;
};
Expand All @@ -49,6 +54,15 @@ const TopologyViewWorkloadComponent = ({
removeSelectedIdParam,
] = useSideBar();

const topologyViewPermissionResult = usePermission({
permission: topologyViewPermission,
});

const catalogEntityPermissionResult = usePermission({
permission: catalogEntityReadPermission,
resourceRef: catalogEntityReadPermission.resourceType,
Copy link
Member

@divyanshiGupta divyanshiGupta May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PatAKnight I removed lgtm so that @AndrienkoAleksandr can also reveiw the PR but it still got merged. :P
Last time I checked with @AndrienkoAleksandr this is not the way catalogEntityReadPermission authorization works as it expects the complete resourceRef like user:default/div. I have opened one UI issue https://issues.redhat.com/browse/RHIDP-1757 regarding this as we are also using catalogEntityReadPermission in similar way for disabling create role button but when any conditions are added to a role of admin itself this /api/permission/authorize returns 500 with this error for catalogEntityReadPermission :
"Entity reference \"catalog-entity\" had missing or empty kind (e.g. did not start with \"component:\" or similar)"

});

const allErrors: ClusterErrors = [
...(responseError ? [{ message: responseError }] : []),
...(selectedClusterErrors ?? []),
Expand Down Expand Up @@ -126,8 +140,15 @@ const TopologyViewWorkloadComponent = ({
sideBarOpen={sideBarOpen}
minSideBarSize="400px"
>
{loaded && dataModel?.nodes?.length === 0 ? (
<TopologyEmptyState />
{isDataModelEmpty ||
!(
topologyViewPermissionResult.allowed &&
catalogEntityPermissionResult.allowed
PatAKnight marked this conversation as resolved.
Show resolved Hide resolved
) ? (
<TopologyEmptyState
title="Permission required"
description="To view Topology, contact your administrator to give you the topology.view.read and catalog.entity.read permissions"
/>
) : (
<VisualizationSurface state={{ selectedIds: [selectedId] }} />
)}
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3226,6 +3226,16 @@
ajv "^8.10.0"
lodash "^4.17.21"

"@backstage/catalog-model@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@backstage/catalog-model/-/catalog-model-1.5.0.tgz#7f5c4a80a3341555db5209fbc6fc2d25f6500707"
integrity sha512-CfLO5/DMGahneuLU4KTQEs1tgNhBciUtyGUDZB4Ii9i1Uha1poWcqp4HKg61lj1hmXNDUHmlbFqY9W7kmzRC0A==
dependencies:
"@backstage/errors" "^1.2.4"
"@backstage/types" "^1.1.1"
ajv "^8.10.0"
lodash "^4.17.21"

"@backstage/cli-common@^0.1.13":
version "0.1.13"
resolved "https://registry.yarnpkg.com/@backstage/cli-common/-/cli-common-0.1.13.tgz#cbeda6a359ca4437fc782f0ac51bb957e8d49e73"
Expand Down Expand Up @@ -4077,6 +4087,15 @@
"@backstage/plugin-permission-common" "^0.7.13"
"@backstage/plugin-search-common" "^1.2.11"

"@backstage/plugin-catalog-common@^1.0.23":
version "1.0.23"
resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-common/-/plugin-catalog-common-1.0.23.tgz#2ba1fe13450f6283e049acc83aa4fcebda6153e8"
integrity sha512-u04VUq/2wNjF9ikpGxdt1kXSQf5VlPDWTwzYyJYKD80qGa6l/klUXJ3IBs8P4XyQObkPNyS/Tho/H8XDFNeqEw==
dependencies:
"@backstage/catalog-model" "^1.5.0"
"@backstage/plugin-permission-common" "^0.7.13"
"@backstage/plugin-search-common" "^1.2.11"

"@backstage/plugin-catalog-graph@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@backstage/plugin-catalog-graph/-/plugin-catalog-graph-0.4.4.tgz#ff26dcec8ae437d07bbdc98093aa52503fe2a1b1"
Expand Down
Loading