@backstage/plugin-kubernetes-backend 0.21.10 → 0.21.11-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,25 +1,27 @@
1
1
  # @backstage/plugin-kubernetes-backend
2
2
 
3
- ## 0.21.10
3
+ ## 0.21.11-next.1
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - c14f8be: Reject unsupported service account authentication in catalog-provided cluster definitions.
8
- - 050a4e1: Improved handling of Kubernetes resource response data.
9
-
10
- ## 0.21.9
11
-
12
- ### Patch Changes
13
-
14
- - 89d4c81: The catalog cluster locator now validates Kubernetes API server URLs to block SSRF targets (non-public addresses, cloud metadata endpoints, and non-HTTPS URLs by default). Operators may list trusted hostnames in `dangerouslyAllowClusterUrls` on the catalog locator method to permit HTTP or non-public addresses for those hosts only (for example local minikube). Catalog entities cannot use the `serviceAccount` auth provider, cannot enable TLS verification skipping unless `dangerouslyAllowSkipTLSVerify` is set on the locator method, and only permitted annotations are passed through as auth metadata. Kubernetes API fetches no longer follow HTTP redirects automatically.
15
-
16
- Kubernetes Secret redaction on resource query endpoints now masks both `data` and `stringData` values regardless of how Secrets are fetched, including via the custom resources query path.
7
+ - 1d7b704: Reject unsupported service account authentication in catalog-provided cluster definitions.
8
+ - 28bdf75: Improved handling of Kubernetes resource response data.
9
+ - Updated dependencies
10
+ - @backstage/plugin-kubernetes-node@0.4.8-next.1
11
+ - @backstage/plugin-permission-node@0.11.4-next.0
12
+ - @backstage/catalog-client@1.16.2-next.0
13
+ - @backstage/backend-plugin-api@1.10.1-next.0
14
+ - @backstage/plugin-catalog-node@2.2.5-next.0
17
15
 
18
- ## 0.21.8
16
+ ## 0.21.10-next.0
19
17
 
20
18
  ### Patch Changes
21
19
 
22
- - 9d950fa: Improved entity resolution on the deprecated services endpoint.
20
+ - b11c9b4: Improved entity resolution on the deprecated services endpoint.
21
+ - d9a57de: Add `KubernetesWatcher` interface for streaming Kubernetes resource changes via an async iterator. The watcher is separated from `KubernetesFetcher` because watching is a long-lived streaming connection that only works with server-side auth providers. Watch supports all event types (ADDED, MODIFIED, DELETED, BOOKMARK, ERROR) with errors yielded as data rather than thrown.
22
+ - Updated dependencies
23
+ - @backstage/plugin-kubernetes-common@0.9.13-next.0
24
+ - @backstage/plugin-kubernetes-node@0.4.8-next.0
23
25
 
24
26
  ## 0.21.7
25
27
 
@@ -150,19 +150,6 @@
150
150
  "type": "string",
151
151
  "const": "catalog",
152
152
  "visibility": "frontend"
153
- },
154
- "dangerouslyAllowClusterUrls": {
155
- "type": "array",
156
- "items": {
157
- "type": "string"
158
- },
159
- "description": "Hostname patterns (for example `127.0.0.1` or `*.example.com`) for which catalog cluster API server URLs may use HTTP or non-public addresses. Omitted hostnames must use HTTPS and pass SSRF checks.",
160
- "visibility": "frontend"
161
- },
162
- "dangerouslyAllowSkipTLSVerify": {
163
- "type": "boolean",
164
- "description": "When true, catalog cluster entities may set skip TLS verify via annotation. Defaults to false.",
165
- "visibility": "frontend"
166
153
  }
167
154
  },
