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:
MT Lewis
2021-11-26 10:21:11 +00:00
parent 4e6b2319a2
commit 5510cee039
3 changed files with 17 additions and 63 deletions
@@ -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 };
}
}
+2 -20
View File
@@ -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>;
}