Add oidc authProvider handling to kubernetes frontend plugin

Signed-off-by: Daniel Bravo <dbravo@vmware.com>
This commit is contained in:
Daniel Bravo
2022-05-02 15:46:00 -05:00
parent d2c5a32323
commit 2f50c323fb
10 changed files with 191 additions and 11 deletions
@@ -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');
});
});
@@ -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;
@@ -246,6 +246,7 @@ export class KubernetesBuilder {
name: cd.name,
dashboardUrl: cd.dashboardUrl,
authProvider: cd.authProvider,
oidcTokenProvider: cd.oidcTokenProvider,
})),
});
});
+3
View File
@@ -30,6 +30,9 @@ import { Entity } from '@backstage/catalog-model';
export interface KubernetesRequestBody {
auth?: {
google: string;
};
oidc?: {
[key: string]: string;
};
entity: Entity;
+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.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,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<KubernetesRequestBody> {
const authToken: string = await this.authProvider.getIdToken();
if ('oidc' in requestBody) {
requestBody.oidc![this.providerName] = authToken;
} else {
requestBody.oidc = { [this.providerName]: authToken };
}
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 });
},
}),
],