168
155
  "required": [
@@ -2,9 +2,6 @@
2
2
 
3
3
  var catalogClient = require('@backstage/catalog-client');
4
4
  var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
5
- var catalogClusterAuthMetadata = require('./catalogClusterAuthMetadata.cjs.js');
6
- var CatalogClusterLocatorOptions = require('./CatalogClusterLocatorOptions.cjs.js');
7
- var validateClusterApiServerUrl = require('./validateClusterApiServerUrl.cjs.js');
8
5
 
9
6
  function isObject(obj) {
10
7
  return typeof obj === "object" && obj !== null && !Array.isArray(obj);
@@ -12,21 +9,12 @@ function isObject(obj) {
12
9
  class CatalogClusterLocator {
13
10
  catalogService;
14
11
  auth;
15
- options;
16
- logger;
17
- constructor(catalogService, auth, options, logger) {
12
+ constructor(catalogService, auth) {
18
13
  this.catalogService = catalogService;
19
14
  this.auth = auth;
20
- this.options = options;
21
- this.logger = logger;
22
15
  }
23
- static fromConfig(catalogApi, auth, clusterLocatorConfig, logger) {
24
- return new CatalogClusterLocator(
25
- catalogApi,
26
- auth,
27
- CatalogClusterLocatorOptions.readCatalogClusterLocatorOptions(clusterLocatorConfig),
28
- logger
29
- );
16
+ static fromConfig(catalogApi, auth) {
17
+ return new CatalogClusterLocator(catalogApi, auth);
30
18
  }
31
19
  async getClusters(options) {
32
20
  const apiServerKey = `metadata.annotations.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER}`;
@@ -47,54 +35,28 @@ class CatalogClusterLocator {
47
35
  credentials: options?.credentials ?? await this.auth.getNoneCredentials()
48
36
  }
49
37
  );
50
- const clusterDetails = [];
51
- for (const entity of clusters.items) {
52
- const details = await this.toClusterDetails(entity);
53
- if (details) {
54
- clusterDetails.push(details);
38
+ return clusters.items.map((entity) => {
39
+ const annotations = entity.metadata.annotations;
40
+ const authProvider = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
41
+ if (authProvider === "serviceAccount") {
42
+ throw new Error(
43
+ `Invalid cluster '${entity.metadata.name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`
44
+ );
55
45
  }
56
- }
57
- return clusterDetails;
58
- }
59
- async toClusterDetails(entity) {
60
- const name = entity.metadata.name;
61
- const annotations = entity.metadata.annotations;
62
- if (!annotations) {
63
- this.logger.warn(
64
- `Ignoring kubernetes-cluster Resource "${name}" without annotations`
65
- );
66
- return void 0;
67
- }
68
- const authProvider = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
69
- if (authProvider === "serviceAccount") {
70
- throw new Error(
71
- `Invalid cluster '${name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`
72
- );
73
- }
74
- const apiServerUrl = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER];
75
- try {
76
- await validateClusterApiServerUrl.validateClusterApiServerUrl(apiServerUrl, this.options);
77
- } catch (error) {
78
- this.logger.warn(
79
- `Ignoring kubernetes-cluster Resource "${name}": ${error instanceof Error ? error.message : String(error)}`
80
- );
81
- return void 0;
82
- }
83
- const allowSkipTlsVerify = this.options.dangerouslyAllowSkipTLSVerify ?? false;
84
- const skipTLSVerify = allowSkipTlsVerify && annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true";
85
- const clusterDetails = {
86
- name,
87
- title: entity.metadata.title,
88
- url: apiServerUrl,
89
- authMetadata: catalogClusterAuthMetadata.filterCatalogClusterAuthMetadata(annotations),
90
- caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
91
- skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
92
- skipTLSVerify,
93
- dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
94
- dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
95
- dashboardParameters: this.getDashboardParameters(annotations)
96
- };
97
- return clusterDetails;
46
+ const clusterDetails = {
47
+ name: entity.metadata.name,
48
+ title: entity.metadata.title,
49
+ url: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER],
50
+ authMetadata: annotations,
51
+ caData: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_API_SERVER_CA],
52
+ skipMetricsLookup: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === "true",
53
+ skipTLSVerify: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === "true",
54
+ dashboardUrl: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_URL],
55
+ dashboardApp: annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_APP],
56
+ dashboardParameters: this.getDashboardParameters(annotations)
57
+ };
58
+ return clusterDetails;
59
+ });
98
60
  }
99
61
  getDashboardParameters(annotations) {
100
62
  const dashboardParamsString = annotations[pluginKubernetesCommon.ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];
@@ -1 +1 @@
1
- {"version":3,"file":"CatalogClusterLocator.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocator.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';\nimport {\n ANNOTATION_KUBERNETES_API_SERVER,\n ANNOTATION_KUBERNETES_API_SERVER_CA,\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP,\n ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY,\n ANNOTATION_KUBERNETES_DASHBOARD_URL,\n ANNOTATION_KUBERNETES_DASHBOARD_APP,\n ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS,\n} from '@backstage/plugin-kubernetes-common';\nimport { JsonObject } from '@backstage/types';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { filterCatalogClusterAuthMetadata } from './catalogClusterAuthMetadata';\nimport {\n CatalogClusterLocatorOptions,\n readCatalogClusterLocatorOptions,\n} from './CatalogClusterLocatorOptions';\nimport { validateClusterApiServerUrl } from './validateClusterApiServerUrl';\n\nfunction isObject(obj: unknown): obj is JsonObject {\n return typeof obj === 'object' && obj !== null && !Array.isArray(obj);\n}\n\nexport class CatalogClusterLocator implements KubernetesClustersSupplier {\n private catalogService: CatalogService;\n private auth: AuthService;\n private readonly options: CatalogClusterLocatorOptions;\n private readonly logger: LoggerService;\n\n constructor(\n catalogService: CatalogService,\n auth: AuthService,\n options: CatalogClusterLocatorOptions,\n logger: LoggerService,\n ) {\n this.catalogService = catalogService;\n this.auth = auth;\n this.options = options;\n this.logger = logger;\n }\n\n static fromConfig(\n catalogApi: CatalogService,\n auth: AuthService,\n clusterLocatorConfig: Config,\n logger: LoggerService,\n ): CatalogClusterLocator {\n return new CatalogClusterLocator(\n catalogApi,\n auth,\n readCatalogClusterLocatorOptions(clusterLocatorConfig),\n logger,\n );\n }\n\n async getClusters(options?: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;\n const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;\n const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;\n\n const filter: Record<string, symbol | string> = {\n kind: 'Resource',\n 'spec.type': 'kubernetes-cluster',\n [apiServerKey]: CATALOG_FILTER_EXISTS,\n [apiServerCaKey]: CATALOG_FILTER_EXISTS,\n [authProviderKey]: CATALOG_FILTER_EXISTS,\n };\n\n const clusters = await this.catalogService.getEntities(\n {\n filter: [filter],\n },\n {\n credentials:\n options?.credentials ?? (await this.auth.getNoneCredentials()),\n },\n );\n\n const clusterDetails: ClusterDetails[] = [];\n for (const entity of clusters.items) {\n const details = await this.toClusterDetails(entity);\n if (details) {\n clusterDetails.push(details);\n }\n }\n return clusterDetails;\n }\n\n private async toClusterDetails(\n entity: Awaited<ReturnType<CatalogService['getEntities']>>['items'][number],\n ): Promise<ClusterDetails | undefined> {\n const name = entity.metadata.name;\n const annotations = entity.metadata.annotations;\n if (!annotations) {\n this.logger.warn(\n `Ignoring kubernetes-cluster Resource \"${name}\" without annotations`,\n );\n return undefined;\n }\n\n const authProvider = annotations[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n if (authProvider === 'serviceAccount') {\n throw new Error(\n `Invalid cluster '${name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`,\n );\n }\n\n const apiServerUrl = annotations[ANNOTATION_KUBERNETES_API_SERVER];\n try {\n await validateClusterApiServerUrl(apiServerUrl, this.options);\n } catch (error) {\n this.logger.warn(\n `Ignoring kubernetes-cluster Resource \"${name}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return undefined;\n }\n\n const allowSkipTlsVerify =\n this.options.dangerouslyAllowSkipTLSVerify ?? false;\n const skipTLSVerify =\n allowSkipTlsVerify &&\n annotations[ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === 'true';\n\n const clusterDetails: ClusterDetails = {\n name,\n title: entity.metadata.title,\n url: apiServerUrl,\n authMetadata: filterCatalogClusterAuthMetadata(annotations),\n caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],\n skipMetricsLookup:\n annotations[ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === 'true',\n skipTLSVerify,\n dashboardUrl: annotations[ANNOTATION_KUBERNETES_DASHBOARD_URL],\n dashboardApp: annotations[ANNOTATION_KUBERNETES_DASHBOARD_APP],\n dashboardParameters: this.getDashboardParameters(annotations),\n };\n\n return clusterDetails;\n }\n\n private getDashboardParameters(\n annotations: Record<string, string>,\n ): JsonObject | undefined {\n const dashboardParamsString =\n annotations[ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];\n if (dashboardParamsString) {\n try {\n const dashboardParams = JSON.parse(dashboardParamsString);\n return isObject(dashboardParams) ? dashboardParams : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n}\n"],"names":["readCatalogClusterLocatorOptions","ANNOTATION_KUBERNETES_API_SERVER","ANNOTATION_KUBERNETES_API_SERVER_CA","ANNOTATION_KUBERNETES_AUTH_PROVIDER","CATALOG_FILTER_EXISTS","validateClusterApiServerUrl","ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY","filterCatalogClusterAuthMetadata","ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP","ANNOTATION_KUBERNETES_DASHBOARD_URL","ANNOTATION_KUBERNETES_DASHBOARD_APP","ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS"],"mappings":"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"}
1
+ {"version":3,"file":"CatalogClusterLocator.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocator.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n} from '@backstage/backend-plugin-api';\nimport {\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';\nimport {\n ANNOTATION_KUBERNETES_API_SERVER,\n ANNOTATION_KUBERNETES_API_SERVER_CA,\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP,\n ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY,\n ANNOTATION_KUBERNETES_DASHBOARD_URL,\n ANNOTATION_KUBERNETES_DASHBOARD_APP,\n ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS,\n} from '@backstage/plugin-kubernetes-common';\nimport { JsonObject } from '@backstage/types';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nfunction isObject(obj: unknown): obj is JsonObject {\n return typeof obj === 'object' && obj !== null && !Array.isArray(obj);\n}\n\nexport class CatalogClusterLocator implements KubernetesClustersSupplier {\n private catalogService: CatalogService;\n private auth: AuthService;\n\n constructor(catalogService: CatalogService, auth: AuthService) {\n this.catalogService = catalogService;\n this.auth = auth;\n }\n\n static fromConfig(\n catalogApi: CatalogService,\n auth: AuthService,\n ): CatalogClusterLocator {\n return new CatalogClusterLocator(catalogApi, auth);\n }\n\n async getClusters(options?: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const apiServerKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER}`;\n const apiServerCaKey = `metadata.annotations.${ANNOTATION_KUBERNETES_API_SERVER_CA}`;\n const authProviderKey = `metadata.annotations.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}`;\n\n const filter: Record<string, symbol | string> = {\n kind: 'Resource',\n 'spec.type': 'kubernetes-cluster',\n [apiServerKey]: CATALOG_FILTER_EXISTS,\n [apiServerCaKey]: CATALOG_FILTER_EXISTS,\n [authProviderKey]: CATALOG_FILTER_EXISTS,\n };\n\n const clusters = await this.catalogService.getEntities(\n {\n filter: [filter],\n },\n {\n credentials:\n options?.credentials ?? (await this.auth.getNoneCredentials()),\n },\n );\n return clusters.items.map(entity => {\n const annotations = entity.metadata.annotations!;\n const authProvider = annotations[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n if (authProvider === 'serviceAccount') {\n throw new Error(\n `Invalid cluster '${entity.metadata.name}': authProvider 'serviceAccount' is not supported by the catalog cluster locator`,\n );\n }\n\n const clusterDetails: ClusterDetails = {\n name: entity.metadata.name,\n title: entity.metadata.title,\n url: annotations[ANNOTATION_KUBERNETES_API_SERVER],\n authMetadata: annotations,\n caData: annotations[ANNOTATION_KUBERNETES_API_SERVER_CA],\n skipMetricsLookup:\n annotations[ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP] === 'true',\n skipTLSVerify:\n annotations[ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY] === 'true',\n dashboardUrl: annotations[ANNOTATION_KUBERNETES_DASHBOARD_URL],\n dashboardApp: annotations[ANNOTATION_KUBERNETES_DASHBOARD_APP],\n dashboardParameters: this.getDashboardParameters(annotations),\n };\n\n return clusterDetails;\n });\n }\n\n private getDashboardParameters(\n annotations: Record<string, string>,\n ): JsonObject | undefined {\n const dashboardParamsString =\n annotations[ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS];\n if (dashboardParamsString) {\n try {\n const dashboardParams = JSON.parse(dashboardParamsString);\n return isObject(dashboardParams) ? dashboardParams : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_API_SERVER","ANNOTATION_KUBERNETES_API_SERVER_CA","ANNOTATION_KUBERNETES_AUTH_PROVIDER","CATALOG_FILTER_EXISTS","ANNOTATION_KUBERNETES_SKIP_METRICS_LOOKUP","ANNOTATION_KUBERNETES_SKIP_TLS_VERIFY","ANNOTATION_KUBERNETES_DASHBOARD_URL","ANNOTATION_KUBERNETES_DASHBOARD_APP","ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS"],"mappings":"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"}
@@ -60,12 +60,7 @@ const getCombinedClusterSupplier = (rootConfig, catalogService, authStrategy, lo
60
60
  const type = clusterLocatorMethod.getString("type");
61
61
  switch (type) {
62
62
  case "catalog":
63
- return CatalogClusterLocator.CatalogClusterLocator.fromConfig(
64
- catalogService,
65
- auth,
66
- clusterLocatorMethod,
67
- logger
68
- );
63
+ return CatalogClusterLocator.CatalogClusterLocator.fromConfig(catalogService, auth);
69
64
  case "localKubectlProxy":
70
65
  return new LocalKubectlProxyLocator.LocalKubectlProxyClusterLocator();
71
66
  case "config":
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../src/cluster-locator/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport { Duration } from 'luxon';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\nimport { CatalogClusterLocator } from './CatalogClusterLocator';\nimport { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nclass CombinedClustersSupplier implements KubernetesClustersSupplier {\n readonly clusterSuppliers: KubernetesClustersSupplier[];\n readonly logger: LoggerService;\n readonly continueOnError: boolean;\n\n constructor(\n clusterSuppliers: KubernetesClustersSupplier[],\n logger: LoggerService,\n continueOnError: boolean = false,\n ) {\n this.clusterSuppliers = clusterSuppliers;\n this.logger = logger;\n this.continueOnError = continueOnError;\n }\n\n async getClusters(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const clusters = this.continueOnError\n ? await this.getClustersSettled(options)\n : await Promise.all(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n ).then(res => res.flat());\n return this.warnDuplicates(clusters);\n }\n\n private async getClustersSettled(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const results = await Promise.allSettled(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n );\n const clusters: ClusterDetails[] = [];\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n if (result.status === 'fulfilled') {\n clusters.push(...result.value);\n } else {\n this.logger.error(\n `Failed to retrieve clusters from cluster locator method #${i + 1}`,\n toError(result.reason),\n );\n }\n }\n return clusters;\n }\n\n private warnDuplicates(clusters: ClusterDetails[]): ClusterDetails[] {\n const clusterNames = new Set<string>();\n const duplicatedNames = new Set<string>();\n for (const clusterName of clusters.map(c => c.name)) {\n if (clusterNames.has(clusterName)) {\n duplicatedNames.add(clusterName);\n } else {\n clusterNames.add(clusterName);\n }\n }\n for (const clusterName of duplicatedNames) {\n this.logger.warn(`Duplicate cluster name '${clusterName}'`);\n }\n return clusters;\n }\n}\n\nexport const getCombinedClusterSupplier = (\n rootConfig: Config,\n catalogService: CatalogService,\n authStrategy: AuthenticationStrategy,\n logger: LoggerService,\n refreshInterval: Duration | undefined = undefined,\n auth: AuthService,\n): KubernetesClustersSupplier => {\n const clusterSuppliers = rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'catalog':\n return CatalogClusterLocator.fromConfig(\n catalogService,\n auth,\n clusterLocatorMethod,\n logger,\n );\n case 'localKubectlProxy':\n return new LocalKubectlProxyClusterLocator();\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n authStrategy,\n );\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n logger,\n refreshInterval,\n );\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n });\n\n const continueOnError =\n rootConfig.getOptionalBoolean('kubernetes.clusterLocatorContinueOnError') ??\n false;\n\n return new CombinedClustersSupplier(\n clusterSuppliers,\n logger,\n continueOnError,\n );\n};\n"],"names":["toError","CatalogClusterLocator","LocalKubectlProxyClusterLocator","ConfigClusterLocator","GkeClusterLocator"],"mappings":";;;;;;;;AAmCA,MAAM,wBAAA,CAA+D;AAAA,EAC1D,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,eAAA;AAAA,EAET,WAAA,CACE,gBAAA,EACA,MAAA,EACA,eAAA,GAA2B,KAAA,EAC3B;AACA,IAAA,IAAA,CAAK,gBAAA,GAAmB,gBAAA;AACxB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA,EAEA,MAAM,YAAY,OAAA,EAEY;AAC5B,IAAA,MAAM,QAAA,GAAW,KAAK,eAAA,GAClB,MAAM,KAAK,kBAAA,CAAmB,OAAO,CAAA,GACrC,MAAM,OAAA,CAAQ,GAAA;AAAA,MACZ,KAAK,gBAAA,CAAiB,GAAA,CAAI,cAAY,QAAA,CAAS,WAAA,CAAY,OAAO,CAAC;AAAA,KACrE,CAAE,IAAA,CAAK,CAAA,GAAA,KAAO,GAAA,CAAI,MAAM,CAAA;AAC5B,IAAA,OAAO,IAAA,CAAK,eAAe,QAAQ,CAAA;AAAA,EACrC;AAAA,EAEA,MAAc,mBAAmB,OAAA,EAEH;AAC5B,IAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,UAAA;AAAA,MAC5B,KAAK,gBAAA,CAAiB,GAAA,CAAI,cAAY,QAAA,CAAS,WAAA,CAAY,OAAO,CAAC;AAAA,KACrE;AACA,IAAA,MAAM,WAA6B,EAAC;AACpC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACvC,MAAA,MAAM,MAAA,GAAS,QAAQ,CAAC,CAAA;AACxB,MAAA,IAAI,MAAA,CAAO,WAAW,WAAA,EAAa;AACjC,QAAA,QAAA,CAAS,IAAA,CAAK,GAAG,MAAA,CAAO,KAAK,CAAA;AAAA,MAC/B,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,CAAA,yDAAA,EAA4D,IAAI,CAAC,CAAA,CAAA;AAAA,UACjEA,cAAA,CAAQ,OAAO,MAAM;AAAA,SACvB;AAAA,MACF;AAAA,IACF;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEQ,eAAe,QAAA,EAA8C;AACnE,IAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AACrC,IAAA,MAAM,eAAA,uBAAsB,GAAA,EAAY;AACxC,IAAA,KAAA,MAAW,eAAe,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAA,EAAG;AACnD,MAAA,IAAI,YAAA,CAAa,GAAA,CAAI,WAAW,CAAA,EAAG;AACjC,QAAA,eAAA,CAAgB,IAAI,WAAW,CAAA;AAAA,MACjC,CAAA,MAAO;AACL,QAAA,YAAA,CAAa,IAAI,WAAW,CAAA;AAAA,MAC9B;AAAA,IACF;AACA,IAAA,KAAA,MAAW,eAAe,eAAA,EAAiB;AACzC,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,CAAA,wBAAA,EAA2B,WAAW,CAAA,CAAA,CAAG,CAAA;AAAA,IAC5D;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AACF;AAEO,MAAM,0BAAA,GAA6B,CACxC,UAAA,EACA,cAAA,EACA,cACA,MAAA,EACA,eAAA,GAAwC,QACxC,IAAA,KAC+B;AAC/B,EAAA,MAAM,mBAAmB,UAAA,CACtB,cAAA,CAAe,kCAAkC,CAAA,CACjD,IAAI,CAAA,oBAAA,KAAwB;AAC3B,IAAA,MAAM,IAAA,GAAO,oBAAA,CAAqB,SAAA,CAAU,MAAM,CAAA;AAClD,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,SAAA;AACH,QAAA,OAAOC,2CAAA,CAAsB,UAAA;AAAA,UAC3B,cAAA;AAAA,UACA,IAAA;AAAA,UACA,oBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF,KAAK,mBAAA;AACH,QAAA,OAAO,IAAIC,wDAAA,EAAgC;AAAA,MAC7C,KAAK,QAAA;AACH,QAAA,OAAOC,yCAAA,CAAqB,UAAA;AAAA,UAC1B,oBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF,KAAK,KAAA;AACH,QAAA,OAAOC,mCAAA,CAAkB,UAAA;AAAA,UACvB,oBAAA;AAAA,UACA,MAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AACE,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,kDAAkD,IAAI,CAAA,CAAA;AAAA,SACxD;AAAA;AACJ,EACF,CAAC,CAAA;AAEH,EAAA,MAAM,eAAA,GACJ,UAAA,CAAW,kBAAA,CAAmB,0CAA0C,CAAA,IACxE,KAAA;AAEF,EAAA,OAAO,IAAI,wBAAA;AAAA,IACT,gBAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../src/cluster-locator/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { toError } from '@backstage/errors';\nimport { Duration } from 'luxon';\nimport { ConfigClusterLocator } from './ConfigClusterLocator';\nimport { GkeClusterLocator } from './GkeClusterLocator';\nimport { CatalogClusterLocator } from './CatalogClusterLocator';\nimport { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';\nimport {\n AuthService,\n BackstageCredentials,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport {\n AuthenticationStrategy,\n ClusterDetails,\n KubernetesClustersSupplier,\n} from '@backstage/plugin-kubernetes-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\n\nclass CombinedClustersSupplier implements KubernetesClustersSupplier {\n readonly clusterSuppliers: KubernetesClustersSupplier[];\n readonly logger: LoggerService;\n readonly continueOnError: boolean;\n\n constructor(\n clusterSuppliers: KubernetesClustersSupplier[],\n logger: LoggerService,\n continueOnError: boolean = false,\n ) {\n this.clusterSuppliers = clusterSuppliers;\n this.logger = logger;\n this.continueOnError = continueOnError;\n }\n\n async getClusters(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const clusters = this.continueOnError\n ? await this.getClustersSettled(options)\n : await Promise.all(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n ).then(res => res.flat());\n return this.warnDuplicates(clusters);\n }\n\n private async getClustersSettled(options: {\n credentials: BackstageCredentials;\n }): Promise<ClusterDetails[]> {\n const results = await Promise.allSettled(\n this.clusterSuppliers.map(supplier => supplier.getClusters(options)),\n );\n const clusters: ClusterDetails[] = [];\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n if (result.status === 'fulfilled') {\n clusters.push(...result.value);\n } else {\n this.logger.error(\n `Failed to retrieve clusters from cluster locator method #${i + 1}`,\n toError(result.reason),\n );\n }\n }\n return clusters;\n }\n\n private warnDuplicates(clusters: ClusterDetails[]): ClusterDetails[] {\n const clusterNames = new Set<string>();\n const duplicatedNames = new Set<string>();\n for (const clusterName of clusters.map(c => c.name)) {\n if (clusterNames.has(clusterName)) {\n duplicatedNames.add(clusterName);\n } else {\n clusterNames.add(clusterName);\n }\n }\n for (const clusterName of duplicatedNames) {\n this.logger.warn(`Duplicate cluster name '${clusterName}'`);\n }\n return clusters;\n }\n}\n\nexport const getCombinedClusterSupplier = (\n rootConfig: Config,\n catalogService: CatalogService,\n authStrategy: AuthenticationStrategy,\n logger: LoggerService,\n refreshInterval: Duration | undefined = undefined,\n auth: AuthService,\n): KubernetesClustersSupplier => {\n const clusterSuppliers = rootConfig\n .getConfigArray('kubernetes.clusterLocatorMethods')\n .map(clusterLocatorMethod => {\n const type = clusterLocatorMethod.getString('type');\n switch (type) {\n case 'catalog':\n return CatalogClusterLocator.fromConfig(catalogService, auth);\n case 'localKubectlProxy':\n return new LocalKubectlProxyClusterLocator();\n case 'config':\n return ConfigClusterLocator.fromConfig(\n clusterLocatorMethod,\n authStrategy,\n );\n case 'gke':\n return GkeClusterLocator.fromConfig(\n clusterLocatorMethod,\n logger,\n refreshInterval,\n );\n default:\n throw new Error(\n `Unsupported kubernetes.clusterLocatorMethods: \"${type}\"`,\n );\n }\n });\n\n const continueOnError =\n rootConfig.getOptionalBoolean('kubernetes.clusterLocatorContinueOnError') ??\n false;\n\n return new CombinedClustersSupplier(\n clusterSuppliers,\n logger,\n continueOnError,\n );\n};\n"],"names":["toError","CatalogClusterLocator","LocalKubectlProxyClusterLocator","ConfigClusterLocator","GkeClusterLocator"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "0.21.10";
5
+ var version = "0.21.11-next.1";
6
6
  var packageinfo = {
7
7
  version: version};
