backend-common: stop accepting self-reported pluginId in ServerTokenManager
Until we have a solution for assigning plugin/backend identities in a way that's not open to impersonation, we can't build on top of this information, so it's better to leave it out for now. Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -29,32 +29,22 @@ describe('ServerTokenManager', () => {
|
||||
describe('getToken', () => {
|
||||
it('should return a token if secret in config exists', async () => {
|
||||
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
|
||||
expect((await tokenManager.getToken('test-plugin')).token).toBeDefined();
|
||||
expect((await tokenManager.getToken()).token).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return a token string if using a noop TokenManager', async () => {
|
||||
const tokenManager = ServerTokenManager.noop();
|
||||
expect((await tokenManager.getToken('test-plugin')).token).toBeDefined();
|
||||
expect((await tokenManager.getToken()).token).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
it('should not throw if token is valid', async () => {
|
||||
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
|
||||
const { token } = await tokenManager.getToken('test-plugin');
|
||||
const { token } = await tokenManager.getToken();
|
||||
await expect(tokenManager.authenticate(token)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should allow retrieving the pluginId from valid tokens', async () => {
|
||||
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
|
||||
const { token } = await tokenManager.getToken('test-plugin-123');
|
||||
|
||||
await expect(tokenManager.authenticate(token)).resolves.toEqual({
|
||||
pluginId: 'test-plugin-123',
|
||||
token,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if token is invalid', async () => {
|
||||
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
|
||||
await expect(
|
||||
@@ -66,7 +56,7 @@ describe('ServerTokenManager', () => {
|
||||
const tokenManager1 = ServerTokenManager.fromConfig(configWithSecret);
|
||||
const tokenManager2 = ServerTokenManager.fromConfig(configWithSecret);
|
||||
|
||||
const { token } = await tokenManager1.getToken('test-plugin');
|
||||
const { token } = await tokenManager1.getToken();
|
||||
|
||||
await expect(tokenManager2.authenticate(token)).resolves.not.toThrow();
|
||||
});
|
||||
@@ -90,10 +80,10 @@ describe('ServerTokenManager', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const { token: token1 } = await tokenManager1.getToken('test-plugin');
|
||||
const { token: token1 } = await tokenManager1.getToken();
|
||||
await expect(tokenManager3.authenticate(token1)).resolves.not.toThrow();
|
||||
|
||||
const { token: token2 } = await tokenManager2.getToken('test-plugin');
|
||||
const { token: token2 } = await tokenManager2.getToken();
|
||||
await expect(tokenManager3.authenticate(token2)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
@@ -109,7 +99,7 @@ describe('ServerTokenManager', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const { token } = await tokenManager1.getToken('test-plugin');
|
||||
const { token } = await tokenManager1.getToken();
|
||||
|
||||
await expect(tokenManager2.authenticate(token)).rejects.toThrowError(
|
||||
/invalid server token/i,
|
||||
@@ -124,7 +114,7 @@ describe('ServerTokenManager', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const { token } = await noopTokenManager.getToken('test-plugin');
|
||||
const { token } = await noopTokenManager.getToken();
|
||||
|
||||
await expect(tokenManager.authenticate(token)).rejects.toThrowError(
|
||||
/invalid server token/i,
|
||||
@@ -140,7 +130,7 @@ describe('ServerTokenManager', () => {
|
||||
});
|
||||
|
||||
it('should accept tokens it generates', async () => {
|
||||
const { token } = await noopTokenManager.getToken('test-plugin');
|
||||
const { token } = await noopTokenManager.getToken();
|
||||
|
||||
await expect(noopTokenManager.authenticate(token)).resolves.not.toThrow();
|
||||
});
|
||||
@@ -150,7 +140,7 @@ describe('ServerTokenManager', () => {
|
||||
await expect(
|
||||
noopTokenManager.authenticate(
|
||||
(
|
||||
await noopTokenManager2.getToken('test-plugin')
|
||||
await noopTokenManager2.getToken()
|
||||
).token,
|
||||
),
|
||||
).resolves.not.toThrow();
|
||||
@@ -159,21 +149,8 @@ describe('ServerTokenManager', () => {
|
||||
it('should not accept signed tokens', async () => {
|
||||
const tokenManager = ServerTokenManager.fromConfig(configWithSecret);
|
||||
await expect(
|
||||
noopTokenManager.authenticate(
|
||||
(
|
||||
await tokenManager.getToken('test-plugin')
|
||||
).token,
|
||||
),
|
||||
noopTokenManager.authenticate((await tokenManager.getToken()).token),
|
||||
).rejects.toThrowError(/invalid server token/i);
|
||||
});
|
||||
|
||||
it('should allow retrieving the pluginId from valid tokens', async () => {
|
||||
const { token } = await noopTokenManager.getToken('test-plugin-123');
|
||||
|
||||
await expect(noopTokenManager.authenticate(token)).resolves.toEqual({
|
||||
pluginId: 'test-plugin-123',
|
||||
token,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { JWKS, JWK, JWT } from 'jose';
|
||||
import { Config } from '@backstage/config';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { ServerIdentity, TokenManager } from './types';
|
||||
import { TokenManager } from './types';
|
||||
|
||||
/**
|
||||
* Creates and validates tokens for use during backend-to-backend
|
||||
@@ -54,24 +54,19 @@ export class ServerTokenManager implements TokenManager {
|
||||
}
|
||||
}
|
||||
|
||||
async getToken(pluginId: string): Promise<{ token: string }> {
|
||||
// TODO(mtlewis): should we wrap pluginId in a urn?
|
||||
const jwt = JWT.sign({ sub: pluginId }, this.signingKey, {
|
||||
async getToken(): Promise<{ token: string }> {
|
||||
const jwt = JWT.sign({ sub: 'backstage-server' }, this.signingKey, {
|
||||
algorithm: this.signingAlgorithm,
|
||||
});
|
||||
|
||||
return { token: jwt };
|
||||
}
|
||||
|
||||
async authenticate(token: string): Promise<ServerIdentity> {
|
||||
let decodedJwt = {};
|
||||
async authenticate(token: string): Promise<void> {
|
||||
try {
|
||||
JWT.verify(token, this.verificationKeys);
|
||||
decodedJwt = JWT.decode(token);
|
||||
} catch (e) {
|
||||
throw new AuthenticationError('Invalid server token');
|
||||
}
|
||||
|
||||
return { pluginId: decodedJwt.sub, token };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,30 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A (pluginId, token) pair.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ServerIdentity = {
|
||||
/**
|
||||
* The ID of the plugin backend to which this
|
||||
* identity corresponds.
|
||||
*/
|
||||
pluginId: string;
|
||||
|
||||
/**
|
||||
* The token used to authenticate the plugin backend.
|
||||
*/
|
||||
token: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for creating and validating tokens.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TokenManager {
|
||||
getToken: (pluginId: string) => Promise<{ token: string }>;
|
||||
authenticate: (token: string) => Promise<ServerIdentity | undefined>;
|
||||
getToken: () => Promise<{ token: string }>;
|
||||
authenticate: (token: string) => Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user