From 1df3afb21e8dd2a0481e6b5bb54229e9792efc94 Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Fri, 8 Sep 2023 13:11:07 -0400 Subject: [PATCH] make KubernetesCredential a tagged union This is easier to read than using primitives like `undefined` and `string` to represent these states. Signed-off-by: Jamie Klassen --- plugins/kubernetes-backend/api-report.md | 9 +++- .../src/auth/AksStrategy.test.ts | 5 ++- .../src/auth/AksStrategy.ts | 3 +- .../src/auth/AnonymousStrategy.ts | 2 +- .../src/auth/AwsIamStrategy.test.ts | 21 ++++++---- .../src/auth/AwsIamStrategy.ts | 15 ++++--- .../src/auth/AzureIdentityStrategy.test.ts | 22 +++++----- .../src/auth/AzureIdentityStrategy.ts | 6 ++- .../src/auth/DispatchStrategy.test.ts | 10 ++++- .../src/auth/GoogleServiceAccountStrategy.ts | 6 +-- .../src/auth/GoogleStrategy.ts | 6 +-- .../src/auth/OidcStrategy.test.ts | 5 ++- .../src/auth/OidcStrategy.ts | 6 +-- .../src/auth/ServiceAccountStrategy.ts | 3 +- plugins/kubernetes-backend/src/auth/types.ts | 4 +- .../service/KubernetesFanOutHandler.test.ts | 23 ++++++++--- .../src/service/KubernetesFetcher.test.ts | 34 +++++++-------- .../src/service/KubernetesFetcher.ts | 6 ++- .../src/service/KubernetesProxy.test.ts | 41 ++++++++++++++----- .../src/service/KubernetesProxy.ts | 10 ++--- 20 files changed, 151 insertions(+), 86 deletions(-) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index dad792843f..5aa124394a 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -278,7 +278,14 @@ export interface KubernetesClustersSupplier { } // @public -export type KubernetesCredential = string | undefined; +export type KubernetesCredential = + | { + type: 'bearer token'; + token: string; + } + | { + type: 'anonymous'; + }; // @public (undocumented) export interface KubernetesEnvironment { diff --git a/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts index 373d053059..dfd8c85ce4 100644 --- a/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts @@ -24,6 +24,9 @@ describe('AksStrategy', () => { { aks: 'aksToken' }, ); - expect(credential).toBe('aksToken'); + expect(credential).toStrictEqual({ + type: 'bearer token', + token: 'aksToken', + }); }); }); diff --git a/plugins/kubernetes-backend/src/auth/AksStrategy.ts b/plugins/kubernetes-backend/src/auth/AksStrategy.ts index 01fe8ffc8e..db4d423060 100644 --- a/plugins/kubernetes-backend/src/auth/AksStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AksStrategy.ts @@ -26,7 +26,8 @@ export class AksStrategy implements AuthenticationStrategy { _: ClusterDetails, requestAuth: KubernetesRequestAuth, ): Promise { - return requestAuth.aks; + const token = requestAuth.aks; + return token ? { type: 'bearer token', token } : { type: 'anonymous' }; } public validate(_: AuthMetadata) {} } diff --git a/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts b/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts index 2b3215f118..20e688c2a3 100644 --- a/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts @@ -22,7 +22,7 @@ import { AuthenticationStrategy, KubernetesCredential } from './types'; */ export class AnonymousStrategy implements AuthenticationStrategy { public async getCredential(): Promise { - return undefined; + return { type: 'anonymous' }; } public validate() {} diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts index e142717541..873c78e177 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts @@ -65,9 +65,10 @@ describe('AwsIamStrategy tests', () => { url: '', authMetadata: {}, }); - expect(credential).toEqual( - 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', - ); + expect(credential).toEqual({ + type: 'bearer token', + token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', + }); }); it('returns a signed url for AWS credentials with assume role', async () => { @@ -81,9 +82,10 @@ describe('AwsIamStrategy tests', () => { }, }); - expect(credential).toEqual( - 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', - ); + expect(credential).toEqual({ + type: 'bearer token', + token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', + }); expect(fromTemporaryCredentials).toHaveBeenCalledWith({ clientConfig: { region: 'us-east-1', @@ -109,9 +111,10 @@ describe('AwsIamStrategy tests', () => { [ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: 'external-id', }, }); - expect(credential).toEqual( - 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', - ); + expect(credential).toEqual({ + type: 'bearer token', + token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', + }); expect(fromTemporaryCredentials).toHaveBeenCalledWith({ clientConfig: { region: 'us-east-1', diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts index 367df5271a..90ea3ba1b2 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts @@ -51,14 +51,17 @@ export class AwsIamStrategy implements AuthenticationStrategy { this.credsManager = DefaultAwsCredentialsManager.fromConfig(opts.config); } - public getCredential( + public async getCredential( clusterDetails: ClusterDetails, ): Promise { - return this.getBearerToken( - clusterDetails.name, - clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], - clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], - ); + return { + type: 'bearer token', + token: await this.getBearerToken( + clusterDetails.name, + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], + ), + }; } public validate() {} diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts index 398bb99516..7e1f99aeeb 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts @@ -51,7 +51,7 @@ describe('AzureIdentityStrategy tests', () => { ); const credential = await strategy.getCredential(); - expect(credential).toEqual('MY_TOKEN_1'); + expect(credential).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); }); it('should re-use token before expiry', async () => { @@ -61,10 +61,10 @@ describe('AzureIdentityStrategy tests', () => { ); const credential = await strategy.getCredential(); - expect(credential).toEqual('MY_TOKEN_1'); + expect(credential).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); const credential2 = await strategy.getCredential(); - expect(credential2).toEqual('MY_TOKEN_1'); + expect(credential2).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); }); it('should issue new token 15 minutes befory expiry', async () => { @@ -76,12 +76,12 @@ describe('AzureIdentityStrategy tests', () => { ); const credential = await strategy.getCredential(); - expect(credential).toEqual('MY_TOKEN_1'); + expect(credential).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins const credential2 = await strategy.getCredential(); - expect(credential2).toEqual('MY_TOKEN_2'); + expect(credential2).toEqual({ type: 'bearer token', token: 'MY_TOKEN_2' }); }); it('should re-use existing token if there is afailure', async () => { @@ -93,20 +93,20 @@ describe('AzureIdentityStrategy tests', () => { ); const credential = await strategy.getCredential(); - expect(credential).toEqual('MY_TOKEN_1'); + expect(credential).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min const credential2 = await strategy.getCredential(); - expect(credential2).toEqual('MY_TOKEN_2'); + expect(credential2).toEqual({ type: 'bearer token', token: 'MY_TOKEN_2' }); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min const credential3 = await strategy.getCredential(); - expect(credential3).toEqual('MY_TOKEN_2'); + expect(credential3).toEqual({ type: 'bearer token', token: 'MY_TOKEN_2' }); const credential4 = await strategy.getCredential(); - expect(credential4).toEqual('MY_TOKEN_4'); + expect(credential4).toEqual({ type: 'bearer token', token: 'MY_TOKEN_4' }); }); it('should throw if existing token expired and failed to fetch a new one', async () => { @@ -118,12 +118,12 @@ describe('AzureIdentityStrategy tests', () => { ); const credential = await strategy.getCredential(); - expect(credential).toEqual('MY_TOKEN_1'); + expect(credential).toEqual({ type: 'bearer token', token: 'MY_TOKEN_1' }); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min const credential2 = await strategy.getCredential(); - expect(credential2).toEqual('MY_TOKEN_2'); + expect(credential2).toEqual({ type: 'bearer token', token: 'MY_TOKEN_2' }); jest.setSystemTime(Date.now() + 17 * 60 * 1000); // advance time by 17min diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts index f4dbc45612..cca0f3dede 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts @@ -39,14 +39,16 @@ export class AzureIdentityStrategy implements AuthenticationStrategy { public async getCredential(): Promise { if (!this.tokenRequiresRefresh()) { - return this.accessToken.token; + return { type: 'bearer token', token: this.accessToken.token }; } if (!this.newTokenPromise) { this.newTokenPromise = this.fetchNewToken(); } - return this.newTokenPromise; + return this.newTokenPromise + ? { type: 'bearer token', token: await this.newTokenPromise } + : { type: 'anonymous' }; } public validate() {} diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts index 0675b21a9a..f7db6ccd95 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts @@ -43,7 +43,10 @@ describe('getCredential', () => { name: 'randomName', authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, }; - mockStrategy.getCredential.mockResolvedValue('added by mock strategy'); + mockStrategy.getCredential.mockResolvedValue({ + type: 'bearer token', + token: 'added by mock strategy', + }); const returnedValue = await strategy.getCredential( clusterDetails, @@ -54,7 +57,10 @@ describe('getCredential', () => { clusterDetails, authObject, ); - expect(returnedValue).toBe('added by mock strategy'); + expect(returnedValue).toStrictEqual({ + type: 'bearer token', + token: 'added by mock strategy', + }); }); it('throws an error when asked for a strategy for an unsupported auth type', () => { diff --git a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts index 66e0286372..4aeba504af 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts @@ -23,14 +23,14 @@ import * as container from '@google-cloud/container'; export class GoogleServiceAccountStrategy implements AuthenticationStrategy { public async getCredential(): Promise { const client = new container.v1.ClusterManagerClient(); - const accessToken = await client.auth.getAccessToken(); + const token = await client.auth.getAccessToken(); - if (!accessToken) { + if (!token) { throw new Error( 'Unable to obtain access token for the current Google Application Default Credentials', ); } - return accessToken; + return { type: 'bearer token', token }; } public validate() {} diff --git a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts index 3111e02651..ff8d02cb14 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts @@ -27,13 +27,13 @@ export class GoogleStrategy implements AuthenticationStrategy { _: ClusterDetails, requestAuth: KubernetesRequestAuth, ): Promise { - const authToken = requestAuth.google; - if (!authToken) { + const token = requestAuth.google; + if (!token) { throw new Error( 'Google token not found under auth.google in request body', ); } - return authToken; + return { type: 'bearer token', token }; } public validate(_: AuthMetadata) {} } diff --git a/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts b/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts index 0502f91fad..fe25b03c72 100644 --- a/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts @@ -38,7 +38,10 @@ describe('OidcStrategy', () => { }, ); - expect(credential).toBe('fakeToken'); + expect(credential).toStrictEqual({ + type: 'bearer token', + token: 'fakeToken', + }); }); it('fails when token provider is not configured', async () => { diff --git a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts index 21410686de..f5ab1cbd8d 100644 --- a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts @@ -38,14 +38,14 @@ export class OidcStrategy implements AuthenticationStrategy { ); } - const authToken = authConfig.oidc?.[oidcTokenProvider]; + const token = authConfig.oidc?.[oidcTokenProvider]; - if (!authToken) { + if (!token) { throw new Error( `Auth token not found under oidc.${oidcTokenProvider} in request body`, ); } - return authToken; + return { type: 'bearer token', token }; } public validate(authMetadata: AuthMetadata) { diff --git a/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts b/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts index 29462dc5c7..24ade91e48 100644 --- a/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts @@ -25,7 +25,8 @@ export class ServiceAccountStrategy implements AuthenticationStrategy { public async getCredential( clusterDetails: ClusterDetails, ): Promise { - return clusterDetails.authMetadata.serviceAccountToken; + const token = clusterDetails.authMetadata.serviceAccountToken; + return token ? { type: 'bearer token', token } : { type: 'anonymous' }; } public validate() {} diff --git a/plugins/kubernetes-backend/src/auth/types.ts b/plugins/kubernetes-backend/src/auth/types.ts index e9a6ce3a16..4094f37c3b 100644 --- a/plugins/kubernetes-backend/src/auth/types.ts +++ b/plugins/kubernetes-backend/src/auth/types.ts @@ -21,7 +21,9 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; * Authentication data used to make a request to Kubernetes * @public */ -export type KubernetesCredential = string | undefined; +export type KubernetesCredential = + | { type: 'bearer token'; token: string } + | { type: 'anonymous' }; /** * diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 35c8ead904..75283a34f7 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -28,7 +28,10 @@ import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; +import { + KubernetesRequestAuth, + ObjectsByEntityResponse, +} from '@backstage/plugin-kubernetes-common'; import { Config, ConfigReader } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; @@ -186,7 +189,12 @@ describe('KubernetesFanOutHandler', () => { }, customResources: customResources, authStrategy: { - getCredential: jest.fn().mockResolvedValue(undefined), + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockResolvedValue({ type: 'anonymous' }), validate: jest.fn(), }, config, @@ -373,7 +381,7 @@ describe('KubernetesFanOutHandler', () => { expect(fetchPodMetricsByNamespaces).toHaveBeenCalledTimes(1); expect(fetchPodMetricsByNamespaces).toHaveBeenCalledWith( expect.anything(), - undefined, + { type: 'anonymous' }, new Set(['ns-test-component-test-cluster']), expect.anything(), ); @@ -665,7 +673,7 @@ describe('KubernetesFanOutHandler', () => { expect(fetchPodMetricsByNamespaces).toHaveBeenCalledTimes(1); expect(fetchPodMetricsByNamespaces).toHaveBeenCalledWith( expect.anything(), - undefined, + { type: 'anonymous' }, new Set(['ns-a', 'ns-b']), expect.anything(), ); @@ -1132,7 +1140,12 @@ describe('KubernetesFanOutHandler', () => { }, ], authStrategy: { - getCredential: jest.fn().mockResolvedValue('token'), + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockResolvedValue({ type: 'bearer token', token: 'token' }), validate: jest.fn(), }, config, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 6feac880ec..9f751ff8e1 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -141,7 +141,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -202,7 +202,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999/k8s/clusters/1234', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -258,7 +258,7 @@ describe('KubernetesFetcher', () => { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'localKubectlProxy', }, }, - credential: undefined, + credential: { type: 'anonymous' }, objectTypesToFetch: new Set([ { group: '', @@ -315,7 +315,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -389,7 +389,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [ @@ -481,7 +481,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -569,7 +569,7 @@ describe('KubernetesFetcher', () => { url: 'http://badurl.does.not.exist', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -606,7 +606,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: 'service-label=value', customResources: [], @@ -674,7 +674,7 @@ describe('KubernetesFetcher', () => { authMetadata: {}, caData: 'MOCKCA', }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: new Set([ { group: '', @@ -710,7 +710,7 @@ describe('KubernetesFetcher', () => { url: 'https://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: new Set([ { group: '', @@ -754,7 +754,7 @@ describe('KubernetesFetcher', () => { authMetadata: {}, caFile: '/path/to/ca.crt', }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: new Set([ { group: '', @@ -792,7 +792,7 @@ describe('KubernetesFetcher', () => { authMetadata: {}, skipTLSVerify: true, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: new Set([ { group: '', @@ -842,7 +842,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - credential: 'token', + credential: { type: 'bearer token', token: 'token' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', namespace: 'some-namespace', @@ -886,7 +886,7 @@ describe('KubernetesFetcher', () => { url: 'http://ignored', authMetadata: {}, }, - credential: undefined, // no token + credential: { type: 'anonymous' }, objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -930,7 +930,7 @@ describe('KubernetesFetcher', () => { url: 'http://ignored', authMetadata: {}, }, - credential: undefined, // no token + credential: { type: 'anonymous' }, objectTypesToFetch: new Set([ { group: '', @@ -1027,7 +1027,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - 'token', + { type: 'bearer token', token: 'token' }, new Set(['ns-a']), ); expect(result).toMatchObject({ @@ -1114,7 +1114,7 @@ describe('KubernetesFetcher', () => { url: 'http://localhost:9999', authMetadata: {}, }, - 'token', + { type: 'bearer token', token: 'token' }, new Set(['ns-a', 'ns-b']), ); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 61a68a06a5..42556c8473 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -215,7 +215,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { let url: URL; let requestInit: RequestInit; if ( - credential || + credential.type === 'bearer token' || clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER] === 'localKubectlProxy' ) { @@ -252,7 +252,9 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { headers: { Accept: 'application/json', 'Content-Type': 'application/json', - ...(credential && { Authorization: `Bearer ${credential}` }), + ...(credential.type === 'bearer token' && { + Authorization: `Bearer ${credential.token}`, + }), }, }; diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 9c3e7e9187..70d639a6e8 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -23,6 +23,7 @@ import { AuthorizeResult, PermissionEvaluator, } from '@backstage/plugin-permission-common'; +import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; import { getMockReq, getMockRes } from '@jest-mock/express'; import express from 'express'; import Router from 'express-promise-router'; @@ -33,7 +34,11 @@ import request from 'supertest'; import { AddressInfo, WebSocket, WebSocketServer } from 'ws'; import { LocalKubectlProxyClusterLocator } from '../cluster-locator/LocalKubectlProxyLocator'; -import { AuthenticationStrategy, AnonymousStrategy } from '../auth'; +import { + AuthenticationStrategy, + AnonymousStrategy, + KubernetesCredential, +} from '../auth'; import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; import { APPLICATION_JSON, @@ -47,6 +52,7 @@ import type { Request } from 'express'; describe('KubernetesProxy', () => { let proxy: KubernetesProxy; + let authStrategy: jest.Mocked; const worker = setupServer(); const logger = getVoidLogger(); @@ -59,11 +65,6 @@ describe('KubernetesProxy', () => { authorizeConditional: jest.fn(), }; - const authStrategy: jest.Mocked = { - getCredential: jest.fn(), - validate: jest.fn(), - }; - setupRequestMockHandlers(worker); const buildMockRequest = (clusterName: any, path: string): Request => { @@ -124,6 +125,15 @@ describe('KubernetesProxy', () => { beforeEach(() => { jest.resetAllMocks(); + authStrategy = { + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockResolvedValue({ type: 'anonymous' }), + validate: jest.fn(), + }; proxy = new KubernetesProxy({ logger, clusterSupplier, authStrategy }); permissionApi.authorize.mockResolvedValue([ { result: AuthorizeResult.ALLOW }, @@ -325,7 +335,10 @@ describe('KubernetesProxy', () => { }, ]); - authStrategy.getCredential.mockResolvedValue('strategy-provided-token'); + authStrategy.getCredential.mockResolvedValue({ + type: 'bearer token', + token: 'strategy-provided-token', + }); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -369,7 +382,10 @@ describe('KubernetesProxy', () => { }, ]); - authStrategy.getCredential.mockResolvedValue('my-token'); + authStrategy.getCredential.mockResolvedValue({ + type: 'bearer token', + token: 'my-token', + }); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -418,7 +434,10 @@ describe('KubernetesProxy', () => { }, ]); - authStrategy.getCredential.mockResolvedValue('tokenA'); + authStrategy.getCredential.mockResolvedValue({ + type: 'bearer token', + token: 'tokenA', + }); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -691,7 +710,7 @@ describe('KubernetesProxy', () => { event: 'connection' | 'open' | 'close' | 'error' | 'message', ) => new Promise(resolve => ws.once(event, x => resolve(x?.toString()))); - beforeAll(async () => { + beforeEach(async () => { await new Promise(resolve => { expressServer = express() .use( @@ -722,7 +741,7 @@ describe('KubernetesProxy', () => { wsEchoServer.on('error', console.error); }); - afterAll(() => { + afterEach(() => { wsEchoServer.close(); expressServer.close(); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index ce96659c30..6e699d1220 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -112,11 +112,11 @@ export class KubernetesProxy { if (authHeader) { req.headers.authorization = authHeader; } else { - const serviceAccountToken = await this.getClusterForRequest(req).then( - cd => this.authStrategy.getCredential(cd, {}), - ); - if (serviceAccountToken) { - req.headers.authorization = `Bearer ${serviceAccountToken}`; + const credential = await this.getClusterForRequest(req).then(cd => { + return this.authStrategy.getCredential(cd, {}); + }); + if (credential.type === 'bearer token') { + req.headers.authorization = `Bearer ${credential.token}`; } }