diff --git a/plugins/cad-backend/README.md b/plugins/cad-backend/README.md index 8d6eaff9..71171746 100644 --- a/plugins/cad-backend/README.md +++ b/plugins/cad-backend/README.md @@ -60,7 +60,10 @@ configAsData: # Determines how the client will authenticate with the Kubernetes cluster. authProvider: current-context - # The service account token to be used when using the 'service-account' auth provider. + # Optional. Determines the OIDC token provider to use when using the 'oidc' auth provider. + oidcTokenProvider: okta + + # Optional. The service account token to be used when using the 'service-account' auth provider. serviceAccountToken: ${CAD_SERVICE_ACCOUNT_TOKEN} ``` @@ -83,8 +86,17 @@ Valid values: | ------ | ----------- | | current-context | Authenticate to the cluster with the user in the kubeconfig current context | | google | Authenticate to the cluster using the user's authentication token from the [Google auth plugin](https://backstage.io/docs/auth/) | +| oidc | Authenticate to the cluster using OIDC (OpenID Connect) | | service-account | Authenticate to the cluster using a Kubernetes service account token | +`clusterLocatorMethod.oidcTokenProvider` determines which configured [Backstage auth provider](https://backstage.io/docs/auth/) to +use to authenticate to the cluster with. This field is required with the `oidc` auth provider. + +Valid values: +| Values | Description | +| ------ | ----------- | +| okta | Authenticate to the cluster with the [Okta Backstage auth provider](https://backstage.io/docs/auth/okta/provider) | + `clusterLocatorMethod.serviceAccountToken` defines the service account token to be used with the `service-account` auth provider. You can get the service account token with the following command: ```bash diff --git a/plugins/cad-backend/src/service/config.ts b/plugins/cad-backend/src/service/config.ts index f89e1162..496f5ee9 100644 --- a/plugins/cad-backend/src/service/config.ts +++ b/plugins/cad-backend/src/service/config.ts @@ -24,9 +24,15 @@ export enum ClusterLocatorMethodType { export enum ClusterLocatorAuthProvider { CURRENT_CONTEXT = 'current-context', GOOGLE = 'google', + OIDC = 'oidc', SERVICE_ACCOUNT = 'service-account', } +export enum OIDCTokenProvider { + NONE = 'none', + OKTA = 'okta', +} + export const getClusterLocatorMethodType = ( config: Config, ): ClusterLocatorMethodType => { @@ -53,6 +59,28 @@ export const getClusterLocatorMethodAuthProvider = ( return authProvider as ClusterLocatorAuthProvider; }; +export const getClusterLocatorMethodOIDCTokenProvider = ( + config: Config, +): OIDCTokenProvider => { + const authProvider = getClusterLocatorMethodAuthProvider(config); + + if (authProvider === ClusterLocatorAuthProvider.OIDC) { + const oidcTokenProvider = config.getString( + 'clusterLocatorMethod.oidcTokenProvider', + ); + + if (!Object.values(OIDCTokenProvider)) { + throw new Error( + `Unknown clusterLocatorMethod.oidcTokenProvider, ${oidcTokenProvider}`, + ); + } + + return oidcTokenProvider as OIDCTokenProvider; + } + + return OIDCTokenProvider.NONE; +}; + export const getClusterLocatorMethodServiceAccountToken = ( config: Config, ): string => { diff --git a/plugins/cad-backend/src/service/router.ts b/plugins/cad-backend/src/service/router.ts index 05ddb286..fa6cbe88 100644 --- a/plugins/cad-backend/src/service/router.ts +++ b/plugins/cad-backend/src/service/router.ts @@ -23,8 +23,10 @@ import { Logger } from 'winston'; import { ClusterLocatorAuthProvider, getClusterLocatorMethodAuthProvider, + getClusterLocatorMethodOIDCTokenProvider, getClusterLocatorMethodServiceAccountToken, getClusterLocatorMethodType, + OIDCTokenProvider, } from './config'; import { getKubernetesConfig } from './lib'; @@ -33,6 +35,36 @@ export interface RouterOptions { logger: Logger; } +const getClientAuthentication = ( + authProvider: ClusterLocatorAuthProvider, + oidcTokenProvider: OIDCTokenProvider, +): string => { + switch (authProvider) { + case ClusterLocatorAuthProvider.GOOGLE: + return 'google'; + + case ClusterLocatorAuthProvider.OIDC: + switch (oidcTokenProvider) { + case OIDCTokenProvider.OKTA: + return 'oidc.okta'; + + default: + throw new Error( + `Client authenticaiton cannot be determined for OIDC token provider ${oidcTokenProvider}`, + ); + } + + case ClusterLocatorAuthProvider.SERVICE_ACCOUNT: + case ClusterLocatorAuthProvider.CURRENT_CONTEXT: + return 'none'; + + default: + throw new Error( + `Client authenticaiton cannot be determined for auth provider ${authProvider}`, + ); + } +}; + export async function createRouter({ config, logger, @@ -45,6 +77,7 @@ export async function createRouter({ const clusterLocatorMethodType = getClusterLocatorMethodType(cadConfig); const clusterLocatorMethodAuthProvider = getClusterLocatorMethodAuthProvider(cadConfig); + const oidcTokenProvider = getClusterLocatorMethodOIDCTokenProvider(cadConfig); const kubeConfig = getKubernetesConfig(clusterLocatorMethodType); const currentCluster = kubeConfig.getCurrentCluster(); @@ -56,6 +89,13 @@ export async function createRouter({ const serviceAccountToken = getClusterLocatorMethodServiceAccountToken(cadConfig); + const clientAuthentication = getClientAuthentication( + clusterLocatorMethodAuthProvider, + oidcTokenProvider, + ); + + logger.info(`Using '${clientAuthentication}' for client authentication`); + const k8sApiServerUrl = currentCluster.server; const healthCheck = ( @@ -69,13 +109,8 @@ export async function createRouter({ _: express.Request, response: express.Response, ): void => { - const authentication = - clusterLocatorMethodAuthProvider === ClusterLocatorAuthProvider.GOOGLE - ? 'google' - : 'none'; - response.send({ - authentication, + authentication: clientAuthentication, }); }; @@ -114,10 +149,7 @@ export async function createRouter({ kubeConfig.applyToRequest(requestOptions); - const endUserProviders = [ClusterLocatorAuthProvider.GOOGLE]; - const useEndUserAuthz = endUserProviders.includes( - clusterLocatorMethodAuthProvider, - ); + const useEndUserAuthz = clientAuthentication !== 'none'; if (useEndUserAuthz) { requestOptions.headers = requestOptions.headers ?? {}; requestOptions.headers.authorization = request.headers.authorization; diff --git a/plugins/cad/src/apis/PorchRestApi.ts b/plugins/cad/src/apis/PorchRestApi.ts index 1b6fd354..a50b1607 100644 --- a/plugins/cad/src/apis/PorchRestApi.ts +++ b/plugins/cad/src/apis/PorchRestApi.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { DiscoveryApi, FetchApi, OAuthApi } from '@backstage/core-plugin-api'; +import { + DiscoveryApi, + FetchApi, + OAuthApi, + OpenIdConnectApi, +} from '@backstage/core-plugin-api'; import { ConfigAsDataApi } from '.'; import { ListApiGroups } from '../types/ApiGroup'; import { ListConfigManagements } from '../types/ConfigManagement'; @@ -77,18 +82,31 @@ export class PorchRestAPI implements ConfigAsDataApi { private discovery: DiscoveryApi, private fetchApi: FetchApi, private googleAuthApi: OAuthApi, + private oktaAuthApi: OpenIdConnectApi, ) {} private async getAuthorizationToken(): Promise { - if (this.authentication === 'google') { - const accessToken = await this.googleAuthApi.getAccessToken( + const authProvider = this.authentication; + + if (authProvider === 'google') { + const googleAccessToken = await this.googleAuthApi.getAccessToken( 'https://www.googleapis.com/auth/cloud-platform.read-only', ); - return `Bearer ${accessToken}`; + return `Bearer ${googleAccessToken}`; + } + + if (authProvider === 'oidc.okta') { + const oktaIdToken = await this.oktaAuthApi.getIdToken(); + + return `Bearer ${oktaIdToken}`; + } + + if (authProvider === 'none') { + return undefined; } - return undefined; + throw new Error(`Authentication provider ${authProvider} not found`); } private async cadFetch(path: string, init?: RequestInit): Promise { diff --git a/plugins/cad/src/plugin.ts b/plugins/cad/src/plugin.ts index c27493c4..09be8217 100644 --- a/plugins/cad/src/plugin.ts +++ b/plugins/cad/src/plugin.ts @@ -21,6 +21,7 @@ import { discoveryApiRef, fetchApiRef, googleAuthApiRef, + oktaAuthApiRef, } from '@backstage/core-plugin-api'; import { configAsDataApiRef, PorchRestAPI } from './apis'; import { rootRouteRef } from './routes'; @@ -37,9 +38,10 @@ export const cadPlugin = createPlugin({ discoveryApi: discoveryApiRef, fetchApi: fetchApiRef, googleAuthApi: googleAuthApiRef, + oktaAuthApi: oktaAuthApiRef, }, - factory: ({ discoveryApi, fetchApi, googleAuthApi }) => - new PorchRestAPI(discoveryApi, fetchApi, googleAuthApi), + factory: ({ discoveryApi, fetchApi, googleAuthApi, oktaAuthApi }) => + new PorchRestAPI(discoveryApi, fetchApi, googleAuthApi, oktaAuthApi), }), ], });