Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAC][Timeline] - Add audit log to RBAC wrapped search strategy #112040

Merged
merged 29 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
51079d6
audit log added to rac alerts table search strategy
yctercero Sep 7, 2021
1cdf6ad
remove console logs
yctercero Sep 8, 2021
c7341e8
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Sep 13, 2021
cdf65c2
update security doc audit actions to include alert audit actions
yctercero Sep 14, 2021
ef26e9c
continued cleanup
yctercero Sep 14, 2021
f861b5c
fixing types
yctercero Sep 22, 2021
151514d
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Sep 22, 2021
5a4a023
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Sep 28, 2021
17f6cdd
Merge branch 'master' into searchstrat_audit_log
kibanamachine Sep 28, 2021
0008855
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Oct 20, 2021
d3bde9a
fixed up tests and added logic to filter alerts table by consumer
yctercero Oct 20, 2021
7a6642f
removing added filter logic, just audit logging
yctercero Oct 25, 2021
a51f936
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Oct 25, 2021
296eb4d
updated doc per feedback
yctercero Oct 25, 2021
2395f7a
got audit log tests working
yctercero Oct 26, 2021
43a9cdf
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Oct 26, 2021
d77cdfb
Merge branch 'master' of github.com:elastic/kibana into searchstrat_a…
yctercero Oct 26, 2021
3e27056
enable logging test
yctercero Oct 26, 2021
2e7b784
Merge branch 'master' into searchstrat_audit_log
kibanamachine Oct 27, 2021
1e6e73e
Merge branch 'master' into searchstrat_audit_log
kibanamachine Oct 28, 2021
9838392
Merge branch 'master' into searchstrat_audit_log
kibanamachine Oct 29, 2021
a92dc54
Merge branch 'main' into searchstrat_audit_log
kibanamachine Oct 29, 2021
4f8e967
Merge branch 'main' into searchstrat_audit_log
kibanamachine Nov 1, 2021
a7f0c5d
update tests
yctercero Nov 2, 2021
ad4db59
Merge branch 'searchstrat_audit_log' of github.com:yctercero/kibana i…
yctercero Nov 2, 2021
b61e2cd
updating test
yctercero Nov 3, 2021
e47df03
Merge branch 'main' into searchstrat_audit_log
kibanamachine Nov 3, 2021
3980a99
missed one change on test
yctercero Nov 3, 2021
e22fc9b
Merge branch 'searchstrat_audit_log' of github.com:yctercero/kibana i…
yctercero Nov 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/user/security/audit-logging.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ Refer to the corresponding {es} logs for potential write errors.
| `unknown` | User is updating a space.
| `failure` | User is not authorized to update a space.

.2+| `alert_update`
| `unknown` | User is updating an alert.
| `failure` | User is not authorized to update an alert.

3+a|
====== Type: deletion

Expand Down Expand Up @@ -242,6 +246,14 @@ Refer to the corresponding {es} logs for potential write errors.
| `success` | User has accessed a space as part of a search operation.
| `failure` | User is not authorized to search for spaces.

.2+| `alert_get`
| `success` | User has accessed an alert.
| `failure` | User is not authorized to access an alert.

.2+| `alert_find`
| `success` | User has accessed alerts.
yctercero marked this conversation as resolved.
Show resolved Hide resolved
| `failure` | User is not authorized to access alerts.

