diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index aee7b16bae..1c5cb766dc 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -24,10 +24,10 @@ import { TokenCredential } from '@azure/identity'; // @public (undocumented) export class AksStrategy implements AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - auth: KubernetesRequestAuth, - ): Promise; + getCredential( + _: ClusterDetails, + requestAuth: KubernetesRequestAuth, + ): Promise; // (undocumented) validate(_: AuthMetadata): void; } @@ -35,10 +35,10 @@ export class AksStrategy implements AuthenticationStrategy { // @public (undocumented) export interface AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( + getCredential( clusterDetails: ClusterDetails, authConfig: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) validate(authMetadata: AuthMetadata): void; } @@ -50,22 +50,18 @@ export type AuthMetadata = Record; export class AwsIamStrategy implements AuthenticationStrategy { constructor(opts: { config: Config }); // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise; + getCredential(clusterDetails: ClusterDetails): Promise; // (undocumented) - validate(_: AuthMetadata): void; + validate(): void; } // @public (undocumented) export class AzureIdentityStrategy implements AuthenticationStrategy { constructor(logger: Logger, tokenCredential?: TokenCredential); // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise; + getCredential(): Promise; // (undocumented) - validate(_: AuthMetadata): void; + validate(): void; } // @public (undocumented) @@ -110,10 +106,10 @@ export const DEFAULT_OBJECTS: ObjectToFetch[]; export class DispatchStrategy implements AuthenticationStrategy { constructor(options: DispatchStrategyOptions); // (undocumented) - decorateClusterDetailsWithAuth( + getCredential( clusterDetails: ClusterDetails, auth: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) validate(authMetadata: AuthMetadata): void; } @@ -136,20 +132,18 @@ export interface FetchResponseWrapper { // @public (undocumented) export class GoogleServiceAccountStrategy implements AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise; + getCredential(): Promise; // (undocumented) - validate(_: AuthMetadata): void; + validate(): void; } // @public (undocumented) export class GoogleStrategy implements AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - authConfig: KubernetesRequestAuth, - ): Promise; + getCredential( + _: ClusterDetails, + requestAuth: KubernetesRequestAuth, + ): Promise; // (undocumented) validate(_: AuthMetadata): void; } @@ -275,6 +269,9 @@ export interface KubernetesClustersSupplier { getClusters(): Promise; } +// @public +export type KubernetesCredential = string | undefined; + // @public (undocumented) export interface KubernetesEnvironment { // (undocumented) @@ -296,6 +293,7 @@ export interface KubernetesFetcher { // (undocumented) fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, + credential: KubernetesCredential, namespaces: Set, labelSelector?: string, ): Promise; @@ -388,11 +386,9 @@ export interface KubernetesServiceLocator { // @public (undocumented) export class NoopStrategy implements AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise; + getCredential(): Promise; // (undocumented) - validate(_: AuthMetadata): void; + validate(): void; } // @public (undocumented) @@ -400,6 +396,8 @@ export interface ObjectFetchParams { // (undocumented) clusterDetails: ClusterDetails; // (undocumented) + credential: KubernetesCredential; + // (undocumented) customResources: CustomResource[]; // (undocumented) labelSelector: string; @@ -429,10 +427,10 @@ export interface ObjectToFetch { // @public (undocumented) export class OidcStrategy implements AuthenticationStrategy { // (undocumented) - decorateClusterDetailsWithAuth( + getCredential( clusterDetails: ClusterDetails, authConfig: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) validate(authMetadata: AuthMetadata): void; } diff --git a/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts index 6974e302ee..373d053059 100644 --- a/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AksStrategy.test.ts @@ -19,11 +19,11 @@ describe('AksStrategy', () => { it('uses auth.aks value as bearer token', async () => { const strategy = new AksStrategy(); - const details = await strategy.decorateClusterDetailsWithAuth( - { name: '', url: '', authMetadata: { authProvider: 'aks' } }, + const credential = await strategy.getCredential( + { name: '', url: '', authMetadata: {} }, { aks: 'aksToken' }, ); - expect(details.authMetadata.serviceAccountToken).toBe('aksToken'); + expect(credential).toBe('aksToken'); }); }); diff --git a/plugins/kubernetes-backend/src/auth/AksStrategy.ts b/plugins/kubernetes-backend/src/auth/AksStrategy.ts index 0ed2967406..01fe8ffc8e 100644 --- a/plugins/kubernetes-backend/src/auth/AksStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AksStrategy.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { AuthMetadata, ClusterDetails } from '../types/types'; -import { AuthenticationStrategy } from './types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; /** @@ -22,19 +22,11 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; * @public */ export class AksStrategy implements AuthenticationStrategy { - public async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - auth: KubernetesRequestAuth, - ): Promise { - return { - ...clusterDetails, - ...(auth.aks && { - authMetadata: { - serviceAccountToken: auth.aks, - ...clusterDetails.authMetadata, - }, - }), - }; + public async getCredential( + _: ClusterDetails, + requestAuth: KubernetesRequestAuth, + ): Promise { + return requestAuth.aks; } public validate(_: AuthMetadata) {} } diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts index 0d5fb9a83f..e142717541 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts @@ -60,12 +60,12 @@ describe('AwsIamStrategy tests', () => { it('returns a signed url for AWS credentials without assume role', async () => { const strategy = new AwsIamStrategy({ config }); - const authPromise = strategy.decorateClusterDetailsWithAuth({ + const credential = await strategy.getCredential({ name: 'test-cluster', url: '', - authMetadata: { authProvider: 'aws' }, + authMetadata: {}, }); - expect((await authPromise).authMetadata.serviceAccountToken).toEqual( + expect(credential).toEqual( 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', ); }); @@ -73,15 +73,15 @@ describe('AwsIamStrategy tests', () => { it('returns a signed url for AWS credentials with assume role', async () => { const strategy = new AwsIamStrategy({ config }); - const authPromise = strategy.decorateClusterDetailsWithAuth({ + const credential = await strategy.getCredential({ name: 'test-cluster', url: '', authMetadata: { - authProvider: 'aws', [ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: 'SomeRole', }, }); - expect((await authPromise).authMetadata.serviceAccountToken).toEqual( + + expect(credential).toEqual( 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', ); expect(fromTemporaryCredentials).toHaveBeenCalledWith({ @@ -101,16 +101,15 @@ describe('AwsIamStrategy tests', () => { it('returns a signed url for AWS credentials and passes the external id', async () => { const strategy = new AwsIamStrategy({ config }); - const authPromise = strategy.decorateClusterDetailsWithAuth({ + const credential = await strategy.getCredential({ name: 'test-cluster', url: '', authMetadata: { - authProvider: 'aws', [ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: 'SomeRole', [ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: 'external-id', }, }); - expect((await authPromise).authMetadata.serviceAccountToken).toEqual( + expect(credential).toEqual( 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', ); expect(fromTemporaryCredentials).toHaveBeenCalledWith({ @@ -136,10 +135,10 @@ describe('AwsIamStrategy tests', () => { it('throws the right error', async () => { const strategy = new AwsIamStrategy({ config }); await expect( - strategy.decorateClusterDetailsWithAuth({ + strategy.getCredential({ name: 'test-cluster', url: '', - authMetadata: { authProvider: 'aws' }, + authMetadata: {}, }), ).rejects.toThrow('no way'); }); diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts index 21a556002b..367df5271a 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts @@ -25,8 +25,8 @@ import { ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, } from '@backstage/plugin-kubernetes-common'; -import { AuthMetadata, ClusterDetails } from '../types/types'; -import { AuthenticationStrategy } from './types'; +import { ClusterDetails } from '../types/types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; /** * @@ -51,26 +51,17 @@ export class AwsIamStrategy implements AuthenticationStrategy { this.credsManager = DefaultAwsCredentialsManager.fromConfig(opts.config); } - public async decorateClusterDetailsWithAuth( + public getCredential( clusterDetails: ClusterDetails, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( - {}, - clusterDetails, + ): Promise { + return this.getBearerToken( + clusterDetails.name, + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], ); - - clusterDetailsWithAuthToken.authMetadata = { - serviceAccountToken: await this.getBearerToken( - clusterDetails.name, - clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], - clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], - ), - ...clusterDetailsWithAuthToken.authMetadata, - }; - return clusterDetailsWithAuthToken; } - public validate(_: AuthMetadata) {} + public validate() {} private async getBearerToken( clusterName: string, diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts index e6aeb9dea1..398bb99516 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts @@ -40,24 +40,18 @@ class StaticTokenCredential implements TokenCredential { } describe('AzureIdentityStrategy tests', () => { - const cd = { - name: 'My Cluster', - url: 'mycluster.privatelink.westeurope.azmk8s.io', - authMetadata: { authProvider: 'azure' }, - }; - afterEach(() => { jest.useRealTimers(); }); - it('should decorate cluster with Azure token', async () => { + it('should get Azure token', async () => { const strategy = new AzureIdentityStrategy( logger, new StaticTokenCredential(5 * 60 * 1000), ); - const response = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential = await strategy.getCredential(); + expect(credential).toEqual('MY_TOKEN_1'); }); it('should re-use token before expiry', async () => { @@ -66,11 +60,11 @@ describe('AzureIdentityStrategy tests', () => { new StaticTokenCredential(20 * 60 * 1000), ); - const response = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential = await strategy.getCredential(); + expect(credential).toEqual('MY_TOKEN_1'); - const response2 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response2.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential2 = await strategy.getCredential(); + expect(credential2).toEqual('MY_TOKEN_1'); }); it('should issue new token 15 minutes befory expiry', async () => { @@ -81,13 +75,13 @@ describe('AzureIdentityStrategy tests', () => { new StaticTokenCredential(16 * 60 * 1000), // token expires in 16min ); - const response = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential = await strategy.getCredential(); + expect(credential).toEqual('MY_TOKEN_1'); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins - const response2 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response2.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_2'); + const credential2 = await strategy.getCredential(); + expect(credential2).toEqual('MY_TOKEN_2'); }); it('should re-use existing token if there is afailure', async () => { @@ -98,21 +92,21 @@ describe('AzureIdentityStrategy tests', () => { new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min ); - const response = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential = await strategy.getCredential(); + expect(credential).toEqual('MY_TOKEN_1'); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min - const response2 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response2.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_2'); + const credential2 = await strategy.getCredential(); + expect(credential2).toEqual('MY_TOKEN_2'); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min - const response3 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response3.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_2'); + const credential3 = await strategy.getCredential(); + expect(credential3).toEqual('MY_TOKEN_2'); - const response4 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response4.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_4'); + const credential4 = await strategy.getCredential(); + expect(credential4).toEqual('MY_TOKEN_4'); }); it('should throw if existing token expired and failed to fetch a new one', async () => { @@ -123,16 +117,16 @@ describe('AzureIdentityStrategy tests', () => { new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min ); - const response = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_1'); + const credential = await strategy.getCredential(); + expect(credential).toEqual('MY_TOKEN_1'); jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min - const response2 = await strategy.decorateClusterDetailsWithAuth(cd); - expect(response2.authMetadata.serviceAccountToken).toEqual('MY_TOKEN_2'); + const credential2 = await strategy.getCredential(); + expect(credential2).toEqual('MY_TOKEN_2'); jest.setSystemTime(Date.now() + 17 * 60 * 1000); // advance time by 17min - await expect(strategy.decorateClusterDetailsWithAuth(cd)).rejects.toThrow(); + await expect(strategy.getCredential()).rejects.toThrow(); }); }); diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts index f04af9d37e..f4dbc45612 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts @@ -15,8 +15,7 @@ */ import { Logger } from 'winston'; -import { AuthenticationStrategy } from './types'; -import { AuthMetadata, ClusterDetails } from '../types/types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import { AccessToken, DefaultAzureCredential, @@ -38,24 +37,7 @@ export class AzureIdentityStrategy implements AuthenticationStrategy { private readonly tokenCredential: TokenCredential = new DefaultAzureCredential(), ) {} - public async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( - {}, - clusterDetails, - ); - - clusterDetailsWithAuthToken.authMetadata = { - serviceAccountToken: await this.getToken(), - ...clusterDetailsWithAuthToken.authMetadata, - }; - return clusterDetailsWithAuthToken; - } - - public validate(_: AuthMetadata) {} - - private async getToken(): Promise { + public async getCredential(): Promise { if (!this.tokenRequiresRefresh()) { return this.accessToken.token; } @@ -67,6 +49,8 @@ export class AzureIdentityStrategy implements AuthenticationStrategy { return this.newTokenPromise; } + public validate() {} + private async fetchNewToken(): Promise { try { this.logger.info('Fetching new Azure token for AKS'); diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts index d791972421..0675b21a9a 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts @@ -22,14 +22,14 @@ import { DispatchStrategy } from './DispatchStrategy'; import { ClusterDetails } from '../types'; import { AuthenticationStrategy } from './types'; -describe('decorateClusterDetailsWithAuth', () => { +describe('getCredential', () => { let strategy: DispatchStrategy; let mockStrategy: jest.Mocked; const authObject: KubernetesRequestAuth = {}; beforeEach(() => { mockStrategy = { - decorateClusterDetailsWithAuth: jest.fn(), + getCredential: jest.fn(), validate: jest.fn(), }; strategy = new DispatchStrategy({ @@ -37,43 +37,29 @@ describe('decorateClusterDetailsWithAuth', () => { }); }); - it('can decorate cluster details if the auth provider is in the strategy map', async () => { - const expectedClusterDetails: ClusterDetails = { + it('gets credential if specified auth provider is in the strategy map', async () => { + const clusterDetails: ClusterDetails = { url: 'notanything.com', name: 'randomName', - authMetadata: { - [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google', - serviceAccountToken: 'added by mock strategy', - }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, }; + mockStrategy.getCredential.mockResolvedValue('added by mock strategy'); - mockStrategy.decorateClusterDetailsWithAuth.mockResolvedValue( - expectedClusterDetails, - ); - - const returnedValue = await strategy.decorateClusterDetailsWithAuth( - { - name: 'googleCluster', - url: 'anything.com', - authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, - }, + const returnedValue = await strategy.getCredential( + clusterDetails, authObject, ); - expect(mockStrategy.decorateClusterDetailsWithAuth).toHaveBeenCalledWith( - { - name: 'googleCluster', - url: 'anything.com', - authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, - }, + expect(mockStrategy.getCredential).toHaveBeenCalledWith( + clusterDetails, authObject, ); - expect(returnedValue).toBe(expectedClusterDetails); + expect(returnedValue).toBe('added by mock strategy'); }); it('throws an error when asked for a strategy for an unsupported auth type', () => { expect(() => - strategy.decorateClusterDetailsWithAuth( + strategy.getCredential( { name: 'test-cluster', url: 'anything.com', diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts index f4150ad8b6..b16340f8bc 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuthenticationStrategy } from './types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import { AuthMetadata, ClusterDetails } from '../types'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, @@ -41,17 +41,14 @@ export class DispatchStrategy implements AuthenticationStrategy { this.strategyMap = options.authStrategyMap; } - public decorateClusterDetailsWithAuth( + public getCredential( clusterDetails: ClusterDetails, auth: KubernetesRequestAuth, - ) { + ): Promise { const authProvider = clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER]; if (this.strategyMap[authProvider]) { - return this.strategyMap[authProvider].decorateClusterDetailsWithAuth( - clusterDetails, - auth, - ); + return this.strategyMap[authProvider].getCredential(clusterDetails, auth); } throw new Error( `authProvider "${authProvider}" has no AuthenticationStrategy associated with it`, diff --git a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts index dcb230eaf7..66e0286372 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AuthenticationStrategy } from './types'; -import { AuthMetadata, ClusterDetails } from '../types/types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import * as container from '@google-cloud/container'; /** @@ -22,28 +21,17 @@ import * as container from '@google-cloud/container'; * @public */ export class GoogleServiceAccountStrategy implements AuthenticationStrategy { - public async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( - {}, - clusterDetails, - ); + public async getCredential(): Promise { const client = new container.v1.ClusterManagerClient(); const accessToken = await client.auth.getAccessToken(); - if (accessToken) { - clusterDetailsWithAuthToken.authMetadata = { - serviceAccountToken: accessToken, - ...clusterDetailsWithAuthToken.authMetadata, - }; - } else { + if (!accessToken) { throw new Error( 'Unable to obtain access token for the current Google Application Default Credentials', ); } - return clusterDetailsWithAuthToken; + return accessToken; } - public validate(_: AuthMetadata) {} + public validate() {} } diff --git a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts index 79d9df2059..3111e02651 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuthenticationStrategy } from './types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import { AuthMetadata, ClusterDetails } from '../types/types'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; @@ -23,27 +23,17 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; * @public */ export class GoogleStrategy implements AuthenticationStrategy { - public async decorateClusterDetailsWithAuth( - clusterDetails: ClusterDetails, - authConfig: KubernetesRequestAuth, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( - {}, - clusterDetails, - ); - const authToken: string | undefined = authConfig.google; - - if (authToken) { - clusterDetailsWithAuthToken.authMetadata = { - serviceAccountToken: authToken, - ...clusterDetailsWithAuthToken.authMetadata, - }; - } else { + public async getCredential( + _: ClusterDetails, + requestAuth: KubernetesRequestAuth, + ): Promise { + const authToken = requestAuth.google; + if (!authToken) { throw new Error( 'Google token not found under auth.google in request body', ); } - return clusterDetailsWithAuthToken; + return authToken; } public validate(_: AuthMetadata) {} } diff --git a/plugins/kubernetes-backend/src/auth/NoopStrategy.ts b/plugins/kubernetes-backend/src/auth/NoopStrategy.ts index 80841e2a19..1af8f09cf8 100644 --- a/plugins/kubernetes-backend/src/auth/NoopStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/NoopStrategy.ts @@ -14,19 +14,19 @@ * limitations under the License. */ -import { AuthenticationStrategy } from './types'; -import { AuthMetadata, ClusterDetails } from '../types/types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; +import { ClusterDetails } from '../types/types'; /** * * @public */ export class NoopStrategy implements AuthenticationStrategy { - public async decorateClusterDetailsWithAuth( + public async getCredential( clusterDetails: ClusterDetails, - ): Promise { - return clusterDetails; + ): Promise { + return clusterDetails.authMetadata.serviceAccountToken; } - public validate(_: AuthMetadata) {} + public validate() {} } diff --git a/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts b/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts index 7843cc6544..0502f91fad 100644 --- a/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/OidcStrategy.test.ts @@ -15,7 +15,6 @@ */ import { ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER } from '@backstage/plugin-kubernetes-common'; -import { AuthMetadata } from '../types/types'; import { OidcStrategy } from './OidcStrategy'; describe('OidcStrategy', () => { @@ -24,14 +23,13 @@ describe('OidcStrategy', () => { strategy = new OidcStrategy(); }); - describe('decorateClusterDetailsWithAuth', () => { - it('returns cluster details with auth token', async () => { - const details = await strategy.decorateClusterDetailsWithAuth( + describe('getCredential', () => { + it('returns auth token', async () => { + const credential = await strategy.getCredential( { name: 'test', url: '', authMetadata: { - authProvider: 'oidc', [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'okta', }, }, @@ -40,16 +38,16 @@ describe('OidcStrategy', () => { }, ); - expect(details.authMetadata.serviceAccountToken).toBe('fakeToken'); + expect(credential).toBe('fakeToken'); }); - it('fails when oidcTokenProvider is not configured', async () => { + it('fails when token provider is not configured', async () => { await expect( - strategy.decorateClusterDetailsWithAuth( + strategy.getCredential( { name: 'test', url: '', - authMetadata: { authProvider: 'oidc' }, + authMetadata: {}, }, {}, ), @@ -60,12 +58,11 @@ describe('OidcStrategy', () => { it('fails when token is not included in request body', async () => { await expect( - strategy.decorateClusterDetailsWithAuth( + strategy.getCredential( { name: 'test', url: '', authMetadata: { - authProvider: 'oidc', [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'okta', }, }, @@ -77,10 +74,7 @@ describe('OidcStrategy', () => { describe('validate', () => { it('fails when token provider is not specified', () => { - const authMetadata: AuthMetadata = { - authProvider: 'oidc', - }; - expect(() => strategy.validate(authMetadata)).toThrow( + expect(() => strategy.validate({})).toThrow( `Must specify a token provider for 'oidc' strategy`, ); }); diff --git a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts index 083c28dd1b..21410686de 100644 --- a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts @@ -17,7 +17,7 @@ import { ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; -import { AuthenticationStrategy } from './types'; +import { AuthenticationStrategy, KubernetesCredential } from './types'; import { AuthMetadata, ClusterDetails } from '../types/types'; /** @@ -25,15 +25,10 @@ import { AuthMetadata, ClusterDetails } from '../types/types'; * @public */ export class OidcStrategy implements AuthenticationStrategy { - public async decorateClusterDetailsWithAuth( + public async getCredential( clusterDetails: ClusterDetails, authConfig: KubernetesRequestAuth, - ): Promise { - const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( - {}, - clusterDetails, - ); - + ): Promise { const oidcTokenProvider = clusterDetails.authMetadata[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]; @@ -43,19 +38,14 @@ export class OidcStrategy implements AuthenticationStrategy { ); } - const authToken: string | undefined = authConfig.oidc?.[oidcTokenProvider]; + const authToken = authConfig.oidc?.[oidcTokenProvider]; - if (authToken) { - clusterDetailsWithAuthToken.authMetadata = { - serviceAccountToken: authToken, - ...clusterDetailsWithAuthToken.authMetadata, - }; - } else { + if (!authToken) { throw new Error( `Auth token not found under oidc.${oidcTokenProvider} in request body`, ); } - return clusterDetailsWithAuthToken; + return authToken; } public validate(authMetadata: AuthMetadata) { diff --git a/plugins/kubernetes-backend/src/auth/types.ts b/plugins/kubernetes-backend/src/auth/types.ts index ed57ecc1f6..e9a6ce3a16 100644 --- a/plugins/kubernetes-backend/src/auth/types.ts +++ b/plugins/kubernetes-backend/src/auth/types.ts @@ -17,14 +17,20 @@ import { AuthMetadata, ClusterDetails } from '../types/types'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; +/** + * Authentication data used to make a request to Kubernetes + * @public + */ +export type KubernetesCredential = string | undefined; + /** * * @public */ export interface AuthenticationStrategy { - decorateClusterDetailsWithAuth( + getCredential( clusterDetails: ClusterDetails, authConfig: KubernetesRequestAuth, - ): Promise; + ): Promise; validate(authMetadata: AuthMetadata): void; } diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index 73c81e0f59..e48752bbd0 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -30,7 +30,7 @@ describe('ConfigClusterLocator', () => { beforeEach(() => { authStrategy = { - decorateClusterDetailsWithAuth: jest.fn(), + getCredential: jest.fn(), validate: jest.fn(), }; }); diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts index fef1e8a3ca..f919561490 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; import '@backstage/backend-common'; import { ConfigReader, Config } from '@backstage/config'; import { GkeClusterLocator } from './GkeClusterLocator'; @@ -106,7 +107,7 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: true, }, @@ -143,7 +144,7 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, @@ -185,14 +186,14 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, { name: 'some-other-cluster', url: 'https://6.7.8.9', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, @@ -240,14 +241,14 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, { name: 'some-other-cluster', url: 'https://6.7.8.9', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, @@ -301,7 +302,7 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: false, }, @@ -365,7 +366,7 @@ describe('GkeClusterLocator', () => { { name: 'some-cluster', url: 'https://1.2.3.4', - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify: false, skipMetricsLookup: true, dashboardApp: 'gke', diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts index 0e45898639..74e7ebc9af 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; import * as container from '@google-cloud/container'; @@ -120,7 +121,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { // TODO filter out clusters which don't have name or endpoint name: r.name ?? 'unknown', url: `https://${r.endpoint ?? ''}`, - authMetadata: { authProvider: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, skipTLSVerify, skipMetricsLookup, ...(exposeDashboard diff --git a/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts index d393087da2..9bbab42443 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; export class LocalKubectlProxyClusterLocator @@ -26,7 +27,9 @@ export class LocalKubectlProxyClusterLocator { name: 'local', url: 'http:/localhost:8001', - authMetadata: { authProvider: 'localKubectlProxy' }, + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'localKubectlProxy', + }, skipMetricsLookup: true, }, ]; diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 196de9865d..e7a58b3f98 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -51,7 +51,7 @@ describe('getCombinedClusterSupplier', () => { 'ctx', ); const mockStrategy: jest.Mocked = { - decorateClusterDetailsWithAuth: jest.fn(), + getCredential: jest.fn(), validate: jest.fn(), }; diff --git a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts index 18cc72ffb1..093831164e 100644 --- a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts +++ b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts @@ -16,7 +16,7 @@ import '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; -import { AuthMetadata, ServiceLocatorRequestContext } from '../types/types'; +import { ServiceLocatorRequestContext } from '../types/types'; import { MultiTenantServiceLocator } from './MultiTenantServiceLocator'; describe('MultiTenantConfigClusterLocator', () => { @@ -40,10 +40,7 @@ describe('MultiTenantConfigClusterLocator', () => { { name: 'cluster1', url: 'http://localhost:8080', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: '12345', - }, + authMetadata: {}, }, ]; }, @@ -59,10 +56,7 @@ describe('MultiTenantConfigClusterLocator', () => { { name: 'cluster1', url: 'http://localhost:8080', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: '12345', - }, + authMetadata: {}, }, ], }); @@ -75,15 +69,12 @@ describe('MultiTenantConfigClusterLocator', () => { { name: 'cluster1', url: 'http://localhost:8080', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - } as AuthMetadata, + authMetadata: {}, }, { name: 'cluster2', url: 'http://localhost:8081', - authMetadata: { authProvider: 'google' } as AuthMetadata, + authMetadata: {}, }, ]; }, @@ -99,15 +90,12 @@ describe('MultiTenantConfigClusterLocator', () => { { name: 'cluster1', url: 'http://localhost:8080', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, { name: 'cluster2', url: 'http://localhost:8081', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, ], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 9001c080aa..a428c7ca0e 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -32,6 +32,7 @@ import { KubernetesServiceLocator, ObjectFetchParams, } from '../types/types'; +import { KubernetesCredential } from '../auth/types'; import { KubernetesBuilder } from './KubernetesBuilder'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; import { CatalogApi } from '@backstage/catalog-client'; @@ -251,6 +252,7 @@ describe('KubernetesBuilder', () => { const fetcher: KubernetesFetcher = { fetchPodMetricsByNamespaces( _clusterDetails: ClusterDetails, + _credential: KubernetesCredential, _namespaces: Set, ): Promise { return Promise.resolve({ errors: [], responses: [] }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 8fc22946d4..35c8ead904 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -22,6 +22,7 @@ import { KubernetesServiceLocator, ServiceLocatorRequestContext, } from '../types/types'; +import { KubernetesCredential } from '../auth/types'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { rest } from 'msw'; @@ -77,7 +78,7 @@ describe('KubernetesFanOutHandler', () => { const cluster1 = { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, customResources: [ { group: 'some-other-crd.example.com', @@ -90,7 +91,7 @@ describe('KubernetesFanOutHandler', () => { const cluster2 = { name: 'cluster-two', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, customResources: [ { group: 'crd-two.example.com', @@ -185,9 +186,7 @@ describe('KubernetesFanOutHandler', () => { }, customResources: customResources, authStrategy: { - decorateClusterDetailsWithAuth: async (clusterDetails, _) => { - return clusterDetails; - }, + getCredential: jest.fn().mockResolvedValue(undefined), validate: jest.fn(), }, config, @@ -316,7 +315,11 @@ describe('KubernetesFanOutHandler', () => { ); fetchPodMetricsByNamespaces.mockImplementation( - (_clusterDetails: ClusterDetails, namespaces: Set) => + ( + _clusterDetails: ClusterDetails, + _: KubernetesCredential, + namespaces: Set, + ) => Promise.resolve({ errors: [], responses: Array.from(namespaces).map(() => { @@ -352,7 +355,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, ], }), @@ -370,6 +373,7 @@ describe('KubernetesFanOutHandler', () => { expect(fetchPodMetricsByNamespaces).toHaveBeenCalledTimes(1); expect(fetchPodMetricsByNamespaces).toHaveBeenCalledWith( expect.anything(), + undefined, new Set(['ns-test-component-test-cluster']), expect.anything(), ); @@ -467,7 +471,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, cluster2, ], @@ -522,7 +526,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'profile-cluster-1', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, customResourceProfile: 'build', customResources: [ { @@ -571,7 +575,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'profile-cluster-1', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, ], }), @@ -612,7 +616,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, ], }), @@ -661,6 +665,7 @@ describe('KubernetesFanOutHandler', () => { expect(fetchPodMetricsByNamespaces).toHaveBeenCalledTimes(1); expect(fetchPodMetricsByNamespaces).toHaveBeenCalledWith( expect.anything(), + undefined, new Set(['ns-a', 'ns-b']), expect.anything(), ); @@ -710,7 +715,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, ], }), @@ -750,13 +755,13 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, dashboardUrl: 'https://k8s.foo.coom', }, { name: 'other-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, ], }), @@ -803,17 +808,17 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, { name: 'other-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, { name: 'empty-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, ], }), @@ -859,22 +864,22 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, { name: 'other-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, { name: 'empty-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, { name: 'error-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, ], }), @@ -941,13 +946,13 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, dashboardUrl: 'https://k8s.foo.coom', }, { name: 'other-cluster', url: '', - authMetadata: { authProvider: 'google' }, + authMetadata: {}, }, ], }), @@ -957,7 +962,11 @@ describe('KubernetesFanOutHandler', () => { // and an error for the second call. fetchPodMetricsByNamespaces .mockImplementationOnce( - (_clusterDetails: ClusterDetails, namespaces: Set) => + ( + _clusterDetails: ClusterDetails, + _: KubernetesCredential, + namespaces: Set, + ) => Promise.resolve({ errors: [], responses: Array.from(namespaces).map(() => { @@ -1041,7 +1050,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, skipMetricsLookup: true, }, ], @@ -1090,19 +1099,13 @@ describe('KubernetesFanOutHandler', () => { { name: 'works', url: 'https://works', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, skipMetricsLookup: true, }, { name: 'fails', url: 'https://fails', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, skipMetricsLookup: true, }, ], @@ -1129,9 +1132,7 @@ describe('KubernetesFanOutHandler', () => { }, ], authStrategy: { - decorateClusterDetailsWithAuth: async (clusterDetails, _) => { - return clusterDetails; - }, + getCredential: jest.fn().mockResolvedValue('token'), validate: jest.fn(), }, config, @@ -1229,7 +1230,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'test-cluster', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, cluster2, ], @@ -1303,7 +1304,7 @@ describe('KubernetesFanOutHandler', () => { { name: 'profile-cluster-1', url: '', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, customResourceProfile: 'build', }, ], diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 98ad41947b..f2f1c6d38f 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -27,9 +27,8 @@ import { CustomResource, CustomResourcesByEntity, KubernetesObjectsByEntity, - ServiceLocatorRequestContext, } from '../types/types'; -import { AuthenticationStrategy } from '../auth/types'; +import { AuthenticationStrategy, KubernetesCredential } from '../auth/types'; import { ClientContainerStatus, ClientCurrentResourceUsage, @@ -48,14 +47,6 @@ import { PodStatus, } from '@kubernetes/client-node'; -const isRejected = ( - input: PromiseSettledResult, -): input is PromiseRejectedResult => input.status === 'rejected'; - -const isFulfilled = ( - input: PromiseSettledResult, -): input is PromiseFulfilledResult => input.status === 'fulfilled'; - /** * * @public @@ -245,14 +236,13 @@ export class KubernetesFanOutHandler { entity.metadata?.annotations?.['backstage.io/kubernetes-id'] || entity.metadata?.name; - const clusterDetailsDecoratedForAuth: ClusterDetails[] = - await this.decorateClusterDetailsWithAuth(entity, auth, { - objectTypesToFetch: objectTypesToFetch, - customResources: customResources ?? [], - }); + const { clusters } = await this.serviceLocator.getClustersByEntity(entity, { + objectTypesToFetch: objectTypesToFetch, + customResources: customResources ?? [], + }); this.logger.info( - `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth + `entity.metadata.name=${entityName} clusterDetails=[${clusters .map(c => c.name) .join(', ')}]`, ); @@ -266,16 +256,21 @@ export class KubernetesFanOutHandler { entity.metadata?.annotations?.['backstage.io/kubernetes-namespace']; return Promise.all( - clusterDetailsDecoratedForAuth.map(clusterDetailsItem => { + clusters.map(async clusterDetails => { + const credential = await this.authStrategy.getCredential( + clusterDetails, + auth, + ); return this.fetcher .fetchObjectsForService({ serviceId: entityName, - clusterDetails: clusterDetailsItem, - objectTypesToFetch: objectTypesToFetch, + clusterDetails, + credential, + objectTypesToFetch, labelSelector, customResources: ( customResources || - clusterDetailsItem.customResources || + clusterDetails.customResources || this.customResources ).map(c => ({ ...c, @@ -284,7 +279,12 @@ export class KubernetesFanOutHandler { namespace, }) .then(result => - this.getMetricsForPods(clusterDetailsItem, labelSelector, result), + this.getMetricsForPods( + clusterDetails, + credential, + labelSelector, + result, + ), ) .catch( (e): Promise => @@ -300,33 +300,11 @@ export class KubernetesFanOutHandler { ]) : Promise.reject(e), ) - .then(r => this.toClusterObjects(clusterDetailsItem, r)); + .then(r => this.toClusterObjects(clusterDetails, r)); }), ).then(this.toObjectsByEntityResponse); } - private async decorateClusterDetailsWithAuth( - entity: Entity, - auth: KubernetesRequestAuth, - requestContext: ServiceLocatorRequestContext, - ) { - const clusterDetails: ClusterDetails[] = ( - await this.serviceLocator.getClustersByEntity(entity, requestContext) - ).clusters; - - // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them - const promiseResults = await Promise.allSettled( - clusterDetails.map(cd => { - return this.authStrategy.decorateClusterDetailsWithAuth(cd, auth); - }), - ); - - promiseResults.filter(isRejected).map(item => { - this.logger.info(`Failed to decorate cluster details: ${item.reason}`); - }); - return promiseResults.filter(isFulfilled).map(item => item.value); - } - toObjectsByEntityResponse( clusterObjects: ClusterObjects[], ): ObjectsByEntityResponse { @@ -367,6 +345,7 @@ export class KubernetesFanOutHandler { async getMetricsForPods( clusterDetails: ClusterDetails, + credential: KubernetesCredential, labelSelector: string, result: FetchResponseWrapper, ): Promise { @@ -387,6 +366,7 @@ export class KubernetesFanOutHandler { const podMetrics = await this.fetcher.fetchPodMetricsByNamespaces( clusterDetails, + credential, namespaces, labelSelector, ); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 0c47c59184..6feac880ec 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { ObjectToFetch } from '../types/types'; @@ -138,11 +139,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -201,11 +200,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999/k8s/clusters/1234', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -257,8 +254,11 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999/k8s/clusters/1234', - authMetadata: { authProvider: 'localKubectlProxy' }, + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'localKubectlProxy', + }, }, + credential: undefined, objectTypesToFetch: new Set([ { group: '', @@ -313,11 +313,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -389,11 +387,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [ @@ -483,11 +479,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -573,11 +567,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://badurl.does.not.exist', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -612,11 +604,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: 'service-label=value', customResources: [], @@ -681,12 +671,10 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, caData: 'MOCKCA', }, + credential: 'token', objectTypesToFetch: new Set([ { group: '', @@ -720,11 +708,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: new Set([ { group: '', @@ -765,12 +751,10 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, caFile: '/path/to/ca.crt', }, + credential: 'token', objectTypesToFetch: new Set([ { group: '', @@ -805,12 +789,10 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, skipTLSVerify: true, }, + credential: 'token', objectTypesToFetch: new Set([ { group: '', @@ -858,11 +840,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + credential: 'token', objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', namespace: 'some-namespace', @@ -898,14 +878,15 @@ describe('KubernetesFetcher', () => { }); }); describe('Backstage not running on k8s', () => { - it('fails if cluster details has no token', () => { + it('fails if no credential is provided', () => { const result = sut.fetchObjectsForService({ serviceId: 'some-service', clusterDetails: { name: 'unauthenticated-cluster', url: 'http://ignored', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, + credential: undefined, // no token objectTypesToFetch: OBJECTS_TO_FETCH, labelSelector: '', customResources: [], @@ -947,8 +928,9 @@ describe('KubernetesFetcher', () => { clusterDetails: { name: 'overridden-to-in-cluster', url: 'http://ignored', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, + credential: undefined, // no token objectTypesToFetch: new Set([ { group: '', @@ -1043,11 +1025,9 @@ describe('KubernetesFetcher', () => { { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + 'token', new Set(['ns-a']), ); expect(result).toMatchObject({ @@ -1132,11 +1112,9 @@ describe('KubernetesFetcher', () => { { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'token', - }, + authMetadata: {}, }, + '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 167452d362..61a68a06a5 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -32,7 +32,9 @@ import { KubernetesFetcher, ObjectFetchParams, } from '../types/types'; +import { KubernetesCredential } from '../auth/types'; import { + ANNOTATION_KUBERNETES_AUTH_PROVIDER, FetchResponse, KubernetesFetchError, KubernetesErrorTypes, @@ -95,6 +97,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { .map(({ objectType, group, apiVersion, plural }) => this.fetchResource( params.clusterDetails, + params.credential, group, apiVersion, plural, @@ -125,6 +128,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, + credential: KubernetesCredential, namespaces: Set, labelSelector?: string, ): Promise { @@ -132,13 +136,22 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { const [podMetrics, podList] = await Promise.all([ this.fetchResource( clusterDetails, + credential, 'metrics.k8s.io', 'v1beta1', 'pods', ns, labelSelector, ), - this.fetchResource(clusterDetails, '', 'v1', 'pods', ns, labelSelector), + this.fetchResource( + clusterDetails, + credential, + '', + 'v1', + 'pods', + ns, + labelSelector, + ), ]); if (podMetrics.ok && podList.ok) { return topPods( @@ -183,6 +196,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { private fetchResource( clusterDetails: ClusterDetails, + credential: KubernetesCredential, group: string, apiVersion: string, plural: string, @@ -201,10 +215,11 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { let url: URL; let requestInit: RequestInit; if ( - clusterDetails.authMetadata.serviceAccountToken || - clusterDetails.authMetadata.authProvider === 'localKubectlProxy' + credential || + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER] === + 'localKubectlProxy' ) { - [url, requestInit] = this.fetchArgsFromClusterDetails(clusterDetails); + [url, requestInit] = this.fetchArgs(clusterDetails, credential); } else if (fs.pathExistsSync(Config.SERVICEACCOUNT_TOKEN_PATH)) { [url, requestInit] = this.fetchArgsInCluster(); } else { @@ -228,15 +243,16 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { return fetch(url, requestInit); } - private fetchArgsFromClusterDetails( + private fetchArgs( clusterDetails: ClusterDetails, + credential: KubernetesCredential, ): [URL, RequestInit] { const requestInit: RequestInit = { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: `Bearer ${clusterDetails.authMetadata.serviceAccountToken}`, + ...(credential && { Authorization: `Bearer ${credential}` }), }, }; diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 7f28ceff4e..e1770d45b6 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -51,7 +51,7 @@ describe('KubernetesProxy', () => { const logger = getVoidLogger(); const clusterSupplier: jest.Mocked = { - getClusters: jest.fn(), + getClusters: jest.fn, []>(), }; const permissionApi: jest.Mocked = { @@ -60,7 +60,7 @@ describe('KubernetesProxy', () => { }; const authStrategy: jest.Mocked = { - decorateClusterDetailsWithAuth: jest.fn(), + getCredential: jest.fn(), validate: jest.fn(), }; @@ -146,17 +146,14 @@ describe('KubernetesProxy', () => { { name: 'local', url: 'http:/localhost:8001', - authMetadata: { authProvider: 'localKubectlProxy' }, + authMetadata: {}, skipMetricsLookup: true, - } as ClusterDetails, + }, { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'googleServiceAccount', - serviceAccountToken: 'tokenA', - }, - } as ClusterDetails, + authMetadata: {}, + }, ]); const req = buildMockRequest(undefined, 'api'); @@ -172,11 +169,8 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'googleServiceAccount', - serviceAccountToken: 'tokenA', - }, - } as ClusterDetails, + authMetadata: {}, + }, ]); const req = buildMockRequest('test', 'api'); @@ -203,15 +197,9 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); - - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'cluster1', - url: 'https://localhost:9999', - authMetadata: { authProvider: 'serviceAccount' }, - } as ClusterDetails); + ]); worker.use( rest.get('https://localhost:9999/api', (_: any, res: any, ctx: any) => @@ -247,15 +235,9 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); - - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'cluster1', - url: 'https://localhost:9999', - authMetadata: { authProvider: 'serviceAccount' }, - } as ClusterDetails); + ]); worker.use( rest.get('https://localhost:9999/api', (_: any, res: any, ctx: any) => @@ -291,12 +273,9 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'http://localhost:9999', - authMetadata: { authProvider: '' }, + authMetadata: {}, }, ]); - authStrategy.decorateClusterDetailsWithAuth.mockImplementation( - async x => x, - ); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -310,7 +289,7 @@ describe('KubernetesProxy', () => { expect(response.status).toEqual(200); }); - it('should default to using an authStrategy-provided serviceAccountToken as authorization headers to kubeapi when backstage-kubernetes-auth field is not provided', async () => { + it('should default to using a strategy-provided bearer token as authorization headers to kubeapi when backstage-kubernetes-auth field is not provided', async () => { worker.use( rest.get( 'https://localhost:9999/api/v1/namespaces', @@ -342,18 +321,11 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); + ]); - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'cluster1', - url: 'https://localhost:9999', - authMetadata: { - authProvider: 'serviceAccount', - serviceAccountToken: 'strategy-provided-token', - }, - } as ClusterDetails); + authStrategy.getCredential.mockResolvedValue('strategy-provided-token'); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -393,18 +365,11 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'googleServiceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); + ]); - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'cluster1', - url: 'https://localhost:9999', - authMetadata: { - authProvider: 'googleServiceAccount', - serviceAccountToken: 'my-token', - }, - } as ClusterDetails); + authStrategy.getCredential.mockResolvedValue('my-token'); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -449,18 +414,11 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'googleServiceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); + ]); - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'cluster1', - url: 'https://localhost:9999', - authMetadata: { - authProvider: 'googleServiceAccount', - serviceAccountToken: 'tokenA', - }, - } as ClusterDetails); + authStrategy.getCredential.mockResolvedValue('tokenA'); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -508,9 +466,9 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { authProvider: 'googleServiceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); + ]); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -524,9 +482,7 @@ describe('KubernetesProxy', () => { const response = await requestPromise; - expect(authStrategy.decorateClusterDetailsWithAuth).toHaveBeenCalledTimes( - 0, - ); + expect(authStrategy.getCredential).toHaveBeenCalledTimes(0); expect(response.status).toEqual(200); expect(response.body).toStrictEqual({ kind: 'NamespaceList', @@ -602,16 +558,11 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - authMetadata: { - authProvider: 'google', - serviceAccountToken: 'client-side-token', - }, + authMetadata: {}, }, - ] as ClusterDetails[]); + ]); - authStrategy.decorateClusterDetailsWithAuth.mockRejectedValue( - Error('some internal error'), - ); + authStrategy.getCredential.mockRejectedValue(Error('some internal error')); const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', @@ -648,14 +599,10 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'http://localhost:9999/subpath', - authMetadata: { authProvider: '' }, + authMetadata: {}, }, ]); - authStrategy.decorateClusterDetailsWithAuth.mockImplementation( - async x => x, - ); - const requestPromise = setupProxyPromise({ proxyPath: '/mountpath', requestPath: '/api/v1/namespaces', @@ -701,19 +648,11 @@ describe('KubernetesProxy', () => { { name: 'cluster1', url: 'https://localhost:9999', - serviceAccountToken: '', - authProvider: 'serviceAccount', + authMetadata: {}, caFile: resolvePath(__dirname, '__fixtures__/mock-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)), @@ -793,15 +732,9 @@ describe('KubernetesProxy', () => { { name: 'local', url: `http://localhost:${wsPort}`, - authMetadata: { authProvider: 'serviceAccount' }, + authMetadata: {}, }, - ] as ClusterDetails[]); - - authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({ - name: 'local', - url: `http://localhost:${wsPort}`, - authMetadata: { authProvider: 'serviceAccount' }, - } as ClusterDetails); + ]); const wsProxyAddress = `ws://127.0.0.1:${proxyPort}${proxyPath}${wsPath}`; const wsAddress = `ws://localhost:${wsPort}${wsPath}`; diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index d895a38e6e..ce96659c30 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -112,9 +112,9 @@ export class KubernetesProxy { if (authHeader) { req.headers.authorization = authHeader; } else { - const serviceAccountToken = await this.getClusterForRequest(req) - .then(cd => this.authStrategy.decorateClusterDetailsWithAuth(cd, {})) - .then(cd => cd.authMetadata.serviceAccountToken); + const serviceAccountToken = await this.getClusterForRequest(req).then( + cd => this.authStrategy.getCredential(cd, {}), + ); if (serviceAccountToken) { req.headers.authorization = `Bearer ${serviceAccountToken}`; } diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 48bed695d0..3767791589 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -26,6 +26,7 @@ import type { ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; +import { KubernetesCredential } from '../auth/types'; /** * @@ -34,6 +35,7 @@ import { Config } from '@backstage/config'; export interface ObjectFetchParams { serviceId: string; clusterDetails: ClusterDetails; + credential: KubernetesCredential; objectTypesToFetch: Set; labelSelector: string; customResources: CustomResource[]; @@ -51,6 +53,7 @@ export interface KubernetesFetcher { ): Promise; fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, + credential: KubernetesCredential, namespaces: Set, labelSelector?: string, ): Promise;