Skip to content

Commit

Permalink
[RAC][Timeline] - Add audit log to RBAC wrapped search strategy (#112040
Browse files Browse the repository at this point in the history
) (#117435)

### Summary

Went back to add audit logging to the alerts table search strategy used to query RAC alerts. This PR also includes tests for the logging.
# Conflicts:
#	x-pack/test/timeline/common/config.ts
#	x-pack/test/timeline/security_and_spaces/tests/trial/events.ts
  • Loading branch information
yctercero authored Nov 3, 2021
1 parent ce2a967 commit fcd5a23
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 21 deletions.
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 an alert as part of a search operation.
| `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
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
44 changes: 39 additions & 5 deletions x-pack/plugins/timelines/server/search_strategy/timeline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,20 @@ 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;

Expand All @@ -59,6 +63,7 @@ export const timelineSearchStrategyProvider = <T extends TimelineFactoryQueryTyp
deps,
queryFactory,
alerting,
auditLogger: securityAuditLogger,
});
} else {
return timelineSearchStrategy({ es, request, options, deps, queryFactory });
Expand Down Expand Up @@ -104,16 +109,16 @@ const timelineAlertsSearchStrategy = <T extends TimelineFactoryQueryTypes>({
deps,
queryFactory,
alerting,
auditLogger,
}: {
es: ISearchStrategy;
request: TimelineStrategyRequestType<T>;
options: ISearchOptions;
deps: SearchStrategyDependencies;
alerting: AlertingPluginStartContract;
queryFactory: TimelineFactory<T>;
auditLogger: AuditLogger | undefined;
}) => {
// Based on what solution alerts you want to see, figures out what corresponding
// index to query (ex: siem --> .alerts-security.alerts)
const indices = request.defaultIndex ?? request.indexType;
const requestWithAlertsIndices = { ...request, defaultIndex: indices, indexName: indices };

Expand All @@ -133,17 +138,46 @@ 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,
});
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
4 changes: 4 additions & 0 deletions x-pack/scripts/functional_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/saved_object_api_integration/security_only/config_trial.ts'),
require.resolve('../test/saved_object_api_integration/security_only/config_basic.ts'),
require.resolve('../test/saved_object_api_integration/spaces_only/config.ts'),
// TODO: Enable once RBAC timeline search strategy
// tests updated
// require.resolve('../test/timeline/security_and_spaces/config_basic.ts'),
require.resolve('../test/timeline/security_and_spaces/config_trial.ts'),
require.resolve('../test/ui_capabilities/security_and_spaces/config.ts'),
require.resolve('../test/ui_capabilities/security_only/config.ts'),
require.resolve('../test/ui_capabilities/spaces_only/config.ts'),
Expand Down
8 changes: 7 additions & 1 deletion x-pack/test/timeline/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { CA_CERT_PATH } from '@kbn/dev-utils';
import { FtrConfigProviderContext } from '@kbn/test';
import { resolve } from 'path';

import { services } from './services';
import { getAllExternalServiceSimulatorPaths } from '../../alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin';
Expand Down Expand Up @@ -40,6 +41,7 @@ const enabledActionTypes = [

export function createTestConfig(name: string, options: CreateTestConfigOptions) {
const { license = 'trial', disabledPlugins = [], ssl = false, testFiles = [] } = options;
const auditLogPath = resolve(__dirname, './audit.log');

return async ({ readConfigFile }: FtrConfigProviderContext) => {
const xPackApiIntegrationTestsConfig = await readConfigFile(
Expand Down Expand Up @@ -83,7 +85,11 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
// TO DO: Remove feature flags once we're good to go
'--xpack.securitySolution.enableExperimental=["ruleRegistryEnabled"]',
'--xpack.ruleRegistry.write.enabled=true',
`--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`,
'--xpack.security.audit.enabled=true',
'--xpack.security.audit.appender.type=file',
`--xpack.security.audit.appender.fileName=${auditLogPath}`,
'--xpack.security.audit.appender.layout.type=json',
`--server.xsrf.allowlist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`,
...(ssl
? [
`--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
Expand Down
Loading

0 comments on commit fcd5a23

Please sign in to comment.