Merge pull request #21993 from Ericsson/configuration_token_expiry

Ability for Users to configure auth token expiration [19341]
This commit is contained in:
Patrik Oldsberg
2024-02-03 12:58:56 +01:00
committed by GitHub
7 changed files with 119 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Ability for user to configure backstage token expiration
+11
View File
@@ -165,3 +165,14 @@ To try out SAML, you can use the mock identity provider:
## Links
- [The Backstage homepage](https://backstage.io)
## Configuring Token Expiration in App Config
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:
backstageTokenExpiration: { minutes: <user_defined_value> }
```
+6
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
/** Configuration options for the auth plugin */
auth?: {
@@ -184,6 +186,10 @@ export interface Config {
cfaccess?: {
teamName: string;
};
/**
* The backstage token expiration.
*/
backstageTokenExpiration?: HumanDuration;
};
};
}
+1
View File
@@ -51,6 +51,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",
@@ -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,64 @@ 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 120 minutes as backstage session expiration', () => {
const config = new ConfigReader({
app: {
baseUrl: 'http://example.com/extra-path',
},
auth: {
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);
});
});
+29 -5
View File
@@ -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,27 @@ 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 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;
}
+1
View File
@@ -4908,6 +4908,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