backend-app-api: remove publicKeyStoreService implementation

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2024-04-03 14:21:11 +02:00
parent 8c0401a1d5
commit 6ce253d7ca
12 changed files with 88 additions and 87 deletions
@@ -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'
@@ -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<JWK>;
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,
});
@@ -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[] }>;
};
@@ -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<TPrincipal = unknown> =
@@ -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 }),
@@ -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<void>;
}
export type KeyStore = {
addKey(key: KeyPayload): Promise<any>;
listKeys(): Promise<{ keys: KeyPayload[] }>;
};
export type KeyPayload = {
id: string;
key: InternalKey;
expiresAt: Date;
};
export type InternalKey = JsonObject & { kid: string };
@@ -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<JsonObject[]>;
addKey(options: { key: JsonObject; expiresAt: Date }): Promise<void>;
}
/**
* @public
*/
@@ -53,7 +47,6 @@ export const httpRouterServiceFactory = createServiceFactory(
rootHttpRouter: coreServices.rootHttpRouter,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
publicKeyStore: coreServices.publicKeyStore,
},
async factory({
auth,
@@ -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';
@@ -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';
@@ -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) {
@@ -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(),
@@ -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}.
*
@@ -68,4 +68,3 @@ export type {
} from './UrlReaderService';
export type { BackstageUserInfo, UserInfoService } from './UserInfoService';
export type { IdentityService } from './IdentityService';
export type { PublicKeyStoreService } from './PublicKeyStoreService';