Add oidc authProvider to kubernetes-backend plugin

Signed-off-by: Daniel Bravo <dbravo@vmware.com>
This commit is contained in:
Daniel Bravo
2022-04-30 09:30:38 -05:00
parent 1ef98cfe48
commit d2c5a32323
8 changed files with 139 additions and 2 deletions
+3 -1
View File
@@ -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;
}>;
@@ -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;
}
@@ -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',
@@ -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`,
@@ -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');
});
});
@@ -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<ClusterDetails> {
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;
}
}
@@ -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.
+1 -1
View File
@@ -30,7 +30,7 @@ import { Entity } from '@backstage/catalog-model';
export interface KubernetesRequestBody {
auth?: {
google?: string;
[key: string]: string;
};
entity: Entity;
}