From ca3da299e2615ca9ef167ec55872f48e77394365 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 5 Nov 2024 16:24:21 +0100 Subject: [PATCH 01/19] Introduce kubernetes resource permission Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 6 ++ .../src/auth/requirePermission.ts | 49 +++++++++++++++++ .../src/routes/resourceRoutes.test.ts | 55 ++++++++++++++++++- .../src/routes/resourcesRoutes.ts | 5 ++ .../src/service/KubernetesBuilder.test.ts | 41 ++++++++++---- .../src/service/KubernetesBuilder.ts | 4 ++ plugins/kubernetes-common/report.api.md | 3 + plugins/kubernetes-common/src/index.ts | 1 + plugins/kubernetes-common/src/permissions.ts | 13 ++++- 9 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 .changeset/real-foxes-film.md create mode 100644 plugins/kubernetes-backend/src/auth/requirePermission.ts diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md new file mode 100644 index 0000000000..757146d624 --- /dev/null +++ b/.changeset/real-foxes-film.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': minor +--- + +Introduced resource permission type to be used with the kubernetes endpoint's permission framework integration for all endpoints except the proxy endpoints. diff --git a/plugins/kubernetes-backend/src/auth/requirePermission.ts b/plugins/kubernetes-backend/src/auth/requirePermission.ts new file mode 100644 index 0000000000..c95cd12c72 --- /dev/null +++ b/plugins/kubernetes-backend/src/auth/requirePermission.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + HttpAuthService, + PermissionsService, +} from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { kubernetesResourcePermission } from '@backstage/plugin-kubernetes-common'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +import express from 'express'; + +export async function requirePermission( + permissionApi: PermissionsService, + httpAuth: HttpAuthService, + req: express.Request, +) { + const decision = ( + await permissionApi.authorize( + [ + { + permission: kubernetesResourcePermission, + }, + ], + { + credentials: await httpAuth.credentials(req), + }, + ) + )[0]; + + if (decision.result === AuthorizeResult.DENY) { + const err = new NotAllowedError('Unauthorized'); + throw err; + } +} diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index 7ace17e805..462312a404 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -18,17 +18,48 @@ import request from 'supertest'; import { mockCredentials, mockServices, + type ServiceMock, startTestBackend, } from '@backstage/backend-test-utils'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; -import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + createBackendModule, + type PermissionsService, +} from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; describe('resourcesRoutes', () => { let app: ExtendedHttpServer; + const permissionsMock: ServiceMock = + mockServices.permissions.mock({ + authorize: jest.fn(), + authorizeConditional: jest.fn(), + }); - beforeAll(async () => { + const startPermissionDeniedTestServer = async () => { + permissionsMock.authorize.mockResolvedValue([ + { result: AuthorizeResult.DENY }, + ]); + const { server } = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { type: 'multiTenant' }, + clusterLocatorMethods: [], + }, + }, + }), + permissionsMock.factory, + import('@backstage/plugin-kubernetes-backend'), + ], + }); + return server; + }; + + beforeEach(async () => { const objectsProviderMock = { getKubernetesObjectsByEntity: jest.fn().mockImplementation(args => { if (args.entity.metadata.name === 'inject500') { @@ -109,6 +140,8 @@ describe('resourcesRoutes', () => { }, }), import('@backstage/plugin-kubernetes-backend'), + import('@backstage/plugin-permission-backend'), + import('@backstage/plugin-permission-backend-module-allow-all-policy'), createBackendModule({ pluginId: 'kubernetes', moduleId: 'test-objects-provider', @@ -127,6 +160,10 @@ describe('resourcesRoutes', () => { app = server; }); + afterEach(() => { + app.stop(); + }); + describe('POST /resources/workloads/query', () => { // eslint-disable-next-line jest/expect-expect it('200 happy path', async () => { @@ -269,6 +306,13 @@ describe('resourcesRoutes', () => { response: { statusCode: 401 }, }); }); + it('403 when permission blocks endpoint', async () => { + app = await startPermissionDeniedTestServer(); + const response = await request(app).post( + '/api/kubernetes/resources/workloads/query', + ); + expect(response.status).toEqual(403); + }); // eslint-disable-next-line jest/expect-expect it('500 handle gracefully', async () => { await request(app) @@ -548,6 +592,13 @@ describe('resourcesRoutes', () => { response: { statusCode: 401 }, }); }); + it('403 when permission blocks endpoint', async () => { + app = await startPermissionDeniedTestServer(); + const response = await request(app).post( + '/api/kubernetes/resources/custom/query', + ); + expect(response.status).toEqual(403); + }); // eslint-disable-next-line jest/expect-expect it('500 handle gracefully', async () => { await request(app) diff --git a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts index 838e039d27..091080bdcc 100644 --- a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts +++ b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts @@ -23,6 +23,8 @@ import { InputError } from '@backstage/errors'; import express, { Request } from 'express'; import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { requirePermission } from '../auth/requirePermission'; export const addResourceRoutesToRouter = ( router: express.Router, @@ -30,6 +32,7 @@ export const addResourceRoutesToRouter = ( objectsProvider: KubernetesObjectsProvider, auth: AuthService, httpAuth: HttpAuthService, + permissionApi: PermissionEvaluator, ) => { const getEntityByReq = async (req: Request) => { const rawEntityRef = req.body.entityRef; @@ -62,6 +65,7 @@ export const addResourceRoutesToRouter = ( }; router.post('/resources/workloads/query', async (req, res) => { + await requirePermission(permissionApi, httpAuth, req); const entity = await getEntityByReq(req); const response = await objectsProvider.getKubernetesObjectsByEntity( { @@ -74,6 +78,7 @@ export const addResourceRoutesToRouter = ( }); router.post('/resources/custom/query', async (req, res) => { + await requirePermission(permissionApi, httpAuth, req); const entity = await getEntityByReq(req); if (!req.body.customResources) { diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index b38bf9121a..8669299d2e 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -92,6 +92,19 @@ describe('API integration tests', () => { }); }, }); + const startPermissionDeniedTestServer = async () => { + permissionsMock.authorize.mockResolvedValue([ + { result: AuthorizeResult.DENY }, + ]); + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + permissionsMock.factory, + import('@backstage/plugin-kubernetes-backend'), + ], + }); + return server; + }; beforeEach(async () => { jest.resetAllMocks(); @@ -305,6 +318,12 @@ describe('API integration tests', () => { items: [expect.objectContaining({ title: 'cluster-title' })], }); }); + + it('returns 403 response when permission blocks endpoint', async () => { + app = await startPermissionDeniedTestServer(); + const response = await request(app).get('/api/kubernetes/clusters'); + expect(response.status).toEqual(403); + }); }); describe('post /services/:serviceId', () => { @@ -504,6 +523,14 @@ describe('API integration tests', () => { }), ); }); + + it('returns 403 response when permission blocks endpoint', async () => { + app = await startPermissionDeniedTestServer(); + const response = await request(app).post( + '/api/kubernetes/services/test-service', + ); + expect(response.status).toEqual(403); + }); }); describe('/proxy', () => { @@ -571,18 +598,7 @@ metadata: }); it('returns 403 response when permission blocks endpoint', async () => { - permissionsMock.authorize.mockResolvedValue([ - { result: AuthorizeResult.DENY }, - ]); - - const { server } = await startTestBackend({ - features: [ - minimalValidConfigService, - permissionsMock.factory, - import('@backstage/plugin-kubernetes-backend'), - ], - }); - app = server; + app = await startPermissionDeniedTestServer(); const proxyEndpointRequest = request(app) .post('/api/kubernetes/proxy/api/v1/namespaces') @@ -779,6 +795,7 @@ metadata: expect(response.body).toMatchObject({ permissions: [ { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, + { type: 'basic', name: 'kubernetes.resource', attributes: {} }, ], rules: [], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 6a4e9cc296..ea5c725d8f 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -73,6 +73,7 @@ import { } from './KubernetesFanOutHandler'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { KubernetesProxy } from './KubernetesProxy'; +import { requirePermission } from '../auth/requirePermission'; /** * @deprecated Please migrate to the new backend system as this will be removed in the future. @@ -393,6 +394,7 @@ export class KubernetesBuilder { ); // @deprecated router.post('/services/:serviceId', async (req, res) => { + await requirePermission(permissionApi, httpAuth, req); const serviceId = req.params.serviceId; const requestBody: ObjectsByEntityRequest = req.body; try { @@ -413,6 +415,7 @@ export class KubernetesBuilder { }); router.get('/clusters', async (req, res) => { + await requirePermission(permissionApi, httpAuth, req); const credentials = await httpAuth.credentials(req); const clusterDetails = await this.fetchClusterDetails(clusterSupplier, { credentials, @@ -447,6 +450,7 @@ export class KubernetesBuilder { objectsProvider, authService, httpAuth, + permissionApi, ); return router; diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index 8cdac7ed45..b40ae89c82 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -347,6 +347,9 @@ export interface KubernetesRequestBody { entity: Entity; } +// @public +export const kubernetesResourcePermission: BasicPermission; + // @public (undocumented) export interface LimitRangeFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/src/index.ts b/plugins/kubernetes-common/src/index.ts index faaaaab92c..76a4c0b7e7 100644 --- a/plugins/kubernetes-common/src/index.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -25,6 +25,7 @@ export * from './catalog-entity-constants'; export * from './certificate-authority-constants'; export { kubernetesProxyPermission, + kubernetesResourcePermission, kubernetesPermissions, } from './permissions'; export * from './error-detection'; diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index d3ccbe8171..928b5c7d06 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -24,8 +24,19 @@ export const kubernetesProxyPermission = createPermission({ attributes: {}, }); +/** This permission is used to check access to the resources endpoints + * @public + */ +export const kubernetesResourcePermission = createPermission({ + name: 'kubernetes.resource', + attributes: {}, +}); + /** * List of all Kubernetes permissions. * @public */ -export const kubernetesPermissions = [kubernetesProxyPermission]; +export const kubernetesPermissions = [ + kubernetesProxyPermission, + kubernetesResourcePermission, +]; From 4cb841c42075aecf19ce0804e45ab0dffcefe8ac Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 19 Nov 2024 10:40:38 +0100 Subject: [PATCH 02/19] Support future compatibility for permissions Co-authored by: Vincenzo Scamporlino Signed-off-by: Dominika Zemanovicova --- plugins/kubernetes-backend/src/auth/requirePermission.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/kubernetes-backend/src/auth/requirePermission.ts b/plugins/kubernetes-backend/src/auth/requirePermission.ts index c95cd12c72..8f0463bd5b 100644 --- a/plugins/kubernetes-backend/src/auth/requirePermission.ts +++ b/plugins/kubernetes-backend/src/auth/requirePermission.ts @@ -42,8 +42,8 @@ export async function requirePermission( ) )[0]; - if (decision.result === AuthorizeResult.DENY) { - const err = new NotAllowedError('Unauthorized'); - throw err; + if (decision.result === AuthorizeResult.ALLOW) { + return; } + throw new NotAllowedError('Unauthorized'); } From 73115bcc898254fa4d97198331cdd450c71672be Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Fri, 22 Nov 2024 10:36:10 +0100 Subject: [PATCH 03/19] Split resource permission Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 4 +++- .../src/auth/requirePermission.ts | 9 ++++++--- .../src/routes/resourcesRoutes.ts | 15 ++++++++++++-- .../src/service/KubernetesBuilder.test.ts | 2 ++ .../src/service/KubernetesBuilder.ts | 16 +++++++++++++-- plugins/kubernetes-common/report.api.md | 6 ++++++ plugins/kubernetes-common/src/index.ts | 2 ++ plugins/kubernetes-common/src/permissions.ts | 20 ++++++++++++++++++- 8 files changed, 65 insertions(+), 9 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index 757146d624..750db7e175 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -3,4 +3,6 @@ '@backstage/plugin-kubernetes-common': minor --- -Introduced resource permission type to be used with the kubernetes endpoint's permission framework integration for all endpoints except the proxy endpoints. +The `/clusters` endpoint is now protected by the `kubernetes.cluster` permission. +The `/services` endpoint is now protected by the `kubernetes.service` permission. +The `/resources` endpoints are now protected by the `kubernetes.resource` permission. diff --git a/plugins/kubernetes-backend/src/auth/requirePermission.ts b/plugins/kubernetes-backend/src/auth/requirePermission.ts index 8f0463bd5b..9934ed653d 100644 --- a/plugins/kubernetes-backend/src/auth/requirePermission.ts +++ b/plugins/kubernetes-backend/src/auth/requirePermission.ts @@ -19,13 +19,16 @@ import type { PermissionsService, } from '@backstage/backend-plugin-api'; import { NotAllowedError } from '@backstage/errors'; -import { kubernetesResourcePermission } from '@backstage/plugin-kubernetes-common'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + type BasicPermission, +} from '@backstage/plugin-permission-common'; import express from 'express'; export async function requirePermission( permissionApi: PermissionsService, + permissionRequired: BasicPermission, httpAuth: HttpAuthService, req: express.Request, ) { @@ -33,7 +36,7 @@ export async function requirePermission( await permissionApi.authorize( [ { - permission: kubernetesResourcePermission, + permission: permissionRequired, }, ], { diff --git a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts index 091080bdcc..ab3c7d8e06 100644 --- a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts +++ b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts @@ -25,6 +25,7 @@ import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { requirePermission } from '../auth/requirePermission'; +import { kubernetesResourcePermission } from '@backstage/plugin-kubernetes-common'; export const addResourceRoutesToRouter = ( router: express.Router, @@ -65,7 +66,12 @@ export const addResourceRoutesToRouter = ( }; router.post('/resources/workloads/query', async (req, res) => { - await requirePermission(permissionApi, httpAuth, req); + await requirePermission( + permissionApi, + kubernetesResourcePermission, + httpAuth, + req, + ); const entity = await getEntityByReq(req); const response = await objectsProvider.getKubernetesObjectsByEntity( { @@ -78,7 +84,12 @@ export const addResourceRoutesToRouter = ( }); router.post('/resources/custom/query', async (req, res) => { - await requirePermission(permissionApi, httpAuth, req); + await requirePermission( + permissionApi, + kubernetesResourcePermission, + httpAuth, + req, + ); const entity = await getEntityByReq(req); if (!req.body.customResources) { diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 8669299d2e..84de5d5575 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -796,6 +796,8 @@ metadata: permissions: [ { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, { type: 'basic', name: 'kubernetes.resource', attributes: {} }, + { type: 'basic', name: 'kubernetes.service', attributes: {} }, + { type: 'basic', name: 'kubernetes.cluster', attributes: {} }, ], rules: [], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index ea5c725d8f..ed864dcf60 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -18,7 +18,9 @@ import { Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, + kubernetesClusterPermission, kubernetesPermissions, + kubernetesServicePermission, } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; @@ -394,7 +396,12 @@ export class KubernetesBuilder { ); // @deprecated router.post('/services/:serviceId', async (req, res) => { - await requirePermission(permissionApi, httpAuth, req); + await requirePermission( + permissionApi, + kubernetesServicePermission, + httpAuth, + req, + ); const serviceId = req.params.serviceId; const requestBody: ObjectsByEntityRequest = req.body; try { @@ -415,7 +422,12 @@ export class KubernetesBuilder { }); router.get('/clusters', async (req, res) => { - await requirePermission(permissionApi, httpAuth, req); + await requirePermission( + permissionApi, + kubernetesClusterPermission, + httpAuth, + req, + ); const credentials = await httpAuth.credentials(req); const clusterDetails = await this.fetchClusterDetails(clusterSupplier, { credentials, diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index b40ae89c82..bc953c9781 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -317,6 +317,9 @@ export interface JobsFetchResponse { type: 'jobs'; } +// @public +export const kubernetesClusterPermission: BasicPermission; + // @public (undocumented) export type KubernetesErrorTypes = | 'BAD_REQUEST' @@ -350,6 +353,9 @@ export interface KubernetesRequestBody { // @public export const kubernetesResourcePermission: BasicPermission; +// @public +export const kubernetesServicePermission: BasicPermission; + // @public (undocumented) export interface LimitRangeFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/src/index.ts b/plugins/kubernetes-common/src/index.ts index 76a4c0b7e7..43a0500a9c 100644 --- a/plugins/kubernetes-common/src/index.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -25,7 +25,9 @@ export * from './catalog-entity-constants'; export * from './certificate-authority-constants'; export { kubernetesProxyPermission, + kubernetesClusterPermission, kubernetesResourcePermission, + kubernetesServicePermission, kubernetesPermissions, } from './permissions'; export * from './error-detection'; diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index 928b5c7d06..c96c183af6 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -24,7 +24,7 @@ export const kubernetesProxyPermission = createPermission({ attributes: {}, }); -/** This permission is used to check access to the resources endpoints +/** This permission is used to check access to the /resources endpoints * @public */ export const kubernetesResourcePermission = createPermission({ @@ -32,6 +32,22 @@ export const kubernetesResourcePermission = createPermission({ attributes: {}, }); +/** This permission is used to check access to the /services endpoint + * @public + */ +export const kubernetesServicePermission = createPermission({ + name: 'kubernetes.service', + attributes: {}, +}); + +/** This permission is used to check access to the /clusters endpoint + * @public + */ +export const kubernetesClusterPermission = createPermission({ + name: 'kubernetes.cluster', + attributes: {}, +}); + /** * List of all Kubernetes permissions. * @public @@ -39,4 +55,6 @@ export const kubernetesResourcePermission = createPermission({ export const kubernetesPermissions = [ kubernetesProxyPermission, kubernetesResourcePermission, + kubernetesServicePermission, + kubernetesClusterPermission, ]; From 66803d5ab7a0b61e6e9e76245a61e30fa49d876a Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Mon, 9 Dec 2024 10:22:41 +0100 Subject: [PATCH 04/19] Update names to plural Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 6 +++--- .../src/routes/resourcesRoutes.ts | 6 +++--- .../src/service/KubernetesBuilder.test.ts | 6 +++--- .../src/service/KubernetesBuilder.ts | 8 ++++---- plugins/kubernetes-common/report.api.md | 6 +++--- plugins/kubernetes-common/src/index.ts | 6 +++--- plugins/kubernetes-common/src/permissions.ts | 18 +++++++++--------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index 750db7e175..5fabbacd9e 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -3,6 +3,6 @@ '@backstage/plugin-kubernetes-common': minor --- -The `/clusters` endpoint is now protected by the `kubernetes.cluster` permission. -The `/services` endpoint is now protected by the `kubernetes.service` permission. -The `/resources` endpoints are now protected by the `kubernetes.resource` permission. +The `/clusters` endpoint is now protected by the `kubernetes.clusters` permission. +The `/services` endpoint is now protected by the `kubernetes.services` permission. +The `/resources` endpoints are now protected by the `kubernetes.resources` permission. diff --git a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts index ab3c7d8e06..6e737a1e4f 100644 --- a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts +++ b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts @@ -25,7 +25,7 @@ import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { requirePermission } from '../auth/requirePermission'; -import { kubernetesResourcePermission } from '@backstage/plugin-kubernetes-common'; +import { kubernetesResourcesPermission } from '@backstage/plugin-kubernetes-common'; export const addResourceRoutesToRouter = ( router: express.Router, @@ -68,7 +68,7 @@ export const addResourceRoutesToRouter = ( router.post('/resources/workloads/query', async (req, res) => { await requirePermission( permissionApi, - kubernetesResourcePermission, + kubernetesResourcesPermission, httpAuth, req, ); @@ -86,7 +86,7 @@ export const addResourceRoutesToRouter = ( router.post('/resources/custom/query', async (req, res) => { await requirePermission( permissionApi, - kubernetesResourcePermission, + kubernetesResourcesPermission, httpAuth, req, ); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 84de5d5575..145c9e6a06 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -795,9 +795,9 @@ metadata: expect(response.body).toMatchObject({ permissions: [ { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, - { type: 'basic', name: 'kubernetes.resource', attributes: {} }, - { type: 'basic', name: 'kubernetes.service', attributes: {} }, - { type: 'basic', name: 'kubernetes.cluster', attributes: {} }, + { type: 'basic', name: 'kubernetes.resources', attributes: {} }, + { type: 'basic', name: 'kubernetes.services', attributes: {} }, + { type: 'basic', name: 'kubernetes.clusters', attributes: {} }, ], rules: [], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index ed864dcf60..a8b1deaa9c 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -18,9 +18,9 @@ import { Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, - kubernetesClusterPermission, + kubernetesClustersPermission, kubernetesPermissions, - kubernetesServicePermission, + kubernetesServicesPermission, } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; @@ -398,7 +398,7 @@ export class KubernetesBuilder { router.post('/services/:serviceId', async (req, res) => { await requirePermission( permissionApi, - kubernetesServicePermission, + kubernetesServicesPermission, httpAuth, req, ); @@ -424,7 +424,7 @@ export class KubernetesBuilder { router.get('/clusters', async (req, res) => { await requirePermission( permissionApi, - kubernetesClusterPermission, + kubernetesClustersPermission, httpAuth, req, ); diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index bc953c9781..079169d8ab 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -318,7 +318,7 @@ export interface JobsFetchResponse { } // @public -export const kubernetesClusterPermission: BasicPermission; +export const kubernetesClustersPermission: BasicPermission; // @public (undocumented) export type KubernetesErrorTypes = @@ -351,10 +351,10 @@ export interface KubernetesRequestBody { } // @public -export const kubernetesResourcePermission: BasicPermission; +export const kubernetesResourcesPermission: BasicPermission; // @public -export const kubernetesServicePermission: BasicPermission; +export const kubernetesServicesPermission: BasicPermission; // @public (undocumented) export interface LimitRangeFetchResponse { diff --git a/plugins/kubernetes-common/src/index.ts b/plugins/kubernetes-common/src/index.ts index 43a0500a9c..d6e97dc8e6 100644 --- a/plugins/kubernetes-common/src/index.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -25,9 +25,9 @@ export * from './catalog-entity-constants'; export * from './certificate-authority-constants'; export { kubernetesProxyPermission, - kubernetesClusterPermission, - kubernetesResourcePermission, - kubernetesServicePermission, + kubernetesClustersPermission, + kubernetesResourcesPermission, + kubernetesServicesPermission, kubernetesPermissions, } from './permissions'; export * from './error-detection'; diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index c96c183af6..7c7a087dc5 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -27,24 +27,24 @@ export const kubernetesProxyPermission = createPermission({ /** This permission is used to check access to the /resources endpoints * @public */ -export const kubernetesResourcePermission = createPermission({ - name: 'kubernetes.resource', +export const kubernetesResourcesPermission = createPermission({ + name: 'kubernetes.resources', attributes: {}, }); /** This permission is used to check access to the /services endpoint * @public */ -export const kubernetesServicePermission = createPermission({ - name: 'kubernetes.service', +export const kubernetesServicesPermission = createPermission({ + name: 'kubernetes.services', attributes: {}, }); /** This permission is used to check access to the /clusters endpoint * @public */ -export const kubernetesClusterPermission = createPermission({ - name: 'kubernetes.cluster', +export const kubernetesClustersPermission = createPermission({ + name: 'kubernetes.clusters', attributes: {}, }); @@ -54,7 +54,7 @@ export const kubernetesClusterPermission = createPermission({ */ export const kubernetesPermissions = [ kubernetesProxyPermission, - kubernetesResourcePermission, - kubernetesServicePermission, - kubernetesClusterPermission, + kubernetesResourcesPermission, + kubernetesServicesPermission, + kubernetesClustersPermission, ]; From 07efa3a8d87d9cd80f960acdb62b237dc3fd0cc1 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 17 Dec 2024 13:39:23 +0100 Subject: [PATCH 05/19] Put /services/:serviceId under resources permission Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 2 +- .../src/service/KubernetesBuilder.test.ts | 1 - .../src/service/KubernetesBuilder.ts | 4 ++-- plugins/kubernetes-common/report.api.md | 3 --- plugins/kubernetes-common/src/index.ts | 1 - plugins/kubernetes-common/src/permissions.ts | 11 +---------- 6 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index 5fabbacd9e..d32585ea7f 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -4,5 +4,5 @@ --- The `/clusters` endpoint is now protected by the `kubernetes.clusters` permission. -The `/services` endpoint is now protected by the `kubernetes.services` permission. +The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources` permission. The `/resources` endpoints are now protected by the `kubernetes.resources` permission. diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 145c9e6a06..8814e729a6 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -796,7 +796,6 @@ metadata: permissions: [ { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, { type: 'basic', name: 'kubernetes.resources', attributes: {} }, - { type: 'basic', name: 'kubernetes.services', attributes: {} }, { type: 'basic', name: 'kubernetes.clusters', attributes: {} }, ], rules: [], diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index a8b1deaa9c..de301b7969 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -20,7 +20,7 @@ import { ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, kubernetesClustersPermission, kubernetesPermissions, - kubernetesServicesPermission, + kubernetesResourcesPermission, } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; @@ -398,7 +398,7 @@ export class KubernetesBuilder { router.post('/services/:serviceId', async (req, res) => { await requirePermission( permissionApi, - kubernetesServicesPermission, + kubernetesResourcesPermission, httpAuth, req, ); diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index 079169d8ab..b7a5e68ea2 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -353,9 +353,6 @@ export interface KubernetesRequestBody { // @public export const kubernetesResourcesPermission: BasicPermission; -// @public -export const kubernetesServicesPermission: BasicPermission; - // @public (undocumented) export interface LimitRangeFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/src/index.ts b/plugins/kubernetes-common/src/index.ts index d6e97dc8e6..d8eaaaf083 100644 --- a/plugins/kubernetes-common/src/index.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -27,7 +27,6 @@ export { kubernetesProxyPermission, kubernetesClustersPermission, kubernetesResourcesPermission, - kubernetesServicesPermission, kubernetesPermissions, } from './permissions'; export * from './error-detection'; diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index 7c7a087dc5..7e57d93c93 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -24,7 +24,7 @@ export const kubernetesProxyPermission = createPermission({ attributes: {}, }); -/** This permission is used to check access to the /resources endpoints +/** This permission is used to check access to the /resources and /services/:serviceId endpoints * @public */ export const kubernetesResourcesPermission = createPermission({ @@ -32,14 +32,6 @@ export const kubernetesResourcesPermission = createPermission({ attributes: {}, }); -/** This permission is used to check access to the /services endpoint - * @public - */ -export const kubernetesServicesPermission = createPermission({ - name: 'kubernetes.services', - attributes: {}, -}); - /** This permission is used to check access to the /clusters endpoint * @public */ @@ -55,6 +47,5 @@ export const kubernetesClustersPermission = createPermission({ export const kubernetesPermissions = [ kubernetesProxyPermission, kubernetesResourcesPermission, - kubernetesServicesPermission, kubernetesClustersPermission, ]; From 51c1a5f6069822064bcc43a77c84125bda6ea065 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Wed, 8 Jan 2025 17:45:01 +0100 Subject: [PATCH 06/19] Introduce permission check in kubernetes frontend Signed-off-by: Dominika Zemanovicova --- plugins/kubernetes-cluster/package.json | 1 + .../KubernetesClusterContent.tsx | 12 ++++++++- .../hooks/useKubernetesClustersPermission.ts | 26 +++++++++++++++++++ plugins/kubernetes/package.json | 1 + plugins/kubernetes/src/KubernetesContent.tsx | 21 +++++++++++++++ .../hooks/useKubernetesClustersPermission.ts | 26 +++++++++++++++++++ .../hooks/useKubernetesResourcesPermission.ts | 26 +++++++++++++++++++ yarn.lock | 2 ++ 8 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts create mode 100644 plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts create mode 100644 plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 12b7dcad21..78ff79e43b 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -47,6 +47,7 @@ "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/plugin-kubernetes-react": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@kubernetes-models/apimachinery": "^2.0.0", "@kubernetes-models/base": "^5.0.0", "@material-ui/core": "^4.12.2", diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index c46ac4c8e2..d005a6a2aa 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -24,6 +24,8 @@ import { useKubernetesClusterError, } from '../KubernetesClusterErrorContext/KubernetesClusterErrorContext'; import { WarningPanel } from '@backstage/core-components'; +import { useKubernetesClustersPermission } from '../../hooks/useKubernetesClustersPermission'; +import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; const ContentGrid = () => { const { error } = useKubernetesClusterError(); @@ -57,7 +59,15 @@ const ContentGrid = () => { * @public */ export const KubernetesClusterContent = () => { - return ( + const hasKubernetesClustersPermission = useKubernetesClustersPermission(); + + return !hasKubernetesClustersPermission ? ( + + ) : ( diff --git a/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts b/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts new file mode 100644 index 0000000000..4b71d381f6 --- /dev/null +++ b/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; +import { usePermission } from '@backstage/plugin-permission-react'; + +export const useKubernetesClustersPermission = () => { + const kubernetesClustersPermissionResult = usePermission({ + permission: kubernetesClustersPermission, + }); + + return kubernetesClustersPermissionResult.allowed; +}; diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index c42471e331..9dbc2c2c6a 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -66,6 +66,7 @@ "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/plugin-kubernetes-react": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@kubernetes-models/apimachinery": "^2.0.0", "@kubernetes-models/base": "^5.0.0", "@kubernetes/client-node": "1.0.0-rc7", diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index acd1ecc521..f9bde67f62 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -28,13 +28,18 @@ import { import { DetectedError, detectErrors, + kubernetesClustersPermission, + kubernetesResourcesPermission, } from '@backstage/plugin-kubernetes-common'; import { Content, EmptyState, Page, Progress, + WarningPanel, } from '@backstage/core-components'; +import { useKubernetesClustersPermission } from './hooks/useKubernetesClustersPermission'; +import { useKubernetesResourcesPermission } from './hooks/useKubernetesResourcesPermission'; type KubernetesContentProps = { entity: Entity; @@ -46,11 +51,27 @@ export const KubernetesContent = ({ entity, refreshIntervalMs, }: KubernetesContentProps) => { + const hasKubernetesClustersPermission = useKubernetesClustersPermission(); + const hasKubernetesResourcesPermission = useKubernetesResourcesPermission(); const { kubernetesObjects, error } = useKubernetesObjects( entity, refreshIntervalMs, ); + if (!hasKubernetesClustersPermission || !hasKubernetesResourcesPermission) { + return ( + + + + + + ); + } + const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? []; const clustersWithErrors = diff --git a/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts b/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts new file mode 100644 index 0000000000..4b71d381f6 --- /dev/null +++ b/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; +import { usePermission } from '@backstage/plugin-permission-react'; + +export const useKubernetesClustersPermission = () => { + const kubernetesClustersPermissionResult = usePermission({ + permission: kubernetesClustersPermission, + }); + + return kubernetesClustersPermissionResult.allowed; +}; diff --git a/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts b/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts new file mode 100644 index 0000000000..d5d730f39b --- /dev/null +++ b/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { kubernetesResourcesPermission } from '@backstage/plugin-kubernetes-common'; +import { usePermission } from '@backstage/plugin-permission-react'; + +export const useKubernetesResourcesPermission = () => { + const kubernetesResourcesPermissionResult = usePermission({ + permission: kubernetesResourcesPermission, + }); + + return kubernetesResourcesPermissionResult.allowed; +}; diff --git a/yarn.lock b/yarn.lock index e212f245b1..0cffefeeb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6740,6 +6740,7 @@ __metadata: "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/plugin-kubernetes-react": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@kubernetes-models/apimachinery": ^2.0.0 "@kubernetes-models/base": ^5.0.0 @@ -6868,6 +6869,7 @@ __metadata: "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/plugin-kubernetes-react": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@kubernetes-models/apimachinery": ^2.0.0 "@kubernetes-models/base": ^5.0.0 From 6f6e6846cbedaba4302fe267d1032fcebfae2963 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Wed, 8 Jan 2025 17:55:27 +0100 Subject: [PATCH 07/19] Update changeset Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index d32585ea7f..e7cb2d141a 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -1,8 +1,10 @@ --- '@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-cluster': minor '@backstage/plugin-kubernetes-common': minor +'@backstage/plugin-kubernetes': minor --- -The `/clusters` endpoint is now protected by the `kubernetes.clusters` permission. -The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources` permission. -The `/resources` endpoints are now protected by the `kubernetes.resources` permission. +**BREAKING** The `/clusters` endpoint is now protected by the `kubernetes.clusters` permission. +**BREAKING** The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources` permission. +**BREAKING** The `/resources` endpoints are now protected by the `kubernetes.resources` permission. From 2f993dfc9cd186ad1d641ddda11bdf58e3816441 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Thu, 9 Jan 2025 13:39:55 +0100 Subject: [PATCH 08/19] Mock permission Signed-off-by: Dominika Zemanovicova --- plugins/kubernetes/dev/index.tsx | 28 ++++++-- .../kubernetes/src/KubernetesContent.test.tsx | 67 +++++++++++-------- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/plugins/kubernetes/dev/index.tsx b/plugins/kubernetes/dev/index.tsx index 91b97cbfc5..e5581ac2ef 100644 --- a/plugins/kubernetes/dev/index.tsx +++ b/plugins/kubernetes/dev/index.tsx @@ -36,7 +36,8 @@ import fixture4 from '../src/__fixtures__/2-cronjobs.json'; import fixture5 from '../src/__fixtures__/1-rollouts.json'; import fixture6 from '../src/__fixtures__/3-ingresses.json'; import fixture7 from '../src/__fixtures__/2-statefulsets.json'; -import { TestApiProvider } from '@backstage/test-utils'; +import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; import { StructuredMetadataTable } from '@backstage/core-components'; const mockEntity: Entity = { @@ -149,7 +150,10 @@ createDevApp() title: 'Fixture 1', element: ( @@ -162,7 +166,10 @@ createDevApp() title: 'Fixture 2', element: ( @@ -175,7 +182,10 @@ createDevApp() title: 'Fixture 3', element: ( @@ -188,7 +198,10 @@ createDevApp() title: 'Fixture 4', element: ( @@ -201,7 +214,10 @@ createDevApp() title: 'Fixture 5', element: ( diff --git a/plugins/kubernetes/src/KubernetesContent.test.tsx b/plugins/kubernetes/src/KubernetesContent.test.tsx index 7c21c6f5a7..daeb184275 100644 --- a/plugins/kubernetes/src/KubernetesContent.test.tsx +++ b/plugins/kubernetes/src/KubernetesContent.test.tsx @@ -16,11 +16,16 @@ import React from 'react'; import { screen } from '@testing-library/react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import { KubernetesContent } from './KubernetesContent'; import { useKubernetesObjects } from '@backstage/plugin-kubernetes-react'; import * as oneDeployment from './__fixtures__/1-deployments.json'; import * as twoDeployments from './__fixtures__/2-deployments.json'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; jest.mock('@backstage/plugin-kubernetes-react', () => ({ ...jest.requireActual('@backstage/plugin-kubernetes-react'), @@ -39,15 +44,17 @@ describe('KubernetesContent', () => { error: undefined, }); await renderInTestApp( - , + + + , ); expect(screen.getByText('Your Clusters')).toBeInTheDocument(); // TODO add a prompt for the user to configure their clusters @@ -81,15 +88,17 @@ describe('KubernetesContent', () => { error: undefined, }); await renderInTestApp( - , + + + , ); expect(screen.getByText('cluster-1')).toBeInTheDocument(); @@ -145,15 +154,17 @@ describe('KubernetesContent', () => { error: undefined, }); await renderInTestApp( - , + + + , ); expect(screen.getAllByText('Cluster')).toHaveLength(2); expect(screen.getByText('cluster-a')).toBeInTheDocument(); From 7d7497785f82661d8e77d41bcf4d072932ba5359 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Thu, 9 Jan 2025 19:34:56 +0100 Subject: [PATCH 09/19] Fix permissions loading Signed-off-by: Dominika Zemanovicova --- .../KubernetesClusterContent.tsx | 29 +-- .../hooks/useKubernetesClustersPermission.ts | 26 --- plugins/kubernetes/src/KubernetesContent.tsx | 178 ++++++++---------- .../src/RequireKubernetesPermissions.tsx | 63 +++++++ .../hooks/useKubernetesClustersPermission.ts | 26 --- .../hooks/useKubernetesResourcesPermission.ts | 26 --- 6 files changed, 160 insertions(+), 188 deletions(-) delete mode 100644 plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts create mode 100644 plugins/kubernetes/src/RequireKubernetesPermissions.tsx delete mode 100644 plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts delete mode 100644 plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index d005a6a2aa..d47e28d294 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -24,8 +24,8 @@ import { useKubernetesClusterError, } from '../KubernetesClusterErrorContext/KubernetesClusterErrorContext'; import { WarningPanel } from '@backstage/core-components'; -import { useKubernetesClustersPermission } from '../../hooks/useKubernetesClustersPermission'; import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; +import { RequirePermission } from '@backstage/plugin-permission-react'; const ContentGrid = () => { const { error } = useKubernetesClusterError(); @@ -59,17 +59,20 @@ const ContentGrid = () => { * @public */ export const KubernetesClusterContent = () => { - const hasKubernetesClustersPermission = useKubernetesClustersPermission(); - - return !hasKubernetesClustersPermission ? ( - - ) : ( - - - + return ( + + } + > + + + + ); }; diff --git a/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts b/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts deleted file mode 100644 index 4b71d381f6..0000000000 --- a/plugins/kubernetes-cluster/src/hooks/useKubernetesClustersPermission.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; -import { usePermission } from '@backstage/plugin-permission-react'; - -export const useKubernetesClustersPermission = () => { - const kubernetesClustersPermissionResult = usePermission({ - permission: kubernetesClustersPermission, - }); - - return kubernetesClustersPermissionResult.allowed; -}; diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index f9bde67f62..633d6b2a5a 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -28,18 +28,14 @@ import { import { DetectedError, detectErrors, - kubernetesClustersPermission, - kubernetesResourcesPermission, } from '@backstage/plugin-kubernetes-common'; import { Content, EmptyState, Page, Progress, - WarningPanel, } from '@backstage/core-components'; -import { useKubernetesClustersPermission } from './hooks/useKubernetesClustersPermission'; -import { useKubernetesResourcesPermission } from './hooks/useKubernetesResourcesPermission'; +import { RequireKubernetesPermissions } from './RequireKubernetesPermissions'; type KubernetesContentProps = { entity: Entity; @@ -51,27 +47,11 @@ export const KubernetesContent = ({ entity, refreshIntervalMs, }: KubernetesContentProps) => { - const hasKubernetesClustersPermission = useKubernetesClustersPermission(); - const hasKubernetesResourcesPermission = useKubernetesResourcesPermission(); const { kubernetesObjects, error } = useKubernetesObjects( entity, refreshIntervalMs, ); - if (!hasKubernetesClustersPermission || !hasKubernetesResourcesPermission) { - return ( - - - - - - ); - } - const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? []; const clustersWithErrors = @@ -83,89 +63,93 @@ export const KubernetesContent = ({ : new Map(); return ( - - - - {kubernetesObjects === undefined && error === undefined && ( - - )} + + + + + {kubernetesObjects === undefined && error === undefined && ( + + )} - {/* errors retrieved from the kubernetes clusters */} - {clustersWithErrors.length > 0 && ( - - - + {/* errors retrieved from the kubernetes clusters */} + {clustersWithErrors.length > 0 && ( + + + + - - )} + )} - {/* other errors */} - {error !== undefined && ( - - - + {/* other errors */} + {error !== undefined && ( + + + + - - )} + )} - {kubernetesObjects && ( - - - - - - Your Clusters - - - {kubernetesObjects?.items.length <= 0 && ( - - - - - - )} - {kubernetesObjects?.items.length > 0 && - kubernetesObjects?.items.map((item, i) => { - const podsWithErrors = new Set( - detectedErrors - .get(item.cluster.name) - ?.filter(de => de.sourceRef.kind === 'Pod') - .map(de => de.sourceRef.name), - ); - - return ( - - + + + + + Your Clusters + + + {kubernetesObjects?.items.length <= 0 && ( + + + - ); - })} + + )} + {kubernetesObjects?.items.length > 0 && + kubernetesObjects?.items.map((item, i) => { + const podsWithErrors = new Set( + detectedErrors + .get(item.cluster.name) + ?.filter(de => de.sourceRef.kind === 'Pod') + .map(de => de.sourceRef.name), + ); + + return ( + + + + ); + })} + - - )} - - - + )} + + + + ); }; diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx new file mode 100644 index 0000000000..36f802b127 --- /dev/null +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { ReactNode } from 'react'; +import { + kubernetesClustersPermission, + kubernetesResourcesPermission, +} from '@backstage/plugin-kubernetes-common'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { Content, Page, WarningPanel } from '@backstage/core-components'; + +export type RequireKubernetesPermissionProps = { + children: ReactNode; +}; + +export function RequireKubernetesPermissions( + props: RequireKubernetesPermissionProps, +): JSX.Element | null { + const kubernetesClustersPermissionResult = usePermission({ + permission: kubernetesClustersPermission, + }); + const kubernetesResourcesPermissionResult = usePermission({ + permission: kubernetesResourcesPermission, + }); + + if ( + kubernetesClustersPermissionResult.loading || + kubernetesResourcesPermissionResult.loading + ) { + return null; + } + + if ( + kubernetesClustersPermissionResult.allowed && + kubernetesResourcesPermissionResult.allowed + ) { + return <>{props.children}; + } + + return ( + + + + + + ); +} diff --git a/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts b/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts deleted file mode 100644 index 4b71d381f6..0000000000 --- a/plugins/kubernetes/src/hooks/useKubernetesClustersPermission.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; -import { usePermission } from '@backstage/plugin-permission-react'; - -export const useKubernetesClustersPermission = () => { - const kubernetesClustersPermissionResult = usePermission({ - permission: kubernetesClustersPermission, - }); - - return kubernetesClustersPermissionResult.allowed; -}; diff --git a/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts b/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts deleted file mode 100644 index d5d730f39b..0000000000 --- a/plugins/kubernetes/src/hooks/useKubernetesResourcesPermission.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { kubernetesResourcesPermission } from '@backstage/plugin-kubernetes-common'; -import { usePermission } from '@backstage/plugin-permission-react'; - -export const useKubernetesResourcesPermission = () => { - const kubernetesResourcesPermissionResult = usePermission({ - permission: kubernetesResourcesPermission, - }); - - return kubernetesResourcesPermissionResult.allowed; -}; From 10974de8d9daad411ade8aa1e5a3fdc415b33718 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Thu, 9 Jan 2025 20:10:18 +0100 Subject: [PATCH 10/19] Rename permissions Co-authored-by: Vincenzo Scamporlino Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 6 +++--- .../src/routes/resourcesRoutes.ts | 6 +++--- .../src/service/KubernetesBuilder.test.ts | 4 ++-- .../src/service/KubernetesBuilder.ts | 8 ++++---- .../KubernetesClusterContent.tsx | 6 +++--- plugins/kubernetes-common/report.api.md | 4 ++-- plugins/kubernetes-common/src/index.ts | 4 ++-- plugins/kubernetes-common/src/permissions.ts | 20 +++++++++++-------- .../src/RequireKubernetesPermissions.tsx | 10 +++++----- 9 files changed, 36 insertions(+), 32 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index e7cb2d141a..16f0f0cbd9 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -5,6 +5,6 @@ '@backstage/plugin-kubernetes': minor --- -**BREAKING** The `/clusters` endpoint is now protected by the `kubernetes.clusters` permission. -**BREAKING** The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources` permission. -**BREAKING** The `/resources` endpoints are now protected by the `kubernetes.resources` permission. +**BREAKING** The `/clusters` endpoint is now protected by the `kubernetes.clusters.read` permission. +**BREAKING** The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources.read` permission. +**BREAKING** The `/resources` endpoints are now protected by the `kubernetes.resources.read` permission. diff --git a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts index 6e737a1e4f..d500d3cb8b 100644 --- a/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts +++ b/plugins/kubernetes-backend/src/routes/resourcesRoutes.ts @@ -25,7 +25,7 @@ import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; import { AuthService, HttpAuthService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { requirePermission } from '../auth/requirePermission'; -import { kubernetesResourcesPermission } from '@backstage/plugin-kubernetes-common'; +import { kubernetesResourcesReadPermission } from '@backstage/plugin-kubernetes-common'; export const addResourceRoutesToRouter = ( router: express.Router, @@ -68,7 +68,7 @@ export const addResourceRoutesToRouter = ( router.post('/resources/workloads/query', async (req, res) => { await requirePermission( permissionApi, - kubernetesResourcesPermission, + kubernetesResourcesReadPermission, httpAuth, req, ); @@ -86,7 +86,7 @@ export const addResourceRoutesToRouter = ( router.post('/resources/custom/query', async (req, res) => { await requirePermission( permissionApi, - kubernetesResourcesPermission, + kubernetesResourcesReadPermission, httpAuth, req, ); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 8814e729a6..3046232bad 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -795,8 +795,8 @@ metadata: expect(response.body).toMatchObject({ permissions: [ { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, - { type: 'basic', name: 'kubernetes.resources', attributes: {} }, - { type: 'basic', name: 'kubernetes.clusters', attributes: {} }, + { type: 'basic', name: 'kubernetes.resources.read', attributes: {} }, + { type: 'basic', name: 'kubernetes.clusters.read', attributes: {} }, ], rules: [], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index de301b7969..2d2c592f53 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -18,9 +18,9 @@ import { Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, - kubernetesClustersPermission, + kubernetesClustersReadPermission, kubernetesPermissions, - kubernetesResourcesPermission, + kubernetesResourcesReadPermission, } from '@backstage/plugin-kubernetes-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; @@ -398,7 +398,7 @@ export class KubernetesBuilder { router.post('/services/:serviceId', async (req, res) => { await requirePermission( permissionApi, - kubernetesResourcesPermission, + kubernetesResourcesReadPermission, httpAuth, req, ); @@ -424,7 +424,7 @@ export class KubernetesBuilder { router.get('/clusters', async (req, res) => { await requirePermission( permissionApi, - kubernetesClustersPermission, + kubernetesClustersReadPermission, httpAuth, req, ); diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index d47e28d294..3f53f7022b 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -24,7 +24,7 @@ import { useKubernetesClusterError, } from '../KubernetesClusterErrorContext/KubernetesClusterErrorContext'; import { WarningPanel } from '@backstage/core-components'; -import { kubernetesClustersPermission } from '@backstage/plugin-kubernetes-common'; +import { kubernetesClustersReadPermission } from '@backstage/plugin-kubernetes-common'; import { RequirePermission } from '@backstage/plugin-permission-react'; const ContentGrid = () => { @@ -61,12 +61,12 @@ const ContentGrid = () => { export const KubernetesClusterContent = () => { return ( } > diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index b7a5e68ea2..cc99e5063d 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -318,7 +318,7 @@ export interface JobsFetchResponse { } // @public -export const kubernetesClustersPermission: BasicPermission; +export const kubernetesClustersReadPermission: BasicPermission; // @public (undocumented) export type KubernetesErrorTypes = @@ -351,7 +351,7 @@ export interface KubernetesRequestBody { } // @public -export const kubernetesResourcesPermission: BasicPermission; +export const kubernetesResourcesReadPermission: BasicPermission; // @public (undocumented) export interface LimitRangeFetchResponse { diff --git a/plugins/kubernetes-common/src/index.ts b/plugins/kubernetes-common/src/index.ts index d8eaaaf083..8378ac8199 100644 --- a/plugins/kubernetes-common/src/index.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -25,8 +25,8 @@ export * from './catalog-entity-constants'; export * from './certificate-authority-constants'; export { kubernetesProxyPermission, - kubernetesClustersPermission, - kubernetesResourcesPermission, + kubernetesClustersReadPermission, + kubernetesResourcesReadPermission, kubernetesPermissions, } from './permissions'; export * from './error-detection'; diff --git a/plugins/kubernetes-common/src/permissions.ts b/plugins/kubernetes-common/src/permissions.ts index 7e57d93c93..718251567c 100644 --- a/plugins/kubernetes-common/src/permissions.ts +++ b/plugins/kubernetes-common/src/permissions.ts @@ -27,17 +27,21 @@ export const kubernetesProxyPermission = createPermission({ /** This permission is used to check access to the /resources and /services/:serviceId endpoints * @public */ -export const kubernetesResourcesPermission = createPermission({ - name: 'kubernetes.resources', - attributes: {}, +export const kubernetesResourcesReadPermission = createPermission({ + name: 'kubernetes.resources.read', + attributes: { + action: 'read', + }, }); /** This permission is used to check access to the /clusters endpoint * @public */ -export const kubernetesClustersPermission = createPermission({ - name: 'kubernetes.clusters', - attributes: {}, +export const kubernetesClustersReadPermission = createPermission({ + name: 'kubernetes.clusters.read', + attributes: { + action: 'read', + }, }); /** @@ -46,6 +50,6 @@ export const kubernetesClustersPermission = createPermission({ */ export const kubernetesPermissions = [ kubernetesProxyPermission, - kubernetesResourcesPermission, - kubernetesClustersPermission, + kubernetesResourcesReadPermission, + kubernetesClustersReadPermission, ]; diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx index 36f802b127..071a5aa4f9 100644 --- a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -15,8 +15,8 @@ */ import React, { ReactNode } from 'react'; import { - kubernetesClustersPermission, - kubernetesResourcesPermission, + kubernetesClustersReadPermission, + kubernetesResourcesReadPermission, } from '@backstage/plugin-kubernetes-common'; import { usePermission } from '@backstage/plugin-permission-react'; import { Content, Page, WarningPanel } from '@backstage/core-components'; @@ -29,10 +29,10 @@ export function RequireKubernetesPermissions( props: RequireKubernetesPermissionProps, ): JSX.Element | null { const kubernetesClustersPermissionResult = usePermission({ - permission: kubernetesClustersPermission, + permission: kubernetesClustersReadPermission, }); const kubernetesResourcesPermissionResult = usePermission({ - permission: kubernetesResourcesPermission, + permission: kubernetesResourcesReadPermission, }); if ( @@ -55,7 +55,7 @@ export function RequireKubernetesPermissions( From ba816e45a54d7e1dae266230f5bd4edb4c2829db Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Fri, 10 Jan 2025 10:16:09 +0100 Subject: [PATCH 11/19] Move page and content Signed-off-by: Dominika Zemanovicova --- plugins/kubernetes/src/KubernetesContent.tsx | 20 +++++++++---------- .../src/RequireKubernetesPermissions.tsx | 14 +++++-------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index 633d6b2a5a..f980e87f0b 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -63,12 +63,12 @@ export const KubernetesContent = ({ : new Map(); return ( - - - - + + + + {kubernetesObjects === undefined && error === undefined && ( )} @@ -147,9 +147,9 @@ export const KubernetesContent = ({ )} - - - - + + + + ); }; diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx index 071a5aa4f9..7912393303 100644 --- a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -19,7 +19,7 @@ import { kubernetesResourcesReadPermission, } from '@backstage/plugin-kubernetes-common'; import { usePermission } from '@backstage/plugin-permission-react'; -import { Content, Page, WarningPanel } from '@backstage/core-components'; +import { WarningPanel } from '@backstage/core-components'; export type RequireKubernetesPermissionProps = { children: ReactNode; @@ -50,14 +50,10 @@ export function RequireKubernetesPermissions( } return ( - - - - - + /> ); } From 1457580af2efb9d8a8e306cf4835654c5b6ad606 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Wed, 22 Jan 2025 15:39:05 +0100 Subject: [PATCH 12/19] Specify which admin to contact Signed-off-by: Dominika Zemanovicova --- .../KubernetesClusterContent/KubernetesClusterContent.tsx | 2 +- plugins/kubernetes/src/RequireKubernetesPermissions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index 3f53f7022b..88e0f8b703 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -65,7 +65,7 @@ export const KubernetesClusterContent = () => { errorPage={ } diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx index 7912393303..65855fc44c 100644 --- a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -52,7 +52,7 @@ export function RequireKubernetesPermissions( return ( ); From d131384763b52327fec41af950650dcd5804864c Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Wed, 22 Jan 2025 15:39:51 +0100 Subject: [PATCH 13/19] Add docs Signed-off-by: Dominika Zemanovicova --- docs/features/kubernetes/permissions.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/features/kubernetes/permissions.md diff --git a/docs/features/kubernetes/permissions.md b/docs/features/kubernetes/permissions.md new file mode 100644 index 0000000000..fd0ab23418 --- /dev/null +++ b/docs/features/kubernetes/permissions.md @@ -0,0 +1,18 @@ +--- +id: permissions +title: Permissions +description: Configuring permissions for Kubernetes plugin +--- + +The Kubernetes plugin integrates with the permission framework. Administrators can define PermissionPolicies +to restrict access to the `/clusters`, `/services/:serviceId`, `/resources` and `/proxy` endpoints. + +This feature assumes your Backstage instance has enabled the [permissions framework](https://backstage.io/docs/permissions/getting-started). + +### Available permissions + +| Name | Policy | Description | +| ------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| kubernetes.clusters.read | read | Allows the user to read Kubernetes clusters information | +| kubernetes.resources.read | read | Allows the user to read Kubernetes resources information | +| kubernetes.proxy | | Allows the user to make arbitrary requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/) | From 2e1a3b446ef4114cd612bc040bd77e6c3e1bc02a Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Wed, 22 Jan 2025 15:42:41 +0100 Subject: [PATCH 14/19] Specify endpoints Signed-off-by: Dominika Zemanovicova --- docs/features/kubernetes/permissions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/features/kubernetes/permissions.md b/docs/features/kubernetes/permissions.md index fd0ab23418..5f1475d4b3 100644 --- a/docs/features/kubernetes/permissions.md +++ b/docs/features/kubernetes/permissions.md @@ -11,8 +11,8 @@ This feature assumes your Backstage instance has enabled the [permissions framew ### Available permissions -| Name | Policy | Description | -| ------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | -| kubernetes.clusters.read | read | Allows the user to read Kubernetes clusters information | -| kubernetes.resources.read | read | Allows the user to read Kubernetes resources information | -| kubernetes.proxy | | Allows the user to make arbitrary requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/) | +| Name | Policy | Description | +| ------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| kubernetes.clusters.read | read | Allows the user to read Kubernetes clusters information under `/clusters` | +| kubernetes.resources.read | read | Allows the user to read Kubernetes resources information under `/services/:serviceId` and `/resources` | +| kubernetes.proxy | | Allows the user to make arbitrary requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/) under `/proxy` | From 45bac6d53e2a7e56c975fa97a424a1897df696f1 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Mon, 3 Feb 2025 16:58:31 +0100 Subject: [PATCH 15/19] Change to patch Signed-off-by: Dominika Zemanovicova --- .changeset/real-foxes-film.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.changeset/real-foxes-film.md b/.changeset/real-foxes-film.md index 16f0f0cbd9..22f1ecead2 100644 --- a/.changeset/real-foxes-film.md +++ b/.changeset/real-foxes-film.md @@ -1,10 +1,10 @@ --- -'@backstage/plugin-kubernetes-backend': minor -'@backstage/plugin-kubernetes-cluster': minor -'@backstage/plugin-kubernetes-common': minor -'@backstage/plugin-kubernetes': minor +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-cluster': patch +'@backstage/plugin-kubernetes-common': patch +'@backstage/plugin-kubernetes': patch --- -**BREAKING** The `/clusters` endpoint is now protected by the `kubernetes.clusters.read` permission. -**BREAKING** The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources.read` permission. -**BREAKING** The `/resources` endpoints are now protected by the `kubernetes.resources.read` permission. +The `/clusters` endpoint is now protected by the `kubernetes.clusters.read` permission. +The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources.read` permission. +The `/resources` endpoints are now protected by the `kubernetes.resources.read` permission. From 4c76f0582683f1b877440a054e1a305b91afb3a5 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Mon, 3 Feb 2025 17:38:35 +0100 Subject: [PATCH 16/19] Change alert message Signed-off-by: Dominika Zemanovicova --- .../KubernetesClusterContent/KubernetesClusterContent.tsx | 2 +- plugins/kubernetes/src/RequireKubernetesPermissions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index 88e0f8b703..11293a41a7 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -65,7 +65,7 @@ export const KubernetesClusterContent = () => { errorPage={ } diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx index 65855fc44c..92a88e5b9c 100644 --- a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -52,7 +52,7 @@ export function RequireKubernetesPermissions( return ( ); From af8ec8757600b3bbd995334430b2ab0f43ea5fe2 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 4 Feb 2025 10:39:01 +0100 Subject: [PATCH 17/19] Use translation Signed-off-by: Dominika Zemanovicova --- plugins/kubernetes-cluster/package.json | 19 +++++++-- .../kubernetes-cluster/report-alpha.api.md | 18 +++++++++ plugins/kubernetes-cluster/src/alpha.ts | 17 ++++++++ .../KubernetesClusterContent.tsx | 9 +++-- plugins/kubernetes-cluster/src/translation.ts | 30 ++++++++++++++ plugins/kubernetes/report-alpha.api.md | 40 ++++++++++++------- .../src/RequireKubernetesPermissions.tsx | 8 ++-- plugins/kubernetes/src/alpha/index.ts | 2 +- plugins/kubernetes/src/alpha/translation.ts | 30 ++++++++++++++ 9 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 plugins/kubernetes-cluster/report-alpha.api.md create mode 100644 plugins/kubernetes-cluster/src/alpha.ts create mode 100644 plugins/kubernetes-cluster/src/translation.ts create mode 100644 plugins/kubernetes/src/alpha/translation.ts diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 78ff79e43b..cd204eaf9c 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -10,9 +10,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "keywords": [ "backstage", @@ -26,8 +24,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], diff --git a/plugins/kubernetes-cluster/report-alpha.api.md b/plugins/kubernetes-cluster/report-alpha.api.md new file mode 100644 index 0000000000..13314d6c83 --- /dev/null +++ b/plugins/kubernetes-cluster/report-alpha.api.md @@ -0,0 +1,18 @@ +## API Report File for "@backstage/plugin-kubernetes-cluster" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +// @alpha (undocumented) +export const kubernetesClusterTranslationRef: TranslationRef< + 'kubernetes-cluster', + { + readonly 'kubernetesClusterContentPage.permissionAlert.message': "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' permission."; + readonly 'kubernetesClusterContentPage.permissionAlert.title': 'Permission required'; + } +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/kubernetes-cluster/src/alpha.ts b/plugins/kubernetes-cluster/src/alpha.ts new file mode 100644 index 0000000000..e49b7ac9c9 --- /dev/null +++ b/plugins/kubernetes-cluster/src/alpha.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { kubernetesClusterTranslationRef } from './translation'; diff --git a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx index 11293a41a7..2a824d329a 100644 --- a/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx +++ b/plugins/kubernetes-cluster/src/components/KubernetesClusterContent/KubernetesClusterContent.tsx @@ -26,6 +26,8 @@ import { import { WarningPanel } from '@backstage/core-components'; import { kubernetesClustersReadPermission } from '@backstage/plugin-kubernetes-common'; import { RequirePermission } from '@backstage/plugin-permission-react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesClusterTranslationRef } from '../../translation'; const ContentGrid = () => { const { error } = useKubernetesClusterError(); @@ -59,14 +61,15 @@ const ContentGrid = () => { * @public */ export const KubernetesClusterContent = () => { + const { t } = useTranslationRef(kubernetesClusterTranslationRef); + return ( } > diff --git a/plugins/kubernetes-cluster/src/translation.ts b/plugins/kubernetes-cluster/src/translation.ts new file mode 100644 index 0000000000..ba99c6744e --- /dev/null +++ b/plugins/kubernetes-cluster/src/translation.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const kubernetesClusterTranslationRef = createTranslationRef({ + id: 'kubernetes-cluster', + messages: { + kubernetesClusterContentPage: { + permissionAlert: { + title: 'Permission required', + message: + "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' permission.", + }, + }, + }, +}); diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index 4f779df258..ea04986595 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -13,6 +13,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @public (undocumented) const _default: FrontendPlugin< @@ -47,21 +48,6 @@ const _default: FrontendPlugin< routeRef?: RouteRef | undefined; }; }>; - 'api:kubernetes': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; 'entity-content:kubernetes/kubernetes': ExtensionDefinition<{ kind: 'entity-content'; name: 'kubernetes'; @@ -113,6 +99,21 @@ const _default: FrontendPlugin< filter?: string | ((entity: Entity) => boolean) | undefined; }; }>; + 'api:kubernetes': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; 'api:kubernetes/proxy': ExtensionDefinition<{ kind: 'api'; name: 'proxy'; @@ -162,5 +163,14 @@ const _default: FrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const kubernetesTranslationRef: TranslationRef< + 'kubernetes', + { + readonly 'kubernetesContentPage.permissionAlert.message': "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission."; + readonly 'kubernetesContentPage.permissionAlert.title': 'Permission required'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx index 92a88e5b9c..efc83e8ef8 100644 --- a/plugins/kubernetes/src/RequireKubernetesPermissions.tsx +++ b/plugins/kubernetes/src/RequireKubernetesPermissions.tsx @@ -20,6 +20,8 @@ import { } from '@backstage/plugin-kubernetes-common'; import { usePermission } from '@backstage/plugin-permission-react'; import { WarningPanel } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesTranslationRef } from './alpha/translation'; export type RequireKubernetesPermissionProps = { children: ReactNode; @@ -34,6 +36,7 @@ export function RequireKubernetesPermissions( const kubernetesResourcesPermissionResult = usePermission({ permission: kubernetesResourcesReadPermission, }); + const { t } = useTranslationRef(kubernetesTranslationRef); if ( kubernetesClustersPermissionResult.loading || @@ -51,9 +54,8 @@ export function RequireKubernetesPermissions( return ( ); } diff --git a/plugins/kubernetes/src/alpha/index.ts b/plugins/kubernetes/src/alpha/index.ts index 2f137f09ee..23888f968f 100644 --- a/plugins/kubernetes/src/alpha/index.ts +++ b/plugins/kubernetes/src/alpha/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +export { kubernetesTranslationRef } from './translation'; export { default } from './plugin'; diff --git a/plugins/kubernetes/src/alpha/translation.ts b/plugins/kubernetes/src/alpha/translation.ts new file mode 100644 index 0000000000..7f9704af84 --- /dev/null +++ b/plugins/kubernetes/src/alpha/translation.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const kubernetesTranslationRef = createTranslationRef({ + id: 'kubernetes', + messages: { + kubernetesContentPage: { + permissionAlert: { + title: 'Permission required', + message: + "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission.", + }, + }, + }, +}); From f47658612a923e9888be4742a2ada2f8078cff62 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 4 Feb 2025 11:56:04 +0100 Subject: [PATCH 18/19] Update internationalization kubernetes docs Signed-off-by: Dominika Zemanovicova --- docs/features/kubernetes/configuration.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 1a921103b1..c323d5118a 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -80,11 +80,13 @@ The default value is `false`. #### Internationalization -To customize or translate the **Delete Pod** text, use the following approach: +To customize or translate text in some of the components, use the following approach: ```js import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; import { kubernetesReactTranslationRef } from '@backstage/plugin-kubernetes-react/alpha'; +import { kubernetesTranslationRef } from '@backstage/plugin-kubernetes/alpha'; +import { kubernetesClusterTranslationRef } from '@backstage/plugin-kubernetes-cluster/alpha'; const app = createApp({ __experimentalTranslations: { @@ -94,7 +96,21 @@ const app = createApp({ messages: { "podDrawer.buttons.delete": 'Restart Pod' } - }) + }), + createTranslationMessages({ + ref: kubernetesTranslationRef, + messages: { + 'kubernetesContentPage.permissionAlert.title': 'Insufficient permissions', + 'kubernetesContentPage.permissionAlert.message': 'You do not have permissions to view Kubernetes objects.', + }, + }), + createTranslationMessages({ + ref: kubernetesClusterTranslationRef, + messages: { + 'kubernetesClusterContentPage.permissionAlert.title': 'Insufficient permissions', + 'kubernetesClusterContentPage.permissionAlert.message': 'You do not have permissions to view Kubernetes objects.', + }, + }), ] }, ... From d93b7fc6fbdb584772f1021e65b5fbc27a6856ef Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 4 Feb 2025 12:43:38 +0100 Subject: [PATCH 19/19] chore: re-order Signed-off-by: blam --- plugins/kubernetes/report-alpha.api.md | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index ea04986595..19f0554291 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -48,6 +48,21 @@ const _default: FrontendPlugin< routeRef?: RouteRef | undefined; }; }>; + 'api:kubernetes': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + AnyApiFactory, + 'core.api.factory', + {} + >; + inputs: {}; + params: { + factory: AnyApiFactory; + }; + }>; 'entity-content:kubernetes/kubernetes': ExtensionDefinition<{ kind: 'entity-content'; name: 'kubernetes'; @@ -99,21 +114,6 @@ const _default: FrontendPlugin< filter?: string | ((entity: Entity) => boolean) | undefined; }; }>; - 'api:kubernetes': ExtensionDefinition<{ - kind: 'api'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - AnyApiFactory, - 'core.api.factory', - {} - >; - inputs: {}; - params: { - factory: AnyApiFactory; - }; - }>; 'api:kubernetes/proxy': ExtensionDefinition<{ kind: 'api'; name: 'proxy';