diff --git a/plugins/auth-backend/src/identity/KeyStores.test.ts b/plugins/auth-backend/src/identity/KeyStores.test.ts new file mode 100644 index 0000000000..957c4c7953 --- /dev/null +++ b/plugins/auth-backend/src/identity/KeyStores.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2021 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 { DatabaseManager } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; + +import { MemoryKeyStore } from './MemoryKeyStore'; +import { DatabaseKeyStore } from './DatabaseKeyStore'; +import { FirestoreKeyStore } from './FirestoreKeyStore'; +import { KeyStores } from './KeyStores'; + +describe('KeyStores', () => { + const defaultConfigOptions = { + auth: { + keyStore: { + provider: 'memory', + }, + }, + }; + const defaultConfig = new ConfigReader(defaultConfigOptions); + + it('reads auth section from config', async () => { + const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig'); + const keyStore = await KeyStores.fromConfig(defaultConfig); + + expect(keyStore).toBeInstanceOf(MemoryKeyStore); + expect(configSpy).toHaveBeenCalledWith('auth.keyStore'); + expect( + defaultConfig + .getOptionalConfig('auth.keyStore') + ?.getOptionalString('provider'), + ).toBe(defaultConfigOptions.auth.keyStore.provider); + }); + + it('can handle without auth config', async () => { + const config = new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }); + const database = + DatabaseManager.fromConfig(config).forPlugin('auth-backend'); + const keyStore = await KeyStores.fromConfig(config, { database }); + + expect(keyStore).toBeInstanceOf(DatabaseKeyStore); + }); + + it('can handle additional provider config', async () => { + const configOptions = { + auth: { + keyStore: { + provider: 'firestore', + firestore: { + projectId: 'my-project', + }, + }, + }, + }; + const config = new ConfigReader(configOptions); + const keyStore = await KeyStores.fromConfig(config); + + expect(keyStore).toBeInstanceOf(FirestoreKeyStore); + expect( + config + .getOptionalConfig('auth.keyStore') + ?.getOptionalConfig('firestore') + ?.getOptionalString('projectId'), + ).toBe(configOptions.auth.keyStore.firestore.projectId); + }); +}); diff --git a/plugins/auth-backend/src/identity/KeyStores.ts b/plugins/auth-backend/src/identity/KeyStores.ts new file mode 100644 index 0000000000..0991f7ea7b --- /dev/null +++ b/plugins/auth-backend/src/identity/KeyStores.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2021 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 { Logger } from 'winston'; + +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; + +import { DatabaseKeyStore } from './DatabaseKeyStore'; +import { MemoryKeyStore } from './MemoryKeyStore'; +import { FirestoreKeyStore } from './FirestoreKeyStore'; +import { KeyStore } from './types'; + +type Options = { + logger?: Logger; + database?: PluginDatabaseManager; +}; + +export class KeyStores { + /** + * Looks at the `auth.keyStore` section in the application configuration + * and returns a KeyStore store. Defaults to `postgres` + * + * @returns a KeyStore store + */ + static async fromConfig( + config: Config, + options?: Options, + ): Promise { + const { logger, database } = options ?? {}; + + const ks = config.getOptionalConfig('auth.keyStore'); + const provider = ks?.getOptionalString('provider') ?? 'postgres'; + const providerConfig = ks?.getOptionalConfig(provider); + + logger?.info(`Configuring "${provider}" as KeyStore provider`); + + if (provider === 'postgres') { + if (!database) { + throw new Error('This KeyStore provider requires a database'); + } + + return await DatabaseKeyStore.create({ + database: await database.getClient(), + }); + } + + if (provider === 'memory') { + return new MemoryKeyStore(); + } + + if (provider === 'firestore') { + return FirestoreKeyStore.create({ + projectId: providerConfig?.getOptionalString('projectId'), + keyFilename: providerConfig?.getOptionalString('keyFilename'), + path: providerConfig?.getOptionalString('path'), + }); + } + + throw new Error(`Unknown KeyStore provider: ${provider}`); + } +} diff --git a/plugins/auth-backend/src/identity/index.ts b/plugins/auth-backend/src/identity/index.ts index d88339e8e4..73858d7e07 100644 --- a/plugins/auth-backend/src/identity/index.ts +++ b/plugins/auth-backend/src/identity/index.ts @@ -18,4 +18,7 @@ export { createOidcRouter } from './router'; export { IdentityClient } from './IdentityClient'; export { TokenFactory } from './TokenFactory'; export { DatabaseKeyStore } from './DatabaseKeyStore'; +export { MemoryKeyStore } from './MemoryKeyStore'; +export { FirestoreKeyStore } from './FirestoreKeyStore'; +export { KeyStores } from './KeyStores'; export type { KeyStore, TokenIssuer, TokenParams } from './types';