rename translators to strategies
to encompass their upcoming new responsibilities Signed-off-by: Jamie Klassen <jklassen@vmware.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import type { RequestHandler } from 'express';
|
||||
import { TokenCredential } from '@azure/identity';
|
||||
|
||||
// @public (undocumented)
|
||||
export class AksKubernetesAuthTranslator {
|
||||
export class AksStrategy implements AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
@@ -31,9 +31,16 @@ export class AksKubernetesAuthTranslator {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AwsIamKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export interface AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
): Promise<ClusterDetails>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AwsIamStrategy implements AuthenticationStrategy {
|
||||
constructor(opts: { config: Config });
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
@@ -42,9 +49,7 @@ export class AwsIamKubernetesAuthTranslator
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class AzureIdentityKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class AzureIdentityStrategy implements AuthenticationStrategy {
|
||||
constructor(logger: Logger, tokenCredential?: TokenCredential);
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
@@ -92,10 +97,8 @@ export interface CustomResourcesByEntity extends KubernetesObjectsByEntity {
|
||||
export const DEFAULT_OBJECTS: ObjectToFetch[];
|
||||
|
||||
// @public
|
||||
export class DispatchingKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
constructor(options: DispatchingKubernetesAuthTranslatorOptions);
|
||||
export class DispatchStrategy implements AuthenticationStrategy {
|
||||
constructor(options: DispatchStrategyOptions);
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
@@ -104,9 +107,9 @@ export class DispatchingKubernetesAuthTranslator
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type DispatchingKubernetesAuthTranslatorOptions = {
|
||||
authTranslatorMap: {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
export type DispatchStrategyOptions = {
|
||||
authStrategyMap: {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -119,23 +122,19 @@ export interface FetchResponseWrapper {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class GoogleKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
): Promise<ClusterDetails>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class GoogleServiceAccountAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class GoogleStrategy implements AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
): Promise<ClusterDetails>;
|
||||
}
|
||||
|
||||
@@ -145,23 +144,14 @@ export const HEADER_KUBERNETES_AUTH: string;
|
||||
// @public
|
||||
export const HEADER_KUBERNETES_CLUSTER: string;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KubernetesAuthTranslator {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
): Promise<ClusterDetails>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class KubernetesBuilder {
|
||||
constructor(env: KubernetesEnvironment);
|
||||
// (undocumented)
|
||||
build(): KubernetesBuilderReturn;
|
||||
// (undocumented)
|
||||
protected buildAuthTranslatorMap(): {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
protected buildAuthStrategyMap(): {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
};
|
||||
// (undocumented)
|
||||
protected buildClusterSupplier(
|
||||
@@ -210,8 +200,8 @@ export class KubernetesBuilder {
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): Promise<ClusterDetails[]>;
|
||||
// (undocumented)
|
||||
protected getAuthTranslatorMap(): {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
protected getAuthStrategyMap(): {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
};
|
||||
// (undocumented)
|
||||
protected getClusterSupplier(): KubernetesClustersSupplier;
|
||||
@@ -233,8 +223,8 @@ export class KubernetesBuilder {
|
||||
// (undocumented)
|
||||
protected getServiceLocatorMethod(): ServiceLocatorMethod;
|
||||
// (undocumented)
|
||||
setAuthTranslatorMap(authTranslatorMap: {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
setAuthStrategyMap(authStrategyMap: {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
}): void;
|
||||
// (undocumented)
|
||||
setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier): this;
|
||||
@@ -259,8 +249,8 @@ export type KubernetesBuilderReturn = Promise<{
|
||||
proxy: KubernetesProxy;
|
||||
objectsProvider: KubernetesObjectsProvider;
|
||||
serviceLocator: KubernetesServiceLocator;
|
||||
authTranslatorMap: {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
authStrategyMap: {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -365,7 +355,7 @@ export type KubernetesProxyCreateRequestHandlerOptions = {
|
||||
export type KubernetesProxyOptions = {
|
||||
logger: Logger;
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
authTranslator: KubernetesAuthTranslator;
|
||||
authStrategy: AuthenticationStrategy;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -380,7 +370,7 @@ export interface KubernetesServiceLocator {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class NoopKubernetesAuthTranslator implements KubernetesAuthTranslator {
|
||||
export class NoopStrategy implements AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
@@ -419,7 +409,7 @@ export interface ObjectToFetch {
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class OidcKubernetesAuthTranslator implements KubernetesAuthTranslator {
|
||||
export class OidcStrategy implements AuthenticationStrategy {
|
||||
// (undocumented)
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
|
||||
+4
-4
@@ -13,13 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AksKubernetesAuthTranslator } from './AksKubernetesAuthTranslator';
|
||||
import { AksStrategy } from './AksStrategy';
|
||||
|
||||
describe('AksKubernetesAuthTranslator', () => {
|
||||
describe('AksStrategy', () => {
|
||||
it('uses auth.aks value as bearer token', async () => {
|
||||
const translator = new AksKubernetesAuthTranslator();
|
||||
const strategy = new AksStrategy();
|
||||
|
||||
const details = await translator.decorateClusterDetailsWithAuth(
|
||||
const details = await strategy.decorateClusterDetailsWithAuth(
|
||||
{ name: '', authProvider: 'aks', url: '' },
|
||||
{ aks: 'aksToken' },
|
||||
);
|
||||
+2
-1
@@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ClusterDetails } from '../types/types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AksKubernetesAuthTranslator {
|
||||
export class AksStrategy implements AuthenticationStrategy {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
auth: KubernetesRequestAuth,
|
||||
+10
-10
@@ -18,7 +18,7 @@ import {
|
||||
ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE,
|
||||
ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator';
|
||||
import { AwsIamStrategy } from './AwsIamStrategy';
|
||||
|
||||
let presign = jest.fn(async () => ({
|
||||
hostname: 'https://example.com',
|
||||
@@ -55,12 +55,12 @@ jest.mock('@aws-sdk/credential-providers', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AwsIamKubernetesAuthTranslator tests', () => {
|
||||
describe('AwsIamStrategy tests', () => {
|
||||
beforeEach(() => {});
|
||||
it('returns a signed url for AWS credentials without assume role', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
|
||||
const strategy = new AwsIamStrategy({ config });
|
||||
|
||||
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
|
||||
const authPromise = strategy.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
@@ -71,9 +71,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
|
||||
});
|
||||
|
||||
it('returns a signed url for AWS credentials with assume role', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
|
||||
const strategy = new AwsIamStrategy({ config });
|
||||
|
||||
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
|
||||
const authPromise = strategy.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
@@ -97,9 +97,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
|
||||
});
|
||||
|
||||
it('returns a signed url for AWS credentials and passes the external id', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
|
||||
const strategy = new AwsIamStrategy({ config });
|
||||
|
||||
const authPromise = authTranslator.decorateClusterDetailsWithAuth({
|
||||
const authPromise = strategy.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
@@ -132,9 +132,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => {
|
||||
});
|
||||
});
|
||||
it('throws the right error', async () => {
|
||||
const authTranslator = new AwsIamKubernetesAuthTranslator({ config });
|
||||
const strategy = new AwsIamStrategy({ config });
|
||||
await expect(
|
||||
authTranslator.decorateClusterDetailsWithAuth({
|
||||
strategy.decorateClusterDetailsWithAuth({
|
||||
name: 'test-cluster',
|
||||
url: '',
|
||||
authProvider: 'aws',
|
||||
+2
-4
@@ -26,7 +26,7 @@ import {
|
||||
ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -44,9 +44,7 @@ const defaultRegion = 'us-east-1';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AwsIamKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class AwsIamStrategy implements AuthenticationStrategy {
|
||||
private readonly credsManager: AwsCredentialsManager;
|
||||
constructor(opts: { config: Config }) {
|
||||
this.credsManager = DefaultAwsCredentialsManager.fromConfig(opts.config);
|
||||
+19
-21
@@ -16,7 +16,7 @@
|
||||
|
||||
import { AccessToken, TokenCredential } from '@azure/identity';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator';
|
||||
import { AzureIdentityStrategy } from './AzureIdentityStrategy';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
@@ -39,7 +39,7 @@ class StaticTokenCredential implements TokenCredential {
|
||||
}
|
||||
}
|
||||
|
||||
describe('AzureIdentityKubernetesAuthTranslator tests', () => {
|
||||
describe('AzureIdentityStrategy tests', () => {
|
||||
const cd = {
|
||||
authProvider: 'Azure',
|
||||
name: 'My Cluster',
|
||||
@@ -51,90 +51,88 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => {
|
||||
});
|
||||
|
||||
it('should decorate cluster with Azure token', async () => {
|
||||
const authTranslator = new AzureIdentityKubernetesAuthTranslator(
|
||||
const strategy = new AzureIdentityStrategy(
|
||||
logger,
|
||||
new StaticTokenCredential(5 * 60 * 1000),
|
||||
);
|
||||
|
||||
const response = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
});
|
||||
|
||||
it('should re-use token before expiry', async () => {
|
||||
const authTranslator = new AzureIdentityKubernetesAuthTranslator(
|
||||
const strategy = new AzureIdentityStrategy(
|
||||
logger,
|
||||
new StaticTokenCredential(20 * 60 * 1000),
|
||||
);
|
||||
|
||||
const response = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
|
||||
const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response2 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response2.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
});
|
||||
|
||||
it('should issue new token 15 minutes befory expiry', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const authTranslator = new AzureIdentityKubernetesAuthTranslator(
|
||||
const strategy = new AzureIdentityStrategy(
|
||||
logger,
|
||||
new StaticTokenCredential(16 * 60 * 1000), // token expires in 16min
|
||||
);
|
||||
|
||||
const response = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
|
||||
jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins
|
||||
|
||||
const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response2 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response2.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_2');
|
||||
});
|
||||
|
||||
it('should re-use existing token if there is afailure', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const authTranslator = new AzureIdentityKubernetesAuthTranslator(
|
||||
const strategy = new AzureIdentityStrategy(
|
||||
logger,
|
||||
new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min
|
||||
);
|
||||
|
||||
const response = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
|
||||
jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min
|
||||
|
||||
const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response2 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response2.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_2');
|
||||
|
||||
jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min
|
||||
|
||||
const response3 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response3 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response3.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_2');
|
||||
|
||||
const response4 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response4 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response4.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_4');
|
||||
});
|
||||
|
||||
it('should throw if existing token expired and failed to fetch a new one', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const authTranslator = new AzureIdentityKubernetesAuthTranslator(
|
||||
const strategy = new AzureIdentityStrategy(
|
||||
logger,
|
||||
new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min
|
||||
);
|
||||
|
||||
const response = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_1');
|
||||
|
||||
jest.setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min
|
||||
|
||||
const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd);
|
||||
const response2 = await strategy.decorateClusterDetailsWithAuth(cd);
|
||||
expect(response2.authMetadata!.serviceAccountToken).toEqual('MY_TOKEN_2');
|
||||
|
||||
jest.setSystemTime(Date.now() + 17 * 60 * 1000); // advance time by 17min
|
||||
|
||||
await expect(
|
||||
authTranslator.decorateClusterDetailsWithAuth(cd),
|
||||
).rejects.toThrow();
|
||||
await expect(strategy.decorateClusterDetailsWithAuth(cd)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
+2
-4
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
import {
|
||||
AccessToken,
|
||||
@@ -29,9 +29,7 @@ const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class AzureIdentityKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class AzureIdentityStrategy implements AuthenticationStrategy {
|
||||
private accessToken: AccessToken = { token: '', expiresOnTimestamp: 0 };
|
||||
private newTokenPromise: Promise<string> | undefined;
|
||||
|
||||
+15
-15
@@ -14,50 +14,50 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DispatchingKubernetesAuthTranslator } from './DispatchingKubernetesAuthTranslator';
|
||||
import { DispatchStrategy } from './DispatchStrategy';
|
||||
import { ClusterDetails } from '../types';
|
||||
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
|
||||
describe('decorateClusterDetailsWithAuth', () => {
|
||||
let authTranslator: DispatchingKubernetesAuthTranslator;
|
||||
let mockTranslator: jest.Mocked<KubernetesAuthTranslator>;
|
||||
let strategy: DispatchStrategy;
|
||||
let mockStrategy: jest.Mocked<AuthenticationStrategy>;
|
||||
const authObject: KubernetesRequestAuth = {};
|
||||
|
||||
beforeEach(() => {
|
||||
mockTranslator = { decorateClusterDetailsWithAuth: jest.fn() };
|
||||
authTranslator = new DispatchingKubernetesAuthTranslator({
|
||||
authTranslatorMap: { google: mockTranslator },
|
||||
mockStrategy = { decorateClusterDetailsWithAuth: jest.fn() };
|
||||
strategy = new DispatchStrategy({
|
||||
authStrategyMap: { google: mockStrategy },
|
||||
});
|
||||
});
|
||||
|
||||
it('can decorate cluster details if the auth provider is in the translator map', async () => {
|
||||
it('can decorate cluster details if the auth provider is in the strategy map', async () => {
|
||||
const expectedClusterDetails: ClusterDetails = {
|
||||
url: 'notanything.com',
|
||||
name: 'randomName',
|
||||
authProvider: 'google',
|
||||
authMetadata: { serviceAccountToken: 'added by mock translator' },
|
||||
authMetadata: { serviceAccountToken: 'added by mock strategy' },
|
||||
};
|
||||
|
||||
mockTranslator.decorateClusterDetailsWithAuth.mockResolvedValue(
|
||||
mockStrategy.decorateClusterDetailsWithAuth.mockResolvedValue(
|
||||
expectedClusterDetails,
|
||||
);
|
||||
|
||||
const returnedValue = await authTranslator.decorateClusterDetailsWithAuth(
|
||||
const returnedValue = await strategy.decorateClusterDetailsWithAuth(
|
||||
{ name: 'googleCluster', url: 'anything.com', authProvider: 'google' },
|
||||
authObject,
|
||||
);
|
||||
|
||||
expect(mockTranslator.decorateClusterDetailsWithAuth).toHaveBeenCalledWith(
|
||||
expect(mockStrategy.decorateClusterDetailsWithAuth).toHaveBeenCalledWith(
|
||||
{ name: 'googleCluster', url: 'anything.com', authProvider: 'google' },
|
||||
authObject,
|
||||
);
|
||||
expect(returnedValue).toBe(expectedClusterDetails);
|
||||
});
|
||||
|
||||
it('throws an error when asked for an auth translator for an unsupported auth type', () => {
|
||||
it('throws an error when asked for a strategy for an unsupported auth type', () => {
|
||||
expect(() =>
|
||||
authTranslator.decorateClusterDetailsWithAuth(
|
||||
strategy.decorateClusterDetailsWithAuth(
|
||||
{
|
||||
name: 'test-cluster',
|
||||
url: 'anything.com',
|
||||
@@ -66,7 +66,7 @@ describe('decorateClusterDetailsWithAuth', () => {
|
||||
authObject,
|
||||
),
|
||||
).toThrow(
|
||||
'authProvider "linode" has no KubernetesAuthTranslator associated with it',
|
||||
'authProvider "linode" has no AuthenticationStrategy associated with it',
|
||||
);
|
||||
});
|
||||
});
|
||||
+12
-14
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types';
|
||||
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
@@ -22,35 +22,33 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type DispatchingKubernetesAuthTranslatorOptions = {
|
||||
authTranslatorMap: {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
export type DispatchStrategyOptions = {
|
||||
authStrategyMap: {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* used to direct a KubernetesAuthProvider to its corresponding KubernetesAuthTranslator
|
||||
* used to direct a KubernetesAuthProvider to its corresponding AuthenticationStrategy
|
||||
* @public
|
||||
*/
|
||||
export class DispatchingKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
private readonly translatorMap: { [key: string]: KubernetesAuthTranslator };
|
||||
export class DispatchStrategy implements AuthenticationStrategy {
|
||||
private readonly strategyMap: { [key: string]: AuthenticationStrategy };
|
||||
|
||||
constructor(options: DispatchingKubernetesAuthTranslatorOptions) {
|
||||
this.translatorMap = options.authTranslatorMap;
|
||||
constructor(options: DispatchStrategyOptions) {
|
||||
this.strategyMap = options.authStrategyMap;
|
||||
}
|
||||
|
||||
public decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
auth: KubernetesRequestAuth,
|
||||
) {
|
||||
if (this.translatorMap[clusterDetails.authProvider]) {
|
||||
return this.translatorMap[
|
||||
if (this.strategyMap[clusterDetails.authProvider]) {
|
||||
return this.strategyMap[
|
||||
clusterDetails.authProvider
|
||||
].decorateClusterDetailsWithAuth(clusterDetails, auth);
|
||||
}
|
||||
throw new Error(
|
||||
`authProvider "${clusterDetails.authProvider}" has no KubernetesAuthTranslator associated with it`,
|
||||
`authProvider "${clusterDetails.authProvider}" has no AuthenticationStrategy associated with it`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
import * as container from '@google-cloud/container';
|
||||
|
||||
@@ -21,9 +21,7 @@ import * as container from '@google-cloud/container';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GoogleServiceAccountAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<ClusterDetails> {
|
||||
+2
-4
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
|
||||
@@ -22,9 +22,7 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GoogleKubernetesAuthTranslator
|
||||
implements KubernetesAuthTranslator
|
||||
{
|
||||
export class GoogleStrategy implements AuthenticationStrategy {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
+2
-2
@@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class NoopKubernetesAuthTranslator implements KubernetesAuthTranslator {
|
||||
export class NoopStrategy implements AuthenticationStrategy {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<ClusterDetails> {
|
||||
+6
-6
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import { ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER } from '@backstage/plugin-kubernetes-common';
|
||||
import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator';
|
||||
import { OidcStrategy } from './OidcStrategy';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
|
||||
describe('OidcKubernetesAuthTranslator tests', () => {
|
||||
const at = new OidcKubernetesAuthTranslator();
|
||||
describe('OidcStrategy tests', () => {
|
||||
const strategy = new OidcStrategy();
|
||||
const baseClusterDetails: ClusterDetails = {
|
||||
name: 'test',
|
||||
authProvider: 'oidc',
|
||||
@@ -27,7 +27,7 @@ describe('OidcKubernetesAuthTranslator tests', () => {
|
||||
};
|
||||
|
||||
it('returns cluster details with auth token', async () => {
|
||||
const details = await at.decorateClusterDetailsWithAuth(
|
||||
const details = await strategy.decorateClusterDetailsWithAuth(
|
||||
{
|
||||
authMetadata: { [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'okta' },
|
||||
...baseClusterDetails,
|
||||
@@ -42,7 +42,7 @@ describe('OidcKubernetesAuthTranslator tests', () => {
|
||||
|
||||
it('returns error when oidcTokenProvider is not configured', async () => {
|
||||
await expect(
|
||||
at.decorateClusterDetailsWithAuth(baseClusterDetails, {}),
|
||||
strategy.decorateClusterDetailsWithAuth(baseClusterDetails, {}),
|
||||
).rejects.toThrow(
|
||||
'oidc authProvider requires a configured oidcTokenProvider',
|
||||
);
|
||||
@@ -50,7 +50,7 @@ describe('OidcKubernetesAuthTranslator tests', () => {
|
||||
|
||||
it('returns error when token is not included in request body', async () => {
|
||||
await expect(
|
||||
at.decorateClusterDetailsWithAuth(
|
||||
strategy.decorateClusterDetailsWithAuth(
|
||||
{
|
||||
authMetadata: { [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'okta' },
|
||||
...baseClusterDetails,
|
||||
+2
-2
@@ -17,14 +17,14 @@ import {
|
||||
ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,
|
||||
KubernetesRequestAuth,
|
||||
} from '@backstage/plugin-kubernetes-common';
|
||||
import { KubernetesAuthTranslator } from './types';
|
||||
import { AuthenticationStrategy } from './types';
|
||||
import { ClusterDetails } from '../types/types';
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class OidcKubernetesAuthTranslator implements KubernetesAuthTranslator {
|
||||
export class OidcStrategy implements AuthenticationStrategy {
|
||||
async decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
+8
-8
@@ -14,12 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './AksKubernetesAuthTranslator';
|
||||
export * from './AwsIamKubernetesAuthTranslator';
|
||||
export * from './AzureIdentityKubernetesAuthTranslator';
|
||||
export * from './GoogleKubernetesAuthTranslator';
|
||||
export * from './GoogleServiceAccountAuthProvider';
|
||||
export * from './DispatchingKubernetesAuthTranslator';
|
||||
export * from './NoopKubernetesAuthTranslator';
|
||||
export * from './OidcKubernetesAuthTranslator';
|
||||
export * from './AksStrategy';
|
||||
export * from './AwsIamStrategy';
|
||||
export * from './AzureIdentityStrategy';
|
||||
export * from './GoogleStrategy';
|
||||
export * from './GoogleServiceAccountStrategy';
|
||||
export * from './DispatchStrategy';
|
||||
export * from './NoopStrategy';
|
||||
export * from './OidcStrategy';
|
||||
export * from './types';
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface KubernetesAuthTranslator {
|
||||
export interface AuthenticationStrategy {
|
||||
decorateClusterDetailsWithAuth(
|
||||
clusterDetails: ClusterDetails,
|
||||
authConfig: KubernetesRequestAuth,
|
||||
@@ -20,6 +20,6 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './kubernetes-auth-translator';
|
||||
export * from './auth';
|
||||
export * from './service';
|
||||
export * from './types';
|
||||
|
||||
@@ -25,16 +25,16 @@ import { Logger } from 'winston';
|
||||
|
||||
import { getCombinedClusterSupplier } from '../cluster-locator';
|
||||
import {
|
||||
KubernetesAuthTranslator,
|
||||
DispatchingKubernetesAuthTranslator,
|
||||
GoogleKubernetesAuthTranslator,
|
||||
NoopKubernetesAuthTranslator,
|
||||
AwsIamKubernetesAuthTranslator,
|
||||
GoogleServiceAccountAuthTranslator,
|
||||
AzureIdentityKubernetesAuthTranslator,
|
||||
OidcKubernetesAuthTranslator,
|
||||
AksKubernetesAuthTranslator,
|
||||
} from '../kubernetes-auth-translator';
|
||||
AuthenticationStrategy,
|
||||
DispatchStrategy,
|
||||
GoogleStrategy,
|
||||
NoopStrategy,
|
||||
AwsIamStrategy,
|
||||
GoogleServiceAccountStrategy,
|
||||
AzureIdentityStrategy,
|
||||
OidcStrategy,
|
||||
AksStrategy,
|
||||
} from '../auth';
|
||||
|
||||
import { addResourceRoutesToRouter } from '../routes/resourcesRoutes';
|
||||
import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';
|
||||
@@ -80,7 +80,7 @@ export type KubernetesBuilderReturn = Promise<{
|
||||
proxy: KubernetesProxy;
|
||||
objectsProvider: KubernetesObjectsProvider;
|
||||
serviceLocator: KubernetesServiceLocator;
|
||||
authTranslatorMap: { [key: string]: KubernetesAuthTranslator };
|
||||
authStrategyMap: { [key: string]: AuthenticationStrategy };
|
||||
}>;
|
||||
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ export class KubernetesBuilder {
|
||||
private fetcher?: KubernetesFetcher;
|
||||
private serviceLocator?: KubernetesServiceLocator;
|
||||
private proxy?: KubernetesProxy;
|
||||
private authTranslatorMap?: { [key: string]: KubernetesAuthTranslator };
|
||||
private authStrategyMap?: { [key: string]: AuthenticationStrategy };
|
||||
|
||||
static createBuilder(env: KubernetesEnvironment) {
|
||||
return new KubernetesBuilder(env);
|
||||
@@ -128,7 +128,7 @@ export class KubernetesBuilder {
|
||||
|
||||
const clusterSupplier = this.getClusterSupplier();
|
||||
|
||||
const authTranslatorMap = this.getAuthTranslatorMap();
|
||||
const authStrategyMap = this.getAuthStrategyMap();
|
||||
|
||||
const proxy = this.getProxy(logger, clusterSupplier);
|
||||
|
||||
@@ -159,7 +159,7 @@ export class KubernetesBuilder {
|
||||
objectsProvider,
|
||||
router,
|
||||
serviceLocator,
|
||||
authTranslatorMap,
|
||||
authStrategyMap,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -193,10 +193,10 @@ export class KubernetesBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setAuthTranslatorMap(authTranslatorMap: {
|
||||
[key: string]: KubernetesAuthTranslator;
|
||||
public setAuthStrategyMap(authStrategyMap: {
|
||||
[key: string]: AuthenticationStrategy;
|
||||
}) {
|
||||
this.authTranslatorMap = authTranslatorMap;
|
||||
this.authStrategyMap = authStrategyMap;
|
||||
}
|
||||
|
||||
protected buildCustomResources() {
|
||||
@@ -234,11 +234,11 @@ export class KubernetesBuilder {
|
||||
protected buildObjectsProvider(
|
||||
options: KubernetesObjectsProviderOptions,
|
||||
): KubernetesObjectsProvider {
|
||||
const authTranslatorMap = this.getAuthTranslatorMap();
|
||||
const authStrategyMap = this.getAuthStrategyMap();
|
||||
this.objectsProvider = new KubernetesFanOutHandler({
|
||||
...options,
|
||||
authTranslator: new DispatchingKubernetesAuthTranslator({
|
||||
authTranslatorMap,
|
||||
authStrategy: new DispatchStrategy({
|
||||
authStrategyMap,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -290,14 +290,14 @@ export class KubernetesBuilder {
|
||||
logger: Logger,
|
||||
clusterSupplier: KubernetesClustersSupplier,
|
||||
): KubernetesProxy {
|
||||
const authTranslatorMap = this.getAuthTranslatorMap();
|
||||
const authTranslator = new DispatchingKubernetesAuthTranslator({
|
||||
authTranslatorMap,
|
||||
const authStrategyMap = this.getAuthStrategyMap();
|
||||
const authStrategy = new DispatchStrategy({
|
||||
authStrategyMap,
|
||||
});
|
||||
this.proxy = new KubernetesProxy({
|
||||
logger,
|
||||
clusterSupplier,
|
||||
authTranslator,
|
||||
authStrategy,
|
||||
});
|
||||
return this.proxy;
|
||||
}
|
||||
@@ -356,18 +356,18 @@ export class KubernetesBuilder {
|
||||
return router;
|
||||
}
|
||||
|
||||
protected buildAuthTranslatorMap() {
|
||||
this.authTranslatorMap = {
|
||||
google: new GoogleKubernetesAuthTranslator(),
|
||||
aks: new AksKubernetesAuthTranslator(),
|
||||
aws: new AwsIamKubernetesAuthTranslator({ config: this.env.config }),
|
||||
azure: new AzureIdentityKubernetesAuthTranslator(this.env.logger),
|
||||
serviceAccount: new NoopKubernetesAuthTranslator(),
|
||||
googleServiceAccount: new GoogleServiceAccountAuthTranslator(),
|
||||
oidc: new OidcKubernetesAuthTranslator(),
|
||||
localKubectlProxy: new NoopKubernetesAuthTranslator(),
|
||||
protected buildAuthStrategyMap() {
|
||||
this.authStrategyMap = {
|
||||
google: new GoogleStrategy(),
|
||||
aks: new AksStrategy(),
|
||||
aws: new AwsIamStrategy({ config: this.env.config }),
|
||||
azure: new AzureIdentityStrategy(this.env.logger),
|
||||
serviceAccount: new NoopStrategy(),
|
||||
googleServiceAccount: new GoogleServiceAccountStrategy(),
|
||||
oidc: new OidcStrategy(),
|
||||
localKubectlProxy: new NoopStrategy(),
|
||||
};
|
||||
return this.authTranslatorMap;
|
||||
return this.authStrategyMap;
|
||||
}
|
||||
|
||||
protected async fetchClusterDetails(
|
||||
@@ -450,7 +450,7 @@ export class KubernetesBuilder {
|
||||
return this.proxy ?? this.buildProxy(logger, clusterSupplier);
|
||||
}
|
||||
|
||||
protected getAuthTranslatorMap() {
|
||||
return this.authTranslatorMap ?? this.buildAuthTranslatorMap();
|
||||
protected getAuthStrategyMap() {
|
||||
return this.authStrategyMap ?? this.buildAuthStrategyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ describe('KubernetesFanOutHandler', () => {
|
||||
getClustersByEntity,
|
||||
},
|
||||
customResources: customResources,
|
||||
authTranslator: {
|
||||
authStrategy: {
|
||||
decorateClusterDetailsWithAuth: async (clusterDetails, _) => {
|
||||
return clusterDetails;
|
||||
},
|
||||
@@ -1100,7 +1100,7 @@ describe('KubernetesFanOutHandler', () => {
|
||||
objectType: 'services',
|
||||
},
|
||||
],
|
||||
authTranslator: {
|
||||
authStrategy: {
|
||||
decorateClusterDetailsWithAuth: async (clusterDetails, _) => {
|
||||
return clusterDetails;
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
KubernetesObjectsByEntity,
|
||||
ServiceLocatorRequestContext,
|
||||
} from '../types/types';
|
||||
import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types';
|
||||
import { AuthenticationStrategy } from '../auth/types';
|
||||
import {
|
||||
ClientContainerStatus,
|
||||
ClientCurrentResourceUsage,
|
||||
@@ -137,7 +137,7 @@ export const DEFAULT_OBJECTS: ObjectToFetch[] = [
|
||||
|
||||
export interface KubernetesFanOutHandlerOptions
|
||||
extends KubernetesObjectsProviderOptions {
|
||||
authTranslator: KubernetesAuthTranslator;
|
||||
authStrategy: AuthenticationStrategy;
|
||||
}
|
||||
|
||||
export interface KubernetesRequestBody extends ObjectsByEntityRequest {}
|
||||
@@ -196,7 +196,7 @@ export class KubernetesFanOutHandler {
|
||||
private readonly serviceLocator: KubernetesServiceLocator;
|
||||
private readonly customResources: CustomResource[];
|
||||
private readonly objectTypesToFetch: Set<ObjectToFetch>;
|
||||
private readonly authTranslator: KubernetesAuthTranslator;
|
||||
private readonly authStrategy: AuthenticationStrategy;
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
@@ -204,14 +204,14 @@ export class KubernetesFanOutHandler {
|
||||
serviceLocator,
|
||||
customResources,
|
||||
objectTypesToFetch = DEFAULT_OBJECTS,
|
||||
authTranslator,
|
||||
authStrategy,
|
||||
}: KubernetesFanOutHandlerOptions) {
|
||||
this.logger = logger;
|
||||
this.fetcher = fetcher;
|
||||
this.serviceLocator = serviceLocator;
|
||||
this.customResources = customResources;
|
||||
this.objectTypesToFetch = new Set(objectTypesToFetch);
|
||||
this.authTranslator = authTranslator;
|
||||
this.authStrategy = authStrategy;
|
||||
}
|
||||
|
||||
async getCustomResourcesByEntity({
|
||||
@@ -317,7 +317,7 @@ export class KubernetesFanOutHandler {
|
||||
// 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.authTranslator.decorateClusterDetailsWithAuth(cd, auth);
|
||||
return this.authStrategy.decorateClusterDetailsWithAuth(cd, auth);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -33,10 +33,7 @@ import request from 'supertest';
|
||||
import { AddressInfo, WebSocket, WebSocketServer } from 'ws';
|
||||
|
||||
import { LocalKubectlProxyClusterLocator } from '../cluster-locator/LocalKubectlProxyLocator';
|
||||
import {
|
||||
KubernetesAuthTranslator,
|
||||
NoopKubernetesAuthTranslator,
|
||||
} from '../kubernetes-auth-translator';
|
||||
import { AuthenticationStrategy, NoopStrategy } from '../auth';
|
||||
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
|
||||
import {
|
||||
APPLICATION_JSON,
|
||||
@@ -62,7 +59,7 @@ describe('KubernetesProxy', () => {
|
||||
authorizeConditional: jest.fn(),
|
||||
};
|
||||
|
||||
const authTranslator: jest.Mocked<KubernetesAuthTranslator> = {
|
||||
const authStrategy: jest.Mocked<AuthenticationStrategy> = {
|
||||
decorateClusterDetailsWithAuth: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -126,7 +123,7 @@ describe('KubernetesProxy', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
proxy = new KubernetesProxy({ logger, clusterSupplier, authTranslator });
|
||||
proxy = new KubernetesProxy({ logger, clusterSupplier, authStrategy });
|
||||
permissionApi.authorize.mockResolvedValue([
|
||||
{ result: AuthorizeResult.ALLOW },
|
||||
]);
|
||||
@@ -205,7 +202,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'cluster1',
|
||||
url: 'https://localhost:9999',
|
||||
authProvider: 'serviceAccount',
|
||||
@@ -249,7 +246,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'cluster1',
|
||||
url: 'https://localhost:9999',
|
||||
authProvider: 'serviceAccount',
|
||||
@@ -292,7 +289,7 @@ describe('KubernetesProxy', () => {
|
||||
authProvider: '',
|
||||
},
|
||||
]);
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockImplementation(
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockImplementation(
|
||||
async x => x,
|
||||
);
|
||||
|
||||
@@ -308,7 +305,7 @@ describe('KubernetesProxy', () => {
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
it('should default to using a authTranslator provided serviceAccountToken as authorization headers to kubeapi when backstage-kubernetes-auth field is not provided', async () => {
|
||||
it('should default to using an authStrategy-provided serviceAccountToken as authorization headers to kubeapi when backstage-kubernetes-auth field is not provided', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://localhost:9999/api/v1/namespaces',
|
||||
@@ -319,7 +316,7 @@ describe('KubernetesProxy', () => {
|
||||
|
||||
if (
|
||||
req.headers.get('Authorization') !==
|
||||
'Bearer translator-provided-token'
|
||||
'Bearer strategy-provided-token'
|
||||
) {
|
||||
return res(ctx.status(403));
|
||||
}
|
||||
@@ -344,11 +341,11 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'cluster1',
|
||||
url: 'https://localhost:9999',
|
||||
authProvider: 'serviceAccount',
|
||||
authMetadata: { serviceAccountToken: 'translator-provided-token' },
|
||||
authMetadata: { serviceAccountToken: 'strategy-provided-token' },
|
||||
} as ClusterDetails);
|
||||
|
||||
const requestPromise = setupProxyPromise({
|
||||
@@ -363,7 +360,7 @@ describe('KubernetesProxy', () => {
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
it('should add a authTranslator provided serviceAccountToken as authorization headers to kubeapi if one isnt provided in request and one isnt set up in cluster details', async () => {
|
||||
it('should add an authStrategy-provided serviceAccountToken as authorization headers to kubeapi if one isnt provided in request and one isnt set up in cluster details', async () => {
|
||||
worker.use(
|
||||
rest.get('https://localhost:9999/api/v1/namespaces', (req, res, ctx) => {
|
||||
if (!req.headers.get('Authorization')) {
|
||||
@@ -393,7 +390,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'cluster1',
|
||||
url: 'https://localhost:9999',
|
||||
authProvider: 'googleServiceAccount',
|
||||
@@ -447,7 +444,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'cluster1',
|
||||
url: 'https://localhost:9999',
|
||||
authProvider: 'googleServiceAccount',
|
||||
@@ -474,7 +471,7 @@ describe('KubernetesProxy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not invoke authTranslator if Backstage-Kubernetes-Authorization field is provided', async () => {
|
||||
it('should not invoke authStrategy if Backstage-Kubernetes-Authorization field is provided', async () => {
|
||||
worker.use(
|
||||
rest.get('https://localhost:9999/api/v1/namespaces', (req, res, ctx) => {
|
||||
if (!req.headers.get('Authorization')) {
|
||||
@@ -516,7 +513,7 @@ describe('KubernetesProxy', () => {
|
||||
|
||||
const response = await requestPromise;
|
||||
|
||||
expect(authTranslator.decorateClusterDetailsWithAuth).toHaveBeenCalledTimes(
|
||||
expect(authStrategy.decorateClusterDetailsWithAuth).toHaveBeenCalledTimes(
|
||||
0,
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
@@ -531,7 +528,7 @@ describe('KubernetesProxy', () => {
|
||||
proxy = new KubernetesProxy({
|
||||
logger: getVoidLogger(),
|
||||
clusterSupplier: new LocalKubectlProxyClusterLocator(),
|
||||
authTranslator: new NoopKubernetesAuthTranslator(),
|
||||
authStrategy: new NoopStrategy(),
|
||||
});
|
||||
|
||||
worker.use(
|
||||
@@ -568,7 +565,7 @@ describe('KubernetesProxy', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a 500 error if authTranslator errors out and Backstage-Kubernetes-Authorization field is not provided', async () => {
|
||||
it('returns a 500 error if authStrategy errors out and Backstage-Kubernetes-Authorization field is not provided', async () => {
|
||||
worker.use(
|
||||
rest.get('https://localhost:9999/api/v1/namespaces', (req, res, ctx) => {
|
||||
if (!req.headers.get('Authorization')) {
|
||||
@@ -599,7 +596,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockRejectedValue(
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockRejectedValue(
|
||||
Error('some internal error'),
|
||||
);
|
||||
|
||||
@@ -642,7 +639,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockImplementation(
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockImplementation(
|
||||
async x => x,
|
||||
);
|
||||
|
||||
@@ -787,7 +784,7 @@ describe('KubernetesProxy', () => {
|
||||
},
|
||||
] as ClusterDetails[]);
|
||||
|
||||
authTranslator.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
authStrategy.decorateClusterDetailsWithAuth.mockResolvedValue({
|
||||
name: 'local',
|
||||
url: `http://localhost:${wsPort}`,
|
||||
authProvider: 'serviceAccount',
|
||||
|
||||
@@ -31,7 +31,7 @@ import { bufferFromFileOrString } from '@kubernetes/client-node';
|
||||
import type { Request, RequestHandler } from 'express';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { Logger } from 'winston';
|
||||
import { KubernetesAuthTranslator } from '../kubernetes-auth-translator';
|
||||
import { AuthenticationStrategy } from '../auth';
|
||||
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
|
||||
|
||||
export const APPLICATION_JSON: string = 'application/json';
|
||||
@@ -68,7 +68,7 @@ export type KubernetesProxyCreateRequestHandlerOptions = {
|
||||
export type KubernetesProxyOptions = {
|
||||
logger: Logger;
|
||||
clusterSupplier: KubernetesClustersSupplier;
|
||||
authTranslator: KubernetesAuthTranslator;
|
||||
authStrategy: AuthenticationStrategy;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -80,12 +80,12 @@ export class KubernetesProxy {
|
||||
private readonly middlewareForClusterName = new Map<string, RequestHandler>();
|
||||
private readonly logger: Logger;
|
||||
private readonly clusterSupplier: KubernetesClustersSupplier;
|
||||
private readonly authTranslator: KubernetesAuthTranslator;
|
||||
private readonly authStrategy: AuthenticationStrategy;
|
||||
|
||||
constructor(options: KubernetesProxyOptions) {
|
||||
this.logger = options.logger;
|
||||
this.clusterSupplier = options.clusterSupplier;
|
||||
this.authTranslator = options.authTranslator;
|
||||
this.authStrategy = options.authStrategy;
|
||||
}
|
||||
|
||||
public createRequestHandler(
|
||||
@@ -113,9 +113,7 @@ export class KubernetesProxy {
|
||||
req.headers.authorization = authHeader;
|
||||
} else {
|
||||
const serviceAccountToken = await this.getClusterForRequest(req)
|
||||
.then(cd =>
|
||||
this.authTranslator.decorateClusterDetailsWithAuth(cd, {}),
|
||||
)
|
||||
.then(cd => this.authStrategy.decorateClusterDetailsWithAuth(cd, {}))
|
||||
.then(cd => cd?.authMetadata?.serviceAccountToken);
|
||||
if (serviceAccountToken) {
|
||||
req.headers.authorization = `Bearer ${serviceAccountToken}`;
|
||||
|
||||
Reference in New Issue
Block a user