diff --git a/.changeset/fifty-cars-compare.md b/.changeset/fifty-cars-compare.md new file mode 100644 index 0000000000..86115a4b02 --- /dev/null +++ b/.changeset/fifty-cars-compare.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-kubernetes-backend': minor +--- + +Add `localKubectlProxy` cluster locator method to make local development simpler to setup. + +Consolidated no-op server side auth decorators. +The following Kubernetes auth decorators are now one class (`ServerSideKubernetesAuthProvider`): + +- `AwsKubernetesAuthProvider` +- `AzureKubernetesAuthProvider` +- `ServiceAccountKubernetesAuthProvider` diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index b78021c8f1..633584bdb5 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -57,10 +57,17 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: +- [`localKubectlProxy`](#localKubectlProxy) - [`config`](#config) - [`gke`](#gke) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) +#### `localKubectlProxy` + +This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001). + +NOTE: This cluster locator method is for local development only and should not be used in production. + #### `config` This cluster locator method will read cluster information from your app-config diff --git a/plugins/kubernetes-backend/examples/dice-roller/README.md b/plugins/kubernetes-backend/examples/dice-roller/README.md index f7bcdd3973..93dd181c10 100644 --- a/plugins/kubernetes-backend/examples/dice-roller/README.md +++ b/plugins/kubernetes-backend/examples/dice-roller/README.md @@ -2,48 +2,34 @@ This can be used to run the kubernetes plugin locally against a mock service. -# Viewing in local Minikube running Backstage locally +# Viewing in local Kind running Backstage locally ## Prerequisites -- kubectl installed -- Minikube installed, with the following addons - - metrics-server - - ingress -- jq installed +- [kubectl installed](https://kubernetes.io/docs/tasks/tools/#kubectl) +- [Kind installed](https://kind.sigs.k8s.io/docs/user/quick-start/) - Backstage locally built and ready to run ## Steps -1. Start minikube -2. Get the Kubernetes master base URL `kubectl cluster-info` -3. Apply manifests `kubectl apply -f dice-roller-manifests.yaml` -4. Get service account token (see below) -5. Start Backstage UI and backend -6. Register existing component in Backstage - - https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml +1. Start kind +2. Apply manifests `kubectl apply -f plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml` +3. Run `kubectl proxy` +4. In separate terminal windows start Backstage UI and backend +5. Register a test component ([example](https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml)) +6. Visit [kubernetes plugin page](http://localhost:3000/catalog/default/component/dice-roller/kubernetes) -Add or update `app-config.local.yaml` with the following: +### Example `app-config.local.yaml` ```yaml kubernetes: serviceLocatorMethod: type: 'multiTenant' clusterLocatorMethods: - - type: 'config' - clusters: - - url: - name: minikube - serviceAccountToken: - authProvider: 'serviceAccount' + - type: 'localKubectlProxy' + +catalog: + locations: + - type: url + target: https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml ``` - -### Getting the service account token - -Mac copy to clipboard: - -``` -kubectl get secret $(kubectl get sa dice-roller -o=json | jq -r '.secrets[0].name') -o=json | jq -r '.data["token"]' | base64 --decode | pbcopy -``` - -Paste into `app-config.local.yaml` `kubernetes.clusters[0].serviceAccountToken` diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts b/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts similarity index 54% rename from plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts rename to plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts index 5671112ac8..b2c11fc744 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/LocalKubectlProxyLocator.ts @@ -14,16 +14,25 @@ * limitations under the License. */ -import { KubernetesAuthProvider } from './types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; -export class ServiceAccountKubernetesAuthProvider - implements KubernetesAuthProvider +export class LocalKubectlProxyClusterLocator + implements KubernetesClustersSupplier { - async decorateRequestBodyForAuth( - requestBody: KubernetesRequestBody, - ): Promise { - // No-op, with service account for auth, cluster config/details should already have serviceAccountToken - return requestBody; + private readonly clusterDetails: ClusterDetails[]; + + public constructor() { + this.clusterDetails = [ + { + name: 'local', + url: 'http:/localhost:8001', + authProvider: 'localKubectlProxy', + skipMetricsLookup: true, + }, + ]; + } + + async getClusters(): Promise { + return this.clusterDetails; } } diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.ts b/plugins/kubernetes-backend/src/cluster-locator/index.ts index 53aeb44f8b..d32f7ffee8 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.ts @@ -19,6 +19,7 @@ import { Duration } from 'luxon'; import { ClusterDetails, KubernetesClustersSupplier } from '../types/types'; import { ConfigClusterLocator } from './ConfigClusterLocator'; import { GkeClusterLocator } from './GkeClusterLocator'; +import { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator'; class CombinedClustersSupplier implements KubernetesClustersSupplier { constructor(readonly clusterSuppliers: KubernetesClustersSupplier[]) {} @@ -45,6 +46,8 @@ export const getCombinedClusterSupplier = ( .map(clusterLocatorMethod => { const type = clusterLocatorMethod.getString('type'); switch (type) { + case 'localKubectlProxy': + return new LocalKubectlProxyClusterLocator(); case 'config': return ConfigClusterLocator.fromConfig(clusterLocatorMethod); case 'gke': diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index c2df3e6c23..24de85d7b7 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -49,6 +49,9 @@ export class KubernetesAuthTranslatorGenerator { case 'oidc': { return new OidcKubernetesAuthTranslator(); } + case 'localKubectlProxy': { + return new NoopKubernetesAuthTranslator(); + } default: { throw new Error( `authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`, diff --git a/plugins/kubernetes/api-report.md b/plugins/kubernetes/api-report.md index dc2c222712..1a95446e5f 100644 --- a/plugins/kubernetes/api-report.md +++ b/plugins/kubernetes/api-report.md @@ -32,17 +32,6 @@ import { V1ReplicaSet } from '@kubernetes/client-node'; import { V1Service } from '@kubernetes/client-node'; import { V1StatefulSet } from '@kubernetes/client-node'; -// Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvider" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "AwsKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export class AwsKubernetesAuthProvider implements KubernetesAuthProvider { - // (undocumented) - decorateRequestBodyForAuth( - requestBody: KubernetesRequestBody, - ): Promise; -} - // Warning: (ae-forgotten-export) The symbol "ClusterProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Cluster" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -147,6 +136,7 @@ export function formatClusterLink( options: FormatClusterLinkOptions, ): string | undefined; +// Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvider" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "GoogleKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -160,18 +150,6 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { ): Promise; } -// Warning: (ae-missing-release-tag) "GoogleServiceAccountAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export class GoogleServiceAccountAuthProvider - implements KubernetesAuthProvider -{ - // (undocumented) - decorateRequestBodyForAuth( - requestBody: KubernetesRequestBody, - ): Promise; -} - // Warning: (ae-missing-release-tag) "GroupedResponses" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -386,10 +364,8 @@ export const PodsTable: ({ // @public (undocumented) export const Router: (props: { refreshIntervalMs?: number }) => JSX.Element; -// Warning: (ae-missing-release-tag) "ServiceAccountKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export class ServiceAccountKubernetesAuthProvider +// @public +export class ServerSideKubernetesAuthProvider implements KubernetesAuthProvider { // (undocumented) diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts deleted file mode 100644 index 789da9af50..0000000000 --- a/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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'; - -export class AwsKubernetesAuthProvider implements KubernetesAuthProvider { - async decorateRequestBodyForAuth( - requestBody: KubernetesRequestBody, - ): Promise { - // No-op, with aws auth, server's AWS credentials are used for access - return requestBody; - } -} diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts deleted file mode 100644 index 60401bbe4d..0000000000 --- a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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'; - -export class AzureKubernetesAuthProvider implements KubernetesAuthProvider { - async decorateRequestBodyForAuth( - requestBody: KubernetesRequestBody, - ): Promise { - // No-op, with azure auth, server's Azure credentials are used for access - return requestBody; - } -} diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index bcd1b2911a..eb3ea456ea 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -17,11 +17,8 @@ import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; -import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; -import { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; +import { ServerSideKubernetesAuthProvider } from './ServerSideAuthProvider'; 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 { @@ -41,16 +38,23 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { ); this.kubernetesAuthProviderMap.set( 'serviceAccount', - new ServiceAccountKubernetesAuthProvider(), + new ServerSideKubernetesAuthProvider(), ); this.kubernetesAuthProviderMap.set( 'googleServiceAccount', - new GoogleServiceAccountAuthProvider(), + new ServerSideKubernetesAuthProvider(), + ); + this.kubernetesAuthProviderMap.set( + 'aws', + new ServerSideKubernetesAuthProvider(), ); - this.kubernetesAuthProviderMap.set('aws', new AwsKubernetesAuthProvider()); this.kubernetesAuthProviderMap.set( 'azure', - new AzureKubernetesAuthProvider(), + new ServerSideKubernetesAuthProvider(), + ); + this.kubernetesAuthProviderMap.set( + 'localKubectlProxy', + new ServerSideKubernetesAuthProvider(), ); if (options.oidcProviders) { diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleServiceAccountAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts similarity index 76% rename from plugins/kubernetes/src/kubernetes-auth-provider/GoogleServiceAccountAuthProvider.ts rename to plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts index 9f522bea64..1c475b9568 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleServiceAccountAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/ServerSideAuthProvider.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2022 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. @@ -17,13 +17,18 @@ import { KubernetesAuthProvider } from './types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; -export class GoogleServiceAccountAuthProvider +/** + * No-op KubernetesAuthProvider, authorization will be handled in the kubernetes-backend plugin + * + * @public + */ +export class ServerSideKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( requestBody: KubernetesRequestBody, ): Promise { - // No-op, with google service account auth, server's AWS credentials are used for access + // No-op, auth will be taken care of on the server-side return requestBody; } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/index.ts b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts index 6f8e3f415f..23197321f6 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/index.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts @@ -17,7 +17,5 @@ export { kubernetesAuthProvidersApiRef } from './types'; export type { KubernetesAuthProvidersApi } from './types'; export { KubernetesAuthProviders } from './KubernetesAuthProviders'; -export { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; export { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; -export { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider'; -export { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; +export { ServerSideKubernetesAuthProvider } from './ServerSideAuthProvider';