diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts index 64c2bed2a9..16f05ef9d0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts @@ -51,7 +51,9 @@ describe('authServiceFactory', () => { const searchAuth = await tester.get('search'); const catalogAuth = await tester.get('catalog'); - const { token: searchToken } = await searchAuth.issueToken({}); + const { token: searchToken } = await searchAuth.issueServiceToken({ + forward: await searchAuth.getOwnCredentials(), + }); await expect(searchAuth.authenticate(searchToken)).resolves.toEqual( expect.objectContaining({ @@ -79,7 +81,7 @@ describe('authServiceFactory', () => { const catalogAuth = await tester.get('catalog'); await expect( - catalogAuth.issueToken({ + catalogAuth.issueServiceToken({ forward: { $$type: '@backstage/BackstageCredentials', version: 'v1', @@ -101,7 +103,7 @@ describe('authServiceFactory', () => { const catalogAuth = await tester.get('catalog'); - const { token } = await catalogAuth.issueToken({ + const { token } = await catalogAuth.issueServiceToken({ forward: { $$type: '@backstage/BackstageCredentials', version: 'v1', 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 5190b90e7e..c2b4236f45 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts @@ -43,12 +43,10 @@ export type InternalBackstageCredentials = export function createCredentialsWithServicePrincipal( sub: string, - token: string, ): InternalBackstageCredentials { return { $$type: '@backstage/BackstageCredentials', version: 'v1', - token, principal: { type: 'service', subject: sub, @@ -112,6 +110,7 @@ class DefaultAuthService implements AuthService { constructor( private readonly tokenManager: TokenManager, private readonly identity: IdentityService, + private readonly pluginId: string, ) {} async authenticate(token: string): Promise { @@ -120,10 +119,7 @@ class DefaultAuthService implements AuthService { // Legacy service-to-service token if (sub === 'backstage-server' && !aud) { await this.tokenManager.authenticate(token); - return createCredentialsWithServicePrincipal( - 'external:backstage-plugin', - token, - ); + return createCredentialsWithServicePrincipal('external:backstage-plugin'); } // User Backstage token @@ -158,28 +154,31 @@ class DefaultAuthService implements AuthService { return true; } - async issueToken(options?: { - forward?: BackstageCredentials; - }): Promise<{ token: string }> { - const internalForward = - options?.forward && toInternalBackstageCredentials(options.forward); + async getOwnCredentials(): Promise< + BackstageCredentials + > { + return createCredentialsWithServicePrincipal(`plugin:${this.pluginId}`); + } - if (internalForward) { - const { type } = internalForward.principal; - if (type === 'user') { + async issueServiceToken(options: { + forward: BackstageCredentials; + }): Promise<{ token: string }> { + const internalForward = toInternalBackstageCredentials(options.forward); + const { type } = internalForward.principal; + + switch (type) { + case 'service': + return this.tokenManager.getToken(); + case 'user': if (!internalForward.token) { throw new Error('User credentials is unexpectedly missing token'); } return { token: internalForward.token }; - } else if (type !== 'service') { + default: throw new AuthenticationError( `Refused to issue service token for credential type '${type}'`, ); - } } - - const { token } = await this.tokenManager.getToken(); - return { token }; } } @@ -190,12 +189,13 @@ export const authServiceFactory = createServiceFactory({ config: coreServices.rootConfig, logger: coreServices.rootLogger, discovery: coreServices.discovery, + plugin: coreServices.pluginMetadata, }, createRootContext({ config, logger }) { return ServerTokenManager.fromConfig(config, { logger }); }, - async factory({ discovery }, tokenManager) { + async factory({ discovery, plugin }, tokenManager) { const identity = DefaultIdentityClient.create({ discovery }); - return new DefaultAuthService(tokenManager, identity); + return new DefaultAuthService(tokenManager, identity, plugin.getId()); }, }); diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts index 79c74ffa9f..d23c35fb43 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts @@ -133,10 +133,10 @@ class DefaultHttpAuthService implements HttpAuthService { } async requestHeaders(options: { - forward?: BackstageCredentials; + forward: BackstageCredentials; }): Promise> { return { - Authorization: `Bearer ${await this.auth.issueToken(options)}`, + Authorization: `Bearer ${await this.auth.issueServiceToken(options)}`, }; } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index a3e5930375..23e9172bee 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -22,12 +22,14 @@ export interface AuthService { // (undocumented) authenticate(token: string): Promise; // (undocumented) + getOwnCredentials(): Promise>; + // (undocumented) isPrincipal( credentials: BackstageCredentials, type: TType, ): credentials is BackstageCredentials; // (undocumented) - issueToken(options: { forward?: BackstageCredentials }): Promise<{ + issueServiceToken(options: { forward: BackstageCredentials }): Promise<{ token: string; }>; } @@ -305,8 +307,8 @@ export interface HttpAuthService { // (undocumented) issueUserCookie(res: Response_2): Promise; // (undocumented) - requestHeaders(options?: { - forward?: BackstageCredentials; + requestHeaders(options: { + forward: BackstageCredentials; }): Promise>; } diff --git a/packages/backend-plugin-api/src/services/definitions/AuthService.ts b/packages/backend-plugin-api/src/services/definitions/AuthService.ts index 8a946e5c91..8eeb40a5ca 100644 --- a/packages/backend-plugin-api/src/services/definitions/AuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/AuthService.ts @@ -71,9 +71,10 @@ export interface AuthService { type: TType, ): credentials is BackstageCredentials; + getOwnCredentials(): Promise>; + // TODO: should the caller provide the target plugin ID? - // TODO: how can we make it very difficult to forget to forward credentials - issueToken(options: { - forward?: BackstageCredentials; + issueServiceToken(options: { + forward: BackstageCredentials; }): Promise<{ token: string }>; } diff --git a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts index 949ca1af0a..937bb1ee82 100644 --- a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts @@ -43,8 +43,8 @@ export interface HttpAuthService { BackstageCredentials >; - requestHeaders(options?: { - forward?: BackstageCredentials; + requestHeaders(options: { + forward: BackstageCredentials; }): Promise>; issueUserCookie(res: Response): Promise;