diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 7b3b3d7b25..f7d7430952 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -37,6 +37,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/theme": "workspace:^", diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index bffc966333..b54046fbcd 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -23,17 +23,22 @@ import { } from '@backstage/plugin-kubernetes-common'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider'; +import { NotFoundError } from '@backstage/errors'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; + private readonly kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; constructor(options: { discoveryApi: DiscoveryApi; identityApi: IdentityApi; + kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; }) { this.discoveryApi = options.discoveryApi; this.identityApi = options.identityApi; + this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi; } private async handleResponse(response: Response): Promise { @@ -110,4 +115,68 @@ export class KubernetesBackendClient implements KubernetesApi { return (await this.handleResponse(response)).items; } + + async proxy( + clusterName: string, + path: string, + init?: RequestInit, + ): Promise { + const { authProvider } = await this.getCluster(clusterName); + const token = await this.getBearerToken(authProvider); + + const url = `${await this.discoveryApi.getBaseUrl( + 'kubernetes', + )}/proxy${path}`; + const headers = { + Authorization: `Bearer ${token}`, + ...init?.headers, + [`X-Kubernetes-Cluster`]: clusterName, + }; + const response = await fetch(url, { ...init, headers }); + return this.handleResponse(response); + } + + private async getCluster( + clusterName: string, + ): Promise<{ name: string; authProvider: string }> { + const cluster = await this.getClusters().then(clusters => + clusters.find(c => c.name === clusterName), + ); + if (!cluster) { + throw new NotFoundError(`Cluster ${clusterName} not found`); + } + return cluster; + } + + private async getBearerToken(authProvider: string): Promise { + // const { auth } = + // await this.kubernetesAuthProvidersApi.decorateRequestBodyForAuth( + // authProvider, + // { + // entity: { + // apiVersion: 'v1', + // kind: 'Pods', + // metadata: { + // name: 'podName', + // annotations: { + // 'backstage.io/kubernetes-label-selector': 'k8s-app=kube-dns', + // }, + // }, + // }, + // }, + // ); + return await this.kubernetesAuthProvidersApi.getBearerToken(authProvider); + + // if (auth) { + // if (authProvider === 'google' && auth.google) { + // return auth.google; + // } else if (authProvider.startsWith('oidc.') && auth.oidc) { + // const oidcTokenProvider = authProvider.replace(/^oidc\./, ''); + // return auth.oidc[oidcTokenProvider]; + // } else { + // throw new Error(`invalid auth provider '${authProvider}'`); + // } + // } + // return ''; + } } diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 0005a8d069..22e0644461 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -43,4 +43,9 @@ export interface KubernetesApi { getCustomObjectsByEntity( request: CustomObjectsByEntityRequest, ): Promise; + proxy( + clusterName: string, + path: string, + init?: RequestInit, + ): Promise; } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts index 058ecf07a3..171ebc9960 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts @@ -38,4 +38,9 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { } return requestBody; } + async getBearerToken(): Promise { + return await this.authProvider.getAccessToken( + 'https://www.googleapis.com/auth/cloud-platform', + ); + } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index eb3ea456ea..65dc45e131 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { + KubernetesRequestAuth, + KubernetesRequestBody, +} from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServerSideKubernetesAuthProvider } from './ServerSideAuthProvider'; @@ -91,4 +94,22 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { `authProvider "${authProvider}" has no KubernetesAuthProvider defined for it`, ); } + + async getBearerToken(authProvider: string): Promise { + const kubernetesAuthProvider: KubernetesAuthProvider | undefined = + this.kubernetesAuthProviderMap.get(authProvider); + + if (kubernetesAuthProvider) { + return await kubernetesAuthProvider.getBearerToken(); + } + + if (authProvider.startsWith('oidc.')) { + throw new Error( + `KubernetesAuthProviders has no oidcProvider configured for ${authProvider}`, + ); + } + throw new Error( + `authProvider "${authProvider}" has no KubernetesAuthProvider defined for it`, + ); + } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts index f86df5e73e..b992c71391 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts @@ -40,4 +40,8 @@ export class OidcKubernetesAuthProvider implements KubernetesAuthProvider { requestBody.auth = auth; return requestBody; } + + async getBearerToken(): Promise { + return await this.authProvider.getIdToken(); + } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts index 1c475b9568..9290a2eda3 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts @@ -31,4 +31,8 @@ export class ServerSideKubernetesAuthProvider // No-op, auth will be taken care of on the server-side return requestBody; } + + async getBearerToken(): Promise { + return ''; + } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts index ffa0fd5961..de9f91c44e 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts @@ -14,13 +14,17 @@ * limitations under the License. */ -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { + KubernetesRequestAuth, + KubernetesRequestBody, +} from '@backstage/plugin-kubernetes-common'; import { createApiRef } from '@backstage/core-plugin-api'; export interface KubernetesAuthProvider { decorateRequestBodyForAuth( requestBody: KubernetesRequestBody, ): Promise; + getBearerToken(): Promise; } export const kubernetesAuthProvidersApiRef = @@ -33,4 +37,5 @@ export interface KubernetesAuthProvidersApi { authProvider: string, requestBody: KubernetesRequestBody, ): Promise; + getBearerToken(authProvider: string): Promise; } diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index e2b13f43fe..cb85cf06fb 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -43,9 +43,14 @@ export const kubernetesPlugin = createPlugin({ deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef, + kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, }, - factory: ({ discoveryApi, identityApi }) => - new KubernetesBackendClient({ discoveryApi, identityApi }), + factory: ({ discoveryApi, identityApi, kubernetesAuthProvidersApi }) => + new KubernetesBackendClient({ + discoveryApi, + identityApi, + kubernetesAuthProvidersApi, + }), }), createApiFactory({ api: kubernetesAuthProvidersApiRef, diff --git a/yarn.lock b/yarn.lock index 75f4a41905..9a4399e5ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7153,6 +7153,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/test-utils": "workspace:^"