From d2c5a3232398d26a10ad2d69146202f2b5fa192d Mon Sep 17 00:00:00 2001 From: Daniel Bravo Date: Sat, 30 Apr 2022 09:30:38 -0500 Subject: [PATCH 1/4] Add oidc authProvider to kubernetes-backend plugin Signed-off-by: Daniel Bravo --- plugins/kubernetes-backend/schema.d.ts | 4 +- .../cluster-locator/ConfigClusterLocator.ts | 5 ++ .../KubernetesAuthTranslatorGenerator.test.ts | 7 ++ .../KubernetesAuthTranslatorGenerator.ts | 4 ++ .../OidcKubernetesAuthTranslator.test.ts | 65 +++++++++++++++++++ .../OidcKubernetesAuthTranslator.ts | 50 ++++++++++++++ plugins/kubernetes-backend/src/types/types.ts | 4 ++ plugins/kubernetes-common/src/types.ts | 2 +- 8 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts create mode 100644 plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/schema.d.ts index c2ac017713..8a11f7c08a 100644 --- a/plugins/kubernetes-backend/schema.d.ts +++ b/plugins/kubernetes-backend/schema.d.ts @@ -52,7 +52,9 @@ export interface Config { /** @visibility secret */ serviceAccountToken?: string; /** @visibility frontend */ - authProvider: 'aws' | 'google' | 'serviceAccount' | 'azure'; + authProvider: 'aws' | 'google' | 'serviceAccount' | 'azure' | 'oidc'; + /** @visibility frontend */ + oidcTokenProvider?: string; /** @visibility frontend */ skipTLSVerify?: boolean; }>; diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 5598740873..de5cac6d8d 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -64,6 +64,11 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { case 'azure': { return clusterDetails; } + case 'oidc': { + const oidcTokenProvider = c.getString('oidcTokenProvider'); + + return { oidcTokenProvider, ...clusterDetails }; + } case 'serviceAccount': { return clusterDetails; } diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts index 33592b1c41..b31443cdd1 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts @@ -19,6 +19,7 @@ import { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator import { KubernetesAuthTranslatorGenerator } from './KubernetesAuthTranslatorGenerator'; import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator'; import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; +import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; describe('getKubernetesAuthTranslatorInstance', () => { const sut = KubernetesAuthTranslatorGenerator; @@ -43,6 +44,12 @@ describe('getKubernetesAuthTranslatorInstance', () => { ).toBe(true); }); + it('can return an auth translator for oidc auth', () => { + const authTranslator: KubernetesAuthTranslator = + sut.getKubernetesAuthTranslatorInstance('oidc'); + expect(authTranslator instanceof OidcKubernetesAuthTranslator).toBe(true); + }); + it('throws an error when asked for an auth translator for an unsupported auth type', () => { expect(() => sut.getKubernetesAuthTranslatorInstance('linode')).toThrow( 'authProvider "linode" has no KubernetesAuthTranslator associated with it', diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index e9a8a00ae9..4b17653359 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -20,6 +20,7 @@ import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernet import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; import { GoogleServiceAccountAuthTranslator } from './GoogleServiceAccountAuthProvider'; import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator'; +import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; export class KubernetesAuthTranslatorGenerator { static getKubernetesAuthTranslatorInstance( @@ -41,6 +42,9 @@ export class KubernetesAuthTranslatorGenerator { case 'googleServiceAccount': { return new GoogleServiceAccountAuthTranslator(); } + case 'oidc': { + return new OidcKubernetesAuthTranslator(); + } default: { throw new Error( `authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`, diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts new file mode 100644 index 0000000000..3c8edd38b4 --- /dev/null +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; +import { ClusterDetails } from '../types/types'; +import { Entity } from '@backstage/catalog-model'; + +describe('OidcKubernetesAuthTranslator tests', () => { + const at = new OidcKubernetesAuthTranslator(); + const entity: Entity = { + apiVersion: 'v1', + kind: 'service', + metadata: { name: 'test' }, + }; + const baseClusterDetails: ClusterDetails = { + name: 'test', + authProvider: 'oidc', + url: '', + }; + + it('returns cluster details with auth token', async () => { + const details = await at.decorateClusterDetailsWithAuth( + { + oidcTokenProvider: 'okta', + ...baseClusterDetails, + }, + { + auth: { okta: 'fakeToken' }, + entity, + }, + ); + + expect(details.serviceAccountToken).toBe('fakeToken'); + }); + + it('returns error when oidcTokenProvider is not configured', async () => { + await expect( + at.decorateClusterDetailsWithAuth(baseClusterDetails, { entity }), + ).rejects.toThrow( + 'oidc authProvider requires a configured oidcTokenProvider', + ); + }); + + it('returns error when token is not included in request body', async () => { + await expect( + at.decorateClusterDetailsWithAuth( + { oidcTokenProvider: 'okta', ...baseClusterDetails }, + { entity }, + ), + ).rejects.toThrow('Auth token not found under auth.okta in request body'); + }); +}); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts new file mode 100644 index 0000000000..cbcbd3c6ab --- /dev/null +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { KubernetesAuthTranslator } from './types'; +import { ClusterDetails } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; + +export class OidcKubernetesAuthTranslator implements KubernetesAuthTranslator { + async decorateClusterDetailsWithAuth( + clusterDetails: ClusterDetails, + requestBody: KubernetesRequestBody, + ): Promise { + const clusterDetailsWithAuthToken: ClusterDetails = Object.assign( + {}, + clusterDetails, + ); + + const { oidcTokenProvider } = clusterDetails; + + if (!oidcTokenProvider || oidcTokenProvider === '') { + throw new Error( + `oidc authProvider requires a configured oidcTokenProvider`, + ); + } + + const authToken: string | undefined = requestBody.auth?.[oidcTokenProvider]; + + if (authToken) { + clusterDetailsWithAuthToken.serviceAccountToken = authToken; + } else { + throw new Error( + `Auth token not found under auth.${oidcTokenProvider} in request body`, + ); + } + return clusterDetailsWithAuthToken; + } +} diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 706582f439..e8665c57eb 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -105,6 +105,10 @@ export interface ClusterDetails { url: string; authProvider: string; serviceAccountToken?: string | undefined; + /** + * oidc provider used to get id tokens to authenticate against kubernetes + */ + oidcTokenProvider?: string | undefined; skipTLSVerify?: boolean; /** * Whether to skip the lookup to the metrics server to retrieve pod resource usage. diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index cb388bad0a..135be4099c 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -30,7 +30,7 @@ import { Entity } from '@backstage/catalog-model'; export interface KubernetesRequestBody { auth?: { - google?: string; + [key: string]: string; }; entity: Entity; } From 2f50c323fbfcf2ab4edbde6aa07cecd0c84073e5 Mon Sep 17 00:00:00 2001 From: Daniel Bravo Date: Mon, 2 May 2022 15:46:00 -0500 Subject: [PATCH 2/4] Add oidc authProvider handling to kubernetes frontend plugin Signed-off-by: Daniel Bravo --- .../OidcKubernetesAuthTranslator.test.ts | 4 +- .../OidcKubernetesAuthTranslator.ts | 4 +- .../src/service/KubernetesBuilder.ts | 1 + plugins/kubernetes-common/src/types.ts | 3 + plugins/kubernetes/src/api/types.ts | 8 +- .../src/hooks/useKubernetesObjects.ts | 10 ++- .../KubernetesAuthProviders.test.ts | 79 +++++++++++++++++++ .../KubernetesAuthProviders.ts | 26 +++++- .../OidcKubernetesAuthProvider.ts | 41 ++++++++++ plugins/kubernetes/src/plugin.ts | 26 +++++- 10 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts create mode 100644 plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts index 3c8edd38b4..1aacb7f059 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts @@ -38,7 +38,7 @@ describe('OidcKubernetesAuthTranslator tests', () => { ...baseClusterDetails, }, { - auth: { okta: 'fakeToken' }, + oidc: { okta: 'fakeToken' }, entity, }, ); @@ -60,6 +60,6 @@ describe('OidcKubernetesAuthTranslator tests', () => { { oidcTokenProvider: 'okta', ...baseClusterDetails }, { entity }, ), - ).rejects.toThrow('Auth token not found under auth.okta in request body'); + ).rejects.toThrow('Auth token not found under oidc.okta in request body'); }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts index cbcbd3c6ab..05b87f0ce0 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts @@ -36,13 +36,13 @@ export class OidcKubernetesAuthTranslator implements KubernetesAuthTranslator { ); } - const authToken: string | undefined = requestBody.auth?.[oidcTokenProvider]; + const authToken: string | undefined = requestBody.oidc?.[oidcTokenProvider]; if (authToken) { clusterDetailsWithAuthToken.serviceAccountToken = authToken; } else { throw new Error( - `Auth token not found under auth.${oidcTokenProvider} in request body`, + `Auth token not found under oidc.${oidcTokenProvider} in request body`, ); } return clusterDetailsWithAuthToken; diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index a3dfc8b005..18c3fdc931 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -246,6 +246,7 @@ export class KubernetesBuilder { name: cd.name, dashboardUrl: cd.dashboardUrl, authProvider: cd.authProvider, + oidcTokenProvider: cd.oidcTokenProvider, })), }); }); diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 135be4099c..a71d1feaad 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -30,6 +30,9 @@ import { Entity } from '@backstage/catalog-model'; export interface KubernetesRequestBody { auth?: { + google: string; + }; + oidc?: { [key: string]: string; }; entity: Entity; diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index 8b95dd820c..7c5943a6eb 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -28,5 +28,11 @@ export interface KubernetesApi { getObjectsByEntity( requestBody: KubernetesRequestBody, ): Promise; - getClusters(): Promise<{ name: string; authProvider: string }[]>; + getClusters(): Promise< + { + name: string; + authProvider: string; + oidcTokenProvider?: string | undefined; + }[] + >; } diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index d9aca875c7..8e2597c1d2 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -53,8 +53,16 @@ export const useKubernetesObjects = ( } const authProviders: string[] = [ - ...new Set(clusters.map(c => c.authProvider)), + ...new Set( + clusters.map( + c => + `${c.authProvider}${ + c.oidcTokenProvider ? `.${c.oidcTokenProvider}` : '' + }`, + ), + ), ]; + // For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider let requestBody: KubernetesRequestBody = { entity, diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts new file mode 100644 index 0000000000..ffeaa6137c --- /dev/null +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OAuthApi, OpenIdConnectApi } from '@backstage/core-plugin-api'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { KubernetesAuthProviders } from './KubernetesAuthProviders'; + +class MockAuthApi implements OAuthApi, OpenIdConnectApi { + constructor(private readonly token: string) {} + + getAccessToken = jest.fn(async () => { + return this.token; + }); + + getIdToken = jest.fn(async () => { + return this.token; + }); +} + +const requestBody: KubernetesRequestBody = { + entity: { + apiVersion: 'v1', + kind: 'service', + metadata: { name: 'test' }, + }, +}; + +describe('KubernetesAuthProviders tests', () => { + const kap = new KubernetesAuthProviders({ + googleAuthApi: new MockAuthApi('googleToken'), + oidcProviders: { + okta: new MockAuthApi('oktaToken'), + }, + }); + + it('adds token to request body for google authProvider', async () => { + const details = await kap.decorateRequestBodyForAuth('google', requestBody); + + expect(details.auth?.google).toBe('googleToken'); + }); + + it('adds token to request body for oidc authProvider', async () => { + const details = await kap.decorateRequestBodyForAuth( + 'oidc.okta', + requestBody, + ); + + expect(details.oidc?.okta).toBe('oktaToken'); + }); + + it('returns error for unknown authProvider', async () => { + await expect( + kap.decorateRequestBodyForAuth('unknown', requestBody), + ).rejects.toThrow( + 'authProvider "unknown" has no KubernetesAuthProvider defined for it', + ); + }); + + it('returns error for missconfigured oidc authProvider', async () => { + await expect( + kap.decorateRequestBodyForAuth('oidc.random', requestBody), + ).rejects.toThrow( + 'KubernetesAuthProviders has no oidcProvider configured for oidc.random', + ); + }); +}); diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index 00d5a17f72..bcd1b2911a 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -19,9 +19,10 @@ import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; import { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; -import { OAuthApi } from '@backstage/core-plugin-api'; +import { OAuthApi, OpenIdConnectApi } from '@backstage/core-plugin-api'; import { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider'; import { AzureKubernetesAuthProvider } from './AzureKubernetesAuthProvider'; +import { OidcKubernetesAuthProvider } from './OidcKubernetesAuthProvider'; export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { private readonly kubernetesAuthProviderMap: Map< @@ -29,7 +30,10 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { KubernetesAuthProvider >; - constructor(options: { googleAuthApi: OAuthApi }) { + constructor(options: { + googleAuthApi: OAuthApi; + oidcProviders?: { [key: string]: OpenIdConnectApi }; + }) { this.kubernetesAuthProviderMap = new Map(); this.kubernetesAuthProviderMap.set( 'google', @@ -48,6 +52,18 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { 'azure', new AzureKubernetesAuthProvider(), ); + + if (options.oidcProviders) { + Object.keys(options.oidcProviders).forEach(provider => { + this.kubernetesAuthProviderMap.set( + `oidc.${provider}`, + new OidcKubernetesAuthProvider( + provider, + options.oidcProviders![provider], + ), + ); + }); + } } async decorateRequestBodyForAuth( @@ -61,6 +77,12 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { requestBody, ); } + + 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 new file mode 100644 index 0000000000..f308c09c2d --- /dev/null +++ b/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { KubernetesAuthProvider } from './types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { OpenIdConnectApi } from '@backstage/core-plugin-api'; + +export class OidcKubernetesAuthProvider implements KubernetesAuthProvider { + providerName: string; + authProvider: OpenIdConnectApi; + + constructor(providerName: string, authProvider: OpenIdConnectApi) { + this.providerName = providerName; + this.authProvider = authProvider; + } + + async decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise { + const authToken: string = await this.authProvider.getIdToken(); + if ('oidc' in requestBody) { + requestBody.oidc![this.providerName] = authToken; + } else { + requestBody.oidc = { [this.providerName]: authToken }; + } + return requestBody; + } +} diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index a1f2f57621..b54850307e 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -24,6 +24,9 @@ import { discoveryApiRef, identityApiRef, googleAuthApiRef, + microsoftAuthApiRef, + oktaAuthApiRef, + oneloginAuthApiRef, createRoutableExtension, } from '@backstage/core-plugin-api'; @@ -45,9 +48,26 @@ export const kubernetesPlugin = createPlugin({ }), createApiFactory({ api: kubernetesAuthProvidersApiRef, - deps: { googleAuthApi: googleAuthApiRef }, - factory: ({ googleAuthApi }) => { - return new KubernetesAuthProviders({ googleAuthApi }); + deps: { + googleAuthApi: googleAuthApiRef, + microsoftAuthApi: microsoftAuthApiRef, + oktaAuthApi: oktaAuthApiRef, + oneloginAuthApi: oneloginAuthApiRef, + }, + factory: ({ + googleAuthApi, + microsoftAuthApi, + oktaAuthApi, + oneloginAuthApi, + }) => { + const oidcProviders = { + google: googleAuthApi, + microsoft: microsoftAuthApi, + okta: oktaAuthApi, + onelogin: oneloginAuthApi, + }; + + return new KubernetesAuthProviders({ googleAuthApi, oidcProviders }); }, }), ], From 447e060872b6e5302ffa1babe5cc9ec9e55aaee7 Mon Sep 17 00:00:00 2001 From: Daniel Bravo Date: Wed, 4 May 2022 11:29:25 -0500 Subject: [PATCH 3/4] Add changeset and docs for kubernetes oidcTokenProvider feature Signed-off-by: Daniel Bravo --- .changeset/fluffy-sloths-deliver.md | 10 ++++++++ docs/features/kubernetes/configuration.md | 29 +++++++++++++++++++++++ plugins/kubernetes-backend/api-report.md | 1 + plugins/kubernetes-common/api-report.md | 6 ++++- plugins/kubernetes/api-report.md | 9 ++++++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .changeset/fluffy-sloths-deliver.md diff --git a/.changeset/fluffy-sloths-deliver.md b/.changeset/fluffy-sloths-deliver.md new file mode 100644 index 0000000000..ba4e6e4fe5 --- /dev/null +++ b/.changeset/fluffy-sloths-deliver.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +--- + +Add support for 'oidc' as authProvider for kubernetes authentication +and adds optional 'oidcTokenProvider' config value. This will allow +users to authenticate to kubernetes cluster using id tokens obtained +from the configured auth provider in their backstage instance. diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 5b60316fd0..3b8fef453a 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -92,6 +92,8 @@ cluster. Valid values are: | `aws` | This will use AWS credentials to access resources in EKS clusters | | `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | | `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | +| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` | +| field should also be set. | ##### `clusters.\*.skipTLSVerify` @@ -115,6 +117,33 @@ kubectl -n get secret $(kubectl -n get sa ; // (undocumented) @@ -242,7 +244,12 @@ export const kubernetesApiRef: ApiRef; // // @public (undocumented) export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { - constructor(options: { googleAuthApi: OAuthApi }); + constructor(options: { + googleAuthApi: OAuthApi; + oidcProviders?: { + [key: string]: OpenIdConnectApi; + }; + }); // (undocumented) decorateRequestBodyForAuth( authProvider: string, From cdf3ca2b84021c7033a17ab49aa662e3dc143e85 Mon Sep 17 00:00:00 2001 From: Daniel Bravo Date: Fri, 6 May 2022 15:34:33 -0500 Subject: [PATCH 4/4] Address comments on pr for k8s oidc authProvider Signed-off-by: Daniel Bravo --- docs/features/kubernetes/configuration.md | 17 ++++++++--------- plugins/kubernetes-backend/schema.d.ts | 7 ++++++- .../OidcKubernetesAuthTranslator.test.ts | 4 +++- .../OidcKubernetesAuthTranslator.ts | 3 ++- plugins/kubernetes-common/api-report.md | 9 ++++----- plugins/kubernetes-common/src/types.ts | 8 ++++---- .../KubernetesAuthProviders.test.ts | 2 +- .../OidcKubernetesAuthProvider.ts | 8 +++++--- 8 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 3b8fef453a..246e4dcd43 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -85,15 +85,14 @@ array. Users will see this value in the Software Catalog Kubernetes plugin. This determines how the Kubernetes client authenticates with the Kubernetes cluster. Valid values are: -| Value | Description | -| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | -| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | -| `aws` | This will use AWS credentials to access resources in EKS clusters | -| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | -| `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | -| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` | -| field should also be set. | +| Value | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | +| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | +| `aws` | This will use AWS credentials to access resources in EKS clusters | +| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | +| `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | +| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` field should also be set. | ##### `clusters.\*.skipTLSVerify` diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/schema.d.ts index 8a11f7c08a..fed243ef17 100644 --- a/plugins/kubernetes-backend/schema.d.ts +++ b/plugins/kubernetes-backend/schema.d.ts @@ -52,7 +52,12 @@ export interface Config { /** @visibility secret */ serviceAccountToken?: string; /** @visibility frontend */ - authProvider: 'aws' | 'google' | 'serviceAccount' | 'azure' | 'oidc'; + authProvider: + | 'aws' + | 'google' + | 'serviceAccount' + | 'azure' + | 'oidc'; /** @visibility frontend */ oidcTokenProvider?: string; /** @visibility frontend */ diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts index 1aacb7f059..9f983aedc3 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.test.ts @@ -38,7 +38,9 @@ describe('OidcKubernetesAuthTranslator tests', () => { ...baseClusterDetails, }, { - oidc: { okta: 'fakeToken' }, + auth: { + oidc: { okta: 'fakeToken' }, + }, entity, }, ); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts index 05b87f0ce0..c53269abf3 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/OidcKubernetesAuthTranslator.ts @@ -36,7 +36,8 @@ export class OidcKubernetesAuthTranslator implements KubernetesAuthTranslator { ); } - const authToken: string | undefined = requestBody.oidc?.[oidcTokenProvider]; + const authToken: string | undefined = + requestBody.auth?.oidc?.[oidcTokenProvider]; if (authToken) { clusterDetailsWithAuthToken.serviceAccountToken = authToken; diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 381d018b08..e6f96fa253 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -194,14 +194,13 @@ export interface KubernetesFetchError { export interface KubernetesRequestBody { // (undocumented) auth?: { - google: string; + google?: string; + oidc?: { + [key: string]: string; + }; }; // (undocumented) entity: Entity; - // (undocumented) - oidc?: { - [key: string]: string; - }; } // Warning: (ae-missing-release-tag) "ObjectsByEntityResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index a71d1feaad..9b7c89bbd0 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -30,10 +30,10 @@ import { Entity } from '@backstage/catalog-model'; export interface KubernetesRequestBody { auth?: { - google: string; - }; - oidc?: { - [key: string]: string; + google?: string; + oidc?: { + [key: string]: string; + }; }; entity: Entity; } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts index ffeaa6137c..a83cbcfaa4 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.test.ts @@ -58,7 +58,7 @@ describe('KubernetesAuthProviders tests', () => { requestBody, ); - expect(details.oidc?.okta).toBe('oktaToken'); + expect(details.auth?.oidc?.okta).toBe('oktaToken'); }); it('returns error for unknown authProvider', async () => { diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts index f308c09c2d..f86df5e73e 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/OidcKubernetesAuthProvider.ts @@ -31,11 +31,13 @@ export class OidcKubernetesAuthProvider implements KubernetesAuthProvider { requestBody: KubernetesRequestBody, ): Promise { const authToken: string = await this.authProvider.getIdToken(); - if ('oidc' in requestBody) { - requestBody.oidc![this.providerName] = authToken; + const auth = { ...requestBody.auth }; + if (auth.oidc) { + auth.oidc[this.providerName] = authToken; } else { - requestBody.oidc = { [this.providerName]: authToken }; + auth.oidc = { [this.providerName]: authToken }; } + requestBody.auth = auth; return requestBody; } }