8
8
 
@@ -0,0 +1,183 @@
1
+ 'use strict';
2
+
3
+ var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
4
+ var fetch = require('node-fetch');
5
+ var https = require('node:https');
6
+ var fs = require('fs-extra');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ function _interopNamespaceCompat(e) {
11
+ if (e && typeof e === 'object' && 'default' in e) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
29
+ var https__namespace = /*#__PURE__*/_interopNamespaceCompat(https);
30
+ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
31
+
32
+ const statusCodeToErrorType = (statusCode) => {
33
+ switch (statusCode) {
34
+ case 400:
35
+ return "BAD_REQUEST";
36
+ case 401:
37
+ return "UNAUTHORIZED_ERROR";
38
+ case 404:
39
+ return "NOT_FOUND";
40
+ case 500:
41
+ return "SYSTEM_ERROR";
42
+ default:
43
+ return "UNKNOWN_ERROR";
44
+ }
45
+ };
46
+ class KubernetesConnection {
47
+ logger;
48
+ agentCache = /* @__PURE__ */ new Map();
49
+ inClusterCache;
50
+ constructor({ logger }) {
51
+ this.logger = logger;
52
+ }
53
+ buildResourcePath(group, apiVersion, plural, namespace) {
54
+ const encode = (s) => encodeURIComponent(s);
55
+ let path = group ? `/apis/${encode(group)}/${encode(apiVersion)}` : `/api/${encode(apiVersion)}`;
56
+ if (namespace) {
57
+ path += `/namespaces/${encode(namespace)}`;
58
+ }
59
+ path += `/${encode(plural)}`;
60
+ return path;
61
+ }
62
+ async resolveConnection(clusterDetails, credential) {
63
+ const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
64
+ if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
65
+ const [url, requestInit] = await this.fetchArgsInCluster(credential);
66
+ return { ok: true, url, requestInit };
67
+ } else if (!this.isCredentialMissing(authProvider, credential)) {
68
+ const [url, requestInit] = await this.fetchArgs(
69
+ clusterDetails,
70
+ credential
71
+ );
72
+ return { ok: true, url, requestInit };
73
+ }
74
+ return { ok: false, reason: "missing_credentials" };
75
+ }
76
+ async fetchWithConnection(clusterDetails, credential, resourcePath, labelSelector) {
77
+ const result = await this.resolveConnection(clusterDetails, credential);
78
+ if (!result.ok) {
79
+ return Promise.reject(
80
+ new Error(
81
+ `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`
82
+ )
83
+ );
84
+ }
85
+ const { url, requestInit } = result;
86
+ if (url.pathname === "/") {
87
+ url.pathname = resourcePath;
88
+ } else {
89
+ url.pathname += resourcePath;
90
+ }
91
+ if (labelSelector) {
92
+ url.search = `labelSelector=${encodeURIComponent(labelSelector)}`;
93
+ }
94
+ return fetch__default.default(url, requestInit);
95
+ }
96
+ async handleUnsuccessfulResponse(clusterName, res) {
97
+ const resourcePath = new URL(res.url).pathname;
98
+ this.logger.warn(
99
+ `Received ${res.status} status when fetching "${resourcePath}" from cluster "${clusterName}"; body=[${await res.text()}]`
100
+ );
101
+ return {
102
+ errorType: statusCodeToErrorType(res.status),
103
+ statusCode: res.status,
104
+ resourcePath
105
+ };
106
+ }
107
+ isServiceAccountAuthentication(authProvider, clusterDetails) {
108
+ return authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default.default.pathExistsSync(pluginKubernetesCommon.SERVICEACCOUNT_CA_PATH);
109
+ }
110
+ isCredentialMissing(authProvider, credential) {
111
+ return authProvider !== "localKubectlProxy" && credential.type === "anonymous";
112
+ }
113
+ buildRequestHeaders(credential) {
114
+ return {
115
+ Accept: "application/json",
116
+ "Content-Type": "application/json",
117
+ ...credential.type === "bearer token" && {
118
+ Authorization: `Bearer ${credential.token}`
119
+ }
120
+ };
121
+ }
122
+ async fetchArgs(clusterDetails, credential) {
123
+ const { bufferFromFileOrString } = await import('@kubernetes/client-node');
124
+ const requestInit = {
125
+ method: "GET",
126
+ headers: this.buildRequestHeaders(credential),
127
+ redirect: "manual"
128
+ };
129
+ const url = new URL(clusterDetails.url);
130
+ if (url.protocol === "https:") {
131
+ const ca = bufferFromFileOrString(clusterDetails.caFile, clusterDetails.caData) ?? void 0;
132
+ requestInit.agent = this.getOrCreateAgent(clusterDetails, credential, ca);
133
+ }
134
+ return [url, requestInit];
135
+ }
136
+ async fetchArgsInCluster(credential) {
137
+ if (!this.inClusterCache) {
138
+ const { KubeConfig } = await import('@kubernetes/client-node');
139
+ const kc = new KubeConfig();
140
+ kc.loadFromCluster();
141
+ const cluster = kc.getCurrentCluster();
142
+ const url2 = new URL(cluster.server);
143
+ const agent2 = url2.protocol === "https:" ? new https__namespace.Agent({
144
+ ca: fs__default.default.readFileSync(cluster.caFile),
145
+ keepAlive: true
146
+ }) : void 0;
147
+ this.inClusterCache = { url: url2, agent: agent2 };
148
+ }
149
+ const { url, agent } = this.inClusterCache;
150
+ const requestInit = {
151
+ method: "GET",
152
+ headers: this.buildRequestHeaders(credential),
153
+ redirect: "manual",
154
+ ...agent && { agent }
155
+ };
156
+ return [new URL(url.toString()), requestInit];
157
+ }
158
+ buildAgentCacheKey(clusterDetails, credential) {
159
+ const certPart = credential.type === "x509 client certificate" ? `${credential.cert}|${credential.key}` : "";
160
+ return `${clusterDetails.url}|${clusterDetails.skipTLSVerify ?? false}|${clusterDetails.caData ?? ""}|${clusterDetails.caFile ?? ""}|${certPart}`;
161
+ }
162
+ getOrCreateAgent(clusterDetails, credential, ca) {
163
+ const key = this.buildAgentCacheKey(clusterDetails, credential);
164
+ let agent = this.agentCache.get(key);
165
+ if (!agent) {
166
+ agent = new https__namespace.Agent({
167
+ ca,
168
+ rejectUnauthorized: !clusterDetails.skipTLSVerify,
169
+ keepAlive: true,
170
+ ...credential.type === "x509 client certificate" && {
171
+ cert: credential.cert,
172
+ key: credential.key
173
+ }
174
+ });
175
+ this.agentCache.set(key, agent);
176
+ }
177
+ return agent;
178
+ }
179
+ }
180
+
181
+ exports.KubernetesConnection = KubernetesConnection;
182
+ exports.statusCodeToErrorType = statusCodeToErrorType;
183
+ //# sourceMappingURL=KubernetesConnection.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KubernetesConnection.cjs.js","sources":["../../src/service/KubernetesConnection.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cluster } from '@kubernetes/client-node';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n SERVICEACCOUNT_CA_PATH,\n KubernetesErrorTypes,\n KubernetesFetchError,\n} from '@backstage/plugin-kubernetes-common';\nimport fetch, { RequestInit, Response } from 'node-fetch';\nimport * as https from 'node:https';\nimport fs from 'fs-extra';\nimport {\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport const statusCodeToErrorType = (\n statusCode: number,\n): KubernetesErrorTypes => {\n switch (statusCode) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED_ERROR';\n case 404:\n return 'NOT_FOUND';\n case 500:\n return 'SYSTEM_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n};\n\nexport type ConnectionResult =\n | { ok: true; url: URL; requestInit: RequestInit }\n | { ok: false; reason: 'missing_credentials' };\n\nexport class KubernetesConnection {\n private readonly logger: LoggerService;\n private readonly agentCache = new Map<string, https.Agent>();\n private inClusterCache:\n | { url: URL; agent: https.Agent | undefined }\n | undefined;\n\n constructor({ logger }: { logger: LoggerService }) {\n this.logger = logger;\n }\n\n buildResourcePath(\n group: string,\n apiVersion: string,\n plural: string,\n namespace?: string,\n ): string {\n const encode = (s: string) => encodeURIComponent(s);\n let path = group\n ? `/apis/${encode(group)}/${encode(apiVersion)}`\n : `/api/${encode(apiVersion)}`;\n if (namespace) {\n path += `/namespaces/${encode(namespace)}`;\n }\n path += `/${encode(plural)}`;\n return path;\n }\n\n async resolveConnection(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ): Promise<ConnectionResult> {\n const authProvider =\n clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n\n if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {\n const [url, requestInit] = await this.fetchArgsInCluster(credential);\n return { ok: true, url, requestInit };\n } else if (!this.isCredentialMissing(authProvider, credential)) {\n const [url, requestInit] = await this.fetchArgs(\n clusterDetails,\n credential,\n );\n return { ok: true, url, requestInit };\n }\n return { ok: false, reason: 'missing_credentials' };\n }\n\n async fetchWithConnection(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n resourcePath: string,\n labelSelector?: string,\n ): Promise<Response> {\n const result = await this.resolveConnection(clusterDetails, credential);\n if (!result.ok) {\n return Promise.reject(\n new Error(\n `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`,\n ),\n );\n }\n\n const { url, requestInit } = result;\n if (url.pathname === '/') {\n url.pathname = resourcePath;\n } else {\n url.pathname += resourcePath;\n }\n\n if (labelSelector) {\n url.search = `labelSelector=${encodeURIComponent(labelSelector)}`;\n }\n\n return fetch(url, requestInit);\n }\n\n async handleUnsuccessfulResponse(\n clusterName: string,\n res: Response,\n ): Promise<KubernetesFetchError> {\n const resourcePath = new URL(res.url).pathname;\n this.logger.warn(\n `Received ${\n res.status\n } status when fetching \"${resourcePath}\" from cluster \"${clusterName}\"; body=[${await res.text()}]`,\n );\n return {\n errorType: statusCodeToErrorType(res.status),\n statusCode: res.status,\n resourcePath,\n };\n }\n\n private isServiceAccountAuthentication(\n authProvider: string,\n clusterDetails: ClusterDetails,\n ) {\n return (\n authProvider === 'serviceAccount' &&\n !clusterDetails.authMetadata.serviceAccountToken &&\n fs.pathExistsSync(SERVICEACCOUNT_CA_PATH)\n );\n }\n\n private isCredentialMissing(\n authProvider: string,\n credential: KubernetesCredential,\n ) {\n return (\n authProvider !== 'localKubectlProxy' && credential.type === 'anonymous'\n );\n }\n\n private buildRequestHeaders(\n credential: KubernetesCredential,\n ): Record<string, string> {\n return {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n ...(credential.type === 'bearer token' && {\n Authorization: `Bearer ${credential.token}`,\n }),\n };\n }\n\n private async fetchArgs(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ): Promise<[URL, fetch.RequestInit]> {\n const { bufferFromFileOrString } = await import('@kubernetes/client-node');\n const requestInit: RequestInit = {\n method: 'GET',\n headers: this.buildRequestHeaders(credential),\n redirect: 'manual',\n };\n\n const url: URL = new URL(clusterDetails.url);\n if (url.protocol === 'https:') {\n const ca =\n bufferFromFileOrString(clusterDetails.caFile, clusterDetails.caData) ??\n undefined;\n requestInit.agent = this.getOrCreateAgent(clusterDetails, credential, ca);\n }\n return [url, requestInit];\n }\n\n private async fetchArgsInCluster(\n credential: KubernetesCredential,\n ): Promise<[URL, fetch.RequestInit]> {\n if (!this.inClusterCache) {\n const { KubeConfig } = await import('@kubernetes/client-node');\n const kc = new KubeConfig();\n kc.loadFromCluster();\n const cluster = kc.getCurrentCluster() as Cluster;\n const url = new URL(cluster.server);\n const agent =\n url.protocol === 'https:'\n ? new https.Agent({\n ca: fs.readFileSync(cluster.caFile as string),\n keepAlive: true,\n })\n : undefined;\n this.inClusterCache = { url, agent };\n }\n\n const { url, agent } = this.inClusterCache;\n const requestInit: RequestInit = {\n method: 'GET',\n headers: this.buildRequestHeaders(credential),\n redirect: 'manual',\n ...(agent && { agent }),\n };\n return [new URL(url.toString()), requestInit];\n }\n\n private buildAgentCacheKey(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ): string {\n const certPart =\n credential.type === 'x509 client certificate'\n ? `${credential.cert}|${credential.key}`\n : '';\n return `${clusterDetails.url}|${clusterDetails.skipTLSVerify ?? false}|${\n clusterDetails.caData ?? ''\n }|${clusterDetails.caFile ?? ''}|${certPart}`;\n }\n\n private getOrCreateAgent(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ca: Buffer | string | undefined,\n ): https.Agent {\n const key = this.buildAgentCacheKey(clusterDetails, credential);\n\n let agent = this.agentCache.get(key);\n if (!agent) {\n agent = new https.Agent({\n ca,\n rejectUnauthorized: !clusterDetails.skipTLSVerify,\n keepAlive: true,\n ...(credential.type === 'x509 client certificate' && {\n cert: credential.cert,\n key: credential.key,\n }),\n });\n this.agentCache.set(key, agent);\n }\n return agent;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_AUTH_PROVIDER","fetch","fs","SERVICEACCOUNT_CA_PATH","url","agent","https"],"mappings":"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"}
@@ -1,34 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var pluginKubernetesCommon = require('@backstage/plugin-kubernetes-common');
4
- var fetch = require('node-fetch');
5
- var https = require('node:https');
6
- var fs = require('fs-extra');
7
-
8
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
-
10
- function _interopNamespaceCompat(e) {
11
- if (e && typeof e === 'object' && 'default' in e) return e;
12
- var n = Object.create(null);
13
- if (e) {
14
- Object.keys(e).forEach(function (k) {
15
- if (k !== 'default') {
16
- var d = Object.getOwnPropertyDescriptor(e, k);
17
- Object.defineProperty(n, k, d.get ? d : {
18
- enumerable: true,
19
- get: function () { return e[k]; }
20
- });
21
- }
22
- });
23
- }
24
- n.default = e;
25
- return Object.freeze(n);
26
- }
27
-
28
- var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
29
- var https__namespace = /*#__PURE__*/_interopNamespaceCompat(https);
30
- var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
31
-
32
3
  const isError = (fr) => fr.hasOwnProperty("errorType");
