@backstage/backend-app-api 0.7.6-next.2 → 0.7.6

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,91 @@
1
1
  # @backstage/backend-app-api
2
2
 
3
+ ## 0.7.6
4
+
5
+ ### Patch Changes
6
+
7
+ - b7de623: Fixed a potential crash when passing an object with a `null` prototype as log meta.
8
+ - 9539a0b: Deprecated `authServiceFactory`, `httpAuthServiceFactory`, and `userInfoServiceFactory`. Please import them from `@backstage/backend-defaults/auth`, `@backstage/backend-defaults/httpAuth`, and `@backstage/backend-defaults/userInfo` respectively instead.
9
+ - 3e823d3: Limited user tokens will no longer include the `ent` field in its payload. Ownership claims will now be fetched from the user info service.
10
+
11
+ NOTE: Limited tokens issued prior to this change will no longer be valid. Users may have to clear their browser cookies in order to refresh their auth tokens.
12
+
13
+ - 78a0b08: Internal refactor to handle `BackendFeature` contract change.
14
+ - 398b82a: Add support for JWKS tokens in ExternalTokenHandler.
15
+ - 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions.
16
+ - e25e467: Added a new static key based method for plugin-to-plugin auth. This is useful for example if you are running readonly service nodes that cannot use a database for the default public-key signature scheme outlined in [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). Most users should want to stay on the more secure zero-config database signature scheme.
17
+
18
+ You can generate a public and private key pair using `openssl`.
19
+
20
+ - First generate a private key using the ES256 algorithm
21
+
22
+ ```sh
23
+ openssl ecparam -name prime256v1 -genkey -out private.ec.key
24
+ ```
25
+
26
+ - Convert it to PKCS#8 format
27
+
28
+ ```sh
29
+ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private.ec.key -out private.key
30
+ ```
31
+
32
+ - Extract the public key
33
+
34
+ ```sh
35
+ openssl ec -inform PEM -outform PEM -pubout -in private.key -out public.key
36
+ ```
37
+
38
+ After this you have the files `private.key` and `public.key`. Put them in a place where you know their absolute paths, and then set up your app-config accordingly:
39
+
40
+ ```yaml
41
+ backend:
42
+ auth:
43
+ keyStore:
44
+ type: static
45
+ static:
46
+ keys:
47
+ - publicKeyFile: /absolute/path/to/public.key
48
+ privateKeyFile: /absolute/path/to/private.key
49
+ keyId: some-custom-id
50
+ ```
51
+
52
+ - 7d30d95: Fixing issue with log meta fields possibly being circular refs
53
+ - 6a576dc: Stop using `getVoidLogger` in tests to reduce the dependency on the soon-to-deprecate `backstage-common` package.
54
+ - 6551b3d: Deprecated core service factories and implementations and moved them over to
55
+ subpath exports on `@backstage/backend-defaults` instead. E.g.
56
+ `@backstage/backend-defaults/scheduler` is where the service factory and default
57
+ implementation of `coreServices.scheduler` now lives.
58
+ - d617103: Updating the logger redaction message to something less dramatic
59
+ - Updated dependencies
60
+ - @backstage/cli-node@0.2.6
61
+ - @backstage/backend-common@0.23.0
62
+ - @backstage/backend-plugin-api@0.6.19
63
+ - @backstage/backend-tasks@0.5.24
64
+ - @backstage/plugin-auth-node@0.4.14
65
+ - @backstage/plugin-permission-node@0.7.30
66
+ - @backstage/cli-common@0.1.14
67
+ - @backstage/config-loader@1.8.1
68
+ - @backstage/config@1.2.0
69
+ - @backstage/errors@1.2.4
70
+ - @backstage/types@1.1.1
71
+
72
+ ## 0.7.6-next.3
73
+
74
+ ### Patch Changes
75
+
76
+ - Updated dependencies
77
+ - @backstage/cli-node@0.2.6-next.2
78
+ - @backstage/backend-plugin-api@0.6.19-next.3
79
+ - @backstage/plugin-auth-node@0.4.14-next.3
80
+ - @backstage/plugin-permission-node@0.7.30-next.3
81
+ - @backstage/cli-common@0.1.14-next.0
82
+ - @backstage/backend-tasks@0.5.24-next.3
83
+ - @backstage/backend-common@0.23.0-next.3
84
+ - @backstage/config-loader@1.8.1-next.0
85
+ - @backstage/config@1.2.0
86
+ - @backstage/errors@1.2.4
87
+ - @backstage/types@1.1.1
88
+
3
89
  ## 0.7.6-next.2
