diff --git a/.changeset/fifty-pigs-travel.md b/.changeset/fifty-pigs-travel.md new file mode 100644 index 0000000000..28ac23d987 --- /dev/null +++ b/.changeset/fifty-pigs-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +cluster.caFile configuration is used when the Middleware is created on KubernetesProxy diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 15bc8ff68e..aaadbfae0e 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -44,6 +44,7 @@ import { KubernetesProxy, } from './KubernetesProxy'; import fetch from 'cross-fetch'; +import mockFs from 'mock-fs'; import type { Request } from 'express'; @@ -664,6 +665,82 @@ describe('KubernetesProxy', () => { expect(response.status).toEqual(200); }); + describe('when server uses TLS', () => { + let httpsRequest: jest.SpyInstance; + beforeAll(() => { + httpsRequest = jest.spyOn( + // this is pretty egregious reverse engineering of msw. + // If the SetupServerApi constructor was exported, we wouldn't need + // to be quite so hacky here + (worker as any).interceptor.interceptors[0].modules.get('https'), + 'request', + ); + }); + beforeEach(() => { + httpsRequest.mockClear(); + }); + describe('should pass the exact response from Kubernetes using the CA file', () => { + afterEach(() => { + mockFs.restore(); + }); + + it('should trust contents of specified caFile', async () => { + mockFs({ + '/path/to/ca.crt': 'MOCKCA', + }); + + const apiResponse = { + kind: 'APIVersions', + versions: ['v1'], + serverAddressByClientCIDRs: [ + { + clientCIDR: '0.0.0.0/0', + serverAddress: '192.168.0.1:3333', + }, + ], + }; + + clusterSupplier.getClusters.mockResolvedValue([ + { + name: 'cluster1', + url: 'https://localhost:9999', + serviceAccountToken: '', + authProvider: 'serviceAccount', + caFile: '/path/to/ca.crt', + }, + ] as ClusterDetails[]); + + authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({ + name: 'cluster1', + url: 'https://localhost:9999', + serviceAccountToken: '', + authProvider: 'serviceAccount', + } as ClusterDetails); + + worker.use( + rest.get('https://localhost:9999/api', (_: any, res: any, ctx: any) => + res(ctx.status(299), ctx.json(apiResponse)), + ), + ); + + const requestPromise = setupProxyPromise({ + proxyPath: '/mountpath', + requestPath: '/api', + headers: { [HEADER_KUBERNETES_CLUSTER]: 'cluster1' }, + }); + + const response = await requestPromise; + + expect(response.status).toEqual(299); + expect(response.body).toStrictEqual(apiResponse); + + expect(httpsRequest).toHaveBeenCalledTimes(1); + const [[{ ca }]] = httpsRequest.mock.calls; + expect(ca).toEqual('MOCKCA'); + }); + }); + }); + describe('WebSocket', () => { const proxyPath = '/proxy'; const wsPath = '/ws'; diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index 742d078071..8ae1fb9fcc 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -158,7 +158,10 @@ export class KubernetesProxy { protocol: url.protocol, host: url.hostname, port: url.port, - ca: bufferFromFileOrString('', cluster.caData)?.toString(), + ca: bufferFromFileOrString( + cluster.caFile, + cluster.caData, + )?.toString(), }; }, onError: (error, req, res) => {