Merge pull request #24403 from backstage/rugvip/token-manager
backend-app-api: stop requiring keys to be configured for the token manager
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys.
|
||||
@@ -246,7 +246,6 @@ catalog:
|
||||
plugins:
|
||||
- catalog
|
||||
- search
|
||||
- todo
|
||||
|
||||
processors:
|
||||
ldapOrg:
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
BackstageServicePrincipal,
|
||||
BackstageUserPrincipal,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { AuthenticationError, ForwardedError } from '@backstage/errors';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { decodeJwt } from 'jose';
|
||||
import { ExternalTokenHandler } from './external/ExternalTokenHandler';
|
||||
@@ -151,7 +151,13 @@ export class DefaultAuthService implements AuthService {
|
||||
});
|
||||
}
|
||||
// If the target plugin does not support the new auth service, fall back to using old token format
|
||||
return this.tokenManager.getToken();
|
||||
return this.tokenManager.getToken().catch(error => {
|
||||
throw new ForwardedError(
|
||||
`Unable to generate legacy token for communication with the '${targetPluginId}' plugin. ` +
|
||||
`You will typically encounter this error when attempting to call a plugin that does not exist, or is deployed with an old version of Backstage`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
case 'user': {
|
||||
const { token } = internalForward;
|
||||
if (!token) {
|
||||
|
||||
+11
-16
@@ -14,25 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { tokenManagerServiceFactory } from './tokenManagerServiceFactory';
|
||||
import { ServiceFactoryTester } from '@backstage/backend-test-utils';
|
||||
|
||||
describe('tokenManagerFactory', () => {
|
||||
it('should create managers that can share tokens in development', async () => {
|
||||
(process.env as { NODE_ENV?: string }).NODE_ENV = 'development';
|
||||
it('should create a disabled manager without configuration', async () => {
|
||||
const tokenManager = await ServiceFactoryTester.from(
|
||||
tokenManagerServiceFactory,
|
||||
).get();
|
||||
|
||||
const factory = tokenManagerServiceFactory() as any;
|
||||
const deps = {
|
||||
config: new ConfigReader({}),
|
||||
logger: { warn() {} } as unknown as LoggerService,
|
||||
};
|
||||
|
||||
const ctx = await factory.createRootContext?.(deps);
|
||||
const manager1 = await factory.factory!(deps, ctx);
|
||||
const manager2 = await factory.factory!(deps, ctx);
|
||||
|
||||
const { token } = await manager1.getToken();
|
||||
await expect(manager2.authenticate(token)).resolves.toBeUndefined();
|
||||
await expect(tokenManager.authenticate('abc')).rejects.toThrow(
|
||||
'no legacy keys are configured',
|
||||
);
|
||||
await expect(tokenManager.getToken()).rejects.toThrow(
|
||||
'no legacy keys are configured',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ export const tokenManagerServiceFactory = createServiceFactory({
|
||||
createRootContext({ config, logger }) {
|
||||
return ServerTokenManager.fromConfig(config, {
|
||||
logger,
|
||||
allowDisabledTokenManager: true,
|
||||
});
|
||||
},
|
||||
async factory(_deps, tokenManager) {
|
||||
|
||||
@@ -772,7 +772,7 @@ export class ServerTokenManager implements TokenManager {
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: ServerTokenManagerOptions,
|
||||
): ServerTokenManager;
|
||||
): TokenManager;
|
||||
// (undocumented)
|
||||
getToken(): Promise<{
|
||||
token: string;
|
||||
@@ -782,6 +782,7 @@ export class ServerTokenManager implements TokenManager {
|
||||
|
||||
// @public
|
||||
export interface ServerTokenManagerOptions {
|
||||
allowDisabledTokenManager?: boolean;
|
||||
logger: LoggerService;
|
||||
}
|
||||
|
||||
|
||||
@@ -301,6 +301,20 @@ describe('ServerTokenManager', () => {
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should throw errors when disabled', async () => {
|
||||
const manager = ServerTokenManager.fromConfig(new ConfigReader({}), {
|
||||
logger,
|
||||
allowDisabledTokenManager: true,
|
||||
});
|
||||
|
||||
await expect(manager.getToken()).rejects.toThrow(
|
||||
'Unable to generate legacy token',
|
||||
);
|
||||
await expect(manager.authenticate('nah')).rejects.toThrow(
|
||||
'Unable to authenticate legacy token',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NODE_ENV === development', () => {
|
||||
|
||||
@@ -40,6 +40,23 @@ class NoopTokenManager implements TokenManager {
|
||||
async authenticate() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A token manager that throws an error when trying to generate or authenticate tokens.
|
||||
*/
|
||||
class DisabledTokenManager implements TokenManager {
|
||||
async getToken(): Promise<{ token: string }> {
|
||||
throw new Error(
|
||||
"Unable to generate legacy token, no legacy keys are configured in 'backend.auth.keys' or 'backend.auth.externalAccess'",
|
||||
);
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
throw new AuthenticationError(
|
||||
"Unable to authenticate legacy token, no legacy keys are configured in 'backend.auth.keys' or 'backend.auth.externalAccess'",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link ServerTokenManager}.
|
||||
*
|
||||
@@ -50,6 +67,11 @@ export interface ServerTokenManagerOptions {
|
||||
* The logger to use.
|
||||
*/
|
||||
logger: LoggerService;
|
||||
|
||||
/**
|
||||
* Whether to disable the token manager if no keys are configured.
|
||||
*/
|
||||
allowDisabledTokenManager?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +95,10 @@ export class ServerTokenManager implements TokenManager {
|
||||
return new NoopTokenManager();
|
||||
}
|
||||
|
||||
static fromConfig(config: Config, options: ServerTokenManagerOptions) {
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: ServerTokenManagerOptions,
|
||||
): TokenManager {
|
||||
const oldSecrets = config
|
||||
.getOptionalConfigArray('backend.auth.keys')
|
||||
?.map(c => c.getString('secret'));
|
||||
@@ -87,6 +112,14 @@ export class ServerTokenManager implements TokenManager {
|
||||
return new ServerTokenManager(secrets, options);
|
||||
}
|
||||
|
||||
// When using the new backend system with new auth services we instead rely
|
||||
// on the new plugin auth and external access configurations. If no legacy
|
||||
// keys are configured we disable the token manager completely, rather than
|
||||
// requiring users to configure legacy keys.
|
||||
if (options.allowDisabledTokenManager) {
|
||||
return new DisabledTokenManager();
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
'You must configure at least one key in backend.auth.keys for production.',
|
||||
|
||||
Reference in New Issue
Block a user