Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for okta authentication #188

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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