From a9e01071111482eda704bbd428860ba8d4afa650 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 1 Feb 2024 12:19:38 +0100 Subject: [PATCH] auth-backend: refuse to issue excessively large tokens Signed-off-by: Patrik Oldsberg --- .changeset/hip-ears-add.md | 5 +++++ .../src/identity/TokenFactory.test.ts | 17 +++++++++++++++++ .../auth-backend/src/identity/TokenFactory.ts | 14 +++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .changeset/hip-ears-add.md diff --git a/.changeset/hip-ears-add.md b/.changeset/hip-ears-add.md new file mode 100644 index 0000000000..1e7f656507 --- /dev/null +++ b/.changeset/hip-ears-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +The auth backend will now refuse to issue user tokens are excessively large. diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts index 4f6df89210..e1dd12333d 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.test.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts @@ -154,6 +154,23 @@ describe('TokenFactory', () => { }).rejects.toThrow(); }); + it('should refuse to issue excessively large tokens', async () => { + const factory = new TokenFactory({ + issuer: 'my-issuer', + keyStore: new MemoryKeyStore(), + keyDurationSeconds: 5, + logger, + }); + + await expect(() => { + return factory.issueToken({ + claims: { sub: 'user:ns/n', ent: Array(10000).fill('group:ns/n') }, + }); + }).rejects.toThrow( + /^Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims./, + ); + }); + it('should defaults to ES256 when no algorithm string is supplied', async () => { const keyDurationSeconds = 5; const factory = new TokenFactory({ diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index ca16f8821f..f778077267 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -23,6 +23,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { AnyJWK, KeyStore, TokenIssuer, TokenParams } from './types'; const MS_IN_S = 1000; +const MAX_TOKEN_LENGTH = 32768; // At 64 bytes per entity ref this still leaves room for about 500 entities type Options = { logger: LoggerService; @@ -97,7 +98,8 @@ export class TokenFactory implements TokenIssuer { throw new AuthenticationError('No algorithm was provided in the key'); } - return new SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp }) + const claims = { ...additionalClaims, iss, sub, ent, aud, iat, exp }; + const token = await new SignJWT(claims) .setProtectedHeader({ alg: key.alg, kid: key.kid }) .setIssuer(iss) .setAudience(aud) @@ -105,6 +107,16 @@ export class TokenFactory implements TokenIssuer { .setIssuedAt(iat) .setExpirationTime(exp) .sign(await importJWK(key)); + + if (token.length > MAX_TOKEN_LENGTH) { + throw new Error( + `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify( + claims, + )}'`, + ); + } + + return token; } // This will be called by other services that want to verify ID tokens.