From d2c5a3232398d26a10ad2d69146202f2b5fa192d Mon Sep 17 00:00:00 2001 From: Daniel Bravo Date: Sat, 30 Apr 2022 09:30:38 -0500 Subject: [PATCH] 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; }