From f7730fce6ec1495277879abdc07513dc559382c4 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Tue, 31 Aug 2021 12:33:00 -0400 Subject: [PATCH] Add session timeout usage data --- x-pack/plugins/security/server/config.ts | 11 +- .../security_usage_collector.test.ts | 109 ++++++++---------- .../security_usage_collector.ts | 24 ++++ .../schema/xpack_plugins.json | 12 ++ 4 files changed, 94 insertions(+), 62 deletions(-) diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 6ce161a898810..40b1b9d10b801 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -391,11 +391,18 @@ export function createConfig( function getSessionConfig(session: RawConfigType['session'], providers: ProvidersConfigType) { return { cleanupInterval: session.cleanupInterval, - getExpirationTimeouts({ type, name }: AuthenticationProvider) { + getExpirationTimeouts(provider?: AuthenticationProvider) { // Both idle timeout and lifespan from the provider specific session config can have three // possible types of values: `Duration`, `null` and `undefined`. The `undefined` type means that // provider doesn't override session config and we should fall back to the global one instead. - const providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session; + // Note: using an `undefined` provider argument returns the global timeouts. + let providerSessionConfig: + | { idleTimeout?: Duration | null; lifespan?: Duration | null } + | undefined; + if (provider) { + const { type, name } = provider; + providerSessionConfig = providers[type as keyof ProvidersConfigType]?.[name]?.session; + } const [idleTimeout, lifespan] = [ [session.idleTimeout, providerSessionConfig?.idleTimeout], [session.lifespan, providerSessionConfig?.lifespan], diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts index c02e644329805..7d50e05398c09 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.test.ts @@ -40,6 +40,16 @@ describe('Security UsageCollector', () => { }; const collectorFetchContext = createCollectorFetchContextMock(); + const DEFAULT_USAGE = { + auditLoggingEnabled: false, + accessAgreementEnabled: false, + authProviderCount: 1, + enabledAuthProviders: ['basic'], + loginSelectorEnabled: false, + httpAuthSchemes: ['apikey'], + sessionIdleTimeoutMinutes: 60, + sessionLifespanMinutes: 43200, + }; describe('initialization', () => { it('handles an undefined usage collector', () => { @@ -75,14 +85,7 @@ describe('Security UsageCollector', () => { .getCollectorByType('security') ?.fetch(collectorFetchContext); - expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], - }); + expect(usage).toEqual(DEFAULT_USAGE); }); it('reports correctly when security is disabled in Elasticsearch', async () => { @@ -103,6 +106,8 @@ describe('Security UsageCollector', () => { enabledAuthProviders: [], loginSelectorEnabled: false, httpAuthSchemes: [], + sessionIdleTimeoutMinutes: 0, + sessionLifespanMinutes: 0, }); }); @@ -140,14 +145,7 @@ describe('Security UsageCollector', () => { .getCollectorByType('security') ?.fetch(collectorFetchContext); - expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], - }); + expect(usage).toEqual(DEFAULT_USAGE); }); it('reports the types and count of enabled auth providers', async () => { @@ -190,12 +188,10 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, + ...DEFAULT_USAGE, authProviderCount: 3, enabledAuthProviders: ['saml', 'pki'], loginSelectorEnabled: true, - httpAuthSchemes: ['apikey'], }); }); }); @@ -228,12 +224,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, + ...DEFAULT_USAGE, accessAgreementEnabled: true, - authProviderCount: 1, enabledAuthProviders: ['saml'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); it('does not report the access agreement if the license does not permit it', async () => { @@ -266,12 +259,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, + ...DEFAULT_USAGE, accessAgreementEnabled: false, - authProviderCount: 1, enabledAuthProviders: ['saml'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); @@ -307,12 +297,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, + ...DEFAULT_USAGE, accessAgreementEnabled: false, - authProviderCount: 1, enabledAuthProviders: ['saml'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); }); @@ -346,12 +333,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, + ...DEFAULT_USAGE, enabledAuthProviders: ['saml'], loginSelectorEnabled: true, - httpAuthSchemes: ['apikey'], }); }); }); @@ -379,13 +363,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ + ...DEFAULT_USAGE, auditLoggingEnabled: true, auditLoggingType: 'legacy', - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); @@ -411,13 +391,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ + ...DEFAULT_USAGE, auditLoggingEnabled: true, auditLoggingType: 'ecs', - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); @@ -438,12 +414,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ + ...DEFAULT_USAGE, auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], + auditLoggingType: undefined, }); }); }); @@ -468,11 +441,7 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, + ...DEFAULT_USAGE, httpAuthSchemes: ['basic', 'Negotiate'], }); }); @@ -496,13 +465,33 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, + ...DEFAULT_USAGE, httpAuthSchemes: ['basic', 'Negotiate'], }); }); }); + + describe('session expirations', () => { + // Note: can't easily test deprecated 'sessionTimeout' value here because of the way that config deprecation renaming works + it('reports customized session idleTimeout and lifespan', async () => { + const config = createSecurityConfig( + ConfigSchema.validate({ + session: { idleTimeout: '123m', lifespan: '456m' }, + }) + ); + const usageCollection = usageCollectionPluginMock.createSetupContract(); + const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: false }); + registerSecurityUsageCollector({ usageCollection, config, license }); + + const usage = await usageCollection + .getCollectorByType('security') + ?.fetch(collectorFetchContext); + + expect(usage).toEqual({ + ...DEFAULT_USAGE, + sessionIdleTimeoutMinutes: 123, + sessionLifespanMinutes: 456, + }); + }); + }); }); diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 5b8e9ef4f4f84..fa79eeda58bcd 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -18,6 +18,8 @@ interface Usage { authProviderCount: number; enabledAuthProviders: string[]; httpAuthSchemes: string[]; + sessionIdleTimeoutMinutes: number; + sessionLifespanMinutes: number; } interface Deps { @@ -106,6 +108,20 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens }, }, }, + sessionIdleTimeoutMinutes: { + type: 'long', + _meta: { + description: + 'The global session idle timeout expiration that is configured, in minutes (0 if disabled).', + }, + }, + sessionLifespanMinutes: { + type: 'long', + _meta: { + description: + 'The global session lifespan expiration that is configured, in minutes (0 if disabled).', + }, + }, }, fetch: () => { const { @@ -122,6 +138,8 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens authProviderCount: 0, enabledAuthProviders: [], httpAuthSchemes: [], + sessionIdleTimeoutMinutes: 0, + sessionLifespanMinutes: 0, }; } @@ -154,6 +172,10 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens WELL_KNOWN_AUTH_SCHEMES.includes(scheme.toLowerCase()) ); + const sessionExpirations = config.session.getExpirationTimeouts(); // get global expiration values + const sessionIdleTimeoutMinutes = sessionExpirations.idleTimeout?.asMinutes() ?? 0; + const sessionLifespanMinutes = sessionExpirations.lifespan?.asMinutes() ?? 0; + return { auditLoggingEnabled: legacyAuditLoggingEnabled || ecsAuditLoggingEnabled, auditLoggingType, @@ -162,6 +184,8 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens authProviderCount, enabledAuthProviders, httpAuthSchemes, + sessionIdleTimeoutMinutes, + sessionLifespanMinutes, }; }, }); diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index c76ba844fe071..c0bfe26d05914 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5496,6 +5496,18 @@ "description": "The set of enabled http auth schemes. Used for api-based usage, and when credentials are provided via reverse-proxy." } } + }, + "sessionIdleTimeoutMinutes": { + "type": "long", + "_meta": { + "description": "The global session idle timeout expiration that is configured, in minutes (0 if disabled)." + } + }, + "sessionLifespanMinutes": { + "type": "long", + "_meta": { + "description": "The global session lifespan expiration that is configured, in minutes (0 if disabled)." + } } } },