@backstage/plugin-kubernetes-backend 0.14.1 → 0.14.2-next.0

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,5 +1,40 @@
1
1
  # @backstage/plugin-kubernetes-backend
2
2
 
3
+ ## 0.14.2-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 7233f57: Fixed an issue where a misleading error message would be logged when an
8
+ unsupported service locator method was specified.
9
+ - a775596: Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface.
10
+ - 7278d80: The purpose of this patch is to add a new login method which is `googleServiceAccount` configuring the kubernetes properties in the app-config.yaml file with authProvider key
11
+ - daad576: Clusters configured with the `aws` authentication strategy can now customize the
12
+ `x-k8s-aws-id` header value used to generate tokens. This value can be specified
13
+ specified via the `kubernetes.io/x-k8s-aws-id` parameter (in
14
+ `metadata.annotations` for clusters in the catalog, or the `authMetadata` block
15
+ on clusters in the app-config). This is particularly helpful when a Backstage
16
+ instance contains multiple AWS clusters with the same name in different regions
17
+ -- using this new parameter, the clusters can be given different logical names
18
+ to distinguish them but still use the same ID for the purposes of generating
19
+ tokens.
20
+ - f180cba: Enabling authentication to kubernetes clusters with mTLS x509 client certs
21
+ - 7f6ff25: Custom per-cluster auth metadata (mainly for use with custom `AuthenticationStrategy` implementations) can now be specified in the `authMetadata` property of clusters in the app-config.
22
+ - Updated dependencies
23
+ - @backstage/backend-common@0.21.0-next.0
24
+ - @backstage/plugin-kubernetes-node@0.1.4-next.0
25
+ - @backstage/catalog-client@1.6.0-next.0
26
+ - @backstage/plugin-kubernetes-common@0.7.4-next.0
27
+ - @backstage/plugin-auth-node@0.4.4-next.0
28
+ - @backstage/plugin-catalog-node@1.6.2-next.0
29
+ - @backstage/plugin-permission-node@0.7.21-next.0
30
+ - @backstage/backend-plugin-api@0.6.10-next.0
31
+ - @backstage/catalog-model@1.4.3
32
+ - @backstage/config@1.1.1
33
+ - @backstage/errors@1.2.3
34
+ - @backstage/integration-aws-node@0.1.8
35
+ - @backstage/types@1.1.1
36
+ - @backstage/plugin-permission-common@0.7.12
37
+
3
38
  ## 0.14.1
4
39
 
