Enabled a way to include custom auth metadata info on the clusters endpoint

Signed-off-by: Andres Mauricio Gomez P <andmagom@outlook.com>
This commit is contained in:
Andres Mauricio Gomez P
2024-01-09 18:45:25 -05:00
parent e1120241c7
commit a77559663c
20 changed files with 743 additions and 472 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-backend': patch
'@backstage/plugin-kubernetes-node': patch
---
Enabled a way to include custom auth metadata info on the clusters endpoint
+66 -40
View File
@@ -4,6 +4,7 @@
```ts
import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node';
import { AuthMetadata as AuthMetadata_2 } from '@backstage/plugin-kubernetes-node';
import { CatalogApi } from '@backstage/catalog-client';
import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node';
import { Config } from '@backstage/config';
@@ -12,9 +13,12 @@ import { Duration } from 'luxon';
import express from 'express';
import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node';
import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesCredential as KubernetesCredential_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backstage/plugin-kubernetes-node';
import { Logger } from 'winston';
import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
@@ -23,20 +27,24 @@ import { RequestHandler } from 'http-proxy-middleware';
import { TokenCredential } from '@azure/identity';
// @public (undocumented)
export class AksStrategy implements AuthenticationStrategy {
export class AksStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(
_: ClusterDetails,
_: ClusterDetails_2,
requestAuth: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
// @public (undocumented)
export class AnonymousStrategy implements AuthenticationStrategy {
export class AnonymousStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(): Promise<KubernetesCredential>;
getCredential(): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
@@ -48,19 +56,25 @@ export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy;
export type AuthMetadata = k8sAuthTypes.AuthMetadata;
// @public (undocumented)
export class AwsIamStrategy implements AuthenticationStrategy {
export class AwsIamStrategy implements AuthenticationStrategy_2 {
constructor(opts: { config: Config });
// (undocumented)
getCredential(clusterDetails: ClusterDetails): Promise<KubernetesCredential>;
getCredential(
clusterDetails: ClusterDetails_2,
): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
// @public (undocumented)
export class AzureIdentityStrategy implements AuthenticationStrategy {
export class AzureIdentityStrategy implements AuthenticationStrategy_2 {
constructor(logger: Logger, tokenCredential?: TokenCredential);
// (undocumented)
getCredential(): Promise<KubernetesCredential>;
getCredential(): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
@@ -81,21 +95,23 @@ export type CustomResourcesByEntity = k8sAuthTypes.CustomResourcesByEntity;
export const DEFAULT_OBJECTS: ObjectToFetch[];
// @public
export class DispatchStrategy implements AuthenticationStrategy {
export class DispatchStrategy implements AuthenticationStrategy_2 {
constructor(options: DispatchStrategyOptions);
// (undocumented)
getCredential(
clusterDetails: ClusterDetails,
clusterDetails: ClusterDetails_2,
auth: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
): Promise<KubernetesCredential_2>;
// (undocumented)
validateCluster(authMetadata: AuthMetadata): Error[];
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(authMetadata: AuthMetadata_2): Error[];
}
// @public (undocumented)
export type DispatchStrategyOptions = {
authStrategyMap: {
[key: string]: AuthenticationStrategy;
[key: string]: AuthenticationStrategy_2;
};
};
@@ -103,20 +119,24 @@ export type DispatchStrategyOptions = {
export type FetchResponseWrapper = k8sAuthTypes.FetchResponseWrapper;
// @public (undocumented)
export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
export class GoogleServiceAccountStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(): Promise<KubernetesCredential>;
getCredential(): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
// @public (undocumented)
export class GoogleStrategy implements AuthenticationStrategy {
export class GoogleStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(
_: ClusterDetails,
_: ClusterDetails_2,
requestAuth: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
@@ -131,7 +151,7 @@ export const HEADER_KUBERNETES_CLUSTER: string;
export class KubernetesBuilder {
constructor(env: KubernetesEnvironment);
// (undocumented)
addAuthStrategy(key: string, strategy: AuthenticationStrategy): this;
addAuthStrategy(key: string, strategy: AuthenticationStrategy_2): this;
// (undocumented)
build(): KubernetesBuilderReturn;
// (undocumented)
@@ -145,15 +165,15 @@ export class KubernetesBuilder {
// (undocumented)
protected buildCustomResources(): CustomResource_2[];
// (undocumented)
protected buildFetcher(): KubernetesFetcher;
protected buildFetcher(): KubernetesFetcher_2;
// (undocumented)
protected buildHttpServiceLocator(
_clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
): KubernetesServiceLocator_2;
// (undocumented)
protected buildMultiTenantServiceLocator(
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
): KubernetesServiceLocator_2;
// (undocumented)
protected buildObjectsProvider(
options: KubernetesObjectsProviderOptions,
@@ -175,11 +195,11 @@ export class KubernetesBuilder {
protected buildServiceLocator(
method: ServiceLocatorMethod,
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
): KubernetesServiceLocator_2;
// (undocumented)
protected buildSingleTenantServiceLocator(
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesServiceLocator;
): KubernetesServiceLocator_2;
// (undocumented)
static createBuilder(env: KubernetesEnvironment): KubernetesBuilder;
// (undocumented)
@@ -195,7 +215,7 @@ export class KubernetesBuilder {
// (undocumented)
protected getClusterSupplier(): KubernetesClustersSupplier_2;
// (undocumented)
protected getFetcher(): KubernetesFetcher;
protected getFetcher(): KubernetesFetcher_2;
// (undocumented)
protected getObjectsProvider(
options: KubernetesObjectsProviderOptions,
@@ -208,38 +228,38 @@ export class KubernetesBuilder {
clusterSupplier: KubernetesClustersSupplier_2,
): KubernetesProxy;
// (undocumented)
protected getServiceLocator(): KubernetesServiceLocator;
protected getServiceLocator(): KubernetesServiceLocator_2;
// (undocumented)
protected getServiceLocatorMethod(): ServiceLocatorMethod;
// (undocumented)
setAuthStrategyMap(authStrategyMap: {
[key: string]: AuthenticationStrategy;
[key: string]: AuthenticationStrategy_2;
}): void;
// (undocumented)
setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier_2): this;
// (undocumented)
setDefaultClusterRefreshInterval(refreshInterval: Duration): this;
// (undocumented)
setFetcher(fetcher?: KubernetesFetcher): this;
setFetcher(fetcher?: KubernetesFetcher_2): this;
// (undocumented)
setObjectsProvider(objectsProvider?: KubernetesObjectsProvider_2): this;
// (undocumented)
setProxy(proxy?: KubernetesProxy): this;
// (undocumented)
setServiceLocator(serviceLocator?: KubernetesServiceLocator): this;
setServiceLocator(serviceLocator?: KubernetesServiceLocator_2): this;
}
// @public
export type KubernetesBuilderReturn = Promise<{
router: express.Router;
clusterSupplier: KubernetesClustersSupplier_2;
customResources: CustomResource[];
fetcher: KubernetesFetcher;
customResources: CustomResource_2[];
fetcher: KubernetesFetcher_2;
proxy: KubernetesProxy;
objectsProvider: KubernetesObjectsProvider_2;
serviceLocator: KubernetesServiceLocator;
serviceLocator: KubernetesServiceLocator_2;
authStrategyMap: {
[key: string]: AuthenticationStrategy;
[key: string]: AuthenticationStrategy_2;
};
}>;
@@ -321,14 +341,16 @@ export type ObjectsByEntityRequest = KubernetesRequestBody;
export type ObjectToFetch = k8sAuthTypes.ObjectToFetch;
// @public (undocumented)
export class OidcStrategy implements AuthenticationStrategy {
export class OidcStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(
clusterDetails: ClusterDetails,
clusterDetails: ClusterDetails_2,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
): Promise<KubernetesCredential_2>;
// (undocumented)
validateCluster(authMetadata: AuthMetadata): Error[];
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(authMetadata: AuthMetadata_2): Error[];
}
// @public (undocumented)
@@ -348,9 +370,13 @@ export interface RouterOptions {
}
// @public (undocumented)
export class ServiceAccountStrategy implements AuthenticationStrategy {
export class ServiceAccountStrategy implements AuthenticationStrategy_2 {
// (undocumented)
getCredential(clusterDetails: ClusterDetails): Promise<KubernetesCredential>;
getCredential(
clusterDetails: ClusterDetails_2,
): Promise<KubernetesCredential_2>;
// (undocumented)
presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2;
// (undocumented)
validateCluster(): Error[];
}
@@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ClusterDetails } from '../types/types';
import { AuthenticationStrategy, KubernetesCredential } from './types';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
/**
@@ -31,7 +35,12 @@ export class AksStrategy implements AuthenticationStrategy {
? { type: 'bearer token', token: token as string }
: { type: 'anonymous' };
}
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { AuthenticationStrategy, KubernetesCredential } from './types';
import {
AuthMetadata,
AuthenticationStrategy,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
/**
*
@@ -28,4 +32,8 @@ export class AnonymousStrategy implements AuthenticationStrategy {
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -25,8 +25,12 @@ import {
ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE,
ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID,
} from '@backstage/plugin-kubernetes-common';
import { ClusterDetails } from '../types/types';
import { AuthenticationStrategy, KubernetesCredential } from './types';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
/**
*
@@ -128,4 +132,8 @@ export class AwsIamStrategy implements AuthenticationStrategy {
return `k8s-aws-v1.${Buffer.from(url).toString('base64url')}`;
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -15,12 +15,16 @@
*/
import { Logger } from 'winston';
import { AuthenticationStrategy, KubernetesCredential } from './types';
import {
AccessToken,
DefaultAzureCredential,
TokenCredential,
} from '@azure/identity';
import {
AuthMetadata,
AuthenticationStrategy,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope is the same for all Azure Managed Kubernetes
@@ -89,4 +93,8 @@ export class AzureIdentityStrategy implements AuthenticationStrategy {
private tokenExpired(): boolean {
return Date.now() >= this.accessToken.expiresOnTimestamp;
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -31,6 +31,7 @@ describe('getCredential', () => {
mockStrategy = {
getCredential: jest.fn(),
validateCluster: jest.fn(),
presentAuthMetadata: jest.fn(),
};
strategy = new DispatchStrategy({
authStrategyMap: { google: mockStrategy },
@@ -14,12 +14,16 @@
* limitations under the License.
*/
import { AuthenticationStrategy, KubernetesCredential } from './types';
import { AuthMetadata, ClusterDetails } from '../types';
import {
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
KubernetesRequestAuth,
} from '@backstage/plugin-kubernetes-common';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
/**
*
@@ -67,4 +71,8 @@ export class DispatchStrategy implements AuthenticationStrategy {
}
return strategy.validateCluster(authMetadata);
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -13,7 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AuthenticationStrategy, KubernetesCredential } from './types';
import {
AuthMetadata,
AuthenticationStrategy,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
import * as container from '@google-cloud/container';
/**
@@ -36,4 +41,8 @@ export class GoogleServiceAccountStrategy implements AuthenticationStrategy {
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -14,9 +14,13 @@
* limitations under the License.
*/
import { AuthenticationStrategy, KubernetesCredential } from './types';
import { ClusterDetails } from '../types/types';
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
/**
*
@@ -35,7 +39,12 @@ export class GoogleStrategy implements AuthenticationStrategy {
}
return { type: 'bearer token', token: token as string };
}
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -18,8 +18,12 @@ import {
ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER,
KubernetesRequestAuth,
} from '@backstage/plugin-kubernetes-common';
import { AuthenticationStrategy, KubernetesCredential } from './types';
import { AuthMetadata, ClusterDetails } from '../types/types';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
/**
*
@@ -57,4 +61,8 @@ export class OidcStrategy implements AuthenticationStrategy {
}
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -14,10 +14,14 @@
* limitations under the License.
*/
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
} from '@backstage/plugin-kubernetes-node';
import { KubeConfig, User } from '@kubernetes/client-node';
import fs from 'fs-extra';
import { AuthenticationStrategy, KubernetesCredential } from './types';
import { ClusterDetails } from '../types/types';
/**
*
@@ -45,4 +49,8 @@ export class ServiceAccountStrategy implements AuthenticationStrategy {
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
@@ -32,6 +32,7 @@ describe('ConfigClusterLocator', () => {
authStrategy = {
getCredential: jest.fn(),
validateCluster: jest.fn().mockReturnValue([]),
presentAuthMetadata: jest.fn(),
};
});
@@ -53,6 +53,7 @@ describe('getCombinedClusterSupplier', () => {
const mockStrategy: jest.Mocked<AuthenticationStrategy> = {
getCredential: jest.fn(),
validateCluster: jest.fn().mockReturnValue([]),
presentAuthMetadata: jest.fn(),
};
const clusterSupplier = getCombinedClusterSupplier(
File diff suppressed because it is too large Load Diff
@@ -29,7 +29,6 @@ import { Logger } from 'winston';
import { getCombinedClusterSupplier } from '../cluster-locator';
import {
AuthenticationStrategy,
AnonymousStrategy,
DispatchStrategy,
GoogleStrategy,
@@ -45,17 +44,19 @@ import { addResourceRoutesToRouter } from '../routes/resourcesRoutes';
import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator';
import { SingleTenantServiceLocator } from '../service-locator/SingleTenantServiceLocator';
import {
CustomResource,
KubernetesFetcher,
KubernetesObjectsProviderOptions,
KubernetesObjectTypes,
KubernetesServiceLocator,
ObjectsByEntityRequest,
ServiceLocatorMethod,
} from '../types/types';
import {
AuthenticationStrategy,
AuthMetadata,
CustomResource,
KubernetesClustersSupplier,
KubernetesFetcher,
KubernetesObjectsProvider,
KubernetesObjectTypes,
KubernetesServiceLocator,
} from '@backstage/plugin-kubernetes-node';
import {
DEFAULT_OBJECTS,
@@ -369,11 +370,20 @@ export class KubernetesBuilder {
items: clusterDetails.map(cd => {
const oidcTokenProvider =
cd.authMetadata[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
const authProvider =
cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];
const strategy = this.getAuthStrategyMap()[authProvider];
let auth: AuthMetadata = {};
if (strategy) {
auth = strategy.presentAuthMetadata(cd.authMetadata);
}
return {
name: cd.name,
dashboardUrl: cd.dashboardUrl,
authProvider: cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER],
authProvider,
...(oidcTokenProvider && { oidcTokenProvider }),
...(auth && Object.keys(auth).length !== 0 && { auth }),
};
}),
});
@@ -196,6 +196,7 @@ describe('KubernetesFanOutHandler', () => {
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn().mockReturnValue([]),
presentAuthMetadata: jest.fn(),
},
config,
});
@@ -1147,6 +1148,7 @@ describe('KubernetesFanOutHandler', () => {
>()
.mockResolvedValue({ type: 'bearer token', token: 'token' }),
validateCluster: jest.fn().mockReturnValue([]),
presentAuthMetadata: jest.fn(),
},
config,
});
@@ -131,6 +131,7 @@ describe('KubernetesProxy', () => {
>()
.mockResolvedValue({ type: 'anonymous' }),
validateCluster: jest.fn(),
presentAuthMetadata: jest.fn(),
};
proxy = new KubernetesProxy({ logger, clusterSupplier, authStrategy });
permissionApi.authorize.mockResolvedValue([
@@ -514,6 +515,7 @@ describe('KubernetesProxy', () => {
.fn()
.mockReturnValue({ type: 'bearer token', token: 'MY_TOKEN3' }),
validateCluster: jest.fn(),
presentAuthMetadata: jest.fn(),
};
proxy = new KubernetesProxy({
+2
View File
@@ -25,6 +25,8 @@ export interface AuthenticationStrategy {
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
// (undocumented)
presentAuthMetadata(authMetadata: AuthMetadata): AuthMetadata;
// (undocumented)
validateCluster(authMetadata: AuthMetadata): Error[];
}
@@ -151,6 +151,7 @@ export interface AuthenticationStrategy {
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
validateCluster(authMetadata: AuthMetadata): Error[];
presentAuthMetadata(authMetadata: AuthMetadata): AuthMetadata;
}
/**