33
4
  function fetchResultsToResponseWrapper(results) {
34
5
  const errors = [];
@@ -42,26 +13,10 @@ function fetchResultsToResponseWrapper(results) {
42
13
  }
43
14
  return { errors, responses };
44
15
  }
45
- const statusCodeToErrorType = (statusCode) => {
46
- switch (statusCode) {
47
- case 400:
48
- return "BAD_REQUEST";
49
- case 401:
50
- return "UNAUTHORIZED_ERROR";
51
- case 404:
52
- return "NOT_FOUND";
53
- case 500:
54
- return "SYSTEM_ERROR";
55
- default:
56
- return "UNKNOWN_ERROR";
57
- }
58
- };
59
16
  class KubernetesClientBasedFetcher {
60
- logger;
61
- agentCache = /* @__PURE__ */ new Map();
62
- inClusterCache;
63
- constructor({ logger }) {
64
- this.logger = logger;
17
+ connection;
18
+ constructor({ connection }) {
19
+ this.connection = connection;
65
20
  }
66
21
  fetchObjectsForService(params) {
67
22
  const fetchResults = Array.from(params.objectTypesToFetch).concat(params.customResources).map(
@@ -81,7 +36,10 @@ class KubernetesClientBasedFetcher {
81
36
  items
82
37
  )
83
38
  })
84
- ) : this.handleUnsuccessfulResponse(params.clusterDetails.name, r)
39
+ ) : this.connection.handleUnsuccessfulResponse(
40
+ params.clusterDetails.name,
41
+ r
42
+ )
85
43
  )
86
44
  );
87
45
  return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);
@@ -130,132 +88,29 @@ class KubernetesClientBasedFetcher {
130
88
  })
131
89
  );
132
90
  } else if (podMetrics.ok) {
133
- return this.handleUnsuccessfulResponse(clusterDetails.name, podList);
91
+ return this.connection.handleUnsuccessfulResponse(
92
+ clusterDetails.name,
93
+ podList
94
+ );
134
95
  }
