diff --git a/.changeset/eleven-bats-tease.md b/.changeset/eleven-bats-tease.md new file mode 100644 index 0000000000..38c591ea3c --- /dev/null +++ b/.changeset/eleven-bats-tease.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +--- + +PermissionApi of type PermissionEvaluator must now be passed as a parameter when calling proxy.createRequestHandler() for the KubernetesProxyEndpoint. Kubernetes `/proxy` endpoint now requires two tokens in its header per request.The field `X-Kubernetes-Authentication` needs the value of a authentication authorities' bearer token.The field `Authorization` should contain a backstage identity token. diff --git a/.changeset/wise-garlics-camp.md b/.changeset/wise-garlics-camp.md new file mode 100644 index 0000000000..e7639ad71c --- /dev/null +++ b/.changeset/wise-garlics-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-common': patch +--- + +Introduced proxy permission types to be used with the kubernetes proxy endpoint's permission framework integration. diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index a6a4bcfc58..431ddce244 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -17,6 +17,7 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Logger } from 'winston'; import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import type { RequestHandler } from 'express'; import { TokenCredential } from '@azure/identity'; @@ -200,6 +201,7 @@ export class KubernetesBuilder { clusterSupplier: KubernetesClustersSupplier, catalogApi: CatalogApi, proxy: KubernetesProxy, + permissionApi: PermissionEvaluator, ): express.Router; // (undocumented) protected buildServiceLocator( @@ -271,6 +273,8 @@ export interface KubernetesEnvironment { config: Config; // (undocumented) logger: Logger; + // (undocumented) + permissions: PermissionEvaluator; } // @public @@ -340,7 +344,7 @@ export type KubernetesObjectTypes = export class KubernetesProxy { constructor(logger: Logger, clusterSupplier: KubernetesClustersSupplier); // (undocumented) - createRequestHandler(): RequestHandler; + createRequestHandler(permissionApi: PermissionEvaluator): RequestHandler; } // @public @@ -418,6 +422,8 @@ export interface RouterOptions { discovery: PluginEndpointDiscovery; // (undocumented) logger: Logger; + // (undocumented) + permissions: PermissionEvaluator; } // @public (undocumented) diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 4254bb33da..a033e7d52d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -30,6 +30,10 @@ import { HEADER_KUBERNETES_CLUSTER, KubernetesProxy, } from './KubernetesProxy'; +import { + AuthorizeResult, + PermissionEvaluator, +} from '@backstage/plugin-permission-common'; describe('KubernetesProxy', () => { let proxy: KubernetesProxy; @@ -64,6 +68,11 @@ describe('KubernetesProxy', () => { getClusters: jest.fn(), }; + const permissions: jest.Mocked = { + authorize: jest.fn(), + authorizeConditional: jest.fn(), + }; + beforeEach(() => { jest.resetAllMocks(); proxy = new KubernetesProxy(getVoidLogger(), clusterSupplier); @@ -71,13 +80,16 @@ describe('KubernetesProxy', () => { it('should return a ERROR_NOT_FOUND if no clusters are found', async () => { clusterSupplier.getClusters.mockResolvedValue([]); + permissions.authorize.mockReturnValue( + Promise.resolve([{ result: AuthorizeResult.ALLOW }]), + ); const req = buildMockRequest('test', 'api'); const { res, next } = getMockRes(); - await expect(proxy.createRequestHandler()(req, res, next)).rejects.toThrow( - NotFoundError, - ); + await expect( + proxy.createRequestHandler(permissions)(req, res, next), + ).rejects.toThrow(NotFoundError); }); it('should pass the exact response from Kubernetes', async () => { @@ -100,7 +112,13 @@ describe('KubernetesProxy', () => { authProvider: 'serviceAccount', }, ] as ClusterDetails[]); - const app = express().use('/mountpath', proxy.createRequestHandler()); + const app = express().use( + '/mountpath', + proxy.createRequestHandler(permissions), + ); + permissions.authorize.mockReturnValue( + Promise.resolve([{ result: AuthorizeResult.ALLOW }]), + ); const requestPromise = request(app) .get('/mountpath/api') .set(HEADER_KUBERNETES_CLUSTER, 'cluster1'); diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index fb41895f1a..a06780a61a 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; import { Entity } from '@backstage/catalog-model'; import type { JsonObject } from '@backstage/types'; import { PodStatus } from '@kubernetes/client-node'; @@ -206,6 +207,9 @@ export interface JobsFetchResponse { type: 'jobs'; } +// @alpha +export const kubernetesClusterPermissions: BasicPermission[]; + // @public (undocumented) export type KubernetesErrorTypes = | 'BAD_REQUEST' @@ -217,6 +221,12 @@ export type KubernetesErrorTypes = // @public (undocumented) export type KubernetesFetchError = StatusError | RawFetchError; +// @alpha +export const kubernetesProxyCreatePermission: BasicPermission; + +// @alpha +export const kubernetesProxyReadPermission: BasicPermission; + // @public (undocumented) export interface KubernetesRequestAuth { // (undocumented) diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index ef9d8580df..854a188506 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -16,7 +16,7 @@ import { createPermission } from '@backstage/plugin-permission-common'; -/** This permission is used to authorize actions that involve using the kubernetes Proxy Endpoint /proxy +/** This permission is used to authorize `read` actions that involve using the kubernetes Proxy Endpoint /proxy * @alpha */ export const kubernetesProxyReadPermission = createPermission({ @@ -24,6 +24,9 @@ export const kubernetesProxyReadPermission = createPermission({ attributes: { action: 'read' }, }); +/** This permission is used to authorize `create` actions that involve using the kubernetes Proxy Endpoint /proxy + * @alpha + */ export const kubernetesProxyCreatePermission = createPermission({ name: 'kubernetes.proxy.create', attributes: { action: 'create' }, diff --git a/yarn.lock b/yarn.lock index 7c8b674c0f..075384ae64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7463,32 +7463,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-proxy-restrict@^0.0.0, @backstage/plugin-proxy-restrict@workspace:plugins/proxy-restrict": - version: 0.0.0-use.local - resolution: "@backstage/plugin-proxy-restrict@workspace:plugins/proxy-restrict" - dependencies: - "@backstage/cli": "workspace:^" - "@backstage/core-app-api": "workspace:^" - "@backstage/core-components": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" - "@backstage/dev-utils": "workspace:^" - "@backstage/test-utils": "workspace:^" - "@backstage/theme": "workspace:^" - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": ^4.0.0-alpha.57 - "@testing-library/jest-dom": ^5.10.1 - "@testing-library/react": ^12.1.3 - "@testing-library/user-event": ^14.0.0 - "@types/node": "*" - cross-fetch: ^3.1.5 - msw: ^0.49.0 - react-use: ^17.2.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 - languageName: unknown - linkType: soft - "@backstage/plugin-rollbar-backend@workspace:^, @backstage/plugin-rollbar-backend@workspace:plugins/rollbar-backend": version: 0.0.0-use.local resolution: "@backstage/plugin-rollbar-backend@workspace:plugins/rollbar-backend" @@ -22496,7 +22470,6 @@ __metadata: "@backstage/plugin-pagerduty": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-playlist": "workspace:^" - "@backstage/plugin-proxy-restrict": ^0.0.0 "@backstage/plugin-rollbar": "workspace:^" "@backstage/plugin-scaffolder": "workspace:^" "@backstage/plugin-scaffolder-react": "workspace:^"