5
40
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-kubernetes-backend",
3
- "version": "0.14.1",
3
+ "version": "0.14.2-next.0",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/config.d.ts CHANGED
@@ -46,13 +46,16 @@ export interface Config {
46
46
  /** @visibility secret */
47
47
  serviceAccountToken?: string;
48
48
  /** @visibility frontend */
49
- authProvider:
49
+ authProvider?:
50
+ | 'aks'
50
51
  | 'aws'
51
- | 'google'
52
- | 'serviceAccount'
53
52
  | 'azure'
53
+ | 'google'
54
+ | 'googleServiceAccount'
54
55
  | 'oidc'
55
- | 'googleServiceAccount';
56
+ | 'serviceAccount';
57
+ /** @visibility secret */
58
+ authMetadata?: object;
56
59
  /** @visibility frontend */
57
60
  oidcTokenProvider?: string;
58
61
  /** @visibility frontend */
@@ -81,6 +84,8 @@ export interface Config {
81
84
  /** @visibility frontend */
82
85
  region?: string;
83
86
  /** @visibility frontend */
87
+ authProvider?: string;
88
+ /** @visibility frontend */
84
89
  skipTLSVerify?: boolean;
85
90
  /** @visibility frontend */
86
91
  skipMetricsLookup?: boolean;
package/dist/index.cjs.js CHANGED
@@ -61,6 +61,9 @@ class AksStrategy {
61
61
  validateCluster() {
62
62
  return [];
63
63
  }
64
+ presentAuthMetadata(_authMetadata) {
65
+ return {};
66
+ }
64
67
  }
65
68
 
66
69
  class AnonymousStrategy {
@@ -70,6 +73,9 @@ class AnonymousStrategy {
70
73
  validateCluster() {
71
74
  return [];
72
75
  }
76
+ presentAuthMetadata(_authMetadata) {
77
+ return {};
78
+ }
73
79
  }
74
80
 
75
81
  var __defProp$b = Object.defineProperty;
@@ -85,10 +91,11 @@ class AwsIamStrategy {
85
91
  this.credsManager = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(opts.config);
86
92
  }
87
93
  async getCredential(clusterDetails) {
94
+ var _a;
88
95
  return {
89
96
  type: "bearer token",
90
97
  token: await this.getBearerToken(
91
- clusterDetails.name,
98
+ (_a = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_CLUSTER_ID]) != null ? _a : clusterDetails.name,
92
99
  clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE],
93
100
  clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]
94
101
  )
@@ -97,7 +104,7 @@ class AwsIamStrategy {
97
104
  validateCluster() {
98
105
  return [];
99
106
  }
100
- async getBearerToken(clusterName, assumeRole, externalId) {
107
+ async getBearerToken(clusterId, assumeRole, externalId) {
101
108
  var _a, _b;
102
109
  const region = (_a = process.env.AWS_REGION) != null ? _a : defaultRegion;
103
110
  let credentials = (await this.credsManager.getCredentialProvider()).sdkCredentialProvider;
@@ -123,7 +130,7 @@ class AwsIamStrategy {
123
130
  {
124
131
  headers: {
125
132
  host: `sts.${region}.amazonaws.com`,
126
- "x-k8s-aws-id": clusterName
133
+ "x-k8s-aws-id": clusterId
127
134
  },
128
135
  hostname: `sts.${region}.amazonaws.com`,
129
136
  method: "GET",
@@ -147,6 +154,9 @@ class AwsIamStrategy {
147
154
  const url = `https://${request.hostname}${request.path}?${query}`;
148
155
  return `k8s-aws-v1.${Buffer.from(url).toString("base64url")}`;
149
156
  }
157
+ presentAuthMetadata(_authMetadata) {
158
+ return {};
159
+ }
150
160
  }
151
161
 
152
162
  var __defProp$a = Object.defineProperty;
@@ -202,6 +212,9 @@ class AzureIdentityStrategy {
202
212
  tokenExpired() {
203
213
  return Date.now() >= this.accessToken.expiresOnTimestamp;
204
214
  }
215
+ presentAuthMetadata(_authMetadata) {
216
+ return {};
217
+ }
205
218
  }
206
219
 
207
220
  class GoogleStrategy {
@@ -217,6 +230,9 @@ class GoogleStrategy {
217
230
  validateCluster() {
218
231
  return [];
219
232
  }
233
+ presentAuthMetadata(_authMetadata) {
234
+ return {};
235
+ }
220
236
  }
221
237
 
222
238
  class GoogleServiceAccountStrategy {
@@ -233,6 +249,9 @@ class GoogleServiceAccountStrategy {
233
249
  validateCluster() {
234
250
  return [];
235
251
  }
252
+ presentAuthMetadata(_authMetadata) {
253
+ return {};
254
+ }
236
255
  }
237
256
 
238
257
  var __defProp$9 = Object.defineProperty;
@@ -267,6 +286,9 @@ class DispatchStrategy {
267
286
  }
268
287
  return strategy.validateCluster(authMetadata);
269
288
  }
289
+ presentAuthMetadata(_authMetadata) {
290
+ return {};
291
+ }
270
292
  }
271
293
 
272
294
  class ServiceAccountStrategy {
@@ -286,6 +308,9 @@ class ServiceAccountStrategy {
286
308
  validateCluster() {
287
309
  return [];
288
310
  }
311
+ presentAuthMetadata(_authMetadata) {
312
+ return {};
313
+ }
289
314
  }
290
315
 
291
316
  class OidcStrategy {
@@ -312,6 +337,9 @@ class OidcStrategy {
312
337
  }
313
338
  return [];
314
339
  }
340
+ presentAuthMetadata(_authMetadata) {
341
+ return {};
342
+ }
315
343
  }
316
344
 
317
345
  var __defProp$8 = Object.defineProperty;
@@ -328,18 +356,26 @@ class ConfigClusterLocator {
328
356
  static fromConfig(config, authStrategy) {
329
357
  return new ConfigClusterLocator(
330
358
  config.getConfigArray("clusters").map((c) => {
331
- var _a, _b;
332
- const authProvider = c.getString("authProvider");
359
+ var _a, _b, _c;
360
+ const authMetadataBlock = c.getOptional("authMetadata");
361
+ const name = c.getString("name");
362
+ const authProvider = (_a = authMetadataBlock == null ? void 0 : authMetadataBlock[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]) != null ? _a : c.getOptionalString("authProvider");
363
+ if (!authProvider) {
364
+ throw new Error(
365
+ `cluster '${name}' has no auth provider configured; this must be specified via the 'authProvider' or 'authMetadata.${pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`
366
+ );
367
+ }
333
368
  const clusterDetails = {
334
- name: c.getString("name"),
369
+ name,
335
370
  url: c.getString("url"),
336
- skipTLSVerify: (_a = c.getOptionalBoolean("skipTLSVerify")) != null ? _a : false,
337
- skipMetricsLookup: (_b = c.getOptionalBoolean("skipMetricsLookup")) != null ? _b : false,
371
+ skipTLSVerify: (_b = c.getOptionalBoolean("skipTLSVerify")) != null ? _b : false,
372
+ skipMetricsLookup: (_c = c.getOptionalBoolean("skipMetricsLookup")) != null ? _c : false,
338
373
  caData: c.getOptionalString("caData"),
339
374
  caFile: c.getOptionalString("caFile"),
340
375
  authMetadata: {
341
376
  [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider,
342
- ...ConfigClusterLocator.parseAuthMetadata(c)
377
+ ...ConfigClusterLocator.parseAuthMetadata(c),
378
+ ...authMetadataBlock
343
379
  }
344
380
  };
345
381
  const customResources = c.getOptionalConfigArray("customResources");
@@ -437,8 +473,10 @@ class GkeClusterLocator {
437
473
  const matchingResourceLabels = (_b = (_a = config.getOptionalConfigArray("matchingResourceLabels")) == null ? void 0 : _a.map((mrl) => {
438
474
  return { key: mrl.getString("key"), value: mrl.getString("value") };
439
475
  })) != null ? _b : [];
476
+ const storeAuthProviderString = config.getOptionalString("authProvider") === "googleServiceAccount" ? "googleServiceAccount" : "google";
440
477
  const options = {
441
478
  projectId: config.getString("projectId"),
479
+ authProvider: storeAuthProviderString,
442
480
  region: (_c = config.getOptionalString("region")) != null ? _c : "-",
443
481
  skipTLSVerify: (_d = config.getOptionalBoolean("skipTLSVerify")) != null ? _d : false,
444
482
  skipMetricsLookup: (_e = config.getOptionalBoolean("skipMetricsLookup")) != null ? _e : false,
@@ -474,6 +512,7 @@ class GkeClusterLocator {
474
512
  const {
475
513
  projectId,
476
514
  region,
515
+ authProvider,
477
516
  skipTLSVerify,
478
517
  skipMetricsLookup,
479
518
  exposeDashboard,
@@ -497,7 +536,7 @@ class GkeClusterLocator {
497
536
  // TODO filter out clusters which don't have name or endpoint
498
537
  name: (_a2 = r.name) != null ? _a2 : "unknown",
499
538
  url: `https://${(_b = r.endpoint) != null ? _b : ""}`,
500
- authMetadata: { [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: "google" },
539
+ authMetadata: { [pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider },
501
540
  skipTLSVerify,
502
541
  skipMetricsLookup,
503
542
  ...exposeDashboard ? {
@@ -1133,14 +1172,14 @@ class KubernetesClientBasedFetcher {
1133
1172
  let url;
1134
1173
  let requestInit;
1135
1174
  const authProvider = clusterDetails.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
1136
- if (authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default["default"].pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH)) {
1175
+ if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) {
1137
1176
  [url, requestInit] = this.fetchArgsInCluster(credential);
1138
- } else if (credential.type === "bearer token" || authProvider === "localKubectlProxy") {
1177
+ } else if (!this.isCredentialMissing(authProvider, credential)) {
1139
1178
  [url, requestInit] = this.fetchArgs(clusterDetails, credential);
1140
1179
  } else {
1141
1180
  return Promise.reject(
1142
1181
  new Error(
1143
- `no bearer token for cluster '${clusterDetails.name}' and not running in Kubernetes`
1182
+ `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`
1144
1183
  )
1145
1184
  );
1146
1185
  }
@@ -1154,6 +1193,12 @@ class KubernetesClientBasedFetcher {
1154
1193
  }
1155
1194
  return fetch__default["default"](url, requestInit);
1156
1195
  }
1196
+ isServiceAccountAuthentication(authProvider, clusterDetails) {
1197
+ return authProvider === "serviceAccount" && !clusterDetails.authMetadata.serviceAccountToken && fs__default["default"].pathExistsSync(clientNode.Config.SERVICEACCOUNT_CA_PATH);
1198
+ }
1199
+ isCredentialMissing(authProvider, credential) {
1200
+ return authProvider !== "localKubectlProxy" && credential.type === "anonymous";
1201
+ }
1157
1202
  fetchArgs(clusterDetails, credential) {
1158
1203
  var _a;
1159
1204
  const requestInit = {
@@ -1173,7 +1218,11 @@ class KubernetesClientBasedFetcher {
1173
1218
  clusterDetails.caFile,
1174
1219
  clusterDetails.caData
1175
1220
  )) != null ? _a : void 0,
1176
- rejectUnauthorized: !clusterDetails.skipTLSVerify
1221
+ rejectUnauthorized: !clusterDetails.skipTLSVerify,
1222
+ ...credential.type === "x509 client certificate" && {
1223
+ cert: credential.cert,
1224
+ key: credential.key
1225
+ }
1177
1226
  });
1178
1227
  }
1179
1228
  return [url, requestInit];
@@ -1237,20 +1286,6 @@ class KubernetesProxy {
1237
1286
  res.status(403).json({ error: new errors.NotAllowedError("Unauthorized") });
1238
1287
  return;
1239
1288
  }
1240
- const authHeader = req.header(HEADER_KUBERNETES_AUTH);
1241
- if (authHeader) {
1242
- req.headers.authorization = authHeader;
1243
- } else {
1244
- const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
1245
- req.headers
1246
- );
1247
- const credential = await this.getClusterForRequest(req).then((cd) => {
1248
- return this.authStrategy.getCredential(cd, authObj);
1249
- });
1250
- if (credential.type === "bearer token") {
1251
- req.headers.authorization = `Bearer ${credential.token}`;
1252
- }
1253
- }
1254
1289
  const middleware = await this.getMiddleware(req);
1255
1290
  if (((_a = req.header("connection")) == null ? void 0 : _a.toLowerCase()) === "upgrade" && ((_b = req.header("upgrade")) == null ? void 0 : _b.toLowerCase()) === "websocket") {
1256
1291
  middleware.upgrade(req, req.socket, void 0);
@@ -1284,7 +1319,7 @@ class KubernetesProxy {
1284
1319
  var _a;
1285
1320
  const cluster = await this.getClusterForRequest(req);
1286
1321
  const url = new URL(cluster.url);
1287
- return {
1322
+ const target = {
1288
1323
  protocol: url.protocol,
1289
1324
  host: url.hostname,
1290
1325
  port: url.port,
@@ -1293,6 +1328,24 @@ class KubernetesProxy {
1293
1328
  cluster.caData
1294
1329
  )) == null ? void 0 : _a.toString()
1295
1330
  };
1331
+ const authHeader = req.header(HEADER_KUBERNETES_AUTH);
1332
+ if (authHeader) {
1333
+ req.headers.authorization = authHeader;
1334
+ } else {
1335
+ const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth(
1336
+ req.headers
1337
+ );
1338
+ const credential = await this.getClusterForRequest(req).then((cd) => {
1339
+ return this.authStrategy.getCredential(cd, authObj);
1340
+ });
1341
+ if (credential.type === "bearer token") {
1342
+ req.headers.authorization = `Bearer ${credential.token}`;
1343
+ } else if (credential.type === "x509 client certificate") {
1344
+ target.key = credential.key;
1345
+ target.cert = credential.cert;
1346
+ }
1347
+ }
1348
+ return target;
1296
1349
  },
1297
1350
  onError: (error, req, res) => {
1298
1351
  const wrappedError = new errors.ForwardedError(
@@ -1523,7 +1576,7 @@ class KubernetesBuilder {
1523
1576
  break;
1524
1577
  default:
1525
1578
  throw new Error(
1526
- `Unsupported kubernetes.clusterLocatorMethod "${method}"`
1579
+ `Unsupported kubernetes.serviceLocatorMethod "${method}"`
1527
1580
  );
1528
1581
  }
1529
1582
  return this.serviceLocator;
@@ -1580,11 +1633,18 @@ class KubernetesBuilder {
1580
1633
  res.json({
1581
1634
  items: clusterDetails.map((cd) => {
1582
1635
  const oidcTokenProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER];
1636
+ const authProvider = cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER];
1637
+ const strategy = this.getAuthStrategyMap()[authProvider];
1638
+ let auth = {};
1639
+ if (strategy) {
1640
+ auth = strategy.presentAuthMetadata(cd.authMetadata);
1641
+ }
1583
1642
  return {
1584
1643
  name: cd.name,
1585
1644
  dashboardUrl: cd.dashboardUrl,
1586
- authProvider: cd.authMetadata[pluginKubernetesCommon.ANNOTATION_KUBERNETES_AUTH_PROVIDER],
1587
- ...oidcTokenProvider && { oidcTokenProvider }
1645
+ authProvider,
1646
+ ...oidcTokenProvider && { oidcTokenProvider },
1647
+ ...auth && Object.keys(auth).length !== 0 && { auth }
1588
1648
  };
1589
1649
  })
1590
1650
  });