rename translators to strategies

to encompass their upcoming new responsibilities

Signed-off-by: Jamie Klassen <jklassen@vmware.com>
This commit is contained in:
Jamie Klassen
2023-09-06 11:22:30 -04:00
parent d930afdb43
commit 54c957c1bc
22 changed files with 191 additions and 217 deletions
+31 -41
View File
@@ -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,
@@ -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' },
);
@@ -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,
@@ -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',
@@ -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);
@@ -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();
});
});
@@ -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;
@@ -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',
);
});
});
@@ -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`,
);
}
}
@@ -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> {
@@ -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,
@@ -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> {
@@ -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,
@@ -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,
@@ -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';
@@ -21,7 +21,7 @@ import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
*
* @public
*/
export interface KubernetesAuthTranslator {
export interface AuthenticationStrategy {
decorateClusterDetailsWithAuth(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
+1 -1
View File
@@ -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}`;