4
90
 
5
91
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/backend-app-api",
3
- "version": "0.7.6-next.2",
3
+ "version": "0.7.6",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/config.d.ts CHANGED
@@ -33,6 +33,39 @@ export interface Config {
33
33
  */
34
34
  dangerouslyDisableDefaultAuthPolicy?: boolean;
35
35
 
36
+ /** Controls how to store keys for plugin-to-plugin auth */
37
+ pluginKeyStore?:
38
+ | { type: 'database' }
39
+ | {
40
+ type: 'static';
41
+ static: {
42
+ /**
43
+ * Must be declared at least once and the first one will be used for signing.
44
+ */
45
+ keys: Array<{
46
+ /**
47
+ * Path to the public key file in the SPKI format. Should be an absolute path.
48
+ */
49
+ publicKeyFile: string;
50
+ /**
51
+ * Path to the matching private key file in the PKCS#8 format. Should be an absolute path.
52
+ *
53
+ * The first array entry must specify a private key file, the rest must not.
54
+ */
55
+ privateKeyFile?: string;
56
+ /**
57
+ * ID to uniquely identify this key within the JWK set.
58
+ */
59
+ keyId: string;
60
+ /**
61
+ * JWS "alg" (Algorithm) Header Parameter value. Defaults to ES256.
62
+ * Must match the algorithm used to generate the keys in the provided files
63
+ */
64
+ algorithm?: string;
65
+ }>;
66
+ };
67
+ };
68
+
36
69
  /**
37
70
  * Configures methods of external access, ie ways for callers outside of
38
71
  * the Backstage ecosystem to get authorized for access to APIs that do
package/dist/alpha.cjs.js CHANGED
@@ -111,7 +111,7 @@ const featureDiscoveryServiceFactory = backendPluginApi.createServiceFactory({
111
111
  }
112
112
  });
113
113
  function isBackendFeature(value) {
114
- return !!value && typeof value === "object" && value.$$type === "@backstage/BackendFeature";
114
+ return !!value && ["object", "function"].includes(typeof value) && value.$$type === "@backstage/BackendFeature";
115
115
  }
116
116
  function isBackendFeatureFactory(value) {
117
117
  return !!value && typeof value === "function" && value.$$type === "@backstage/BackendFeatureFactory";
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha/featureDiscoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2023 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 BackendFeature,\n RootConfigService,\n RootLoggerService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n featureDiscoveryServiceRef,\n FeatureDiscoveryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { resolve as resolvePath, dirname } from 'path';\nimport fs from 'fs-extra';\nimport { BackstagePackageJson } from '@backstage/cli-node';\n\nconst DETECTED_PACKAGE_ROLES = [\n 'node-library',\n 'backend',\n 'backend-plugin',\n 'backend-plugin-module',\n];\n\n/** @internal */\nasync function findClosestPackageDir(\n searchDir: string,\n): Promise<string | undefined> {\n let path = searchDir;\n\n // Some confidence check to avoid infinite loop\n for (let i = 0; i < 1000; i++) {\n const packagePath = resolvePath(path, 'package.json');\n const exists = await fs.pathExists(packagePath);\n if (exists) {\n return path;\n }\n\n const newPath = dirname(path);\n if (newPath === path) {\n return undefined;\n }\n path = newPath;\n }\n\n throw new Error(\n `Iteration limit reached when searching for root package.json at ${searchDir}`,\n );\n}\n\n/** @internal */\nclass PackageDiscoveryService implements FeatureDiscoveryService {\n constructor(\n private readonly config: RootConfigService,\n private readonly logger: RootLoggerService,\n ) {}\n\n getDependencyNames(path: string) {\n const { dependencies } = require(path) as BackstagePackageJson;\n const packagesConfig = this.config.getOptional('backend.packages');\n\n const dependencyNames = Object.keys(dependencies || {});\n\n if (packagesConfig === 'all') {\n return dependencyNames;\n }\n\n const includedPackagesConfig = this.config.getOptionalStringArray(\n 'backend.packages.include',\n );\n\n const includedPackages = includedPackagesConfig\n ? new Set(includedPackagesConfig)\n : dependencyNames;\n const excludedPackagesSet = new Set(\n this.config.getOptionalStringArray('backend.packages.exclude'),\n );\n\n return [...includedPackages].filter(name => !excludedPackagesSet.has(name));\n }\n\n async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {\n const packagesConfig = this.config.getOptional('backend.packages');\n if (!packagesConfig || Object.keys(packagesConfig).length === 0) {\n return { features: [] };\n }\n\n const packageDir = await findClosestPackageDir(process.argv[1]);\n if (!packageDir) {\n throw new Error('Package discovery failed to find package.json');\n }\n const dependencyNames = this.getDependencyNames(\n resolvePath(packageDir, 'package.json'),\n );\n\n const features: BackendFeature[] = [];\n\n for (const name of dependencyNames) {\n const depPkg = require(require.resolve(`${name}/package.json`, {\n paths: [packageDir],\n })) as BackstagePackageJson;\n if (\n !depPkg?.backstage?.role ||\n !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role)\n ) {\n continue; // Not a backstage backend package, ignore\n }\n\n const exportedModulePaths = [\n require.resolve(name, {\n paths: [packageDir],\n }),\n ];\n\n // Find modules exported as alpha\n try {\n exportedModulePaths.push(\n require.resolve(`${name}/alpha`, { paths: [packageDir] }),\n );\n } catch {\n /* ignore */\n }\n\n for (const modulePath of exportedModulePaths) {\n const mod = require(modulePath);\n\n if (isBackendFeature(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default);\n }\n if (isBackendFeatureFactory(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default());\n }\n }\n }\n\n return { features };\n }\n}\n\n/** @alpha */\nexport const featureDiscoveryServiceFactory = createServiceFactory({\n service: featureDiscoveryServiceRef,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n },\n factory({ config, logger }) {\n return new PackageDiscoveryService(config, logger);\n },\n});\n\nfunction isBackendFeature(value: unknown): value is BackendFeature {\n return (\n !!value &&\n typeof value === 'object' &&\n (value as BackendFeature).$$type === '@backstage/BackendFeature'\n );\n}\n\nfunction isBackendFeatureFactory(\n value: unknown,\n): value is () => BackendFeature {\n return (\n !!value &&\n typeof value === 'function' &&\n (value as any).$$type === '@backstage/BackendFeatureFactory'\n );\n}\n"],"names":["path","resolvePath","fs","dirname","createServiceFactory","featureDiscoveryServiceRef","coreServices"],"mappings":"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"}
1
+ {"version":3,"file":"alpha.cjs.js","sources":["../src/alpha/featureDiscoveryServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2023 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 BackendFeature,\n RootConfigService,\n RootLoggerService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport {\n featureDiscoveryServiceRef,\n FeatureDiscoveryService,\n} from '@backstage/backend-plugin-api/alpha';\nimport { resolve as resolvePath, dirname } from 'path';\nimport fs from 'fs-extra';\nimport { BackstagePackageJson } from '@backstage/cli-node';\n\nconst DETECTED_PACKAGE_ROLES = [\n 'node-library',\n 'backend',\n 'backend-plugin',\n 'backend-plugin-module',\n];\n\n/** @internal */\nasync function findClosestPackageDir(\n searchDir: string,\n): Promise<string | undefined> {\n let path = searchDir;\n\n // Some confidence check to avoid infinite loop\n for (let i = 0; i < 1000; i++) {\n const packagePath = resolvePath(path, 'package.json');\n const exists = await fs.pathExists(packagePath);\n if (exists) {\n return path;\n }\n\n const newPath = dirname(path);\n if (newPath === path) {\n return undefined;\n }\n path = newPath;\n }\n\n throw new Error(\n `Iteration limit reached when searching for root package.json at ${searchDir}`,\n );\n}\n\n/** @internal */\nclass PackageDiscoveryService implements FeatureDiscoveryService {\n constructor(\n private readonly config: RootConfigService,\n private readonly logger: RootLoggerService,\n ) {}\n\n getDependencyNames(path: string) {\n const { dependencies } = require(path) as BackstagePackageJson;\n const packagesConfig = this.config.getOptional('backend.packages');\n\n const dependencyNames = Object.keys(dependencies || {});\n\n if (packagesConfig === 'all') {\n return dependencyNames;\n }\n\n const includedPackagesConfig = this.config.getOptionalStringArray(\n 'backend.packages.include',\n );\n\n const includedPackages = includedPackagesConfig\n ? new Set(includedPackagesConfig)\n : dependencyNames;\n const excludedPackagesSet = new Set(\n this.config.getOptionalStringArray('backend.packages.exclude'),\n );\n\n return [...includedPackages].filter(name => !excludedPackagesSet.has(name));\n }\n\n async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {\n const packagesConfig = this.config.getOptional('backend.packages');\n if (!packagesConfig || Object.keys(packagesConfig).length === 0) {\n return { features: [] };\n }\n\n const packageDir = await findClosestPackageDir(process.argv[1]);\n if (!packageDir) {\n throw new Error('Package discovery failed to find package.json');\n }\n const dependencyNames = this.getDependencyNames(\n resolvePath(packageDir, 'package.json'),\n );\n\n const features: BackendFeature[] = [];\n\n for (const name of dependencyNames) {\n const depPkg = require(require.resolve(`${name}/package.json`, {\n paths: [packageDir],\n })) as BackstagePackageJson;\n if (\n !depPkg?.backstage?.role ||\n !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role)\n ) {\n continue; // Not a backstage backend package, ignore\n }\n\n const exportedModulePaths = [\n require.resolve(name, {\n paths: [packageDir],\n }),\n ];\n\n // Find modules exported as alpha\n try {\n exportedModulePaths.push(\n require.resolve(`${name}/alpha`, { paths: [packageDir] }),\n );\n } catch {\n /* ignore */\n }\n\n for (const modulePath of exportedModulePaths) {\n const mod = require(modulePath);\n\n if (isBackendFeature(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default);\n }\n if (isBackendFeatureFactory(mod.default)) {\n this.logger.info(`Detected: ${name}`);\n features.push(mod.default());\n }\n }\n }\n\n return { features };\n }\n}\n\n/** @alpha */\nexport const featureDiscoveryServiceFactory = createServiceFactory({\n service: featureDiscoveryServiceRef,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n },\n factory({ config, logger }) {\n return new PackageDiscoveryService(config, logger);\n },\n});\n\nfunction isBackendFeature(value: unknown): value is BackendFeature {\n return (\n !!value &&\n ['object', 'function'].includes(typeof value) &&\n (value as BackendFeature).$$type === '@backstage/BackendFeature'\n );\n}\n\nfunction isBackendFeatureFactory(\n value: unknown,\n): value is () => BackendFeature {\n return (\n !!value &&\n typeof value === 'function' &&\n (value as any).$$type === '@backstage/BackendFeatureFactory'\n );\n}\n"],"names":["path","resolvePath","fs","dirname","createServiceFactory","featureDiscoveryServiceRef","coreServices"],"mappings":"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"}