Add oidc authProvider handling to kubernetes frontend plugin
Signed-off-by: Daniel Bravo <dbravo@vmware.com>
This commit is contained in:
+2
-2
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -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,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,9 @@ import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export interface KubernetesRequestBody {
|
||||
auth?: {
|
||||
google: string;
|
||||
};
|
||||
oidc?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
entity: Entity;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user