Skip to content

Commit

Permalink
feat: add support for okta authentication (#188)
Browse files Browse the repository at this point in the history
This change adds support for using Okta authentication.
  • Loading branch information
ChristopherFry authored Oct 31, 2022
1 parent f5d16d6 commit 3fd56ba
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 18 deletions.
14 changes: 13 additions & 1 deletion plugins/cad-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
```
Expand All @@ -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
Expand Down
28 changes: 28 additions & 0 deletions plugins/cad-backend/src/service/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -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 => {
Expand Down
52 changes: 42 additions & 10 deletions plugins/cad-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import { Logger } from 'winston';
import {
ClusterLocatorAuthProvider,
getClusterLocatorMethodAuthProvider,
getClusterLocatorMethodOIDCTokenProvider,
getClusterLocatorMethodServiceAccountToken,
getClusterLocatorMethodType,
OIDCTokenProvider,
} from './config';
import { getKubernetesConfig } from './lib';

Expand All @@ -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,
Expand All @@ -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();
Expand All @@ -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 = (
Expand All @@ -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,
});
};

Expand Down Expand Up @@ -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;
Expand Down
28 changes: 23 additions & 5 deletions plugins/cad/src/apis/PorchRestApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string | undefined> {
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<any> {
Expand Down
6 changes: 4 additions & 2 deletions plugins/cad/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
discoveryApiRef,
fetchApiRef,
googleAuthApiRef,
oktaAuthApiRef,
} from '@backstage/core-plugin-api';
import { configAsDataApiRef, PorchRestAPI } from './apis';
import { rootRouteRef } from './routes';
Expand All @@ -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),
}),
],
});
Expand Down

0 comments on commit 3fd56ba

Please sign in to comment.