From 31d335868e88e5d570bb27028898955d4be4ea18 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Wed, 1 Sep 2021 09:06:29 -0400 Subject: [PATCH 01/65] Disable sync toggle in flyout (#110714) --- .../public/components/create/form.test.tsx | 12 +++++- .../cases/public/components/create/form.tsx | 1 - .../app/cases/create/flyout.test.tsx | 42 +++++++++++++++---- .../components/app/cases/create/flyout.tsx | 1 + .../timeline/cases/add_to_case_action.tsx | 3 ++ .../actions/timeline/cases/create/flyout.tsx | 5 ++- .../components/t_grid/standalone/index.tsx | 2 +- 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index 9c3071fe27ee5..c9d087a08ba2c 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { act, waitFor } from '@testing-library/react'; +import { act, render, waitFor } from '@testing-library/react'; import { useForm, Form, FormHook } from '../../common/shared_imports'; import { useGetTags } from '../../containers/use_get_tags'; @@ -119,4 +119,14 @@ describe('CreateCaseForm', () => { }); }); }); + + it('hides the sync alerts toggle', () => { + const { queryByText } = render( + + + + ); + + expect(queryByText('Sync alert')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index cbd4fd7654259..d9b9287534601 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -53,7 +53,6 @@ export const CreateCaseForm: React.FC = React.memo( withSteps = true, }) => { const { isSubmitting } = useFormContext(); - const firstStep = useMemo( () => ({ title: i18n.STEP_ONE_TITLE, diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx index f92f12c79a56d..dc3db695a3fbf 100644 --- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.test.tsx @@ -10,16 +10,13 @@ import { mount } from 'enzyme'; import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { CreateCaseFlyout } from './flyout'; +import { render } from '@testing-library/react'; + +import { useKibana } from '../../../../utils/kibana_react'; +import { CASES_OWNER } from '../constants'; + +jest.mock('../../../../utils/kibana_react'); -jest.mock('../../../../utils/kibana_react', () => ({ - useKibana: () => ({ - services: { - cases: { - getCreateCase: jest.fn(), - }, - }, - }), -})); const onCloseFlyout = jest.fn(); const onSuccess = jest.fn(); const defaultProps = { @@ -28,8 +25,17 @@ const defaultProps = { }; describe('CreateCaseFlyout', () => { + const mockCreateCase = jest.fn(); + beforeEach(() => { jest.resetAllMocks(); + (useKibana as jest.Mock).mockReturnValue({ + services: { + cases: { + getCreateCase: mockCreateCase, + }, + }, + }); }); it('renders', () => { @@ -52,4 +58,22 @@ describe('CreateCaseFlyout', () => { wrapper.find(`[data-test-subj='euiFlyoutCloseButton']`).first().simulate('click'); expect(onCloseFlyout).toBeCalled(); }); + + it('does not show the sync alerts toggle', () => { + render( + + + + ); + + expect(mockCreateCase).toBeCalledTimes(1); + expect(mockCreateCase).toBeCalledWith({ + onCancel: onCloseFlyout, + onSuccess, + afterCaseCreated: undefined, + withSteps: false, + owner: [CASES_OWNER], + disableAlerts: true, + }); + }); }); diff --git a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx index df29d02e8d830..896bc27a97674 100644 --- a/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx +++ b/x-pack/plugins/observability/public/components/app/cases/create/flyout.tsx @@ -68,6 +68,7 @@ function CreateCaseFlyoutComponent({ onSuccess, withSteps: false, owner: [CASES_OWNER], + disableAlerts: true, })} diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx index b6d581f52cbe5..73be0c13faf51 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/add_to_case_action.tsx @@ -26,6 +26,7 @@ export interface AddToCaseActionProps { } | null; appId: string; onClose?: Function; + disableAlerts?: boolean; } const AddToCaseActionComponent: React.FC = ({ @@ -35,6 +36,7 @@ const AddToCaseActionComponent: React.FC = ({ casePermissions, appId, onClose, + disableAlerts, }) => { const eventId = event?.ecs._id ?? ''; const eventIndex = event?.ecs._index ?? ''; @@ -104,6 +106,7 @@ const AddToCaseActionComponent: React.FC = ({ onSuccess={onCaseSuccess} useInsertTimeline={useInsertTimeline} appId={appId} + disableAlerts={disableAlerts} /> )} {isAllCaseModalOpen && cases.getAllCasesSelectorModal(getAllCasesSelectorModalProps)} diff --git a/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx b/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx index 826b9cd8dc4a6..a817bae996aa0 100644 --- a/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx +++ b/x-pack/plugins/timelines/public/components/actions/timeline/cases/create/flyout.tsx @@ -20,6 +20,7 @@ export interface CreateCaseModalProps { onSuccess: (theCase: Case) => Promise; useInsertTimeline?: Function; appId: string; + disableAlerts?: boolean; } const StyledFlyout = styled(EuiFlyout)` @@ -53,6 +54,7 @@ const CreateCaseFlyoutComponent: React.FC = ({ onCloseFlyout, onSuccess, appId, + disableAlerts, }) => { const { cases } = useKibana().services; const createCaseProps = useMemo(() => { @@ -62,8 +64,9 @@ const CreateCaseFlyoutComponent: React.FC = ({ onSuccess, withSteps: false, owner: [appId], + disableAlerts, }; - }, [afterCaseCreated, onCloseFlyout, onSuccess, appId]); + }, [afterCaseCreated, onCloseFlyout, onSuccess, appId, disableAlerts]); return ( diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index d1d8662c567cc..ee9b7be48df63 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -411,7 +411,7 @@ const TGridStandaloneComponent: React.FC = ({ ) : null} - + ); From 77890b1ccf695c258c7ce8b2cf99adfa54986515 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Wed, 1 Sep 2021 06:51:15 -0700 Subject: [PATCH 02/65] [APM] Adds missing transaction events fallback badge to transaction details (#109846) (#110753) --- .../public/components/app/transaction_details/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index 9da1ee25246dd..06acaeeb5dd3b 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -12,6 +12,8 @@ import { ChartPointerEventContextProvider } from '../../../context/chart_pointer import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useTimeRange } from '../../../hooks/use_time_range'; +import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher'; +import { AggregatedTransactionsBadge } from '../../shared/aggregated_transactions_badge'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { TransactionDetailsTabs } from './transaction_details_tabs'; @@ -34,8 +36,14 @@ export function TransactionDetails() { }), }); + const { kuery } = query; + const { fallbackToTransactions } = useFallbackToTransactionsFetcher({ + kuery, + }); + return ( <> + {fallbackToTransactions && } From b17d87e508cbe14fdb1a6914ba2159fed5224105 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Wed, 1 Sep 2021 10:35:36 -0400 Subject: [PATCH 03/65] Security usage data (#110548) --- .../core_usage_data_service.mock.ts | 1 + .../core_usage_data_service.test.ts | 106 ++++++++++--- .../core_usage_data_service.ts | 21 +++ src/core/server/core_usage_data/types.ts | 7 + src/core/server/server.api.md | 1 + .../collectors/core/core_usage_collector.ts | 7 + src/plugins/telemetry/schema/oss_plugins.json | 6 + x-pack/plugins/security/server/config.test.ts | 64 ++++---- x-pack/plugins/security/server/config.ts | 11 +- .../security_usage_collector.test.ts | 144 +++++++++++------- .../security_usage_collector.ts | 55 ++++++- .../schema/xpack_plugins.json | 24 +++ 12 files changed, 332 insertions(+), 115 deletions(-) diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts index a03f79096004b..941ac5afacb40 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts @@ -47,6 +47,7 @@ const createStartContractMock = () => { keystoreConfigured: false, truststoreConfigured: false, }, + principal: 'unknown', }, http: { basePathConfigured: false, diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index 7ecfa37492242..478cfe5daff46 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { ConfigPath } from '@kbn/config'; import { BehaviorSubject, Observable } from 'rxjs'; import { HotObservable } from 'rxjs/internal/testing/HotObservable'; import { TestScheduler } from 'rxjs/testing'; @@ -29,12 +30,31 @@ import { CORE_USAGE_STATS_TYPE } from './constants'; import { CoreUsageStatsClient } from './core_usage_stats_client'; describe('CoreUsageDataService', () => { + function getConfigServiceAtPathMockImplementation() { + return (path: ConfigPath) => { + if (path === 'elasticsearch') { + return new BehaviorSubject(RawElasticsearchConfig.schema.validate({})); + } else if (path === 'server') { + return new BehaviorSubject(RawHttpConfig.schema.validate({})); + } else if (path === 'logging') { + return new BehaviorSubject(RawLoggingConfig.schema.validate({})); + } else if (path === 'savedObjects') { + return new BehaviorSubject(RawSavedObjectsConfig.schema.validate({})); + } else if (path === 'kibana') { + return new BehaviorSubject(RawKibanaConfig.schema.validate({})); + } + return new BehaviorSubject({}); + }; + } + const getTestScheduler = () => new TestScheduler((actual, expected) => { expect(actual).toEqual(expected); }); let service: CoreUsageDataService; + let configService: ReturnType; + const mockConfig = { unused_config: {}, elasticsearch: { username: 'kibana_system', password: 'changeme' }, @@ -60,27 +80,11 @@ describe('CoreUsageDataService', () => { }, }; - const configService = configServiceMock.create({ - getConfig$: mockConfig, - }); - - configService.atPath.mockImplementation((path) => { - if (path === 'elasticsearch') { - return new BehaviorSubject(RawElasticsearchConfig.schema.validate({})); - } else if (path === 'server') { - return new BehaviorSubject(RawHttpConfig.schema.validate({})); - } else if (path === 'logging') { - return new BehaviorSubject(RawLoggingConfig.schema.validate({})); - } else if (path === 'savedObjects') { - return new BehaviorSubject(RawSavedObjectsConfig.schema.validate({})); - } else if (path === 'kibana') { - return new BehaviorSubject(RawKibanaConfig.schema.validate({})); - } - return new BehaviorSubject({}); - }); - const coreContext = mockCoreContext.create({ configService }); - beforeEach(() => { + configService = configServiceMock.create({ getConfig$: mockConfig }); + configService.atPath.mockImplementation(getConfigServiceAtPathMockImplementation()); + + const coreContext = mockCoreContext.create({ configService }); service = new CoreUsageDataService(coreContext); }); @@ -150,7 +154,7 @@ describe('CoreUsageDataService', () => { describe('start', () => { describe('getCoreUsageData', () => { - it('returns core metrics for default config', async () => { + function setup() { const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( @@ -208,6 +212,11 @@ describe('CoreUsageDataService', () => { exposedConfigsToUsage: new Map(), elasticsearch, }); + return { getCoreUsageData }; + } + + it('returns core metrics for default config', async () => { + const { getCoreUsageData } = setup(); expect(getCoreUsageData()).resolves.toMatchInlineSnapshot(` Object { "config": Object { @@ -226,6 +235,7 @@ describe('CoreUsageDataService', () => { "logQueries": false, "numberOfHostsConfigured": 1, "pingTimeoutMs": 30000, + "principal": "unknown", "requestHeadersWhitelistConfigured": false, "requestTimeoutMs": 30000, "shardTimeoutMs": 30000, @@ -354,6 +364,60 @@ describe('CoreUsageDataService', () => { } `); }); + + describe('elasticsearch.principal', () => { + async function doTest({ + username, + serviceAccountToken, + expectedPrincipal, + }: { + username?: string; + serviceAccountToken?: string; + expectedPrincipal: string; + }) { + const defaultMockImplementation = getConfigServiceAtPathMockImplementation(); + configService.atPath.mockImplementation((path) => { + if (path === 'elasticsearch') { + return new BehaviorSubject( + RawElasticsearchConfig.schema.validate({ username, serviceAccountToken }) + ); + } + return defaultMockImplementation(path); + }); + const { getCoreUsageData } = setup(); + return expect(getCoreUsageData()).resolves.toEqual( + expect.objectContaining({ + config: expect.objectContaining({ + elasticsearch: expect.objectContaining({ principal: expectedPrincipal }), + }), + }) + ); + } + + it('returns expected usage data for elastic.username "elastic"', async () => { + return doTest({ username: 'elastic', expectedPrincipal: 'elastic_user' }); + }); + + it('returns expected usage data for elastic.username "kibana"', async () => { + return doTest({ username: 'kibana', expectedPrincipal: 'kibana_user' }); + }); + + it('returns expected usage data for elastic.username "kibana_system"', async () => { + return doTest({ username: 'kibana_system', expectedPrincipal: 'kibana_system_user' }); + }); + + it('returns expected usage data for elastic.username anything else', async () => { + return doTest({ username: 'anything else', expectedPrincipal: 'other_user' }); + }); + + it('returns expected usage data for elastic.serviceAccountToken', async () => { + // Note: elastic.username and elastic.serviceAccountToken are mutually exclusive + return doTest({ + serviceAccountToken: 'any', + expectedPrincipal: 'kibana_service_account', + }); + }); + }); }); describe('getConfigsUsageData', () => { diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index 7cf38dddc563e..73f63d4d634df 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -29,6 +29,7 @@ import type { CoreUsageDataStart, CoreUsageDataSetup, ConfigUsageData, + CoreConfigUsageData, } from './types'; import { isConfigured } from './is_configured'; import { ElasticsearchServiceStart } from '../elasticsearch'; @@ -253,6 +254,7 @@ export class CoreUsageDataService implements CoreService { `); }); - it('falls back to the global settings if provider is not known', async () => { - expect( - createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts({ - type: 'some type', - name: 'some name', - }) - ).toMatchInlineSnapshot(` - Object { - "idleTimeout": "PT0.123S", - "lifespan": "P30D", - } - `); + it('falls back to the global settings if provider is not known or is undefined', async () => { + [{ type: 'some type', name: 'some name' }, undefined].forEach((provider) => { + expect( + createMockConfig({ session: { idleTimeout: 123 } }).session.getExpirationTimeouts( + provider + ) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "P30D", + } + `); - expect( - createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts({ - type: 'some type', - name: 'some name', - }) - ).toMatchInlineSnapshot(` - Object { - "idleTimeout": "PT1H", - "lifespan": "PT0.456S", - } - `); + expect( + createMockConfig({ session: { lifespan: 456 } }).session.getExpirationTimeouts(provider) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT1H", + "lifespan": "PT0.456S", + } + `); - expect( - createMockConfig({ - session: { idleTimeout: 123, lifespan: 456 }, - }).session.getExpirationTimeouts({ type: 'some type', name: 'some name' }) - ).toMatchInlineSnapshot(` - Object { - "idleTimeout": "PT0.123S", - "lifespan": "PT0.456S", - } - `); + expect( + createMockConfig({ + session: { idleTimeout: 123, lifespan: 456 }, + }).session.getExpirationTimeouts(provider) + ).toMatchInlineSnapshot(` + Object { + "idleTimeout": "PT0.123S", + "lifespan": "PT0.456S", + } + `); + }); }); it('uses provider overrides if specified (only idle timeout)', async () => { diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 6ce161a898810..9daf0aff4c6cb 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 | undefined) { // 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 6a1f6662e796e..0515a1e1969bf 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,17 @@ describe('Security UsageCollector', () => { }; const collectorFetchContext = createCollectorFetchContextMock(); + const DEFAULT_USAGE = { + auditLoggingEnabled: false, + accessAgreementEnabled: false, + authProviderCount: 1, + enabledAuthProviders: ['basic'], + loginSelectorEnabled: false, + httpAuthSchemes: ['apikey'], + sessionIdleTimeoutInMinutes: 60, + sessionLifespanInMinutes: 43200, + sessionCleanupInMinutes: 60, + }; describe('initialization', () => { it('handles an undefined usage collector', () => { @@ -75,14 +86,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 +107,9 @@ describe('Security UsageCollector', () => { enabledAuthProviders: [], loginSelectorEnabled: false, httpAuthSchemes: [], + sessionIdleTimeoutInMinutes: 0, + sessionLifespanInMinutes: 0, + sessionCleanupInMinutes: 0, }); }); @@ -140,14 +147,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 +190,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 +226,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 +261,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 +299,9 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, + ...DEFAULT_USAGE, accessAgreementEnabled: false, - authProviderCount: 1, enabledAuthProviders: ['saml'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], }); }); }); @@ -346,27 +335,29 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, + ...DEFAULT_USAGE, enabledAuthProviders: ['saml'], loginSelectorEnabled: true, - httpAuthSchemes: ['apikey'], }); }); }); describe('audit logging', () => { - it('reports when audit logging is enabled', async () => { + it('reports when legacy audit logging is enabled (and ECS audit logging is not enabled)', async () => { const config = createSecurityConfig( ConfigSchema.validate({ audit: { enabled: true, + appender: undefined, }, }) ); const usageCollection = usageCollectionPluginMock.createSetupContract(); - const license = createSecurityLicense({ isLicenseAvailable: true, allowAuditLogging: true }); + const license = createSecurityLicense({ + isLicenseAvailable: true, + allowLegacyAuditLogging: true, + allowAuditLogging: true, + }); registerSecurityUsageCollector({ usageCollection, config, license }); const usage = await usageCollection @@ -374,12 +365,37 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ + ...DEFAULT_USAGE, auditLoggingEnabled: true, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, - httpAuthSchemes: ['apikey'], + auditLoggingType: 'legacy', + }); + }); + + it('reports when ECS audit logging is enabled (and legacy audit logging is not enabled)', async () => { + const config = createSecurityConfig( + ConfigSchema.validate({ + audit: { + enabled: true, + appender: { type: 'console', layout: { type: 'json' } }, + }, + }) + ); + const usageCollection = usageCollectionPluginMock.createSetupContract(); + const license = createSecurityLicense({ + isLicenseAvailable: true, + allowLegacyAuditLogging: true, + allowAuditLogging: true, + }); + registerSecurityUsageCollector({ usageCollection, config, license }); + + const usage = await usageCollection + .getCollectorByType('security') + ?.fetch(collectorFetchContext); + + expect(usage).toEqual({ + ...DEFAULT_USAGE, + auditLoggingEnabled: true, + auditLoggingType: 'ecs', }); }); @@ -400,12 +416,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, }); }); }); @@ -430,11 +443,7 @@ describe('Security UsageCollector', () => { ?.fetch(collectorFetchContext); expect(usage).toEqual({ - auditLoggingEnabled: false, - accessAgreementEnabled: false, - authProviderCount: 1, - enabledAuthProviders: ['basic'], - loginSelectorEnabled: false, + ...DEFAULT_USAGE, httpAuthSchemes: ['basic', 'Negotiate'], }); }); @@ -458,13 +467,34 @@ 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', () => { + // Note: can't easily test deprecated 'sessionTimeout' value here because of the way that config deprecation renaming works + it('reports customized session idleTimeout, lifespan, and cleanupInterval', async () => { + const config = createSecurityConfig( + ConfigSchema.validate({ + session: { idleTimeout: '123m', lifespan: '456m', cleanupInterval: '789m' }, + }) + ); + 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, + sessionIdleTimeoutInMinutes: 123, + sessionLifespanInMinutes: 456, + sessionCleanupInMinutes: 789, + }); + }); + }); }); 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 813e23a13ff37..15177132e0fb1 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 @@ -12,11 +12,15 @@ import type { ConfigType } from '../config'; interface Usage { auditLoggingEnabled: boolean; + auditLoggingType?: 'ecs' | 'legacy'; loginSelectorEnabled: boolean; accessAgreementEnabled: boolean; authProviderCount: number; enabledAuthProviders: string[]; httpAuthSchemes: string[]; + sessionIdleTimeoutInMinutes: number; + sessionLifespanInMinutes: number; + sessionCleanupInMinutes: number; } interface Deps { @@ -58,6 +62,13 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens 'Indicates if audit logging is both enabled and supported by the current license.', }, }, + auditLoggingType: { + type: 'keyword', + _meta: { + description: + 'If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy).', + }, + }, loginSelectorEnabled: { type: 'boolean', _meta: { @@ -98,6 +109,27 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens }, }, }, + sessionIdleTimeoutInMinutes: { + type: 'long', + _meta: { + description: + 'The global session idle timeout expiration that is configured, in minutes (0 if disabled).', + }, + }, + sessionLifespanInMinutes: { + type: 'long', + _meta: { + description: + 'The global session lifespan expiration that is configured, in minutes (0 if disabled).', + }, + }, + sessionCleanupInMinutes: { + type: 'long', + _meta: { + description: + 'The session cleanup interval that is configured, in minutes (0 if disabled).', + }, + }, }, fetch: () => { const { @@ -114,13 +146,23 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens authProviderCount: 0, enabledAuthProviders: [], httpAuthSchemes: [], + sessionIdleTimeoutInMinutes: 0, + sessionLifespanInMinutes: 0, + sessionCleanupInMinutes: 0, }; } const legacyAuditLoggingEnabled = allowLegacyAuditLogging && config.audit.enabled; - const auditLoggingEnabled = + const ecsAuditLoggingEnabled = allowAuditLogging && config.audit.enabled && config.audit.appender != null; + let auditLoggingType: Usage['auditLoggingType']; + if (ecsAuditLoggingEnabled) { + auditLoggingType = 'ecs'; + } else if (legacyAuditLoggingEnabled) { + auditLoggingType = 'legacy'; + } + const loginSelectorEnabled = config.authc.selector.enabled; const authProviderCount = config.authc.sortedProviders.length; const enabledAuthProviders = [ @@ -139,13 +181,22 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens WELL_KNOWN_AUTH_SCHEMES.includes(scheme.toLowerCase()) ); + const sessionExpirations = config.session.getExpirationTimeouts(undefined); // use `undefined` to get global expiration values + const sessionIdleTimeoutInMinutes = sessionExpirations.idleTimeout?.asMinutes() ?? 0; + const sessionLifespanInMinutes = sessionExpirations.lifespan?.asMinutes() ?? 0; + const sessionCleanupInMinutes = config.session.cleanupInterval?.asMinutes() ?? 0; + return { - auditLoggingEnabled: legacyAuditLoggingEnabled || auditLoggingEnabled, + auditLoggingEnabled: legacyAuditLoggingEnabled || ecsAuditLoggingEnabled, + auditLoggingType, loginSelectorEnabled, accessAgreementEnabled, authProviderCount, enabledAuthProviders, httpAuthSchemes, + sessionIdleTimeoutInMinutes, + sessionLifespanInMinutes, + sessionCleanupInMinutes, }; }, }); 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 41af9b0754841..38e74e15f7ae7 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5455,6 +5455,12 @@ "description": "Indicates if audit logging is both enabled and supported by the current license." } }, + "auditLoggingType": { + "type": "keyword", + "_meta": { + "description": "If auditLoggingEnabled is true, indicates what type is enabled (ECS or legacy)." + } + }, "loginSelectorEnabled": { "type": "boolean", "_meta": { @@ -5490,6 +5496,24 @@ "description": "The set of enabled http auth schemes. Used for api-based usage, and when credentials are provided via reverse-proxy." } } + }, + "sessionIdleTimeoutInMinutes": { + "type": "long", + "_meta": { + "description": "The global session idle timeout expiration that is configured, in minutes (0 if disabled)." + } + }, + "sessionLifespanInMinutes": { + "type": "long", + "_meta": { + "description": "The global session lifespan expiration that is configured, in minutes (0 if disabled)." + } + }, + "sessionCleanupInMinutes": { + "type": "long", + "_meta": { + "description": "The session cleanup interval that is configured, in minutes (0 if disabled)." + } } } }, From b74345cbb7db96cf8517561eb883ec0eb0a98e1d Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Date: Wed, 1 Sep 2021 15:43:20 +0100 Subject: [PATCH 04/65] [Observability] [RAC] Add basic functional tests for observability alerts (#109876) * Add basic functional tests for observability alerts --- .../apps/observability/alerts/index.ts | 52 ++ .../functional/apps/observability/index.ts | 1 + x-pack/test/functional/config.js | 4 + .../observability/alerts/data.json.gz | Bin 0 -> 1745 bytes .../observability/alerts/mappings.json | 731 ++++++++++++++++++ 5 files changed, 788 insertions(+) create mode 100644 x-pack/test/functional/apps/observability/alerts/index.ts create mode 100644 x-pack/test/functional/es_archives/observability/alerts/data.json.gz create mode 100644 x-pack/test/functional/es_archives/observability/alerts/mappings.json diff --git a/x-pack/test/functional/apps/observability/alerts/index.ts b/x-pack/test/functional/apps/observability/alerts/index.ts new file mode 100644 index 0000000000000..c93c8b0d633ed --- /dev/null +++ b/x-pack/test/functional/apps/observability/alerts/index.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import querystring from 'querystring'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +// Based on the x-pack/test/functional/es_archives/observability/alerts archive. +const DATE_WITH_DATA = { + rangeFrom: '2021-08-31T13:36:22.109Z', + rangeTo: '2021-09-01T13:36:22.109Z', +}; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + + describe('Observability alerts', function () { + this.tags('includeFirefox'); + + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + await pageObjects.common.navigateToUrlWithBrowserHistory( + 'observability', + '/alerts', + `?${querystring.stringify(DATE_WITH_DATA)}` + ); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + }); + + describe('Alerts table', () => { + it('Renders the table', async () => { + await testSubjects.existOrFail('events-viewer-panel'); + }); + + it('Renders the correct number of cells', async () => { + // NOTE: This isn't ideal, but EuiDataGrid doesn't really have the concept of "rows" + const cells = await testSubjects.findAll('dataGridRowCell'); + expect(cells.length).to.be(54); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/observability/index.ts b/x-pack/test/functional/apps/observability/index.ts index b7f03b5f27bae..fbb401a67b55d 100644 --- a/x-pack/test/functional/apps/observability/index.ts +++ b/x-pack/test/functional/apps/observability/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Observability specs', function () { this.tags('ciGroup6'); loadTestFile(require.resolve('./feature_controls')); + loadTestFile(require.resolve('./alerts')); }); } diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index c8822b62ebd81..226cebb9afbfa 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -94,6 +94,7 @@ export default async function ({ readConfigFile }) { '--timelion.ui.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects '--xpack.observability.unsafe.cases.enabled=true', + '--xpack.observability.unsafe.alertingExperience.enabled=true', // NOTE: Can be removed once enabled by default ], }, uiSettings: { @@ -207,6 +208,9 @@ export default async function ({ readConfigFile }) { securitySolution: { pathname: '/app/security', }, + observability: { + pathname: '/app/observability', + }, }, // choose where screenshots should be saved diff --git a/x-pack/test/functional/es_archives/observability/alerts/data.json.gz b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..45da36818828400c441aa7f57e7ed70c0538e223 GIT binary patch literal 1745 zcmV;?1}^y@iwFP!000026YZN@Z`(E$$KU%Yga8GaVN`j)_-RA2hXE_lhwY_95%4BQ zh%FhqB+ZI__mQ&GB#vWCi;~!B0^}l-JUl);=chj@=i8ekIchE!{%DdMxzZl}9A7xs zvo^l+EnI}l+{IR7l!h4+z#6Xr^;`gjSO&Z^7&;@kk+2&cM-Xna-ZS*mBg zYHCoL+E?e=iFO!Z=Z)!=-l+gSr({6bfT>awluwJYJ3Uz&XG>qgHP(LRP40jK~( zeg(t{2+I5XK&Vui8TEiWEnySH7$dJNH$+Dt4K|^oAE<39YZ|vP!$E zziqZ#pu0vRu5#;BU!3P5qvvIV0>wDw}T_Y9}QuDxiDTAHK^M8 zNfV-)7t`?}k5-JoG1)8I4~^}Ww`BmNcRTPtfD%3MuI*V-Ud+6kZkYHmFn?861Gs*( zP#%xx3M-+#fq>eOPLy&Bv=9myEQp7WSWDD%^PEpGOQj%Bq013w0weK=x*Veek>waY zvnt)6KogKa{%V>;`wZAQU1V-ee&@sT9?01~`P^6@2kvP{AU16>#|kmC#p zrW~M|g+Os_fRjE$NE*Y8KLDVQCD0L(1YxM!8l-k zM0Gv@5igtckF3f^L%KB5L+t=}I;4`Y5@_$kzRGYb!hXu)?@Qhr>9cZLkIZxgQ_1;L zh;)K5LS(Q;?+s_Njm=p;^C1TszzaDwJ2`y2d$65^&?n)ISGli~ck{CHD<~f)bKTg} zaODHz3YSNp>a;I}{|IIW!#AcuANt-h9&RROyGYFCkb{w3Gp)1tIziJ=9c?V$v?G5X z3`;DScBI3w8v(;1zhOMH6O4?cwg=So-Y|HyLkEeV35Ur5) zM*UDg<5(&Bcs!%McxD?r|Jt8tUH4%7m1hUZGd6)DY;o{NJR4BbFOO%=V~UC8z$;1s zrIG<{GYkyIPE)SA#q!4zXo4a5hyo4w0$P23asPhrF3`H}!S*ZA4iRWjO&CiBA4{MO zsOi0Xgf$x2G|!k(!T=$%3{aFQpfr&{K&Y8hRzf270BrG2Ce@QX9-@?EG4y#qoj?>k zqyvu=aUXzwKTuSXZKL8(J3$yN+-Bd**Y%Qp(Urr&-+&XrC#9Z|Jo8F+)YIdf4&sSLHnm=lXw2X^nLIs_(4x9^#I4aum`L8lQs@HI}{`Q0gyl z!>U4YW-?&j8qDIGcR*SccI$=<9uTjnBttnj_o0^T=+X|Q3Olg-{3=;C4_Bw z*vnw5-X8Zw+V4dSYP=X7XCIeYlYF0I2-5N*t90E|t!>)M4}sgQWDSYEnmp|V?kj{? z4?UA?Ovr_MJ4q>3V?nNeoxeQDaUr~57z1t*4_a1*&ASc@Wpzfm(}*#|Um^E!TwP}L zpGU?Ol_`70)r-f~fQo&2xU$YLNvQ_TSqi8(6evzHK$+tbX=GXE_5)Wq{__falH)ch zl|VaawR^W$U)GJEr*_e%P1%HC6=gL~7q;02BMjeeqR}Anv_!`6En$CJti$PlZ4bwS n(;y^;`v)@wk9Hu|338;??xqhx(txTw2qgUnbhE_avpoO+O|VzP literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/observability/alerts/mappings.json b/x-pack/test/functional/es_archives/observability/alerts/mappings.json new file mode 100644 index 0000000000000..88d12b7d797bb --- /dev/null +++ b/x-pack/test/functional/es_archives/observability/alerts/mappings.json @@ -0,0 +1,731 @@ +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.apm.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.apm.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "strict", + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "id": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "date" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "processor": { + "properties": { + "event": { + "type": "keyword" + } + } + }, + "service": { + "properties": { + "environment": { + "type": "keyword" + }, + "name": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "transaction": { + "properties": { + "type": { + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.apm.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.logs.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.logs.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "strict", + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "id": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "date" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.logs.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".alerts-observability.metrics.alerts-default": { + "is_write_index": true + } + }, + "index": ".internal.alerts-observability.metrics.alerts-default-000001", + "mappings": { + "_meta": { + "kibana": { + "version": "8.0.0" + }, + "namespace": "default" + }, + "dynamic": "strict", + "properties": { + "@timestamp": { + "type": "date" + }, + "ecs": { + "properties": { + "version": { + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "alert": { + "properties": { + "action_group": { + "type": "keyword" + }, + "duration": { + "properties": { + "us": { + "type": "long" + } + } + }, + "end": { + "type": "date" + }, + "evaluation": { + "properties": { + "threshold": { + "scaling_factor": 100, + "type": "scaled_float" + }, + "value": { + "scaling_factor": 100, + "type": "scaled_float" + } + } + }, + "id": { + "type": "keyword" + }, + "reason": { + "type": "keyword" + }, + "rule": { + "properties": { + "author": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "from": { + "type": "date" + }, + "interval": { + "type": "keyword" + }, + "license": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "note": { + "type": "keyword" + }, + "producer": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "rule_id": { + "type": "keyword" + }, + "rule_name_override": { + "type": "keyword" + }, + "rule_type_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, + "tags": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "severity": { + "type": "keyword" + }, + "start": { + "type": "date" + }, + "status": { + "type": "keyword" + }, + "system_status": { + "type": "keyword" + }, + "uuid": { + "type": "keyword" + }, + "workflow_reason": { + "type": "keyword" + }, + "workflow_status": { + "type": "keyword" + }, + "workflow_user": { + "type": "keyword" + } + } + }, + "space_ids": { + "type": "keyword" + }, + "version": { + "type": "version" + } + } + }, + "tags": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".alerts-ilm-policy", + "rollover_alias": ".alerts-observability.metrics.alerts-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file From e9c20f9802e7214bd2fed1fb3108c3e6d7532efd Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 1 Sep 2021 16:47:43 +0200 Subject: [PATCH 05/65] [RAC] Expand cell footer button changes to prevent overflow (#110506) * fix hover actions tooltip footer buttons overflow * reorder actions * override eui styles to match popover actions design --- .../public/common/components/page/index.tsx | 9 ++ .../lib/cell_actions/default_cell_actions.tsx | 98 ++++++++++--------- .../components/hover_actions/actions/copy.tsx | 4 +- 3 files changed, 61 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 051c1bd8ae5cb..9cfd0771425ee 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -31,6 +31,15 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar z-index: 9950 !important; } + /* + overrides the default styling of EuiDataGrid expand popover footer to + make it a column of actions instead of the default actions row + */ + .euiDataGridRowCell__popover .euiPopoverFooter .euiFlexGroup { + flex-direction: column; + align-items: flex-start; + } + /* overrides the default styling of euiComboBoxOptionsList because it's implemented as a popover, so it's not selectable as a child of the styled component diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx index 1481ae3e4248c..ae62c214d7b7a 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/default_cell_actions.tsx @@ -116,6 +116,36 @@ export const defaultCellActions: TGridCellAction[] = [ fieldName: columnId, }); + return ( + <> + {timelines.getHoverActions().getCopyButton({ + Component, + field: columnId, + isHoverAction: false, + ownFocus: false, + showTooltip: false, + value, + })} + + ); + }, + ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ + rowIndex, + columnId, + Component, + }) => { + const { timelines } = useKibanaServices(); + + const pageRowIndex = getPageRowIndex(rowIndex, pageSize); + if (pageRowIndex >= data.length) { + return null; + } + + const value = getMappedNonEcsValue({ + data: data[pageRowIndex], + fieldName: columnId, + }); + const dataProvider: DataProvider[] = useMemo( () => value?.map((x) => ({ @@ -170,58 +200,30 @@ export const defaultCellActions: TGridCellAction[] = [ fieldName: columnId, }); - return ( - <> - {allowTopN({ + const showButton = useMemo( + () => + allowTopN({ browserField: getAllFieldsByName(browserFields)[columnId], fieldName: columnId, hideTopN: false, - }) && ( - - )} - + }), + [columnId] ); - }, - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => ({ - rowIndex, - columnId, - Component, - }) => { - const { timelines } = useKibanaServices(); - - const pageRowIndex = getPageRowIndex(rowIndex, pageSize); - if (pageRowIndex >= data.length) { - return null; - } - const value = getMappedNonEcsValue({ - data: data[pageRowIndex], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getCopyButton({ - Component, - field: columnId, - isHoverAction: false, - ownFocus: false, - showTooltip: false, - value, - })} - - ); + return showButton ? ( + + ) : null; }, ]; diff --git a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx index 1c7fe5c82df85..0c1f8fbacd221 100644 --- a/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx +++ b/x-pack/plugins/timelines/public/components/hover_actions/actions/copy.tsx @@ -69,8 +69,8 @@ const CopyButton: React.FC = React.memo( From 7c2276bd5ada633fd3f3c6887c2fda8f01b38da3 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Wed, 1 Sep 2021 10:53:09 -0400 Subject: [PATCH 06/65] Improve color picker handling in SavedObjects tagging test (#110702) --- .../functional/page_objects/tag_management_page.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts index bce8912f7fef8..8b85bc89b1181 100644 --- a/x-pack/test/functional/page_objects/tag_management_page.ts +++ b/x-pack/test/functional/page_objects/tag_management_page.ts @@ -22,6 +22,7 @@ type TagFormValidation = FillTagFormFields; * Sub page object to manipulate the create/edit tag modal. */ class TagModal extends FtrService { + private readonly browser = this.ctx.getService('browser'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly retry = this.ctx.getService('retry'); private readonly header = this.ctx.getPageObject('header'); @@ -57,8 +58,14 @@ class TagModal extends FtrService { } if (fields.color !== undefined) { await this.testSubjects.setValue('~createModalField-color', fields.color); - // Wait for the popover to be closable before moving to the next input - await new Promise((res) => setTimeout(res, 200)); + // Close the popover before moving to the next input, as it can get in the way of interacting with other elements + await this.testSubjects.existOrFail('euiSaturation'); + await this.retry.try(async () => { + if (await this.testSubjects.exists('euiSaturation', { timeout: 10 })) { + await this.browser.pressKeys(this.browser.keys.ENTER); + } + await this.testSubjects.missingOrFail('euiSaturation', { timeout: 250 }); + }); } if (fields.description !== undefined) { await this.testSubjects.click('createModalField-description'); From a961158cbe82330dcf7fca45095b8154d8597c6c Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 1 Sep 2021 17:11:15 +0200 Subject: [PATCH 07/65] [ML] Functional tests - stability fixes (#110785) This PR stabilizes some functional tests by adding retries to service methods and by increasing timeouts on some existing retries. --- .../services/ml/data_frame_analytics.ts | 15 ++++++----- .../test/functional/services/ml/job_table.ts | 26 ++++++++++++------- .../functional/services/transform/wizard.ts | 6 ++--- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/x-pack/test/functional/services/ml/data_frame_analytics.ts b/x-pack/test/functional/services/ml/data_frame_analytics.ts index 9998a52a3044e..aafe96c2c4967 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics.ts @@ -16,6 +16,7 @@ export function MachineLearningDataFrameAnalyticsProvider( { getService }: FtrProviderContext, mlApi: MlApi ) { + const retry = getService('retry'); const testSubjects = getService('testSubjects'); return { @@ -50,12 +51,14 @@ export function MachineLearningDataFrameAnalyticsProvider( }, async startAnalyticsCreation() { - if (await testSubjects.exists('mlNoDataFrameAnalyticsFound')) { - await testSubjects.click('mlAnalyticsCreateFirstButton'); - } else { - await testSubjects.click('mlAnalyticsButtonCreate'); - } - await testSubjects.existOrFail('analyticsCreateSourceIndexModal'); + await retry.tryForTime(20 * 1000, async () => { + if (await testSubjects.exists('mlNoDataFrameAnalyticsFound', { timeout: 1000 })) { + await testSubjects.click('mlAnalyticsCreateFirstButton'); + } else { + await testSubjects.click('mlAnalyticsButtonCreate'); + } + await testSubjects.existOrFail('analyticsCreateSourceIndexModal'); + }); }, async waitForAnalyticsCompletion(analyticsId: string) { diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts index c711ff0ac8909..4a38aa4efe4dd 100644 --- a/x-pack/test/functional/services/ml/job_table.ts +++ b/x-pack/test/functional/services/ml/job_table.ts @@ -234,13 +234,17 @@ export function MachineLearningJobTableProvider( } public async assertJobRowFields(jobId: string, expectedRow: object) { - await this.refreshJobList(); - const rows = await this.parseJobTable(); - const jobRow = rows.filter((row) => row.id === jobId)[0]; - expect(jobRow).to.eql( - expectedRow, - `Expected job row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify(jobRow)}')` - ); + await retry.tryForTime(5000, async () => { + await this.refreshJobList(); + const rows = await this.parseJobTable(); + const jobRow = rows.filter((row) => row.id === jobId)[0]; + expect(jobRow).to.eql( + expectedRow, + `Expected job row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify( + jobRow + )}')` + ); + }); } public async assertJobRowDetailsCounts( @@ -585,9 +589,11 @@ export function MachineLearningJobTableProvider( } // Save custom URL - await testSubjects.click('mlJobAddCustomUrl'); - const expectedIndex = existingCustomUrls.length; - await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label); + await retry.tryForTime(5000, async () => { + await testSubjects.click('mlJobAddCustomUrl'); + const expectedIndex = existingCustomUrls.length; + await customUrls.assertCustomUrlLabel(expectedIndex, customUrl.label); + }); // Save the job await this.saveEditJobFlyoutChanges(); diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 43d4995276239..607f2ee120ed1 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -130,7 +130,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi column: number, expectedColumnValues: string[] ) { - await retry.tryForTime(2000, async () => { + await retry.tryForTime(20 * 1000, async () => { // get a 2D array of rows and cell values // only parse columns up to the one we want to assert const rows = await this.parseEuiDataGrid(tableSubj, column + 1); @@ -152,7 +152,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }, async assertEuiDataGridColumnValuesNotEmpty(tableSubj: string, column: number) { - await retry.tryForTime(2000, async () => { + await retry.tryForTime(20 * 1000, async () => { // get a 2D array of rows and cell values // only parse columns up to the one we want to assert const rows = await this.parseEuiDataGrid(tableSubj, column + 1); @@ -171,7 +171,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }, async assertIndexPreview(columns: number, expectedNumberOfRows: number) { - await retry.tryForTime(2000, async () => { + await retry.tryForTime(20 * 1000, async () => { // get a 2D array of rows and cell values // only parse the first column as this is sufficient to get assert the row count const rowsData = await this.parseEuiDataGrid('transformIndexPreview', 1); From 69568ebb178a9d4a42ddab64c2c5c4ad8ecb757c Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 1 Sep 2021 16:25:53 +0100 Subject: [PATCH 08/65] [Security Solution] Endpoint Exception status in flayout is not aligned with alerts context menu (#110203) * unit test * add isAlertFromEndpointAlert check * update isAlertFromEndpointAlert * unit test * fix unit test --- .../event_details/alert_summary_view.tsx | 4 +- .../common/utils/endpoint_alert_check.test.ts | 42 ++++++++++++++++--- .../common/utils/endpoint_alert_check.ts | 24 ++++++++++- .../timeline_actions/alert_context_menu.tsx | 16 ++----- .../use_host_isolation_action.tsx | 4 +- .../take_action_dropdown/index.test.tsx | 5 ++- .../components/take_action_dropdown/index.tsx | 11 +---- 7 files changed, 72 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index da6c091ab069a..03c420914d170 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -36,7 +36,7 @@ import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; import { MarkdownRenderer } from '../markdown_editor'; import { LineClamp } from '../line_clamp'; -import { endpointAlertCheck } from '../../utils/endpoint_alert_check'; +import { isAlertFromEndpointEvent } from '../../utils/endpoint_alert_check'; import { getEmptyValue } from '../empty_value'; import { ActionCell } from './table/action_cell'; import { FieldValueCell } from './table/field_value_cell'; @@ -244,7 +244,7 @@ export const getSummaryRows = ({ fieldFromBrowserField: browserField, }; - if (item.id === 'agent.id' && !endpointAlertCheck({ data })) { + if (item.id === 'agent.id' && !isAlertFromEndpointEvent({ data })) { return acc; } diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts index e95f5c15d4ecb..d0a03d62a682b 100644 --- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts +++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts @@ -6,10 +6,11 @@ */ import _ from 'lodash'; +import { Ecs } from '../../../common/ecs'; import { generateMockDetailItemData } from '../mock'; -import { endpointAlertCheck } from './endpoint_alert_check'; +import { isAlertFromEndpointAlert, isAlertFromEndpointEvent } from './endpoint_alert_check'; -describe('Endpoint Alert Check Utility', () => { +describe('isAlertFromEndpointEvent', () => { let mockDetailItemData: ReturnType; beforeEach(() => { @@ -38,16 +39,47 @@ describe('Endpoint Alert Check Utility', () => { }); it('should return true if detections data comes from an endpoint rule', () => { - expect(endpointAlertCheck({ data: mockDetailItemData })).toBe(true); + expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBe(true); }); it('should return false if it is not an Alert (ex. maybe an event)', () => { _.remove(mockDetailItemData, { field: 'signal.rule.id' }); - expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy(); + expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy(); }); it('should return false if it is not an endpoint agent', () => { _.remove(mockDetailItemData, { field: 'agent.type' }); - expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy(); + expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy(); + }); +}); + +describe('isAlertFromEndpointAlert', () => { + it('should return true if detections data comes from an endpoint rule', () => { + const mockEcsData = { + _id: 'mockId', + 'signal.original_event.module': ['endpoint'], + 'signal.original_event.kind': ['alert'], + } as Ecs; + expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBe(true); + }); + + it('should return false if ecsData is undefined', () => { + expect(isAlertFromEndpointAlert({ ecsData: undefined })).toBeFalsy(); + }); + + it('should return false if it is not an Alert', () => { + const mockEcsData = { + _id: 'mockId', + 'signal.original_event.module': ['endpoint'], + } as Ecs; + expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy(); + }); + + it('should return false if it is not an endpoint module', () => { + const mockEcsData = { + _id: 'mockId', + 'signal.original_event.kind': ['alert'], + } as Ecs; + expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy(); }); }); diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts index 30c6e3fdeb672..7e7e7a6bcdd1f 100644 --- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts +++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts @@ -5,15 +5,20 @@ * 2.0. */ -import { find, some } from 'lodash/fp'; +import { find, getOr, some } from 'lodash/fp'; import { TimelineEventsDetailsItem } from '../../../../timelines/common'; +import { Ecs } from '../../../common/ecs'; /** * Checks to see if the given set of Timeline event detail items includes data that indicates its * an endpoint Alert. Note that it will NOT match on Events - only alerts * @param data */ -export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] }): boolean => { +export const isAlertFromEndpointEvent = ({ + data, +}: { + data: TimelineEventsDetailsItem[]; +}): boolean => { const isAlert = some({ category: 'signal', field: 'signal.rule.id' }, data); if (!isAlert) { @@ -23,3 +28,18 @@ export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] const findEndpointAlert = find({ field: 'agent.type' }, data)?.values; return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false; }; + +export const isAlertFromEndpointAlert = ({ + ecsData, +}: { + ecsData: Ecs | null | undefined; +}): boolean => { + if (ecsData == null) { + return false; + } + + const eventModules = getOr([], 'signal.original_event.module', ecsData); + const kinds = getOr([], 'signal.original_event.kind', ecsData); + + return eventModules.includes('endpoint') && kinds.includes('alert'); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index cc719a9999383..f2297b7d567bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -11,7 +11,7 @@ import { EuiButtonIcon, EuiContextMenuPanel, EuiPopover, EuiToolTip } from '@ela import { indexOf } from 'lodash'; import { ExceptionListType } from '@kbn/securitysolution-io-ts-list-types'; -import { get, getOr } from 'lodash/fp'; +import { get } from 'lodash/fp'; import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers'; import { EventsTdContent } from '../../../../timelines/components/timeline/styles'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../../timelines/components/timeline/helpers'; @@ -36,6 +36,7 @@ import { useInvestigateInResolverContextItem } from './investigate_in_resolver'; import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations'; import { useEventFilterAction } from './use_event_filter_action'; import { useAddToCaseActions } from './use_add_to_case_actions'; +import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_alert_check'; interface AlertContextMenuProps { ariaLabel?: string; @@ -78,17 +79,6 @@ const AlertContextMenuComponent: React.FC = ({ const isEvent = useMemo(() => indexOf(ecsRowData.event?.kind, 'event') !== -1, [ecsRowData]); - const isEndpointAlert = useMemo((): boolean => { - if (ecsRowData == null) { - return false; - } - - const eventModules = getOr([], 'signal.original_event.module', ecsRowData); - const kinds = getOr([], 'signal.original_event.kind', ecsRowData); - - return eventModules.includes('endpoint') && kinds.includes('alert'); - }, [ecsRowData]); - const onButtonClick = useCallback(() => { setPopover(!isPopoverOpen); }, [isPopoverOpen]); @@ -153,7 +143,7 @@ const AlertContextMenuComponent: React.FC = ({ }, [closePopover, onAddEventFilterClick]); const { exceptionActionItems } = useExceptionActions({ - isEndpointAlert, + isEndpointAlert: isAlertFromEndpointAlert({ ecsData: ecsRowData }), onAddExceptionTypeClick: handleOnAddExceptionTypeClick, }); const investigateInResolverActionItems = useInvestigateInResolverContextItem({ diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx index e670c000d789a..24bc670a13ec4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx @@ -10,7 +10,7 @@ import type { TimelineEventsDetailsItem } from '../../../../common'; import { isIsolationSupported } from '../../../../common/endpoint/service/host_isolation/utils'; import { HostStatus } from '../../../../common/endpoint/types'; import { useIsolationPrivileges } from '../../../common/hooks/endpoint/use_isolate_privileges'; -import { endpointAlertCheck } from '../../../common/utils/endpoint_alert_check'; +import { isAlertFromEndpointEvent } from '../../../common/utils/endpoint_alert_check'; import { useHostIsolationStatus } from '../../containers/detection_engine/alerts/use_host_isolation_status'; import { ISOLATE_HOST, UNISOLATE_HOST } from './translations'; import { getFieldValue } from './helpers'; @@ -29,7 +29,7 @@ export const useHostIsolationAction = ({ onAddIsolationStatusClick, }: UseHostIsolationActionProps) => { const isEndpointAlert = useMemo(() => { - return endpointAlertCheck({ data: detailsData || [] }); + return isAlertFromEndpointEvent({ data: detailsData || [] }); }, [detailsData]); const agentId = useMemo( diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx index bba652bcdd030..d98b168f209da 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx @@ -34,7 +34,10 @@ jest.mock('../../../common/hooks/use_experimental_features', () => ({ })); jest.mock('../../../common/utils/endpoint_alert_check', () => { - return { endpointAlertCheck: jest.fn().mockReturnValue(true) }; + return { + isAlertFromEndpointAlert: jest.fn().mockReturnValue(true), + isAlertFromEndpointEvent: jest.fn().mockReturnValue(true), + }; }); jest.mock('../../../../common/endpoint/service/host_isolation/utils', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index a6114884b955d..0432e7d353086 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -20,7 +20,7 @@ import { useHostIsolationAction } from '../host_isolation/use_host_isolation_act import { getFieldValue } from '../host_isolation/helpers'; import type { Ecs } from '../../../../common/ecs'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; -import { endpointAlertCheck } from '../../../common/utils/endpoint_alert_check'; +import { isAlertFromEndpointAlert } from '../../../common/utils/endpoint_alert_check'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { useAddToCaseActions } from '../alerts_table/timeline_actions/use_add_to_case_actions'; @@ -87,13 +87,6 @@ export const TakeActionDropdown = React.memo( ]); const isEvent = actionsData.eventKind === 'event'; - const isEndpointAlert = useMemo((): boolean => { - if (detailsData == null) { - return false; - } - return endpointAlertCheck({ data: detailsData }); - }, [detailsData]); - const togglePopoverHandler = useCallback(() => { setIsPopoverOpen(!isPopoverOpen); }, [isPopoverOpen]); @@ -131,7 +124,7 @@ export const TakeActionDropdown = React.memo( ); const { exceptionActionItems } = useExceptionActions({ - isEndpointAlert, + isEndpointAlert: isAlertFromEndpointAlert({ ecsData }), onAddExceptionTypeClick: handleOnAddExceptionTypeClick, }); From 19b23ef47773a211faff551aa39250cf90912b48 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Wed, 1 Sep 2021 09:29:47 -0600 Subject: [PATCH 09/65] [Maps] Add new index handling for users missing privs for index creation (#109754) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../layers/new_vector_layer_wizard/wizard.tsx | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx index 6dac22581efdb..100e9dfa45c1d 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx @@ -13,13 +13,14 @@ import { RenderWizardArguments } from '../layer_wizard_registry'; import { VectorLayer } from '../vector_layer'; import { ESSearchSource } from '../../sources/es_search_source'; import { ADD_LAYER_STEP_ID } from '../../../connected_components/add_layer_panel/view'; -import { getIndexNameFormComponent } from '../../../kibana_services'; +import { getFileUpload, getIndexNameFormComponent } from '../../../kibana_services'; interface State { indexName: string; indexNameError: string; indexingTriggered: boolean; createIndexError: string; + userHasIndexWritePermissions: boolean; } const DEFAULT_MAPPINGS = { @@ -43,6 +44,7 @@ export class NewVectorLayerEditor extends Component { let indexPatternId: string | undefined; try { + const userHasIndexWritePermissions = await this._checkIndexPermissions(); + if (!userHasIndexWritePermissions) { + this._setCreateIndexError( + i18n.translate('xpack.maps.layers.newVectorLayerWizard.indexPermissionsError', { + defaultMessage: `You must have 'create' and 'create_index' index privileges to create and write data to "{indexName}".`, + values: { + indexName: this.state.indexName, + }, + }), + userHasIndexWritePermissions + ); + return; + } const response = await createNewIndexAndPattern({ indexName: this.state.indexName, defaultMappings: DEFAULT_MAPPINGS, @@ -125,10 +149,23 @@ export class NewVectorLayerEditor extends Component +

{this.state.createIndexError}

+ + ); + } return ( Date: Wed, 1 Sep 2021 08:39:20 -0700 Subject: [PATCH 10/65] Upgrade EUI to v37.3.1 (#109926) * Upgrade EUI to v37.3.1 * Update i18n token mappings * Skip i18n_eui_mapping defString checks for functions * Update snapshots * Update failing Security tests with extra nodes * Remove hook cleanup now that elastic/eui#5068 is merged * [i18n PR feedback] Prefer specific token skipping over all functions skipping * Revert "Remove hook cleanup now that elastic/eui#5068 is merged" This reverts commit e40ebfa92946662d401e169cfdfb97c664f92e92. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- .../__snapshots__/i18n_service.test.tsx.snap | 8 ++- src/core/public/i18n/i18n_eui_mapping.test.ts | 5 ++ src/core/public/i18n/i18n_eui_mapping.tsx | 30 +++++--- src/dev/license_checker/config.ts | 2 +- .../field/__snapshots__/field.test.tsx.snap | 28 ++++++-- .../__snapshots__/data_view.test.tsx.snap | 24 ++++--- .../List/__snapshots__/List.test.tsx.snap | 4 +- .../workpad_table.stories.storyshot | 4 +- .../api/__snapshots__/shareable.test.tsx.snap | 10 +-- .../__snapshots__/canvas.stories.storyshot | 8 +-- .../__snapshots__/footer.stories.storyshot | 6 +- .../page_controls.stories.storyshot | 6 +- .../upload_license.test.tsx.snap | 70 +++---------------- .../report_listing.test.tsx.snap | 18 +++-- .../security_solution/cypress/tasks/alerts.ts | 2 +- .../enrichment_range_picker.test.tsx | 1 + .../__snapshots__/index.test.tsx.snap | 6 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../location_status_tags.test.tsx.snap | 2 +- yarn.lock | 8 +-- 22 files changed, 125 insertions(+), 123 deletions(-) diff --git a/package.json b/package.json index 20d20d13fa121..e603190c72698 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.19", "@elastic/ems-client": "7.15.0", - "@elastic/eui": "37.3.0", + "@elastic/eui": "37.3.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap index 4ef5eb8f56d2f..54e223cdc5d41 100644 --- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap +++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap @@ -57,7 +57,7 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiColumnSelector.searchcolumns": "Search columns", "euiColumnSelector.selectAll": "Show all", "euiColumnSorting.button": "Sort fields", - "euiColumnSorting.buttonActive": "fields sorted", + "euiColumnSorting.buttonActive": [Function], "euiColumnSorting.clearAll": "Clear sorting", "euiColumnSorting.emptySorting": "Currently no fields are sorted", "euiColumnSorting.pickFields": "Pick fields to sort by", @@ -104,9 +104,11 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiFieldPassword.maskPassword": "Mask password", "euiFieldPassword.showPassword": "Show password as plain text. Note: this will visually expose your password on the screen.", "euiFilePicker.clearSelectedFiles": "Clear selected files", - "euiFilePicker.filesSelected": "files selected", + "euiFilePicker.filesSelected": [Function], + "euiFilePicker.promptText": "Select or drag and drop a file", "euiFilePicker.removeSelected": "Remove", - "euiFilterButton.filterBadge": [Function], + "euiFilterButton.filterBadgeActiveAriaLabel": [Function], + "euiFilterButton.filterBadgeAvailableAriaLabel": [Function], "euiFlyout.closeAriaLabel": "Close this dialog", "euiForm.addressFormErrors": "Please address the highlighted errors.", "euiFormControlLayoutClearButton.label": "Clear input", diff --git a/src/core/public/i18n/i18n_eui_mapping.test.ts b/src/core/public/i18n/i18n_eui_mapping.test.ts index 1b80257266d4c..d8d48a8e5f1d5 100644 --- a/src/core/public/i18n/i18n_eui_mapping.test.ts +++ b/src/core/public/i18n/i18n_eui_mapping.test.ts @@ -74,6 +74,11 @@ describe('@elastic/eui i18n tokens', () => { }); test('defaultMessage is in sync with defString', () => { + // Certain complex tokens (e.g. ones that have a function as a defaultMessage) + // need custom i18n handling, and can't be checked for basic defString equality + const tokensToSkip = ['euiColumnSorting.buttonActive']; + if (tokensToSkip.includes(token)) return; + // Clean up typical errors from the `@elastic/eui` extraction token tool const normalizedDefString = defString // Quoted words should use double-quotes diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx index 133a2155f7430..4175dac712e82 100644 --- a/src/core/public/i18n/i18n_eui_mapping.tsx +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -272,9 +272,11 @@ export const getEuiContextMapping = (): EuiTokensObject => { 'euiColumnSorting.button': i18n.translate('core.euiColumnSorting.button', { defaultMessage: 'Sort fields', }), - 'euiColumnSorting.buttonActive': i18n.translate('core.euiColumnSorting.buttonActive', { - defaultMessage: 'fields sorted', - }), + 'euiColumnSorting.buttonActive': ({ numberOfSortedFields }: EuiValues) => + i18n.translate('core.euiColumnSorting.buttonActive', { + defaultMessage: '{numberOfSortedFields, plural, one {# field} other {# fields}} sorted', + values: { numberOfSortedFields }, + }), 'euiColumnSortingDraggable.activeSortLabel': ({ display }: EuiValues) => i18n.translate('core.euiColumnSortingDraggable.activeSortLabel', { defaultMessage: '{display} is sorting this data grid', @@ -514,16 +516,26 @@ export const getEuiContextMapping = (): EuiTokensObject => { 'euiFilePicker.clearSelectedFiles': i18n.translate('core.euiFilePicker.clearSelectedFiles', { defaultMessage: 'Clear selected files', }), - 'euiFilePicker.filesSelected': i18n.translate('core.euiFilePicker.filesSelected', { - defaultMessage: 'files selected', + 'euiFilePicker.filesSelected': ({ fileCount }: EuiValues) => + i18n.translate('core.euiFilePicker.filesSelected', { + defaultMessage: '{fileCount} files selected', + values: { fileCount }, + }), + 'euiFilePicker.promptText': i18n.translate('core.euiFilePicker.promptText', { + defaultMessage: 'Select or drag and drop a file', }), 'euiFilePicker.removeSelected': i18n.translate('core.euiFilePicker.removeSelected', { defaultMessage: 'Remove', }), - 'euiFilterButton.filterBadge': ({ count, hasActiveFilters }: EuiValues) => - i18n.translate('core.euiFilterButton.filterBadge', { - defaultMessage: '{count} {hasActiveFilters} filters', - values: { count, hasActiveFilters: hasActiveFilters ? 'active' : 'available' }, + 'euiFilterButton.filterBadgeActiveAriaLabel': ({ count }: EuiValues) => + i18n.translate('core.euiFilterButton.filterBadgeActiveAriaLabel', { + defaultMessage: '{count} active filters', + values: { count }, + }), + 'euiFilterButton.filterBadgeAvailableAriaLabel': ({ count }: EuiValues) => + i18n.translate('core.euiFilterButton.filterBadgeAvailableAriaLabel', { + defaultMessage: '{count} available filters', + values: { count }, }), 'euiFlyout.closeAriaLabel': i18n.translate('core.euiFlyout.closeAriaLabel', { defaultMessage: 'Close this dialog', diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index cb7e3781e2511..ee355d6a9811b 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -75,7 +75,7 @@ export const LICENSE_OVERRIDES = { '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint 'node-sql-parser@3.6.1': ['(GPL-2.0 OR MIT)'], // GPL-2.0* https://github.com/taozhi8833998/node-sql-parser '@elastic/ems-client@7.15.0': ['Elastic License 2.0'], - '@elastic/eui@37.3.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@37.3.1': ['SSPL-1.0 OR Elastic License 2.0'], // TODO can be removed if the https://github.com/jindw/xmldom/issues/239 is released 'xmldom@0.1.27': ['MIT'], diff --git a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap index be5163e89367c..9249f5f98e9c9 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap +++ b/src/plugins/advanced_settings/public/management_app/components/field/__snapshots__/field.test.tsx.snap @@ -1326,7 +1326,12 @@ exports[`Field for image setting should render as read only if saving is disable disabled={true} display="large" fullWidth={true} - initialPromptText="Select or drag and drop a file" + initialPromptText={ + + } onChange={[Function]} /> @@ -1472,7 +1477,12 @@ exports[`Field for image setting should render custom setting icon if it is cust disabled={false} display="large" fullWidth={true} - initialPromptText="Select or drag and drop a file" + initialPromptText={ + + } onChange={[Function]} /> @@ -1526,7 +1536,12 @@ exports[`Field for image setting should render default value if there is no user disabled={false} display="large" fullWidth={true} - initialPromptText="Select or drag and drop a file" + initialPromptText={ + + } onChange={[Function]} /> @@ -1597,7 +1612,12 @@ exports[`Field for image setting should render unsaved value if there are unsave disabled={false} display="large" fullWidth={true} - initialPromptText="Select or drag and drop a file" + initialPromptText={ + + } onChange={[Function]} /> diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index 612ffdcf5029e..9cd0687a1074d 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1403,7 +1403,7 @@ exports[`Inspector Data View component should render single table without select >
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot index c5b6d768c89d8..a5eefde192371 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot @@ -1375,7 +1375,7 @@ exports[`Storyshots shareables/Canvas component 1`] = ` >