From 8e8a25dba58de9582a3dd3379e46791f628affab Mon Sep 17 00:00:00 2001 From: Andy Muldoon Date: Fri, 22 Dec 2023 13:58:07 +0000 Subject: [PATCH 1/8] Ability for Users to configure auth token expiration [19341] Signed-off-by: Andy Muldoon --- .changeset/spotty-kids-pay.md | 5 +++ plugins/auth-backend/README.md | 9 ++++++ plugins/auth-backend/config.d.ts | 6 ++++ plugins/auth-backend/package.json | 1 + .../auth-backend/src/service/router.test.ts | 31 ++++++++++++++++++- plugins/auth-backend/src/service/router.ts | 29 ++++++++++++++--- yarn.lock | 1 + 7 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 .changeset/spotty-kids-pay.md diff --git a/.changeset/spotty-kids-pay.md b/.changeset/spotty-kids-pay.md new file mode 100644 index 0000000000..199d0fdff0 --- /dev/null +++ b/.changeset/spotty-kids-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Ability for user to configure backstage token expiration diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index ce7525fb6b..e9dca26a5b 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -165,3 +165,12 @@ To try out SAML, you can use the mock identity provider: ## Links - [The Backstage homepage](https://backstage.io) + +## Configuring Token Expiration in App Config + +The expiration feature is not enabled unless you set this in your config file: + +``` +auth: + backstageTokenExpiration: { minutes: } +``` diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 34139593d3..d24b2c64b7 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { /** Configuration options for the auth plugin */ auth?: { @@ -212,6 +214,10 @@ export interface Config { cfaccess?: { teamName: string; }; + /** + * The backstage token expiration. + */ + backstageTokenExpiration?: HumanDuration; }; }; } diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index f05551736a..07d80ddabe 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -48,6 +48,7 @@ "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/types": "workspace:^", "@google-cloud/firestore": "^7.0.0", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", diff --git a/plugins/auth-backend/src/service/router.test.ts b/plugins/auth-backend/src/service/router.test.ts index 3a1fde881e..fd339e349e 100644 --- a/plugins/auth-backend/src/service/router.test.ts +++ b/plugins/auth-backend/src/service/router.test.ts @@ -15,7 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { createOriginFilter } from './router'; +import { + createOriginFilter, + getDefaultBackstageTokenExpiryTime, +} from './router'; +import { BACKSTAGE_SESSION_EXPIRATION } from '../lib/session'; describe('Auth origin filtering', () => { const config = new ConfigReader({ @@ -52,3 +56,28 @@ describe('Auth origin filtering', () => { expect(createOriginFilter(config)(origin)).toBeTruthy(); }); }); + +describe('Test for default backstage token expiry time', () => { + it('Will return default backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe( + BACKSTAGE_SESSION_EXPIRATION, + ); + }); + + it('Will return user defined backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + auth: { + backstageTokenExpiration: { minutes: 120 }, + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(7200); + }); +}); diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 320870b3ee..eea858dd9c 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -29,7 +29,6 @@ import { } from '@backstage/backend-common'; import { assertError, NotFoundError } from '@backstage/errors'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; import { createOidcRouter, TokenFactory, KeyStores } from '../identity'; import session from 'express-session'; import connectSessionKnex from 'connect-session-knex'; @@ -41,6 +40,8 @@ import { BACKSTAGE_SESSION_EXPIRATION } from '../lib/session'; import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; /** @public */ export type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -76,9 +77,8 @@ export async function createRouter( const appUrl = config.getString('app.baseUrl'); const authUrl = await discovery.getExternalBaseUrl('auth'); - + const backstageTokenExpiration = getDefaultBackstageTokenExpiryTime(config); const authDb = AuthDatabase.create(database); - const sessionExpirationSeconds = BACKSTAGE_SESSION_EXPIRATION; const keyStore = await KeyStores.fromConfig(config, { logger, @@ -91,7 +91,7 @@ export async function createRouter( { logger: logger.child({ component: 'token-factory' }), issuer: authUrl, - sessionExpirationSeconds: sessionExpirationSeconds, + sessionExpirationSeconds: backstageTokenExpiration, }, keyStore as StaticKeyStore, ); @@ -99,7 +99,7 @@ export async function createRouter( tokenIssuer = new TokenFactory({ issuer: authUrl, keyStore, - keyDurationSeconds: sessionExpirationSeconds, + keyDurationSeconds: backstageTokenExpiration, logger: logger.child({ component: 'token-factory' }), algorithm: tokenFactoryAlgorithm ?? @@ -249,3 +249,22 @@ export function createOriginFilter( return allowedOriginPatterns.some(pattern => pattern.match(origin)); }; } + +/** @internal */ +export function getDefaultBackstageTokenExpiryTime(config: Config) { + const processingIntervalKey = 'auth.backstageTokenExpiration'; + + if (!config.has(processingIntervalKey)) { + return BACKSTAGE_SESSION_EXPIRATION; + } + + const duration = readDurationFromConfig(config, { + key: processingIntervalKey, + }); + const seconds = Math.max( + 600, + Math.round(durationToMilliseconds(duration) / 1000), + ); + + return seconds; +} diff --git a/yarn.lock b/yarn.lock index edb50887c1..c0bb552222 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4859,6 +4859,7 @@ __metadata: "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/types": "workspace:^" "@google-cloud/firestore": ^7.0.0 "@types/body-parser": ^1.19.0 "@types/cookie-parser": ^1.4.2 From 3bd2cc33c3be9da7d390f1db939f32ce8658c60a Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:15:49 +0000 Subject: [PATCH 2/8] Update plugins/auth-backend/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- plugins/auth-backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index e9dca26a5b..3133266614 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -168,7 +168,7 @@ To try out SAML, you can use the mock identity provider: ## Configuring Token Expiration in App Config -The expiration feature is not enabled unless you set this in your config file: +If you need to change Backstage token expiration from the default value of one hour, set the following in your config file: ``` auth: From 9a43c63662a2082c188c4677d0c533f21c44f729 Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:49:27 +0000 Subject: [PATCH 3/8] Update .changeset/spotty-kids-pay.md Co-authored-by: Patrik Oldsberg Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- .changeset/spotty-kids-pay.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/spotty-kids-pay.md b/.changeset/spotty-kids-pay.md index 199d0fdff0..157515201d 100644 --- a/.changeset/spotty-kids-pay.md +++ b/.changeset/spotty-kids-pay.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-auth-backend': minor +'@backstage/plugin-auth-backend': patch --- Ability for user to configure backstage token expiration From 442206eaae5cb2ba0880121a0e38cbb82c56a4a6 Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:50:22 +0000 Subject: [PATCH 4/8] Update plugins/auth-backend/README.md Co-authored-by: Patrik Oldsberg Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- plugins/auth-backend/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index 3133266614..290745454e 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -168,7 +168,9 @@ To try out SAML, you can use the mock identity provider: ## Configuring Token Expiration in App Config -If you need to change Backstage token expiration from the default value of one hour, set the following in your config file: +If you need to change Backstage token expiration from the default value of one hour you can do so through configuration. Note that this is **not** the session duration, but rather the duration that the short-term cryptographic tokens are valid for. The expiration can not be set lower than 10 minutes or above 24 hours. + +This is what the configuration looks like: ``` auth: From 0abf05967d1a0304e769c8adff71de0e38d0fcc6 Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:51:39 +0000 Subject: [PATCH 5/8] Update router.ts with suggested change Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- plugins/auth-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index eea858dd9c..e04053c478 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -262,7 +262,7 @@ export function getDefaultBackstageTokenExpiryTime(config: Config) { key: processingIntervalKey, }); const seconds = Math.max( - 600, + 86400, Math.round(durationToMilliseconds(duration) / 1000), ); From cf9b58c3405b942b3b8e692f58b57db4c6e740ef Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:52:42 +0000 Subject: [PATCH 6/8] Update router.test.ts as per the changes in router.ts Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- plugins/auth-backend/src/service/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.test.ts b/plugins/auth-backend/src/service/router.test.ts index fd339e349e..6c0b060fdb 100644 --- a/plugins/auth-backend/src/service/router.test.ts +++ b/plugins/auth-backend/src/service/router.test.ts @@ -78,6 +78,6 @@ describe('Test for default backstage token expiry time', () => { backstageTokenExpiration: { minutes: 120 }, }, }); - expect(getDefaultBackstageTokenExpiryTime(config)).toBe(7200); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(86400); }); }); From a21497d33dfaed45a22929d0355221f63a1eb6a3 Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:53:16 +0000 Subject: [PATCH 7/8] Update router.ts Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- plugins/auth-backend/src/service/router.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index e04053c478..dddac82c2d 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -261,10 +261,15 @@ export function getDefaultBackstageTokenExpiryTime(config: Config) { const duration = readDurationFromConfig(config, { key: processingIntervalKey, }); - const seconds = Math.max( - 86400, - Math.round(durationToMilliseconds(duration) / 1000), - ); - return seconds; + const roundedDuration = Math.round(durationToMilliseconds(duration) / 1000); + + const minSeconds = Math.max(600, roundedDuration); + + const maxSeconds = Math.min(86400, roundedDuration); + + if (roundedDuration < minSeconds) { + return minSeconds + } + return maxSeconds; } From de1aa5179cefd6cbdab082b2f11c757c5281167d Mon Sep 17 00:00:00 2001 From: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:57:03 +0000 Subject: [PATCH 8/8] Update router.test.ts Signed-off-by: Lavanya Sainik <137502642+lavanya-sainik-ericsson@users.noreply.github.com> --- .../auth-backend/src/service/router.test.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.test.ts b/plugins/auth-backend/src/service/router.test.ts index 6c0b060fdb..8805a60e56 100644 --- a/plugins/auth-backend/src/service/router.test.ts +++ b/plugins/auth-backend/src/service/router.test.ts @@ -69,7 +69,7 @@ describe('Test for default backstage token expiry time', () => { ); }); - it('Will return user defined backstage session expiration', () => { + it('Will return user defined 120 minutes as backstage session expiration', () => { const config = new ConfigReader({ app: { baseUrl: 'http://example.com/extra-path', @@ -78,6 +78,42 @@ describe('Test for default backstage token expiry time', () => { backstageTokenExpiration: { minutes: 120 }, }, }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(7200); + }); + + it('Will return minimum duration of 10 minutes as backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + auth: { + backstageTokenExpiration: { minutes: 2 }, + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(600); + }); + + it('Will return user configured value as backstage session expiration', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + auth: { + backstageTokenExpiration: { minutes: 20 }, + }, + }); + expect(getDefaultBackstageTokenExpiryTime(config)).toBe(1200); + }); + + it('Will return maximum of 24 hour as backstage session expiration if user configured value is more than a day', () => { + const config = new ConfigReader({ + app: { + baseUrl: 'http://example.com/extra-path', + }, + auth: { + backstageTokenExpiration: { minutes: 1500 }, + }, + }); expect(getDefaultBackstageTokenExpiryTime(config)).toBe(86400); }); });