Merge pull request #11328 from dbravovmw/kubernetes-oidc-auth

Add generic OIDC as a Kubernetes auth provider
This commit is contained in:
Fredrik Adelöw
2022-05-10 11:12:56 +02:00
committed by GitHub
20 changed files with 392 additions and 16 deletions
+8 -1
View File
@@ -17,6 +17,7 @@ import type { JsonObject } from '@backstage/types';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { OAuthApi } from '@backstage/core-plugin-api';
import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { V1ConfigMap } from '@kubernetes/client-node';
@@ -225,6 +226,7 @@ export interface KubernetesApi {
{
name: string;
authProvider: string;
oidcTokenProvider?: string | undefined;
}[]
>;
// (undocumented)
@@ -242,7 +244,12 @@ export const kubernetesApiRef: ApiRef<KubernetesApi>;
//
// @public (undocumented)
export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
constructor(options: { googleAuthApi: OAuthApi });
constructor(options: {
googleAuthApi: OAuthApi;
oidcProviders?: {
[key: string]: OpenIdConnectApi;
};
});
// (undocumented)
decorateRequestBodyForAuth(
authProvider: string,
+7 -1
View File
@@ -28,5 +28,11 @@ export interface KubernetesApi {
getObjectsByEntity(
requestBody: KubernetesRequestBody,
): Promise<ObjectsByEntityResponse>;
getClusters(): Promise<{ name: string; authProvider: string }[]>;
getClusters(): Promise<
{
name: string;
authProvider: string;
oidcTokenProvider?: string | undefined;
}[]
>;
}
@@ -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,
@@ -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.auth?.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',
);
});
});
@@ -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<string, KubernetesAuthProvider>();
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`,
);
@@ -0,0 +1,43 @@
/*
* 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<KubernetesRequestBody> {
const authToken: string = await this.authProvider.getIdToken();
const auth = { ...requestBody.auth };
if (auth.oidc) {
auth.oidc[this.providerName] = authToken;
} else {
auth.oidc = { [this.providerName]: authToken };
}
requestBody.auth = auth;
return requestBody;
}
}
+23 -3
View File
@@ -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 });
},
}),
],