From 2c4ee265c7caa0c3cceb26fdd770aa2ade1bfea0 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 3 Oct 2024 15:58:09 +0200 Subject: [PATCH] app-backend: validate env config against schema Signed-off-by: Vincenzo Scamporlino --- .changeset/friendly-coins-approve.md | 5 + .../src/lib/config/readFrontendConfig.test.ts | 99 +++++++++++++++++++ .../src/lib/config/readFrontendConfig.ts | 10 +- 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 .changeset/friendly-coins-approve.md create mode 100644 plugins/app-backend/src/lib/config/readFrontendConfig.test.ts diff --git a/.changeset/friendly-coins-approve.md b/.changeset/friendly-coins-approve.md new file mode 100644 index 0000000000000..71a8240089abb --- /dev/null +++ b/.changeset/friendly-coins-approve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Fixed unexpected behaviour where configuration supplied with `APP_CONFIG_*` environment variables where not filtered by the configuration schema. diff --git a/plugins/app-backend/src/lib/config/readFrontendConfig.test.ts b/plugins/app-backend/src/lib/config/readFrontendConfig.test.ts new file mode 100644 index 0000000000000..a4089b848ae95 --- /dev/null +++ b/plugins/app-backend/src/lib/config/readFrontendConfig.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { readFrontendConfig } from './readFrontendConfig'; +import { ConfigReader } from '@backstage/config'; + +describe('readFrontendConfig', () => { + const mockDir = createMockDirectory(); + + afterEach(() => { + mockDir.clear(); + }); + + it('should validate env config', async () => { + mockDir.setContent({ + 'appDir/.config-schema.json': JSON.stringify({ + schemas: [ + { + value: { + type: 'object', + + properties: { + app: { + type: 'object', + properties: { + secretOfLife: { + type: 'string', + visibility: 'secret', + }, + backendConfig: { + type: 'string', + visibility: 'backend', + }, + publicValue: { + type: 'string', + visibility: 'frontend', + }, + }, + }, + }, + }, + }, + ], + backstageConfigSchemaVersion: 1, + }), + }); + + const config = new ConfigReader({ + app: { + secretOfLife: '42', + backendConfig: 'backend', + publicValue: 'public', + }, + }); + + const frontendConfig = await readFrontendConfig({ + env: { + APP_CONFIG_app_secretOfLife: 'ignored', + APP_CONFIG_app_backendConfig: 'ignored', + APP_CONFIG_app_publicValue: 'injected', + }, + appDistDir: `${mockDir.path}/appDir`, + config, + }); + + expect(frontendConfig).toEqual([ + { + context: 'env', + data: { + app: { + publicValue: 'injected', + }, + }, + deprecatedKeys: [], + filteredKeys: undefined, + }, + { + context: 'app', + data: { app: { publicValue: 'public' } }, + deprecatedKeys: [], + filteredKeys: undefined, + }, + ]); + }); +}); diff --git a/plugins/app-backend/src/lib/config/readFrontendConfig.ts b/plugins/app-backend/src/lib/config/readFrontendConfig.ts index becd1c2f52819..f2b22b720f827 100644 --- a/plugins/app-backend/src/lib/config/readFrontendConfig.ts +++ b/plugins/app-backend/src/lib/config/readFrontendConfig.ts @@ -36,10 +36,9 @@ export async function readFrontendConfig(options: { }): Promise { const { env, appDistDir, config } = options; - const appConfigs = readEnvConfig(env); - const schemaPath = resolvePath(appDistDir, '.config-schema.json'); if (await fs.pathExists(schemaPath)) { + const envConfigs = readEnvConfig(env); const serializedSchema = await fs.readJson(schemaPath); try { @@ -49,11 +48,10 @@ export async function readFrontendConfig(options: { serialized: serializedSchema, })); - const frontendConfigs = await schema.process( - [{ data: config.get() as JsonObject, context: 'app' }], + return await schema.process( + [...envConfigs, { data: config.get() as JsonObject, context: 'app' }], { visibility: ['frontend'], withDeprecatedKeys: true }, ); - appConfigs.push(...frontendConfigs); } catch (error) { throw new Error( 'Invalid app bundle schema. If this error is unexpected you need to run `yarn build` in the app. ' + @@ -63,5 +61,5 @@ export async function readFrontendConfig(options: { } } - return appConfigs; + return []; }