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 <jklassen@vmware.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -24,6 +24,9 @@ describe('AksStrategy', () => {
|
||||
{ aks: 'aksToken' },
|
||||
);
|
||||
|
||||
expect(credential).toBe('aksToken');
|
||||
expect(credential).toStrictEqual({
|
||||
type: 'bearer token',
|
||||
token: 'aksToken',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,8 @@ export class AksStrategy implements AuthenticationStrategy {
|
||||
_: ClusterDetails,
|
||||
requestAuth: KubernetesRequestAuth,
|
||||
): Promise<KubernetesCredential> {
|
||||
return requestAuth.aks;
|
||||
const token = requestAuth.aks;
|
||||
return token ? { type: 'bearer token', token } : { type: 'anonymous' };
|
||||
}
|
||||
public validate(_: AuthMetadata) {}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { AuthenticationStrategy, KubernetesCredential } from './types';
|
||||
*/
|
||||
export class AnonymousStrategy implements AuthenticationStrategy {
|
||||
public async getCredential(): Promise<KubernetesCredential> {
|
||||
return undefined;
|
||||
return { type: 'anonymous' };
|
||||
}
|
||||
|
||||
public validate() {}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -51,14 +51,17 @@ export class AwsIamStrategy implements AuthenticationStrategy {
|
||||
this.credsManager = DefaultAwsCredentialsManager.fromConfig(opts.config);
|
||||
}
|
||||
|
||||
public getCredential(
|
||||
public async getCredential(
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<KubernetesCredential> {
|
||||
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() {}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -39,14 +39,16 @@ export class AzureIdentityStrategy implements AuthenticationStrategy {
|
||||
|
||||
public async getCredential(): Promise<KubernetesCredential> {
|
||||
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() {}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -23,14 +23,14 @@ import * as container from '@google-cloud/container';
|
||||
export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
|
||||
public async getCredential(): Promise<KubernetesCredential> {
|
||||
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() {}
|
||||
|
||||
@@ -27,13 +27,13 @@ export class GoogleStrategy implements AuthenticationStrategy {
|
||||
_: ClusterDetails,
|
||||
requestAuth: KubernetesRequestAuth,
|
||||
): Promise<KubernetesCredential> {
|
||||
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) {}
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -25,7 +25,8 @@ export class ServiceAccountStrategy implements AuthenticationStrategy {
|
||||
public async getCredential(
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<KubernetesCredential> {
|
||||
return clusterDetails.authMetadata.serviceAccountToken;
|
||||
const token = clusterDetails.authMetadata.serviceAccountToken;
|
||||
return token ? { type: 'bearer token', token } : { type: 'anonymous' };
|
||||
}
|
||||
|
||||
public validate() {}
|
||||
|
||||
@@ -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' };
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -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<KubernetesCredential>,
|
||||
[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<KubernetesCredential>,
|
||||
[ClusterDetails, KubernetesRequestAuth]
|
||||
>()
|
||||
.mockResolvedValue({ type: 'bearer token', token: 'token' }),
|
||||
validate: jest.fn(),
|
||||
},
|
||||
config,
|
||||
|
||||
@@ -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<ObjectToFetch>([
|
||||
{
|
||||
group: '',
|
||||
@@ -710,7 +710,7 @@ describe('KubernetesFetcher', () => {
|
||||
url: 'https://localhost:9999',
|
||||
authMetadata: {},
|
||||
},
|
||||
credential: 'token',
|
||||
credential: { type: 'bearer token', token: 'token' },
|
||||
objectTypesToFetch: new Set<ObjectToFetch>([
|
||||
{
|
||||
group: '',
|
||||
@@ -754,7 +754,7 @@ describe('KubernetesFetcher', () => {
|
||||
authMetadata: {},
|
||||
caFile: '/path/to/ca.crt',
|
||||
},
|
||||
credential: 'token',
|
||||
credential: { type: 'bearer token', token: 'token' },
|
||||
objectTypesToFetch: new Set<ObjectToFetch>([
|
||||
{
|
||||
group: '',
|
||||
@@ -792,7 +792,7 @@ describe('KubernetesFetcher', () => {
|
||||
authMetadata: {},
|
||||
skipTLSVerify: true,
|
||||
},
|
||||
credential: 'token',
|
||||
credential: { type: 'bearer token', token: 'token' },
|
||||
objectTypesToFetch: new Set<ObjectToFetch>([
|
||||
{
|
||||
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<ObjectToFetch>([
|
||||
{
|
||||
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']),
|
||||
);
|
||||
|
||||
|
||||
@@ -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}`,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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<AuthenticationStrategy>;
|
||||
const worker = setupServer();
|
||||
const logger = getVoidLogger();
|
||||
|
||||
@@ -59,11 +65,6 @@ describe('KubernetesProxy', () => {
|
||||
authorizeConditional: jest.fn(),
|
||||
};
|
||||
|
||||
const authStrategy: jest.Mocked<AuthenticationStrategy> = {
|
||||
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<KubernetesCredential>,
|
||||
[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();
|
||||
});
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user