From 6ce253d7ca579aaa1753d3220731492d377056d5 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 3 Apr 2024 14:21:11 +0200 Subject: [PATCH] backend-app-api: remove publicKeyStoreService implementation Signed-off-by: Vincenzo Scamporlino --- .../DatabaseKeyStore.ts} | 21 ++-------- .../auth/PluginTokenHandler.ts | 31 +++++++++----- .../implementations/auth/PublicKeysClient.ts | 41 +++++++++++++++++++ .../auth/authServiceFactory.ts | 14 +++---- .../services/implementations/auth/types.ts} | 23 ++++++----- .../httpRouter/httpRouterServiceFactory.ts | 7 ---- .../src/services/implementations/index.ts | 1 - .../implementations/publicKeyStore/index.ts | 17 -------- .../src/auth/createLegacyAuthAdapters.ts | 5 +++ .../backend-defaults/src/CreateBackend.ts | 2 - .../src/services/definitions/coreServices.ts | 12 ------ .../src/services/definitions/index.ts | 1 - 12 files changed, 88 insertions(+), 87 deletions(-) rename packages/backend-app-api/src/services/implementations/{publicKeyStore/publicKeyStoreServiceFactory.ts => auth/DatabaseKeyStore.ts} (86%) create mode 100644 packages/backend-app-api/src/services/implementations/auth/PublicKeysClient.ts rename packages/{backend-plugin-api/src/services/definitions/PublicKeyStoreService.ts => backend-app-api/src/services/implementations/auth/types.ts} (71%) delete mode 100644 packages/backend-app-api/src/services/implementations/publicKeyStore/index.ts diff --git a/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts b/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts similarity index 86% rename from packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts rename to packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts index 718aa641ed..83b3d98cc6 100644 --- a/packages/backend-app-api/src/services/implementations/publicKeyStore/publicKeyStoreServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts @@ -14,16 +14,12 @@ * limitations under the License. */ -import { - DatabaseService, - PublicKeyStoreService, - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { DateTime } from 'luxon'; import { Knex } from 'knex'; import { JsonObject } from '@backstage/types'; import { resolvePackagePath } from '@backstage/backend-common'; +import { KeyStore } from './types'; const MIGRATIONS_TABLE = 'backstage_backend_public_keys__knex_migrations'; const TABLE = 'backstage_backend_public_keys__keys'; @@ -35,7 +31,7 @@ type Row = { }; /** @internal */ -export class DatabaseKeyStore implements PublicKeyStoreService { +export class DatabaseKeyStore implements KeyStore { private constructor(private readonly client: Knex) {} static async create(options: { database: DatabaseService }) { @@ -71,6 +67,7 @@ export class DatabaseKeyStore implements PublicKeyStoreService { return { keys: rows.map(row => ({ + id: row.id, key: JSON.parse(row.key), expiresAt: parseDate(row.expires_at), })), @@ -82,16 +79,6 @@ export class DatabaseKeyStore implements PublicKeyStoreService { // } } -export const publicKeyStoreServiceFactory = createServiceFactory({ - service: coreServices.publicKeyStore, - deps: { - database: coreServices.database, - }, - async factory({ database }) { - return DatabaseKeyStore.create({ database }); - }, -}); - function parseDate(date: string | Date) { const parsedDate = typeof date === 'string' diff --git a/packages/backend-app-api/src/services/implementations/auth/PluginTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/PluginTokenHandler.ts index 2f7f8098f4..4a1b17d3b9 100644 --- a/packages/backend-app-api/src/services/implementations/auth/PluginTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/PluginTokenHandler.ts @@ -14,17 +14,23 @@ * limitations under the License. */ +import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; import { - BackstageCredentials, - LoggerService, - PublicKeyStoreService, -} from '@backstage/backend-plugin-api'; -import { exportJWK, generateKeyPair, JWK, importJWK, SignJWT } from 'jose'; + decodeJwt, + exportJWK, + generateKeyPair, + JWK, + importJWK, + SignJWT, +} from 'jose'; import { DateTime } from 'luxon'; import { v4 as uuid } from 'uuid'; +import { InternalKey, KeyStore } from './types'; +import { DefaultPublicKeysClient, PublicKeysClient } from './PublicKeysClient'; type Options = { - publicKeyStore: PublicKeyStoreService; + publicKeyStore: KeyStore; + discovery: DiscoveryService; logger: LoggerService; /** Value of the issuer claim in issued tokens */ issuer: string; @@ -42,6 +48,7 @@ type Options = { export class PluginTokenHandler { private privateKeyPromise?: Promise; private keyExpiry?: Date; + private publicKeysClient: PublicKeysClient; static create(options: Options) { return new PluginTokenHandler( @@ -49,17 +56,21 @@ export class PluginTokenHandler { options.publicKeyStore, options.keyDurationSeconds, options.algorithm ?? 'ES256', + options.discovery, ); } private constructor( readonly logger: LoggerService, - readonly publicKeyStore: PublicKeyStoreService, + readonly publicKeyStore: KeyStore, readonly keyDurationSeconds: number, readonly algorithm: string, - ) {} + discovery: DiscoveryService, + ) { + this.publicKeysClient = new DefaultPublicKeysClient(discovery); + } - async verifyToken(token: string): Promise<{ subject: string }> { + async verifyToken(_token: string): Promise<{ subject: string }> { return { subject: 'is me' }; } @@ -129,7 +140,7 @@ export class PluginTokenHandler { console.log(`DEBUG: publicKey=`, publicKey); await this.publicKeyStore.addKey({ id: kid, - key: publicKey, + key: publicKey as InternalKey, expiresAt: keyExpiry, }); diff --git a/packages/backend-app-api/src/services/implementations/auth/PublicKeysClient.ts b/packages/backend-app-api/src/services/implementations/auth/PublicKeysClient.ts new file mode 100644 index 0000000000..6040e64c15 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/PublicKeysClient.ts @@ -0,0 +1,41 @@ +/* + * 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 { DiscoveryService } from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; + +export class DefaultPublicKeysClient implements PublicKeysClient { + constructor(private readonly discovery: DiscoveryService) {} + + // TODO: cache stuff + async listPublicKeys(pluginId: string) { + const response = await fetch( + `${await this.discovery.getBaseUrl( + pluginId, + )}/.backstage/auth/v1/jwks.json`, + ); + + if (response.ok) { + return response.json(); + } + throw await ResponseError.fromResponse(response); + } +} + +export type PublicKeysClient = { + listPublicKeys(pluginId: string): Promise<{ keys: JsonObject[] }>; +}; diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts index 17f78c1adc..3522da63e2 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts @@ -30,6 +30,7 @@ import { decodeJwt } from 'jose'; import { UserTokenHandler } from './UserTokenHandler'; import { PluginTokenHandler } from './PluginTokenHandler'; import { JsonObject } from '@backstage/types'; +import { DatabaseKeyStore } from './DatabaseKeyStore'; /** @internal */ export type InternalBackstageCredentials = @@ -248,26 +249,21 @@ export const authServiceFactory = createServiceFactory({ logger: coreServices.rootLogger, discovery: coreServices.discovery, plugin: coreServices.pluginMetadata, + database: coreServices.database, // Re-using the token manager makes sure that we use the same generated keys for // development as plugins that have not yet been migrated. It's important that this // keeps working as long as there are plugins that have not been migrated to the // new auth services in the new backend system. tokenManager: coreServices.tokenManager, - publicKeyStore: coreServices.publicKeyStore, }, - async factory({ - config, - discovery, - plugin, - tokenManager, - logger, - publicKeyStore, - }) { + async factory({ config, discovery, plugin, tokenManager, logger, database }) { const disableDefaultAuthPolicy = Boolean( config.getOptionalBoolean( 'backend.auth.dangerouslyDisableDefaultAuthPolicy', ), ); + + const publicKeyStore = await DatabaseKeyStore.create({ database }); return new DefaultAuthService( tokenManager, new UserTokenHandler({ discovery }), diff --git a/packages/backend-plugin-api/src/services/definitions/PublicKeyStoreService.ts b/packages/backend-app-api/src/services/implementations/auth/types.ts similarity index 71% rename from packages/backend-plugin-api/src/services/definitions/PublicKeyStoreService.ts rename to packages/backend-app-api/src/services/implementations/auth/types.ts index 6f4f80e534..b47ff2588d 100644 --- a/packages/backend-plugin-api/src/services/definitions/PublicKeyStoreService.ts +++ b/packages/backend-app-api/src/services/implementations/auth/types.ts @@ -16,14 +16,15 @@ import { JsonObject } from '@backstage/types'; -/** - * @public - */ -export interface PublicKeyStoreService { - listKeys(): Promise<{ keys: { key: JsonObject; expiresAt: Date }[] }>; - addKey(options: { - id: string; - key: JsonObject; - expiresAt: Date; - }): Promise; -} +export type KeyStore = { + addKey(key: KeyPayload): Promise; + listKeys(): Promise<{ keys: KeyPayload[] }>; +}; + +export type KeyPayload = { + id: string; + key: InternalKey; + expiresAt: Date; +}; + +export type InternalKey = JsonObject & { kid: string }; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts index 7c50dd500d..30878e3e22 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts @@ -23,14 +23,8 @@ import { Handler } from 'express'; import PromiseRouter from 'express-promise-router'; import { createLifecycleMiddleware } from './createLifecycleMiddleware'; import { createCredentialsBarrier } from './createCredentialsBarrier'; -import { JsonObject } from '@backstage/types'; import { createAuthIntegrationRouter } from '../auth'; -export interface PublicKeyStoreService { - listKeys(): Promise; - addKey(options: { key: JsonObject; expiresAt: Date }): Promise; -} - /** * @public */ @@ -53,7 +47,6 @@ export const httpRouterServiceFactory = createServiceFactory( rootHttpRouter: coreServices.rootHttpRouter, auth: coreServices.auth, httpAuth: coreServices.httpAuth, - publicKeyStore: coreServices.publicKeyStore, }, async factory({ auth, diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts index 5f19e4970c..a1114ab3e3 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -25,7 +25,6 @@ export * from './identity'; export * from './lifecycle'; export * from './logger'; export * from './permissions'; -export * from './publicKeyStore'; export * from './rootHttpRouter'; export * from './rootLifecycle'; export * from './rootLogger'; diff --git a/packages/backend-app-api/src/services/implementations/publicKeyStore/index.ts b/packages/backend-app-api/src/services/implementations/publicKeyStore/index.ts deleted file mode 100644 index 2e604cbe18..0000000000 --- a/packages/backend-app-api/src/services/implementations/publicKeyStore/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { publicKeyStoreServiceFactory } from './publicKeyStoreServiceFactory'; diff --git a/packages/backend-common/src/auth/createLegacyAuthAdapters.ts b/packages/backend-common/src/auth/createLegacyAuthAdapters.ts index 64dcc81bc6..0ba23661ef 100644 --- a/packages/backend-common/src/auth/createLegacyAuthAdapters.ts +++ b/packages/backend-common/src/auth/createLegacyAuthAdapters.ts @@ -44,6 +44,7 @@ import { } from '@backstage/plugin-auth-node'; import { decodeJwt } from 'jose'; import { PluginEndpointDiscovery } from '../discovery'; +import { JsonObject } from '@backstage/types'; class AuthCompat implements AuthService { constructor( @@ -165,6 +166,10 @@ class AuthCompat implements AuthService { } return new Date(exp * 1000); } + + listPublicServiceKeys(): Promise<{ keys: JsonObject[] }> { + throw new Error('Not implemented'); + } } function getTokenFromRequest(req: Request) { diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 63f39b18a5..5495665dd8 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -35,7 +35,6 @@ import { authServiceFactory, httpAuthServiceFactory, userInfoServiceFactory, - publicKeyStoreServiceFactory, } from '@backstage/backend-app-api'; export const defaultServiceFactories = [ @@ -50,7 +49,6 @@ export const defaultServiceFactories = [ lifecycleServiceFactory(), loggerServiceFactory(), permissionsServiceFactory(), - publicKeyStoreServiceFactory(), rootHttpRouterServiceFactory(), rootLifecycleServiceFactory(), rootLoggerServiceFactory(), diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index d4770a2b9b..c760afc7b4 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -132,18 +132,6 @@ export namespace coreServices { import('./PluginMetadataService').PluginMetadataService >({ id: 'core.pluginMetadata' }); - /** - * The service reference for the plugin scoped {@link PublicKeyStoreService}. - * - * @public - */ - export const publicKeyStore = createServiceRef< - import('./PublicKeyStoreService').PublicKeyStoreService - >({ - id: 'core.publicKeyStore', - scope: 'plugin', - }); - /** * The service reference for the root scoped {@link RootHttpRouterService}. * diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 73f2f67296..8a5176379f 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -68,4 +68,3 @@ export type { } from './UrlReaderService'; export type { BackstageUserInfo, UserInfoService } from './UserInfoService'; export type { IdentityService } from './IdentityService'; -export type { PublicKeyStoreService } from './PublicKeyStoreService';