3+a|
===== Category: web

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/rule_registry/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type {
export * from './config';
export * from './rule_data_plugin_service';
export * from './rule_data_client';
export * from './alert_data_client/audit_events';

export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { JsonObject } from '@kbn/utility-types';
import { AlertConsumers } from '@kbn/rule-data-utils';

import type { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
import type { Ecs } from '../../../../ecs';
Expand Down Expand Up @@ -43,4 +44,5 @@ export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsP
language: 'eql' | 'kuery' | 'lucene';
excludeEcsData?: boolean;
authFilter?: JsonObject;
alertsConsumers?: AlertConsumers[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { JsonObject } from '@kbn/utility-types';
import { AlertConsumers } from '@kbn/rule-data-utils';

import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common';
import { Inspect, Maybe } from '../../../common';
Expand All @@ -32,4 +33,5 @@ export interface TimelineEventsDetailsRequestOptions
indexName: string;
eventId: string;
authFilter?: JsonObject;
alertConsumers?: AlertConsumers[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { AlertConsumers } from '@kbn/rule-data-utils';
import { IEsSearchRequest } from '../../../../../../src/plugins/data/common';
import { ESQuery } from '../../typed_json';
import {
Expand Down Expand Up @@ -43,6 +44,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest {
docValueFields?: DocValueFields[];
factoryQueryType?: TimelineFactoryQueryTypes;
entityType?: EntityType;
alertConsumers?: AlertConsumers[];
}

export interface TimelineRequestSortField<Field = string> extends SortField<Field> {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/timelines/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"server": true,
"ui": true,
"requiredPlugins": ["alerting", "cases", "data", "dataEnhanced", "kibanaReact", "kibanaUtils"],
"optionalPlugins": []
"optionalPlugins": ["security"]
}
7 changes: 6 additions & 1 deletion x-pack/plugins/timelines/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ import { defineRoutes } from './routes';
import { timelineSearchStrategyProvider } from './search_strategy/timeline';
import { timelineEqlSearchStrategyProvider } from './search_strategy/timeline/eql';
import { indexFieldsProvider } from './search_strategy/index_fields';
import { SecurityPluginSetup } from '../../security/server';

export class TimelinesPlugin
implements Plugin<TimelinesPluginUI, TimelinesPluginStart, SetupPlugins, StartPlugins>
{
private readonly logger: Logger;
private security?: SecurityPluginSetup;

constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}

public setup(core: CoreSetup<StartPlugins, TimelinesPluginStart>, plugins: SetupPlugins) {
this.logger.debug('timelines: Setup');
this.security = plugins.security;

const router = core.http.createRouter();

// Register server side APIs
Expand All @@ -39,7 +43,8 @@ export class TimelinesPlugin
core.getStartServices().then(([_, depsStart]) => {
const TimelineSearchStrategy = timelineSearchStrategyProvider(
depsStart.data,
depsStart.alerting
depsStart.alerting,
this.security
);
const TimelineEqlSearchStrategy = timelineEqlSearchStrategyProvider(depsStart.data);
const IndexFields = indexFieldsProvider();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TimelineRequestSortField,
} from '../../../../../../common/search_strategy';
import { createQueryFilterClauses } from '../../../../../../server/utils/build_query';
import { getAlertConsumersFilter } from '../utils';

export const buildTimelineEventsAllQuery = ({
defaultIndex,
Expand All @@ -25,9 +26,9 @@ export const buildTimelineEventsAllQuery = ({
sort,
timerange,
authFilter,
alertConsumers,
}: Omit<TimelineEventsAllRequestOptions, 'fieldRequested'>) => {
const filterClause = [...createQueryFilterClauses(filterQuery)];

const getTimerangeFilter = (timerangeOption: TimerangeInput | undefined): TimerangeFilter[] => {
if (timerangeOption) {
const { to, from } = timerangeOption;
Expand All @@ -48,7 +49,12 @@ export const buildTimelineEventsAllQuery = ({
return [];
};

const filters = [...filterClause, ...getTimerangeFilter(timerange), { match_all: {} }];
const filters = [
...filterClause,
...getTimerangeFilter(timerange),
...getAlertConsumersFilter(alertConsumers),
{ match_all: {} },
];
const filter = authFilter != null ? [...filters, authFilter] : filters;

const getSortField = (sortFields: TimelineRequestSortField[]) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ import {
} from '../../../../../../common/utils/field_formatters';

export const timelineEventsDetails: TimelineFactory<TimelineEventsQueries.details> = {
buildDsl: ({ authFilter, ...options }: TimelineEventsDetailsRequestOptions) => {
buildDsl: ({ authFilter, alertConsumers, ...options }: TimelineEventsDetailsRequestOptions) => {
const { indexName, eventId, docValueFields = [] } = options;
return buildTimelineDetailsQuery(indexName, eventId, docValueFields, authFilter);
return buildTimelineDetailsQuery(
indexName,
eventId,
docValueFields,
authFilter,
alertConsumers
);
},
parse: async (
options: TimelineEventsDetailsRequestOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
*/

import { JsonObject } from '@kbn/utility-types';
import { AlertConsumers } from '@kbn/rule-data-utils';

import { DocValueFields } from '../../../../../../common/search_strategy';
import { getAlertConsumersFilter } from '../utils';

export const buildTimelineDetailsQuery = (
indexName: string,
id: string,
docValueFields: DocValueFields[],
authFilter?: JsonObject
authFilter?: JsonObject,
alertConsumers?: AlertConsumers[]
) => {
const basicFilter = {
terms: {
Expand All @@ -23,7 +27,7 @@ export const buildTimelineDetailsQuery = (
authFilter != null
? {
bool: {
filter: [basicFilter, authFilter],
filter: [basicFilter, authFilter, ...getAlertConsumersFilter(alertConsumers)],
},
}
: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import {
TimelineRequestBasicOptions,
} from '../../../../../../common/search_strategy';
import { createQueryFilterClauses } from '../../../../../utils/filters';
import { getAlertConsumersFilter } from '../utils';

export const buildTimelineKpiQuery = ({
defaultIndex,
filterQuery,
timerange,
alertConsumers,
}: TimelineRequestBasicOptions) => {
const filterClause = [...createQueryFilterClauses(filterQuery)];

Expand All @@ -41,7 +43,12 @@ export const buildTimelineKpiQuery = ({
return [];
};

const filter = [...filterClause, ...getTimerangeFilter(timerange), { match_all: {} }];
const filter = [
...filterClause,
...getTimerangeFilter(timerange),
...getAlertConsumersFilter(alertConsumers),
{ match_all: {} },
];

const dslQuery = {
allow_no_indices: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { AlertConsumers, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';

export const getAlertConsumersFilter = (alertConsumersToInclude: AlertConsumers[] | undefined) => {
if (alertConsumersToInclude == null || !alertConsumersToInclude.length) {
return [];
}

const consumerFilter = alertConsumersToInclude.map((consumer) => {
return {
bool: {
should: [{ match: { [ALERT_RULE_CONSUMER]: consumer } }],
minimum_should_match: 1,
},
};
});

return [
{
bool: {
filter: [
{
bool: {
should: consumerFilter,
minimum_should_match: 1,
},
},
],
},
},
];
};
55 changes: 51 additions & 4 deletions x-pack/plugins/timelines/server/search_strategy/timeline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
* 2.0.
*/

import { ALERT_RULE_CONSUMER, ALERT_RULE_TYPE_ID, SPACE_IDS } from '@kbn/rule-data-utils';
import {
AlertConsumers,
ALERT_RULE_CONSUMER,
ALERT_RULE_TYPE_ID,
SPACE_IDS,
} from '@kbn/rule-data-utils';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { from } from 'rxjs';

Expand All @@ -32,18 +37,23 @@ import {
ENHANCED_ES_SEARCH_STRATEGY,
ISearchOptions,
} from '../../../../../../src/plugins/data/common';
import { AuditLogger, SecurityPluginSetup } from '../../../../security/server';
import { AlertAuditAction, alertAuditEvent } from '../../../../rule_registry/server';

export const timelineSearchStrategyProvider = <T extends TimelineFactoryQueryTypes>(
data: PluginStart,
alerting: AlertingPluginStartContract
alerting: AlertingPluginStartContract,
security?: SecurityPluginSetup
): ISearchStrategy<TimelineStrategyRequestType<T>, TimelineStrategyResponseType<T>> => {
const esAsInternal = data.search.searchAsInternalUser;
const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY);

return {
search: (request, options, deps) => {
const securityAuditLogger = security?.audit.asScoped(deps.request);
const factoryQueryType = request.factoryQueryType;
const entityType = request.entityType;
const alertConsumers = request.alertConsumers;

if (factoryQueryType == null) {
throw new Error('factoryQueryType is required');
Expand All @@ -59,6 +69,8 @@ export const timelineSearchStrategyProvider = <T extends TimelineFactoryQueryTyp
deps,
queryFactory,
alerting,
auditLogger: securityAuditLogger,
alertConsumers,
});
} else {
return timelineSearchStrategy({ es, request, options, deps, queryFactory });
Expand Down Expand Up @@ -104,13 +116,17 @@ const timelineAlertsSearchStrategy = <T extends TimelineFactoryQueryTypes>({
deps,
queryFactory,
alerting,
alertConsumers = [],
auditLogger,
}: {
es: ISearchStrategy;
request: TimelineStrategyRequestType<T>;
options: ISearchOptions;
deps: SearchStrategyDependencies;
alerting: AlertingPluginStartContract;
queryFactory: TimelineFactory<T>;
alertConsumers?: AlertConsumers[];
auditLogger: AuditLogger | undefined;
}) => {
// Based on what solution alerts you want to see, figures out what corresponding
// index to query (ex: siem --> .alerts-security.alerts)
Expand All @@ -133,17 +149,48 @@ const timelineAlertsSearchStrategy = <T extends TimelineFactoryQueryTypes>({

return from(getAuthFilter()).pipe(
mergeMap(({ filter }) => {
const dsl = queryFactory.buildDsl({ ...requestWithAlertsIndices, authFilter: filter });
const dsl = queryFactory.buildDsl({
...requestWithAlertsIndices,
authFilter: filter,
alertConsumers,
});
return es.search({ ...requestWithAlertsIndices, params: dsl }, options, deps);
}),
map((response) => {
const rawResponse = shimHitsTotal(response.rawResponse, options);

// Do we have to loop over each hit? Yes.
// ecs auditLogger requires that we log each alert independently
if (auditLogger != null) {
rawResponse.hits?.hits?.forEach((hit) => {
auditLogger.log(
alertAuditEvent({
action: AlertAuditAction.FIND,
id: hit._id,
outcome: 'success',
})
);
});
}

return {
...response,
rawResponse: shimHitsTotal(response.rawResponse, options),
rawResponse,
};
}),
mergeMap((esSearchRes) => queryFactory.parse(requestWithAlertsIndices, esSearchRes)),
catchError((err) => {
// check if auth error, if yes, write to ecs logger
if (auditLogger != null && err?.output?.statusCode === 403) {
auditLogger.log(
alertAuditEvent({
action: AlertAuditAction.FIND,
outcome: 'failure',
error: err,
})
);
}

throw err;
})
);
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/timelines/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { DataPluginSetup, DataPluginStart } from '../../../../src/plugins/data/server/plugin';
import { PluginStartContract as AlertingPluginStartContract } from '../../alerting/server';
import { SecurityPluginSetup } from '../../security/server';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TimelinesPluginUI {}
Expand All @@ -16,6 +17,7 @@ export interface TimelinesPluginStart {}

export interface SetupPlugins {
data: DataPluginSetup;
security?: SecurityPluginSetup;
}

export interface StartPlugins {
Expand Down
Loading