135
- return this.handleUnsuccessfulResponse(clusterDetails.name, podMetrics);
136
- }
137
- async handleUnsuccessfulResponse(clusterName, res) {
138
- const resourcePath = new URL(res.url).pathname;
139
- this.logger.warn(
140
- `Received ${res.status} status when fetching "${resourcePath}" from cluster "${clusterName}"; body=[${await res.text()}]`
96
+ return this.connection.handleUnsuccessfulResponse(
97
+ clusterDetails.name,
98
+ podMetrics
141
99
  );
142
- return {
143
- errorType: statusCodeToErrorType(res.status),
144
- statusCode: res.status,
145
- resourcePath
146
- };
147
- }
148
- buildResourcePath(group, apiVersion, plural, namespace) {
149
- const encode = (s) => encodeURIComponent(s);
150
- let path = group ? `/apis/${encode(group)}/${encode(apiVersion)}` : `/api/${encode(apiVersion)}`;
151
- if (namespace) {
152
- path += `/namespaces/${encode(namespace)}`;
153
- }
154
- path += `/${encode(plural)}`;
155
- return path;
156
100
  }
157
101
  async fetchResource(clusterDetails, credential, resource, namespace, labelSelector) {
158
- const resourcePath = this.buildResourcePath(
102
+ const resourcePath = this.connection.buildResourcePath(
159
103
  resource.group,
160
104
  resource.apiVersion,
161
105
  resource.plural,
162
106
  namespace
163
107
  );
164
- let url;
165
- let requestInit;
166
- const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
167
- if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
168
- [url, requestInit] = await this.fetchArgsInCluster(credential);
169
- } else if (!this.isCredentialMissing(authProvider, credential)) {
170
- [url, requestInit] = await this.fetchArgs(clusterDetails, credential);
171
- } else {
172
- return Promise.reject(
173
- new Error(
174
- `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`
175
- )
176
- );
177
- }
178
- if (url.pathname === "/") {
179
- url.pathname = resourcePath;
180
- } else {
181
- url.pathname += resourcePath;
182
- }
183
- if (labelSelector) {
184
- url.search = `labelSelector=${encodeURIComponent(labelSelector)}`;
185
- }
186
- return fetch__default.default(url, requestInit);
187
- }
188
- isServiceAccountAuthentication(authProvider, clusterDetails) {
189
- return authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default.default.pathExistsSync(pluginKubernetesCommon.SERVICEACCOUNT_CA_PATH);
190
- }
191
- isCredentialMissing(authProvider, credential) {
192
- return authProvider !== "localKubectlProxy" && credential.type === "anonymous";
193
- }
194
- buildRequestHeaders(credential) {
195
- return {
196
- Accept: "application/json",
197
- "Content-Type": "application/json",
198
- ...credential.type === "bearer token" && {
199
- Authorization: `Bearer ${credential.token}`
200
- }
201
- };
202
- }
203
- async fetchArgs(clusterDetails, credential) {
204
- const { bufferFromFileOrString } = await import('@kubernetes/client-node');
205
- const requestInit = {
206
- method: "GET",
207
- headers: this.buildRequestHeaders(credential),
208
- redirect: "manual"
209
- };
210
- const url = new URL(clusterDetails.url);
211
- if (url.protocol === "https:") {
212
- const ca = bufferFromFileOrString(clusterDetails.caFile, clusterDetails.caData) ?? void 0;
213
- requestInit.agent = this.getOrCreateAgent(clusterDetails, credential, ca);
214
- }
215
- return [url, requestInit];
216
- }
217
- async fetchArgsInCluster(credential) {
218
- if (!this.inClusterCache) {
219
- const { KubeConfig } = await import('@kubernetes/client-node');
220
- const kc = new KubeConfig();
221
- kc.loadFromCluster();
222
- const cluster = kc.getCurrentCluster();
223
- const url2 = new URL(cluster.server);
224
- const agent2 = url2.protocol === "https:" ? new https__namespace.Agent({
225
- ca: fs__default.default.readFileSync(cluster.caFile),
226
- keepAlive: true
227
- }) : void 0;
228
- this.inClusterCache = { url: url2, agent: agent2 };
229
- }
230
- const { url, agent } = this.inClusterCache;
231
- const requestInit = {
232
- method: "GET",
233
- headers: this.buildRequestHeaders(credential),
234
- redirect: "manual",
235
- ...agent && { agent }
236
- };
237
- return [new URL(url.toString()), requestInit];
238
- }
239
- buildAgentCacheKey(clusterDetails, credential) {
240
- const certPart = credential.type === "x509 client certificate" ? `${credential.cert}|${credential.key}` : "";
241
- return `${clusterDetails.url}|${clusterDetails.skipTLSVerify ?? false}|${clusterDetails.caData ?? ""}|${clusterDetails.caFile ?? ""}|${certPart}`;
242
- }
243
- getOrCreateAgent(clusterDetails, credential, ca) {
244
- const key = this.buildAgentCacheKey(clusterDetails, credential);
245
- let agent = this.agentCache.get(key);
246
- if (!agent) {
247
- agent = new https__namespace.Agent({
248
- ca,
249
- rejectUnauthorized: !clusterDetails.skipTLSVerify,
250
- keepAlive: true,
251
- ...credential.type === "x509 client certificate" && {
252
- cert: credential.cert,
253
- key: credential.key
254
- }
255
- });
256
- this.agentCache.set(key, agent);
257
- }
258
- return agent;
108
+ return this.connection.fetchWithConnection(
109
+ clusterDetails,
110
+ credential,
111
+ resourcePath,
112
+ labelSelector
113
+ );
259
114
  }
260
115
  redactSecretData(items) {
261
116
  return items.map((item) => {
@@ -1 +1 @@
1
- {"version":3,"file":"KubernetesFetcher.cjs.js","sources":["../../src/service/KubernetesFetcher.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Cluster, CoreV1Api, Metrics } from '@kubernetes/client-node';\nimport {\n FetchResponseWrapper,\n KubernetesFetcher,\n ObjectFetchParams,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport {\n ANNOTATION_KUBERNETES_AUTH_PROVIDER,\n SERVICEACCOUNT_CA_PATH,\n FetchResponse,\n KubernetesErrorTypes,\n KubernetesFetchError,\n PodStatusFetchResponse,\n} from '@backstage/plugin-kubernetes-common';\nimport fetch, { RequestInit, Response } from 'node-fetch';\nimport * as https from 'node:https';\nimport fs from 'fs-extra';\nimport { JsonObject } from '@backstage/types';\nimport {\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport interface KubernetesClientBasedFetcherOptions {\n logger: LoggerService;\n}\n\ntype FetchResult = FetchResponse | KubernetesFetchError;\n\nconst isError = (fr: FetchResult): fr is KubernetesFetchError =>\n fr.hasOwnProperty('errorType');\n\nfunction fetchResultsToResponseWrapper(\n results: FetchResult[],\n): FetchResponseWrapper {\n const errors: KubernetesFetchError[] = [];\n const responses: FetchResponse[] = [];\n for (const result of results) {\n if (isError(result)) {\n errors.push(result);\n } else {\n responses.push(result);\n }\n }\n return { errors, responses };\n}\n\nconst statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => {\n switch (statusCode) {\n case 400:\n return 'BAD_REQUEST';\n case 401:\n return 'UNAUTHORIZED_ERROR';\n case 404:\n return 'NOT_FOUND';\n case 500:\n return 'SYSTEM_ERROR';\n default:\n return 'UNKNOWN_ERROR';\n }\n};\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly logger: LoggerService;\n private readonly agentCache = new Map<string, https.Agent>();\n private inClusterCache:\n | { url: URL; agent: https.Agent | undefined }\n | undefined;\n\n constructor({ logger }: KubernetesClientBasedFetcherOptions) {\n this.logger = logger;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(({ objectType, group, apiVersion, plural }) =>\n this.fetchResource(\n params.clusterDetails,\n params.credential,\n { group, apiVersion, plural },\n params.namespace,\n params.labelSelector,\n ).then(\n (r: Response): Promise<FetchResult> =>\n r.ok\n ? r.json().then(\n ({ kind, items }): FetchResponse => ({\n type: objectType,\n resources: this.transformResources(\n { objectType, group, apiVersion, plural },\n kind,\n items,\n ),\n }),\n )\n : this.handleUnsuccessfulResponse(params.clusterDetails.name, r),\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n async fetchPodMetricsByNamespaces(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespaces: Set<string>,\n labelSelector?: string,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(namespaces).map(ns =>\n this.fetchPodMetricsForNamespace(\n clusterDetails,\n credential,\n ns,\n labelSelector,\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n private async fetchPodMetricsForNamespace(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespace: string,\n labelSelector?: string,\n ): Promise<FetchResult> {\n const [podMetrics, podList] = await Promise.all([\n this.fetchResource(\n clusterDetails,\n credential,\n { group: 'metrics.k8s.io', apiVersion: 'v1beta1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n this.fetchResource(\n clusterDetails,\n credential,\n { group: '', apiVersion: 'v1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n ]);\n if (podMetrics.ok && podList.ok) {\n const { topPods } = await import('@kubernetes/client-node');\n return topPods(\n {\n listPodForAllNamespaces: () => podList.json(),\n } as unknown as CoreV1Api,\n {\n getPodMetrics: () => podMetrics.json(),\n } as unknown as Metrics,\n ).then(\n (resources): PodStatusFetchResponse => ({\n type: 'podstatus',\n resources,\n }),\n );\n } else if (podMetrics.ok) {\n return this.handleUnsuccessfulResponse(clusterDetails.name, podList);\n }\n return this.handleUnsuccessfulResponse(clusterDetails.name, podMetrics);\n }\n\n private async handleUnsuccessfulResponse(\n clusterName: string,\n res: Response,\n ): Promise<KubernetesFetchError> {\n const resourcePath = new URL(res.url).pathname;\n this.logger.warn(\n `Received ${\n res.status\n } status when fetching \"${resourcePath}\" from cluster \"${clusterName}\"; body=[${await res.text()}]`,\n );\n return {\n errorType: statusCodeToErrorType(res.status),\n statusCode: res.status,\n resourcePath,\n };\n }\n\n private buildResourcePath(\n group: string,\n apiVersion: string,\n plural: string,\n namespace?: string,\n ): string {\n const encode = (s: string) => encodeURIComponent(s);\n let path = group\n ? `/apis/${encode(group)}/${encode(apiVersion)}`\n : `/api/${encode(apiVersion)}`;\n if (namespace) {\n path += `/namespaces/${encode(namespace)}`;\n }\n path += `/${encode(plural)}`;\n return path;\n }\n\n private async fetchResource(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n resource: Pick<ObjectToFetch, 'group' | 'apiVersion' | 'plural'>,\n namespace?: string,\n labelSelector?: string,\n ): Promise<Response> {\n const resourcePath = this.buildResourcePath(\n resource.group,\n resource.apiVersion,\n resource.plural,\n namespace,\n );\n\n let url: URL;\n let requestInit: RequestInit;\n const authProvider =\n clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER];\n\n if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {\n [url, requestInit] = await this.fetchArgsInCluster(credential);\n } else if (!this.isCredentialMissing(authProvider, credential)) {\n [url, requestInit] = await this.fetchArgs(clusterDetails, credential);\n } else {\n return Promise.reject(\n new Error(\n `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`,\n ),\n );\n }\n\n if (url.pathname === '/') {\n url.pathname = resourcePath;\n } else {\n url.pathname += resourcePath;\n }\n\n if (labelSelector) {\n url.search = `labelSelector=${encodeURIComponent(labelSelector)}`;\n }\n\n return fetch(url, requestInit);\n }\n\n private isServiceAccountAuthentication(\n authProvider: string,\n clusterDetails: ClusterDetails,\n ) {\n return (\n authProvider === 'serviceAccount' &&\n !clusterDetails.authMetadata.serviceAccountToken &&\n fs.pathExistsSync(SERVICEACCOUNT_CA_PATH)\n );\n }\n\n private isCredentialMissing(\n authProvider: string,\n credential: KubernetesCredential,\n ) {\n return (\n authProvider !== 'localKubectlProxy' && credential.type === 'anonymous'\n );\n }\n\n private buildRequestHeaders(\n credential: KubernetesCredential,\n ): Record<string, string> {\n return {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n ...(credential.type === 'bearer token' && {\n Authorization: `Bearer ${credential.token}`,\n }),\n };\n }\n\n private async fetchArgs(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ): Promise<[URL, fetch.RequestInit]> {\n const { bufferFromFileOrString } = await import('@kubernetes/client-node');\n const requestInit: RequestInit = {\n method: 'GET',\n headers: this.buildRequestHeaders(credential),\n redirect: 'manual',\n };\n\n const url: URL = new URL(clusterDetails.url);\n if (url.protocol === 'https:') {\n const ca =\n bufferFromFileOrString(clusterDetails.caFile, clusterDetails.caData) ??\n undefined;\n requestInit.agent = this.getOrCreateAgent(clusterDetails, credential, ca);\n }\n return [url, requestInit];\n }\n\n private async fetchArgsInCluster(\n credential: KubernetesCredential,\n ): Promise<[URL, fetch.RequestInit]> {\n if (!this.inClusterCache) {\n const { KubeConfig } = await import('@kubernetes/client-node');\n const kc = new KubeConfig();\n kc.loadFromCluster();\n const cluster = kc.getCurrentCluster() as Cluster;\n const url = new URL(cluster.server);\n const agent =\n url.protocol === 'https:'\n ? new https.Agent({\n ca: fs.readFileSync(cluster.caFile as string),\n keepAlive: true,\n })\n : undefined;\n this.inClusterCache = { url, agent };\n }\n\n const { url, agent } = this.inClusterCache;\n const requestInit: RequestInit = {\n method: 'GET',\n headers: this.buildRequestHeaders(credential),\n redirect: 'manual',\n ...(agent && { agent }),\n };\n return [new URL(url.toString()), requestInit];\n }\n\n private buildAgentCacheKey(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ): string {\n const certPart =\n credential.type === 'x509 client certificate'\n ? `${credential.cert}|${credential.key}`\n : '';\n return `${clusterDetails.url}|${clusterDetails.skipTLSVerify ?? false}|${\n clusterDetails.caData ?? ''\n }|${clusterDetails.caFile ?? ''}|${certPart}`;\n }\n\n private getOrCreateAgent(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n ca: Buffer | string | undefined,\n ): https.Agent {\n const key = this.buildAgentCacheKey(clusterDetails, credential);\n\n let agent = this.agentCache.get(key);\n if (!agent) {\n agent = new https.Agent({\n ca,\n rejectUnauthorized: !clusterDetails.skipTLSVerify,\n keepAlive: true,\n ...(credential.type === 'x509 client certificate' && {\n cert: credential.cert,\n key: credential.key,\n }),\n });\n this.agentCache.set(key, agent);\n }\n return agent;\n }\n\n private redactSecretData(items: JsonObject[]): JsonObject[] {\n return items.map((item: JsonObject) => {\n const redacted: JsonObject = { ...item };\n if (item.data && typeof item.data === 'object') {\n redacted.data = Object.fromEntries(\n Object.keys(item.data).map(key => [key, '***']),\n );\n }\n if (item.stringData && typeof item.stringData === 'object') {\n redacted.stringData = Object.fromEntries(\n Object.keys(item.stringData).map(key => [key, '***']),\n );\n }\n return redacted;\n });\n }\n\n private transformResources(\n resource: ObjectToFetch,\n kind: string | undefined,\n items: JsonObject[],\n ): JsonObject[] {\n const itemKind = kind?.replace(/(List)$/, '');\n\n // Whether a response holds Secrets is decided from the resource that was\n // requested and from the kind reported by the API server, rather than from\n // the object type, which callers can influence. The request is checked on\n // its own so that masking does not depend on the response at all, and the\n // kind is checked as well to cover requests that reach Secrets by some\n // other shape.\n const containsSecrets =\n resource.objectType === 'secrets' ||\n itemKind === 'Secret' ||\n (resource.group === '' &&\n resource.apiVersion === 'v1' &&\n resource.plural === 'secrets');\n\n const resources = containsSecrets ? this.redactSecretData(items) : items;\n\n if (resource.objectType === 'customresources') {\n if (itemKind === undefined) {\n throw new Error(\n `Missing kind in response when fetching '${resource.plural}'`,\n );\n }\n return resources.map((item: JsonObject) => ({\n ...item,\n kind: itemKind,\n }));\n }\n\n return resources;\n }\n}\n"],"names":["ANNOTATION_KUBERNETES_AUTH_PROVIDER","fetch","fs","SERVICEACCOUNT_CA_PATH","url","agent","https"],"mappings":"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"}
1
+ {"version":3,"file":"KubernetesFetcher.cjs.js","sources":["../../src/service/KubernetesFetcher.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { CoreV1Api, Metrics } from '@kubernetes/client-node';\nimport {\n FetchResponseWrapper,\n KubernetesFetcher,\n ObjectFetchParams,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport {\n FetchResponse,\n KubernetesFetchError,\n PodStatusFetchResponse,\n} from '@backstage/plugin-kubernetes-common';\nimport { Response } from 'node-fetch';\nimport { JsonObject } from '@backstage/types';\nimport {\n ClusterDetails,\n KubernetesCredential,\n} from '@backstage/plugin-kubernetes-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { KubernetesConnection } from './KubernetesConnection';\n\nexport interface KubernetesClientBasedFetcherOptions {\n logger: LoggerService;\n connection: KubernetesConnection;\n}\n\ntype FetchResult = FetchResponse | KubernetesFetchError;\n\nconst isError = (fr: FetchResult): fr is KubernetesFetchError =>\n fr.hasOwnProperty('errorType');\n\nfunction fetchResultsToResponseWrapper(\n results: FetchResult[],\n): FetchResponseWrapper {\n const errors: KubernetesFetchError[] = [];\n const responses: FetchResponse[] = [];\n for (const result of results) {\n if (isError(result)) {\n errors.push(result);\n } else {\n responses.push(result);\n }\n }\n return { errors, responses };\n}\n\nexport class KubernetesClientBasedFetcher implements KubernetesFetcher {\n private readonly connection: KubernetesConnection;\n\n constructor({ connection }: KubernetesClientBasedFetcherOptions) {\n this.connection = connection;\n }\n\n fetchObjectsForService(\n params: ObjectFetchParams,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(params.objectTypesToFetch)\n .concat(params.customResources)\n .map(({ objectType, group, apiVersion, plural }) =>\n this.fetchResource(\n params.clusterDetails,\n params.credential,\n { group, apiVersion, plural },\n params.namespace,\n params.labelSelector,\n ).then(\n (r: Response): Promise<FetchResult> =>\n r.ok\n ? r.json().then(\n ({ kind, items }): FetchResponse => ({\n type: objectType,\n resources: this.transformResources(\n { objectType, group, apiVersion, plural },\n kind,\n items,\n ),\n }),\n )\n : this.connection.handleUnsuccessfulResponse(\n params.clusterDetails.name,\n r,\n ),\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n async fetchPodMetricsByNamespaces(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespaces: Set<string>,\n labelSelector?: string,\n ): Promise<FetchResponseWrapper> {\n const fetchResults = Array.from(namespaces).map(ns =>\n this.fetchPodMetricsForNamespace(\n clusterDetails,\n credential,\n ns,\n labelSelector,\n ),\n );\n\n return Promise.all(fetchResults).then(fetchResultsToResponseWrapper);\n }\n\n private async fetchPodMetricsForNamespace(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n namespace: string,\n labelSelector?: string,\n ): Promise<FetchResult> {\n const [podMetrics, podList] = await Promise.all([\n this.fetchResource(\n clusterDetails,\n credential,\n { group: 'metrics.k8s.io', apiVersion: 'v1beta1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n this.fetchResource(\n clusterDetails,\n credential,\n { group: '', apiVersion: 'v1', plural: 'pods' },\n namespace,\n labelSelector,\n ),\n ]);\n if (podMetrics.ok && podList.ok) {\n const { topPods } = await import('@kubernetes/client-node');\n return topPods(\n {\n listPodForAllNamespaces: () => podList.json(),\n } as unknown as CoreV1Api,\n {\n getPodMetrics: () => podMetrics.json(),\n } as unknown as Metrics,\n ).then(\n (resources): PodStatusFetchResponse => ({\n type: 'podstatus',\n resources,\n }),\n );\n } else if (podMetrics.ok) {\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podList,\n );\n }\n return this.connection.handleUnsuccessfulResponse(\n clusterDetails.name,\n podMetrics,\n );\n }\n\n private async fetchResource(\n clusterDetails: ClusterDetails,\n credential: KubernetesCredential,\n resource: Pick<ObjectToFetch, 'group' | 'apiVersion' | 'plural'>,\n namespace?: string,\n labelSelector?: string,\n ): Promise<Response> {\n const resourcePath = this.connection.buildResourcePath(\n resource.group,\n resource.apiVersion,\n resource.plural,\n namespace,\n );\n\n return this.connection.fetchWithConnection(\n clusterDetails,\n credential,\n resourcePath,\n labelSelector,\n );\n }\n\n private redactSecretData(items: JsonObject[]): JsonObject[] {\n return items.map((item: JsonObject) => {\n const redacted: JsonObject = { ...item };\n if (item.data && typeof item.data === 'object') {\n redacted.data = Object.fromEntries(\n Object.keys(item.data).map(key => [key, '***']),\n );\n }\n if (item.stringData && typeof item.stringData === 'object') {\n redacted.stringData = Object.fromEntries(\n Object.keys(item.stringData).map(key => [key, '***']),\n );\n }\n return redacted;\n });\n }\n\n private transformResources(\n resource: ObjectToFetch,\n kind: string | undefined,\n items: JsonObject[],\n ): JsonObject[] {\n const itemKind = kind?.replace(/(List)$/, '');\n\n // Whether a response holds Secrets is decided from the resource that was\n // requested and from the kind reported by the API server, rather than from\n // the object type, which callers can influence. The request is checked on\n // its own so that masking does not depend on the response at all, and the\n // kind is checked as well to cover requests that reach Secrets by some\n // other shape.\n const containsSecrets =\n resource.objectType === 'secrets' ||\n itemKind === 'Secret' ||\n (resource.group === '' &&\n resource.apiVersion === 'v1' &&\n resource.plural === 'secrets');\n\n const resources = containsSecrets ? this.redactSecretData(items) : items;\n\n if (resource.objectType === 'customresources') {\n if (itemKind === undefined) {\n throw new Error(\n `Missing kind in response when fetching '${resource.plural}'`,\n );\n }\n return resources.map((item: JsonObject) => ({\n ...item,\n kind: itemKind,\n }));\n }\n\n return resources;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var KubernetesFetcher = require('./KubernetesFetcher.cjs.js');
4
+ var KubernetesConnection = require('./KubernetesConnection.cjs.js');
4
5
  var buildDefaultAuthStrategyMap = require('../auth/buildDefaultAuthStrategyMap.cjs.js');
5
6
  var luxon = require('luxon');
6
7
  var index = require('../cluster-locator/index.cjs.js');
@@ -16,9 +17,13 @@ class KubernetesInitializer {
16
17
  static create(opts) {
17
18
  return new KubernetesInitializer(opts);
18
19
  }
19
- async defaultFetcher() {
20
+ defaultConnection() {
21
+ return new KubernetesConnection.KubernetesConnection({ logger: this.opts.logger });
22
+ }
23
+ async defaultFetcher(connection) {
20
24
  return new KubernetesFetcher.KubernetesClientBasedFetcher({
21
- logger: this.opts.logger
25
+ logger: this.opts.logger,
26
+ connection
22
27
  });
23
28
  }
24
29
  async defaultAuthStrategy() {
@@ -96,9 +101,10 @@ class KubernetesInitializer {
96
101
  };
97
102
  }
98
103
  async init() {
104
+ const connection = this.defaultConnection();
99
105
  const fetcher = await this.opts.fetcher?.({
100
- getDefault: () => this.defaultFetcher()
101
- }) ?? await this.defaultFetcher();
106
+ getDefault: () => this.defaultFetcher(connection)
107
+ }) ?? await this.defaultFetcher(connection);
102
108
  const authStrategyMap = this.opts.authStrategyMap ?? await this.defaultAuthStrategy();
103
109
  const clusterSupplier = await this.opts.clusterSupplier?.({
104
110
  getDefault: () => this.defaultClusterSupplier({ authStrategyMap })
@@ -1 +1 @@
1
- {"version":3,"file":"KubernetesInitializer.cjs.js","sources":["../../src/service/KubernetesInitializer.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthenticationStrategy,\n CustomResource,\n KubernetesClustersSupplier,\n KubernetesClusterSupplierFactory,\n KubernetesFetcher,\n KubernetesFetcherFactory,\n KubernetesObjectsProviderFactory,\n KubernetesObjectTypes,\n KubernetesServiceLocator,\n KubernetesServiceLocatorFactory,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\nimport {\n AuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { buildDefaultAuthStrategyMap } from '../auth/buildDefaultAuthStrategyMap';\nimport { Duration } from 'luxon';\nimport { getCombinedClusterSupplier } from '../cluster-locator';\nimport { DispatchStrategy } from '../auth/DispatchStrategy';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { buildDefaultServiceLocator } from '../service-locator/buildDefaultServiceLocator';\nimport {\n ALL_OBJECTS,\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\n\ntype Opts = {\n fetcher?: KubernetesFetcherFactory;\n clusterSupplier?: KubernetesClusterSupplierFactory;\n serviceLocator?: KubernetesServiceLocatorFactory;\n objectsProvider?: KubernetesObjectsProviderFactory;\n authStrategyMap?: Map<string, AuthenticationStrategy>;\n logger: LoggerService;\n config: RootConfigService;\n catalog: CatalogService;\n auth: AuthService;\n};\n\nexport class KubernetesInitializer {\n private readonly opts: Opts;\n\n constructor(opts: Opts) {\n this.opts = opts;\n }\n\n static create(opts: Opts) {\n return new KubernetesInitializer(opts);\n }\n\n private async defaultFetcher() {\n return new KubernetesClientBasedFetcher({\n logger: this.opts.logger,\n });\n }\n\n private async defaultAuthStrategy() {\n return buildDefaultAuthStrategyMap({\n logger: this.opts.logger,\n config: this.opts.config,\n });\n }\n\n private async defaultClusterSupplier(opts: {\n authStrategyMap: Map<string, AuthenticationStrategy>;\n }) {\n const refreshInterval = Duration.fromObject({\n minutes: 60,\n });\n\n return getCombinedClusterSupplier(\n this.opts.config,\n this.opts.catalog,\n new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n this.opts.logger,\n refreshInterval,\n this.opts.auth,\n );\n }\n\n private async defaultServiceLocator(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n }) {\n return buildDefaultServiceLocator({\n config: this.opts.config,\n clusterSupplier: opts.clusterSupplier,\n });\n }\n\n private async defaultObjectsProvider(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n authStrategyMap: Map<string, AuthenticationStrategy>;\n fetcher: KubernetesFetcher;\n serviceLocator: KubernetesServiceLocator;\n customResources: CustomResource[];\n objectTypesToFetch: ObjectToFetch[];\n }) {\n return new KubernetesFanOutHandler({\n logger: this.opts.logger,\n config: this.opts.config,\n fetcher: opts.fetcher,\n serviceLocator: opts.serviceLocator,\n customResources: opts.customResources,\n objectTypesToFetch: opts.objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n });\n }\n\n private async defaultObjectsProviderOptions() {\n const customResources: CustomResource[] = (\n this.opts.config.getOptionalConfigArray('kubernetes.customResources') ??\n []\n ).map(\n c =>\n ({\n group: c.getString('group'),\n apiVersion: c.getString('apiVersion'),\n plural: c.getString('plural'),\n objectType: 'customresources',\n } as CustomResource),\n );\n const objectTypesToFetchStrings = this.opts.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const apiVersionOverrides = this.opts.config.getOptionalConfig(\n 'kubernetes.apiVersionOverrides',\n );\n\n let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = ALL_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n\n if (apiVersionOverrides) {\n objectTypesToFetch ??= DEFAULT_OBJECTS;\n\n for (const obj of objectTypesToFetch) {\n if (apiVersionOverrides.has(obj.objectType)) {\n obj.apiVersion = apiVersionOverrides.getString(obj.objectType);\n }\n }\n }\n\n return {\n customResources,\n objectTypesToFetch: objectTypesToFetch ?? DEFAULT_OBJECTS,\n };\n }\n\n async init() {\n const fetcher =\n (await this.opts.fetcher?.({\n getDefault: () => this.defaultFetcher(),\n })) ?? (await this.defaultFetcher());\n\n const authStrategyMap =\n this.opts.authStrategyMap ?? (await this.defaultAuthStrategy());\n\n const clusterSupplier =\n (await this.opts.clusterSupplier?.({\n getDefault: () => this.defaultClusterSupplier({ authStrategyMap }),\n })) ?? (await this.defaultClusterSupplier({ authStrategyMap }));\n\n const serviceLocator =\n (await this.opts.serviceLocator?.({\n getDefault: () => this.defaultServiceLocator({ clusterSupplier }),\n clusterSupplier,\n })) ?? (await this.defaultServiceLocator({ clusterSupplier }));\n\n const { customResources, objectTypesToFetch } =\n await this.defaultObjectsProviderOptions();\n\n const objectsProvider =\n (await this.opts.objectsProvider?.({\n getDefault: () =>\n this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }),\n fetcher,\n clusterSupplier,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(authStrategyMap.entries()),\n }),\n })) ??\n (await this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }));\n\n return {\n fetcher,\n authStrategyMap,\n clusterSupplier,\n serviceLocator,\n objectsProvider,\n };\n }\n}\n"],"names":["KubernetesClientBasedFetcher","buildDefaultAuthStrategyMap","Duration","getCombinedClusterSupplier","DispatchStrategy","buildDefaultServiceLocator","KubernetesFanOutHandler","ALL_OBJECTS","DEFAULT_OBJECTS"],"mappings":"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"}
1
+ {"version":3,"file":"KubernetesInitializer.cjs.js","sources":["../../src/service/KubernetesInitializer.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthenticationStrategy,\n CustomResource,\n KubernetesClustersSupplier,\n KubernetesClusterSupplierFactory,\n KubernetesFetcher,\n KubernetesFetcherFactory,\n KubernetesObjectsProviderFactory,\n KubernetesObjectTypes,\n KubernetesServiceLocator,\n KubernetesServiceLocatorFactory,\n ObjectToFetch,\n} from '@backstage/plugin-kubernetes-node';\nimport { KubernetesClientBasedFetcher } from './KubernetesFetcher';\nimport { KubernetesConnection } from './KubernetesConnection';\nimport {\n AuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { buildDefaultAuthStrategyMap } from '../auth/buildDefaultAuthStrategyMap';\nimport { Duration } from 'luxon';\nimport { getCombinedClusterSupplier } from '../cluster-locator';\nimport { DispatchStrategy } from '../auth/DispatchStrategy';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { buildDefaultServiceLocator } from '../service-locator/buildDefaultServiceLocator';\nimport {\n ALL_OBJECTS,\n DEFAULT_OBJECTS,\n KubernetesFanOutHandler,\n} from './KubernetesFanOutHandler';\n\ntype Opts = {\n fetcher?: KubernetesFetcherFactory;\n clusterSupplier?: KubernetesClusterSupplierFactory;\n serviceLocator?: KubernetesServiceLocatorFactory;\n objectsProvider?: KubernetesObjectsProviderFactory;\n authStrategyMap?: Map<string, AuthenticationStrategy>;\n logger: LoggerService;\n config: RootConfigService;\n catalog: CatalogService;\n auth: AuthService;\n};\n\nexport class KubernetesInitializer {\n private readonly opts: Opts;\n\n constructor(opts: Opts) {\n this.opts = opts;\n }\n\n static create(opts: Opts) {\n return new KubernetesInitializer(opts);\n }\n\n private defaultConnection() {\n return new KubernetesConnection({ logger: this.opts.logger });\n }\n\n private async defaultFetcher(connection: KubernetesConnection) {\n return new KubernetesClientBasedFetcher({\n logger: this.opts.logger,\n connection,\n });\n }\n\n private async defaultAuthStrategy() {\n return buildDefaultAuthStrategyMap({\n logger: this.opts.logger,\n config: this.opts.config,\n });\n }\n\n private async defaultClusterSupplier(opts: {\n authStrategyMap: Map<string, AuthenticationStrategy>;\n }) {\n const refreshInterval = Duration.fromObject({\n minutes: 60,\n });\n\n return getCombinedClusterSupplier(\n this.opts.config,\n this.opts.catalog,\n new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n this.opts.logger,\n refreshInterval,\n this.opts.auth,\n );\n }\n\n private async defaultServiceLocator(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n }) {\n return buildDefaultServiceLocator({\n config: this.opts.config,\n clusterSupplier: opts.clusterSupplier,\n });\n }\n\n private async defaultObjectsProvider(opts: {\n clusterSupplier: KubernetesClustersSupplier;\n authStrategyMap: Map<string, AuthenticationStrategy>;\n fetcher: KubernetesFetcher;\n serviceLocator: KubernetesServiceLocator;\n customResources: CustomResource[];\n objectTypesToFetch: ObjectToFetch[];\n }) {\n return new KubernetesFanOutHandler({\n logger: this.opts.logger,\n config: this.opts.config,\n fetcher: opts.fetcher,\n serviceLocator: opts.serviceLocator,\n customResources: opts.customResources,\n objectTypesToFetch: opts.objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(opts.authStrategyMap.entries()),\n }),\n });\n }\n\n private async defaultObjectsProviderOptions() {\n const customResources: CustomResource[] = (\n this.opts.config.getOptionalConfigArray('kubernetes.customResources') ??\n []\n ).map(\n c =>\n ({\n group: c.getString('group'),\n apiVersion: c.getString('apiVersion'),\n plural: c.getString('plural'),\n objectType: 'customresources',\n } as CustomResource),\n );\n const objectTypesToFetchStrings = this.opts.config.getOptionalStringArray(\n 'kubernetes.objectTypes',\n ) as KubernetesObjectTypes[];\n\n const apiVersionOverrides = this.opts.config.getOptionalConfig(\n 'kubernetes.apiVersionOverrides',\n );\n\n let objectTypesToFetch: ObjectToFetch[] | undefined = undefined;\n\n if (objectTypesToFetchStrings) {\n objectTypesToFetch = ALL_OBJECTS.filter(obj =>\n objectTypesToFetchStrings.includes(obj.objectType),\n );\n }\n\n if (apiVersionOverrides) {\n objectTypesToFetch ??= DEFAULT_OBJECTS;\n\n for (const obj of objectTypesToFetch) {\n if (apiVersionOverrides.has(obj.objectType)) {\n obj.apiVersion = apiVersionOverrides.getString(obj.objectType);\n }\n }\n }\n\n return {\n customResources,\n objectTypesToFetch: objectTypesToFetch ?? DEFAULT_OBJECTS,\n };\n }\n\n async init() {\n const connection = this.defaultConnection();\n\n const fetcher =\n (await this.opts.fetcher?.({\n getDefault: () => this.defaultFetcher(connection),\n })) ?? (await this.defaultFetcher(connection));\n\n const authStrategyMap =\n this.opts.authStrategyMap ?? (await this.defaultAuthStrategy());\n\n const clusterSupplier =\n (await this.opts.clusterSupplier?.({\n getDefault: () => this.defaultClusterSupplier({ authStrategyMap }),\n })) ?? (await this.defaultClusterSupplier({ authStrategyMap }));\n\n const serviceLocator =\n (await this.opts.serviceLocator?.({\n getDefault: () => this.defaultServiceLocator({ clusterSupplier }),\n clusterSupplier,\n })) ?? (await this.defaultServiceLocator({ clusterSupplier }));\n\n const { customResources, objectTypesToFetch } =\n await this.defaultObjectsProviderOptions();\n\n const objectsProvider =\n (await this.opts.objectsProvider?.({\n getDefault: () =>\n this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }),\n fetcher,\n clusterSupplier,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n authStrategy: new DispatchStrategy({\n authStrategyMap: Object.fromEntries(authStrategyMap.entries()),\n }),\n })) ??\n (await this.defaultObjectsProvider({\n clusterSupplier,\n authStrategyMap,\n fetcher,\n serviceLocator,\n customResources,\n objectTypesToFetch,\n }));\n\n return {\n fetcher,\n authStrategyMap,\n clusterSupplier,\n serviceLocator,\n objectsProvider,\n };\n }\n}\n"],"names":["KubernetesConnection","KubernetesClientBasedFetcher","buildDefaultAuthStrategyMap","Duration","getCombinedClusterSupplier","DispatchStrategy","buildDefaultServiceLocator","KubernetesFanOutHandler","ALL_OBJECTS","DEFAULT_OBJECTS"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-kubernetes-backend",
3
- "version": "0.21.10",
3
+ "version": "0.21.11-next.1",
4
4
  "description": "A Backstage backend plugin that integrates towards Kubernetes",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -49,18 +49,18 @@
49
49
  "@aws-crypto/sha256-js": "^5.0.0",
50
50
  "@aws-sdk/credential-providers": "^3.350.0",
51
51
  "@azure/identity": "^4.0.0",
52
- "@backstage/backend-plugin-api": "^1.10.0",
53
- "@backstage/catalog-client": "^1.16.1",
54
- "@backstage/catalog-model": "^1.10.0",
55
- "@backstage/config": "^1.3.8",
56
- "@backstage/errors": "^1.3.1",
57
- "@backstage/integration-aws-node": "^0.2.1",
58
- "@backstage/plugin-catalog-node": "^2.2.4",
59
- "@backstage/plugin-kubernetes-common": "^0.9.12",
60
- "@backstage/plugin-kubernetes-node": "^0.4.7",
61
- "@backstage/plugin-permission-common": "^0.9.10",
62
- "@backstage/plugin-permission-node": "^0.11.3",
63
- "@backstage/types": "^1.2.2",
52
+ "@backstage/backend-plugin-api": "1.10.1-next.0",
53
+ "@backstage/catalog-client": "1.16.2-next.0",
54
+ "@backstage/catalog-model": "1.10.0",
55
+ "@backstage/config": "1.3.8",
56
+ "@backstage/errors": "1.3.1",
57
+ "@backstage/integration-aws-node": "0.2.1",
58
+ "@backstage/plugin-catalog-node": "2.2.5-next.0",
59
+ "@backstage/plugin-kubernetes-common": "0.9.13-next.0",
60
+ "@backstage/plugin-kubernetes-node": "0.4.8-next.1",
61
+ "@backstage/plugin-permission-common": "0.9.10",
62
+ "@backstage/plugin-permission-node": "0.11.4-next.0",
63
+ "@backstage/types": "1.2.2",
64
64
  "@google-cloud/container": "^5.0.0",
65
65
  "@jest-mock/express": "^2.0.1",
66
66
  "@kubernetes/client-node": "1.4.0",
@@ -70,18 +70,19 @@
70
70
  "express-promise-router": "^4.1.0",
71
71
  "fs-extra": "^11.2.0",
72
72
  "http-proxy-middleware": "^2.0.6",
73
- "ipaddr.js": "^2.3.0",
74
73
  "lodash": "^4.17.21",
75
74
  "luxon": "^3.0.0",
76
- "node-fetch": "^2.7.0"
75
+ "node-fetch": "^2.7.0",
76
+ "split2": "^4.2.0"
77
77
  },
78
78
  "devDependencies": {
79
- "@backstage/backend-defaults": "^0.17.8",
80
- "@backstage/backend-test-utils": "^1.11.6",
81
- "@backstage/cli": "^0.36.5",
82
- "@backstage/plugin-permission-backend": "^0.7.15",
83
- "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.22",
79
+ "@backstage/backend-defaults": "0.17.9-next.0",
80
+ "@backstage/backend-test-utils": "1.11.7-next.0",
81
+ "@backstage/cli": "0.36.6-next.0",
82
+ "@backstage/plugin-permission-backend": "0.7.16-next.0",
83
+ "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.23-next.0",
84
84
  "@types/express": "^4.17.6",
85
+ "@types/split2": "^4.2.0",
85
86
  "msw": "^1.0.0",
86
87
  "supertest": "^7.0.0",
87
88
  "ws": "^8.20.1"
@@ -1,15 +0,0 @@
1
- 'use strict';
2
-
3
- function readCatalogClusterLocatorOptions(config) {
4
- return {
5
- dangerouslyAllowClusterUrls: config.getOptionalStringArray(
6
- "dangerouslyAllowClusterUrls"
7
- ),
8
- dangerouslyAllowSkipTLSVerify: config.getOptionalBoolean(
9
- "dangerouslyAllowSkipTLSVerify"
10
- )
11
- };
12
- }
13
-
14
- exports.readCatalogClusterLocatorOptions = readCatalogClusterLocatorOptions;
15
- //# sourceMappingURL=CatalogClusterLocatorOptions.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CatalogClusterLocatorOptions.cjs.js","sources":["../../src/cluster-locator/CatalogClusterLocatorOptions.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ValidateClusterApiServerUrlOptions } from './validateClusterApiServerUrl';\n\nexport type CatalogClusterLocatorOptions =\n ValidateClusterApiServerUrlOptions & {\n dangerouslyAllowSkipTLSVerify?: boolean;\n };\n\nexport function readCatalogClusterLocatorOptions(\n config: Config,\n): CatalogClusterLocatorOptions {\n return {\n dangerouslyAllowClusterUrls: config.getOptionalStringArray(\n 'dangerouslyAllowClusterUrls',\n ),\n dangerouslyAllowSkipTLSVerify: config.getOptionalBoolean(\n 'dangerouslyAllowSkipTLSVerify',\n ),\n };\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iCACd,MAAA,EAC8B;AAC9B,EAAA,OAAO;AAAA,IACL,6BAA6B,MAAA,CAAO,sBAAA;AAAA,MAClC;AAAA,KACF;AAAA,IACA,+BAA+B,MAAA,CAAO,kBAAA;AAAA,MACpC;AAAA;AACF,GACF;AACF;;;;"}
@@ -1,19 +0,0 @@
1
- 'use strict';
2
-
3
- const KUBERNETES_IO_PREFIX = "kubernetes.io/";
4
- const BLOCKED_AUTH_METADATA_KEYS = /* @__PURE__ */ new Set(["serviceAccountToken"]);
5
- function filterCatalogClusterAuthMetadata(annotations) {
6
- const filtered = {};
7
- for (const [key, value] of Object.entries(annotations)) {
8
- if (BLOCKED_AUTH_METADATA_KEYS.has(key)) {
9
- continue;
10
- }
11
- if (key.startsWith(KUBERNETES_IO_PREFIX)) {
12
- filtered[key] = value;
13
- }
14
- }
15
- return filtered;
16
- }
17
-
18
- exports.filterCatalogClusterAuthMetadata = filterCatalogClusterAuthMetadata;
19
- //# sourceMappingURL=catalogClusterAuthMetadata.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"catalogClusterAuthMetadata.cjs.js","sources":["../../src/cluster-locator/catalogClusterAuthMetadata.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst KUBERNETES_IO_PREFIX = 'kubernetes.io/';\n\nconst BLOCKED_AUTH_METADATA_KEYS = new Set(['serviceAccountToken']);\n\nexport function filterCatalogClusterAuthMetadata(\n annotations: Record<string, string>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(annotations)) {\n if (BLOCKED_AUTH_METADATA_KEYS.has(key)) {\n continue;\n }\n if (key.startsWith(KUBERNETES_IO_PREFIX)) {\n filtered[key] = value;\n }\n }\n\n return filtered;\n}\n"],"names":[],"mappings":";;AAgBA,MAAM,oBAAA,GAAuB,gBAAA;AAE7B,MAAM,0BAAA,mBAA6B,IAAI,GAAA,CAAI,CAAC,qBAAqB,CAAC,CAAA;AAE3D,SAAS,iCACd,WAAA,EACwB;AACxB,EAAA,MAAM,WAAmC,EAAC;AAE1C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtD,IAAA,IAAI,0BAAA,CAA2B,GAAA,CAAI,GAAG,CAAA,EAAG;AACvC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,GAAA,CAAI,UAAA,CAAW,oBAAoB,CAAA,EAAG;AACxC,MAAA,QAAA,CAAS,GAAG,CAAA,GAAI,KAAA;AAAA,IAClB;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;;;;"}
@@ -1,89 +0,0 @@
1
- 'use strict';
2
-
3
- var promises = require('node:dns/promises');
4
- var ipaddr = require('ipaddr.js');
5
-
6
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
-
8
- var ipaddr__default = /*#__PURE__*/_interopDefaultCompat(ipaddr);
9
-
10
- function isNonPublicIp(ip) {
11
- try {
12
- const addr = ipaddr__default.default.parse(ip);
13
- return addr.range() !== "unicast";
14
- } catch {
15
- return true;
16
- }
17
- }
18
- function hostnameMatchesPattern(hostname, pattern) {
19
- const normalizedHost = hostname.toLowerCase();
20
- const normalizedPattern = pattern.toLowerCase();
21
- if (normalizedPattern.startsWith("*.")) {
22
- const suffix = normalizedPattern.slice(1);
23
- const bare = normalizedPattern.slice(2);
24
- return normalizedHost.endsWith(suffix) || normalizedHost === bare;
25
- }
26
- return normalizedHost === normalizedPattern;
27
- }
28
- function isHostnameDangerouslyAllowed(hostname, patterns) {
29
- if (!patterns?.length) {
30
- return false;
31
- }
32
- return patterns.some((pattern) => hostnameMatchesPattern(hostname, pattern));
33
- }
34
- async function validateHostNotPrivate(hostname) {
35
- const addresses = await promises.lookup(hostname, { all: true });
36
- const nonPublic = addresses.find((addr) => isNonPublicIp(addr.address));
37
- if (nonPublic) {
38
- throw new Error(
39
- `Kubernetes cluster API server URL hostname "${hostname}" resolves to a non-public address`
40
- );
41
- }
42
- }
43
- function validateLiteralHost(hostname) {
44
- if (ipaddr__default.default.isValid(hostname) || ipaddr__default.default.IPv6.isValid(hostname)) {
45
- if (isNonPublicIp(hostname)) {
46
- throw new Error(
47
- "Kubernetes cluster API server URL must not use a non-public IP address"
48
- );
49
- }
50
- }
51
- }
52
- async function validateClusterApiServerUrl(apiServerUrl, options = {}) {
53
- let url;
54
- try {
55
- url = new URL(apiServerUrl);
56
- } catch {
57
- throw new Error("Kubernetes cluster API server URL is not a valid URL");
58
- }
59
- if (url.username || url.password) {
60
- throw new Error(
61
- "Kubernetes cluster API server URL must not contain credentials"
62
- );
63
- }
64
- const hostname = url.hostname.toLowerCase();
65
- const ssrfExempt = isHostnameDangerouslyAllowed(
66
- hostname,
67
- options.dangerouslyAllowClusterUrls
68
- );
69
- if (url.protocol !== "https:" && url.protocol !== "http:") {
70
- throw new Error(
71
- "Kubernetes cluster API server URL must use the HTTP or HTTPS scheme"
72
- );
73
- }
74
- if (!ssrfExempt) {
75
- if (url.protocol !== "https:") {
76
- throw new Error(
77
- "Kubernetes cluster API server URL must use the HTTPS scheme"
78
- );
79
- }
80
- validateLiteralHost(hostname);
81
- if (!ipaddr__default.default.isValid(hostname) && !ipaddr__default.default.IPv6.isValid(hostname)) {
82
- await validateHostNotPrivate(hostname);
83
- }
84
- }
85
- return url;
86
- }
87
-
88
- exports.validateClusterApiServerUrl = validateClusterApiServerUrl;
89
- //# sourceMappingURL=validateClusterApiServerUrl.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validateClusterApiServerUrl.cjs.js","sources":["../../src/cluster-locator/validateClusterApiServerUrl.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lookup } from 'node:dns/promises';\nimport ipaddr from 'ipaddr.js';\n\nexport type ValidateClusterApiServerUrlOptions = {\n /**\n * Hostname patterns (for example `127.0.0.1` or `*.example.com`) for which\n * catalog cluster API server URLs may use HTTP or non-public addresses.\n * Configured in app-config only; not controllable from catalog entities.\n */\n dangerouslyAllowClusterUrls?: string[];\n};\n\nfunction isNonPublicIp(ip: string): boolean {\n try {\n const addr = ipaddr.parse(ip);\n return addr.range() !== 'unicast';\n } catch {\n return true;\n }\n}\n\nfunction hostnameMatchesPattern(hostname: string, pattern: string): boolean {\n const normalizedHost = hostname.toLowerCase();\n const normalizedPattern = pattern.toLowerCase();\n\n if (normalizedPattern.startsWith('*.')) {\n const suffix = normalizedPattern.slice(1);\n const bare = normalizedPattern.slice(2);\n return normalizedHost.endsWith(suffix) || normalizedHost === bare;\n }\n\n return normalizedHost === normalizedPattern;\n}\n\nfunction isHostnameDangerouslyAllowed(\n hostname: string,\n patterns: string[] | undefined,\n): boolean {\n if (!patterns?.length) {\n return false;\n }\n return patterns.some(pattern => hostnameMatchesPattern(hostname, pattern));\n}\n\nasync function validateHostNotPrivate(hostname: string): Promise<void> {\n const addresses = await lookup(hostname, { all: true });\n const nonPublic = addresses.find(addr => isNonPublicIp(addr.address));\n if (nonPublic) {\n throw new Error(\n `Kubernetes cluster API server URL hostname \"${hostname}\" resolves to a non-public address`,\n );\n }\n}\n\nfunction validateLiteralHost(hostname: string): void {\n if (ipaddr.isValid(hostname) || ipaddr.IPv6.isValid(hostname)) {\n if (isNonPublicIp(hostname)) {\n throw new Error(\n 'Kubernetes cluster API server URL must not use a non-public IP address',\n );\n }\n }\n}\n\n/**\n * Validates a catalog-provided Kubernetes API server URL against SSRF protections.\n *\n * @throws when the URL is not permitted\n */\nexport async function validateClusterApiServerUrl(\n apiServerUrl: string,\n options: ValidateClusterApiServerUrlOptions = {},\n): Promise<URL> {\n let url: URL;\n try {\n url = new URL(apiServerUrl);\n } catch {\n throw new Error('Kubernetes cluster API server URL is not a valid URL');\n }\n\n if (url.username || url.password) {\n throw new Error(\n 'Kubernetes cluster API server URL must not contain credentials',\n );\n }\n\n const hostname = url.hostname.toLowerCase();\n const ssrfExempt = isHostnameDangerouslyAllowed(\n hostname,\n options.dangerouslyAllowClusterUrls,\n );\n\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new Error(\n 'Kubernetes cluster API server URL must use the HTTP or HTTPS scheme',\n );\n }\n\n if (!ssrfExempt) {\n if (url.protocol !== 'https:') {\n throw new Error(\n 'Kubernetes cluster API server URL must use the HTTPS scheme',\n );\n }\n\n validateLiteralHost(hostname);\n\n if (!ipaddr.isValid(hostname) && !ipaddr.IPv6.isValid(hostname)) {\n await validateHostNotPrivate(hostname);\n }\n }\n\n return url;\n}\n"],"names":["ipaddr","lookup"],"mappings":"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"}