mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Defend workflows] Osquery license check + display errors (#156738)
This commit is contained in:
parent
a61c63dc07
commit
952489fa71
25 changed files with 223 additions and 104 deletions
23
x-pack/plugins/osquery/common/translations/errors.ts
Normal file
23
x-pack/plugins/osquery/common/translations/errors.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const LICENSE_TOO_LOW = i18n.translate(
|
||||
'xpack.osquery.liveQueryActions.error.licenseTooLow',
|
||||
{
|
||||
defaultMessage: 'At least Platinum license is required to use Response Actions.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PARAMETER_NOT_FOUND = i18n.translate(
|
||||
'xpack.osquery.liveQueryActions.error.notFoundParameters',
|
||||
{
|
||||
defaultMessage:
|
||||
"This query hasn't been called due to parameter used and its value not found in the alert.",
|
||||
}
|
||||
);
|
|
@ -59,12 +59,7 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
if (error) {
|
||||
edges.forEach((edge) => {
|
||||
if (edge.fields) {
|
||||
edge.fields['error.skipped'] = edge.fields.error = [
|
||||
i18n.translate('xpack.osquery.liveQueryActionResults.table.skippedErrorText', {
|
||||
defaultMessage:
|
||||
"This query hasn't been called due to parameter used and its value not found in the alert.",
|
||||
}),
|
||||
];
|
||||
edge.fields['error.skipped'] = edge.fields.error = [error];
|
||||
}
|
||||
});
|
||||
} else if (expired) {
|
||||
|
|
|
@ -31,6 +31,7 @@ interface CreateActionHandlerOptions {
|
|||
soClient?: SavedObjectsClientContract;
|
||||
metadata?: Metadata;
|
||||
alertData?: ParsedTechnicalFields;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const createActionHandler = async (
|
||||
|
@ -43,7 +44,7 @@ export const createActionHandler = async (
|
|||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
||||
osqueryContext.getStartServices
|
||||
);
|
||||
const { soClient, metadata, alertData } = options;
|
||||
const { soClient, metadata, alertData, error } = options;
|
||||
const savedObjectsClient = soClient ?? coreStartServices.savedObjects.createInternalRepository();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
@ -98,6 +99,7 @@ export const createActionHandler = async (
|
|||
action_id: uuidv4(),
|
||||
id: packQueryId,
|
||||
...replacedQuery,
|
||||
...(error ? { error } : {}),
|
||||
ecs_mapping: packQuery.ecs_mapping,
|
||||
version: packQuery.version,
|
||||
platform: packQuery.platform,
|
||||
|
@ -106,22 +108,31 @@ export const createActionHandler = async (
|
|||
(value) => !isEmpty(value)
|
||||
);
|
||||
})
|
||||
: await createDynamicQueries({ params, alertData, agents: selectedAgents, osqueryContext }),
|
||||
: await createDynamicQueries({
|
||||
params,
|
||||
alertData,
|
||||
agents: selectedAgents,
|
||||
osqueryContext,
|
||||
error,
|
||||
}),
|
||||
};
|
||||
|
||||
const fleetActions = map(
|
||||
filter(osqueryAction.queries, (query) => !query.error),
|
||||
(query) => ({
|
||||
action_id: query.action_id,
|
||||
'@timestamp': moment().toISOString(),
|
||||
expiration: moment().add(5, 'minutes').toISOString(),
|
||||
type: 'INPUT_ACTION',
|
||||
input_type: 'osquery',
|
||||
agents: query.agents,
|
||||
user_id: metadata?.currentUser,
|
||||
data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']),
|
||||
})
|
||||
);
|
||||
const fleetActions = !error
|
||||
? map(
|
||||
filter(osqueryAction.queries, (query) => !query.error),
|
||||
(query) => ({
|
||||
action_id: query.action_id,
|
||||
'@timestamp': moment().toISOString(),
|
||||
expiration: moment().add(5, 'minutes').toISOString(),
|
||||
type: 'INPUT_ACTION',
|
||||
input_type: 'osquery',
|
||||
agents: query.agents,
|
||||
user_id: metadata?.currentUser,
|
||||
data: pick(query, ['id', 'query', 'ecs_mapping', 'version', 'platform']),
|
||||
})
|
||||
)
|
||||
: [];
|
||||
|
||||
if (fleetActions.length) {
|
||||
await esClientInternal.bulk({
|
||||
refresh: 'wait_for',
|
||||
|
@ -129,24 +140,24 @@ export const createActionHandler = async (
|
|||
fleetActions.map((action) => [{ index: { _index: AGENT_ACTIONS_INDEX } }, action])
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const actionsComponentTemplateExists = await esClientInternal.indices.exists({
|
||||
index: `${ACTIONS_INDEX}*`,
|
||||
});
|
||||
const actionsComponentTemplateExists = await esClientInternal.indices.exists({
|
||||
index: `${ACTIONS_INDEX}*`,
|
||||
});
|
||||
|
||||
if (actionsComponentTemplateExists) {
|
||||
await esClientInternal.bulk({
|
||||
refresh: 'wait_for',
|
||||
body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction],
|
||||
});
|
||||
}
|
||||
|
||||
osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, {
|
||||
...omit(osqueryAction, ['type', 'input_type', 'user_id']),
|
||||
agents: osqueryAction.agents.length,
|
||||
if (actionsComponentTemplateExists) {
|
||||
await esClientInternal.bulk({
|
||||
refresh: 'wait_for',
|
||||
body: [{ index: { _index: `${ACTIONS_INDEX}-default` } }, osqueryAction],
|
||||
});
|
||||
}
|
||||
|
||||
osqueryContext.telemetryEventsSender.reportEvent(TELEMETRY_EBT_LIVE_QUERY_EVENT, {
|
||||
...omit(osqueryAction, ['type', 'input_type', 'user_id', 'error']),
|
||||
agents: osqueryAction.agents.length,
|
||||
});
|
||||
|
||||
return {
|
||||
response: osqueryAction,
|
||||
fleetActionsCount: fleetActions.length,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query';
|
||||
import type { OsqueryActiveLicenses } from './validate_license';
|
||||
import { validateLicense } from './validate_license';
|
||||
import { createActionHandler } from './create_action_handler';
|
||||
|
||||
export const createActionService = (osqueryContext: OsqueryAppContext) => {
|
||||
let licenseSubscription: Subscription | null = null;
|
||||
const licenses: OsqueryActiveLicenses = { isActivePlatinumLicense: false };
|
||||
|
||||
licenseSubscription = osqueryContext.licensing.license$.subscribe((license) => {
|
||||
licenses.isActivePlatinumLicense = license.isActive && license.hasAtLeast('platinum');
|
||||
});
|
||||
|
||||
const create = async (
|
||||
params: CreateLiveQueryRequestBodySchema,
|
||||
alertData?: ParsedTechnicalFields
|
||||
) => {
|
||||
const error = validateLicense(licenses);
|
||||
|
||||
return createActionHandler(osqueryContext, params, { alertData, error });
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
if (licenseSubscription) {
|
||||
licenseSubscription.unsubscribe();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
create,
|
||||
stop,
|
||||
};
|
||||
};
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createDynamicQueries, PARAMETER_NOT_FOUND } from './create_queries';
|
||||
import { createDynamicQueries } from './create_queries';
|
||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
||||
|
||||
describe('create queries', () => {
|
||||
const defualtQueryParams = {
|
||||
|
@ -65,9 +66,7 @@ describe('create queries', () => {
|
|||
expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`);
|
||||
expect(queries[0].error).toBe(undefined);
|
||||
expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};');
|
||||
expect(queries[1].error).toBe(
|
||||
"This query hasn't been called due to parameter used and its value not found in the alert."
|
||||
);
|
||||
expect(queries[1].error).toBe(PARAMETER_NOT_FOUND);
|
||||
expect(queries[2].query).toBe('SELECT * FROM processes;');
|
||||
expect(queries[2].error).toBe(undefined);
|
||||
});
|
||||
|
|
|
@ -7,33 +7,28 @@
|
|||
|
||||
import { isEmpty, map, pickBy } from 'lodash';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query';
|
||||
import { replaceParamsQuery } from '../../../common/utils/replace_params_query';
|
||||
import { isSavedQueryPrebuilt } from '../../routes/saved_query/utils';
|
||||
|
||||
export const PARAMETER_NOT_FOUND = i18n.translate(
|
||||
'xpack.osquery.liveQueryActions.error.notFoundParameters',
|
||||
{
|
||||
defaultMessage:
|
||||
"This query hasn't been called due to parameter used and its value not found in the alert.",
|
||||
}
|
||||
);
|
||||
|
||||
interface CreateDynamicQueriesParams {
|
||||
params: CreateLiveQueryRequestBodySchema;
|
||||
alertData?: ParsedTechnicalFields;
|
||||
agents: string[];
|
||||
osqueryContext: OsqueryAppContext;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const createDynamicQueries = async ({
|
||||
params,
|
||||
alertData,
|
||||
agents,
|
||||
osqueryContext,
|
||||
error,
|
||||
}: CreateDynamicQueriesParams) =>
|
||||
params.queries?.length
|
||||
? map(params.queries, ({ query, ...restQuery }) => {
|
||||
|
@ -43,6 +38,7 @@ export const createDynamicQueries = async ({
|
|||
{
|
||||
...replacedQuery,
|
||||
...restQuery,
|
||||
...(error ? { error } : {}),
|
||||
action_id: uuidv4(),
|
||||
alert_ids: params.alert_ids,
|
||||
agents,
|
||||
|
@ -66,6 +62,7 @@ export const createDynamicQueries = async ({
|
|||
ecs_mapping: params.ecs_mapping,
|
||||
alert_ids: params.alert_ids,
|
||||
agents,
|
||||
...(error ? { error } : {}),
|
||||
},
|
||||
(value) => !isEmpty(value)
|
||||
),
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { LICENSE_TOO_LOW } from '../../../common/translations/errors';
|
||||
|
||||
export interface OsqueryActiveLicenses {
|
||||
isActivePlatinumLicense: boolean;
|
||||
}
|
||||
|
||||
export const validateLicense = (license?: OsqueryActiveLicenses) => {
|
||||
if (!license) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isActivePlatinumLicense } = license;
|
||||
|
||||
if (!isActivePlatinumLicense) {
|
||||
return LICENSE_TOO_LOW;
|
||||
}
|
||||
};
|
|
@ -15,6 +15,7 @@ import type {
|
|||
PackagePolicyClient,
|
||||
} from '@kbn/fleet-plugin/server';
|
||||
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
||||
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||
import type { ConfigType } from '../../common/config';
|
||||
import type { TelemetryEventsSender } from './telemetry/sender';
|
||||
|
||||
|
@ -82,6 +83,7 @@ export interface OsqueryAppContext {
|
|||
security: SecurityPluginStart;
|
||||
getStartServices: CoreSetup['getStartServices'];
|
||||
telemetryEventsSender: TelemetryEventsSender;
|
||||
licensing: LicensingPluginSetup;
|
||||
/**
|
||||
* Object readiness is tied to plugin start method
|
||||
*/
|
||||
|
|
|
@ -17,12 +17,11 @@ import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
|||
import type { DataViewsService } from '@kbn/data-views-plugin/common';
|
||||
import type { NewPackagePolicy, UpdatePackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
|
||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { upgradeIntegration } from './utils/upgrade_integration';
|
||||
import type { PackSavedObjectAttributes } from './common/types';
|
||||
import { updateGlobalPacksCreateCallback } from './lib/update_global_packs';
|
||||
import { packSavedObjectType } from '../common/types';
|
||||
import type { CreateLiveQueryRequestBodySchema } from '../common/schemas/routes/live_query';
|
||||
import { createConfig } from './create_config';
|
||||
import type { OsqueryPluginSetup, OsqueryPluginStart, SetupPlugins, StartPlugins } from './types';
|
||||
import { defineRoutes } from './routes';
|
||||
|
@ -39,10 +38,10 @@ import { TelemetryReceiver } from './lib/telemetry/receiver';
|
|||
import { initializeTransformsIndices } from './create_indices/create_transforms_indices';
|
||||
import { initializeTransforms } from './create_transforms/create_transforms';
|
||||
import { createDataViews } from './create_data_views';
|
||||
import { createActionHandler } from './handlers/action';
|
||||
|
||||
import { registerFeatures } from './utils/register_features';
|
||||
import { CASE_ATTACHMENT_TYPE_ID } from '../common/constants';
|
||||
import { createActionService } from './handlers/action/create_action_service';
|
||||
|
||||
export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginStart> {
|
||||
private readonly logger: Logger;
|
||||
|
@ -50,6 +49,8 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
private readonly osqueryAppContextService = new OsqueryAppContextService();
|
||||
private readonly telemetryReceiver: TelemetryReceiver;
|
||||
private readonly telemetryEventsSender: TelemetryEventsSender;
|
||||
private licenseSubscription: Subscription | null = null;
|
||||
private createActionService: ReturnType<typeof createActionService> | null = null;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
this.context = initializerContext;
|
||||
|
@ -73,6 +74,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
config: (): ConfigType => config,
|
||||
security: plugins.security,
|
||||
telemetryEventsSender: this.telemetryEventsSender,
|
||||
licensing: plugins.licensing,
|
||||
};
|
||||
|
||||
initSavedObjects(core.savedObjects);
|
||||
|
@ -82,6 +84,8 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
usageCollection: plugins.usageCollection,
|
||||
});
|
||||
|
||||
this.createActionService = createActionService(osqueryContext);
|
||||
|
||||
core.getStartServices().then(([{ elasticsearch }, depsStart]) => {
|
||||
const osquerySearchStrategy = osquerySearchStrategyProvider(
|
||||
depsStart.data,
|
||||
|
@ -97,10 +101,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
plugins.cases.attachmentFramework.registerExternalReference({ id: CASE_ATTACHMENT_TYPE_ID });
|
||||
|
||||
return {
|
||||
osqueryCreateAction: (
|
||||
params: CreateLiveQueryRequestBodySchema,
|
||||
alertData?: ParsedTechnicalFields
|
||||
) => createActionHandler(osqueryContext, params, { alertData }),
|
||||
createActionService: this.createActionService,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -180,6 +181,8 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
this.logger.debug('osquery: Stopped');
|
||||
this.telemetryEventsSender.stop();
|
||||
this.osqueryAppContextService.stop();
|
||||
this.licenseSubscription?.unsubscribe();
|
||||
this.createActionService?.stop();
|
||||
}
|
||||
|
||||
async initialize(core: CoreStart, dataViewsService: DataViewsService): Promise<void> {
|
||||
|
|
|
@ -11,7 +11,7 @@ import markdown from 'remark-parse-no-trim';
|
|||
import { some, filter } from 'lodash';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import type { ECSMappingOrUndefined } from '@kbn/osquery-io-ts-types';
|
||||
import { PARAMETER_NOT_FOUND } from '../../handlers/action/create_queries';
|
||||
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
||||
import { replaceParamsQuery } from '../../../common/utils/replace_params_query';
|
||||
import { createLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query';
|
||||
import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query';
|
||||
|
|
|
@ -22,15 +22,12 @@ import type {
|
|||
} from '@kbn/task-manager-plugin/server';
|
||||
import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||
import type { CasesSetup } from '@kbn/cases-plugin/server';
|
||||
import type { CreateLiveQueryRequestBodySchema } from '../common/schemas/routes/live_query';
|
||||
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||
import type { createActionService } from './handlers/action/create_action_service';
|
||||
|
||||
export interface OsqueryPluginSetup {
|
||||
osqueryCreateAction: (
|
||||
payload: CreateLiveQueryRequestBodySchema,
|
||||
alertData?: ParsedTechnicalFields
|
||||
) => void;
|
||||
createActionService: ReturnType<typeof createActionService>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
@ -45,6 +42,7 @@ export interface SetupPlugins {
|
|||
security: SecurityPluginStart;
|
||||
taskManager?: TaskManagerPluginSetup;
|
||||
telemetry?: TelemetryPluginSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
}
|
||||
|
||||
export interface StartPlugins {
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"@kbn/safer-lodash-set",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/securitysolution-ecs",
|
||||
"@kbn/licensing-plugin",
|
||||
"@kbn/core-saved-objects-server"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ export const UNISOLATE_HOST_ROUTE = `${BASE_ENDPOINT_ROUTE}/unisolate`;
|
|||
|
||||
/** Base Actions route. Used to get a list of all actions and is root to other action related routes */
|
||||
export const BASE_ENDPOINT_ACTION_ROUTE = `${BASE_ENDPOINT_ROUTE}/action`;
|
||||
export const BASE_ENDPOINT_ACTION_ALERTS_ROUTE = `${BASE_ENDPOINT_ROUTE}/alerts`;
|
||||
export const BASE_ENDPOINT_ACTION_ALERTS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/alerts`;
|
||||
|
||||
export const ISOLATE_HOST_ROUTE_V2 = `${BASE_ENDPOINT_ACTION_ROUTE}/isolate`;
|
||||
export const UNISOLATE_HOST_ROUTE_V2 = `${BASE_ENDPOINT_ACTION_ROUTE}/unisolate`;
|
||||
|
|
|
@ -49,7 +49,17 @@ export const fillUpNewRule = (name = 'Test', description = 'Test') => {
|
|||
};
|
||||
export const visitRuleActions = (ruleId: string) => {
|
||||
cy.visit(`app/security/rules/id/${ruleId}/edit`);
|
||||
cy.getByTestSubj('edit-rule-actions-tab').wait(500).click();
|
||||
cy.getByTestSubj('edit-rule-actions-tab').should('exist');
|
||||
// strange rerendering behaviour. the following make sure the test doesn't fail
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.find('[data-test-subj="globalLoadingIndicator"]').length) {
|
||||
cy.getByTestSubj('globalLoadingIndicator').should('exist');
|
||||
cy.getByTestSubj('globalLoadingIndicator').should('not.exist');
|
||||
}
|
||||
cy.getByTestSubj('globalLoadingIndicator').should('not.exist');
|
||||
});
|
||||
|
||||
cy.getByTestSubj('edit-rule-actions-tab').click();
|
||||
};
|
||||
export const tryAddingDisabledResponseAction = (itemNumber = 0) => {
|
||||
cy.getByTestSubj('response-actions-wrapper').within(() => {
|
||||
|
|
|
@ -37,6 +37,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz';
|
|||
import { calculateEndpointAuthz } from '../../common/endpoint/service/authz';
|
||||
import type { FeatureUsageService } from './services/feature_usage/service';
|
||||
import type { ExperimentalFeatures } from '../../common/experimental_features';
|
||||
import type { ActionCreateService } from './services';
|
||||
import { doesArtifactHaveData } from './services';
|
||||
import type { actionCreateService } from './services/actions';
|
||||
|
||||
|
@ -237,7 +238,7 @@ export class EndpointAppContextService {
|
|||
return this.startDependencies.messageSigningService;
|
||||
}
|
||||
|
||||
public getActionCreateService(): ReturnType<typeof actionCreateService> {
|
||||
public getActionCreateService(): ActionCreateService {
|
||||
if (!this.startDependencies?.actionCreateService) {
|
||||
throw new EndpointAppContentServicesNotStartedError();
|
||||
}
|
||||
|
|
|
@ -41,17 +41,20 @@ export function registerActionListRoutes(
|
|||
actionListHandler(endpointContext)
|
||||
)
|
||||
);
|
||||
|
||||
// TODO: This route is a temporary solution until we decide on how RBAC should look like for Actions in Alerts
|
||||
router.get(
|
||||
{
|
||||
path: BASE_ENDPOINT_ACTION_ALERTS_ROUTE,
|
||||
validate: EndpointActionListRequestSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{},
|
||||
endpointContext.logFactory.get('endpointActionList'),
|
||||
actionListHandler(endpointContext)
|
||||
)
|
||||
);
|
||||
if (endpointContext.experimentalFeatures.endpointResponseActionsEnabled) {
|
||||
router.get(
|
||||
{
|
||||
path: BASE_ENDPOINT_ACTION_ALERTS_ROUTE,
|
||||
validate: EndpointActionListRequestSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{},
|
||||
endpointContext.logFactory.get('endpointActionList'),
|
||||
actionListHandler(endpointContext)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -132,11 +132,7 @@ describe('Response actions', () => {
|
|||
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
|
||||
endpointAppContextService.start({
|
||||
...startContract,
|
||||
actionCreateService: actionCreateService(
|
||||
mockScopedClient.asInternalUser,
|
||||
endpointContext,
|
||||
licenseService
|
||||
),
|
||||
actionCreateService: actionCreateService(mockScopedClient.asInternalUser, endpointContext),
|
||||
licenseService,
|
||||
});
|
||||
|
||||
|
|
|
@ -68,12 +68,19 @@ interface CreateActionMetadata {
|
|||
enableActionsWithErrors?: boolean;
|
||||
}
|
||||
|
||||
export interface ActionCreateService {
|
||||
createActionFromAlert: (payload: CreateActionPayload) => Promise<ActionDetails>;
|
||||
createAction: (
|
||||
payload: CreateActionPayload,
|
||||
metadata: CreateActionMetadata
|
||||
) => Promise<ActionDetails>;
|
||||
}
|
||||
|
||||
export const actionCreateService = (
|
||||
esClient: ElasticsearchClient,
|
||||
endpointContext: EndpointAppContext,
|
||||
licenseService: LicenseService
|
||||
) => {
|
||||
const createActionFromAlert = async (payload: CreateActionPayload) => {
|
||||
endpointContext: EndpointAppContext
|
||||
): ActionCreateService => {
|
||||
const createActionFromAlert = async (payload: CreateActionPayload): Promise<ActionDetails> => {
|
||||
return createAction({ ...payload }, { minimumLicenseRequired: 'enterprise' });
|
||||
};
|
||||
|
||||
|
@ -86,6 +93,8 @@ export const actionCreateService = (
|
|||
endpointContext.service.getFeatureUsageService().notifyUsage(featureKey);
|
||||
}
|
||||
|
||||
const licenseService = endpointContext.service.getLicenseService();
|
||||
|
||||
const logger = endpointContext.logFactory.get('hostIsolation');
|
||||
|
||||
// fetch the Agent IDs to send the commands to
|
||||
|
@ -341,11 +350,12 @@ interface CheckForAlertsArgs {
|
|||
licenseService: LicenseService;
|
||||
minimumLicenseRequired: LicenseType;
|
||||
}
|
||||
|
||||
const checkForAlertErrors = ({
|
||||
agents,
|
||||
licenseService,
|
||||
minimumLicenseRequired = 'basic',
|
||||
}: CheckForAlertsArgs) => {
|
||||
}: CheckForAlertsArgs): string | undefined => {
|
||||
const licenseError = validateEndpointLicense(licenseService, minimumLicenseRequired);
|
||||
const agentsError = validateAgents(agents);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { AlertsWithAgentType } from './types';
|
|||
|
||||
export const osqueryResponseAction = (
|
||||
responseAction: RuleResponseOsqueryAction,
|
||||
osqueryCreateAction: SetupPlugins['osquery']['osqueryCreateAction'],
|
||||
osqueryCreateActionService: SetupPlugins['osquery']['createActionService'],
|
||||
{ alerts, alertIds, agentIds }: AlertsWithAgentType
|
||||
) => {
|
||||
const temporaryQueries = responseAction.params.queries?.length
|
||||
|
@ -27,7 +27,7 @@ export const osqueryResponseAction = (
|
|||
const { savedQueryId, packId, queries, ecsMapping, ...rest } = responseAction.params;
|
||||
|
||||
if (!containsDynamicQueries) {
|
||||
return osqueryCreateAction({
|
||||
return osqueryCreateActionService.create({
|
||||
...rest,
|
||||
queries,
|
||||
ecs_mapping: ecsMapping,
|
||||
|
@ -37,7 +37,7 @@ export const osqueryResponseAction = (
|
|||
});
|
||||
}
|
||||
each(alerts, (alert) => {
|
||||
return osqueryCreateAction(
|
||||
return osqueryCreateActionService.create(
|
||||
{
|
||||
...rest,
|
||||
queries,
|
||||
|
|
|
@ -53,11 +53,14 @@ describe('ScheduleNotificationResponseActions', () => {
|
|||
saved_query_id: undefined,
|
||||
ecs_mapping: { testField: { field: 'testField', value: 'testValue' } },
|
||||
};
|
||||
const osqueryActionMock = jest.fn();
|
||||
const osqueryActionMock = {
|
||||
create: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
const endpointActionMock = jest.fn();
|
||||
|
||||
const scheduleNotificationResponseActions = getScheduleNotificationResponseActionsService({
|
||||
osqueryCreateAction: osqueryActionMock,
|
||||
osqueryCreateActionService: osqueryActionMock,
|
||||
endpointAppContextService: endpointActionMock as never,
|
||||
});
|
||||
|
||||
|
@ -74,7 +77,7 @@ describe('ScheduleNotificationResponseActions', () => {
|
|||
];
|
||||
scheduleNotificationResponseActions({ signals, responseActions });
|
||||
|
||||
expect(osqueryActionMock).toHaveBeenCalledWith({
|
||||
expect(osqueryActionMock.create).toHaveBeenCalledWith({
|
||||
...defaultQueryResultParams,
|
||||
query: simpleQuery,
|
||||
});
|
||||
|
@ -99,7 +102,7 @@ describe('ScheduleNotificationResponseActions', () => {
|
|||
];
|
||||
scheduleNotificationResponseActions({ signals, responseActions });
|
||||
|
||||
expect(osqueryActionMock).toHaveBeenCalledWith({
|
||||
expect(osqueryActionMock.create).toHaveBeenCalledWith({
|
||||
...defaultPackResultParams,
|
||||
queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }],
|
||||
});
|
||||
|
|
|
@ -20,12 +20,12 @@ type Alerts = Array<ParsedTechnicalFields & { agent?: { id: string } }>;
|
|||
|
||||
interface ScheduleNotificationResponseActionsService {
|
||||
endpointAppContextService: EndpointAppContextService;
|
||||
osqueryCreateAction: SetupPlugins['osquery']['osqueryCreateAction'];
|
||||
osqueryCreateActionService?: SetupPlugins['osquery']['createActionService'];
|
||||
}
|
||||
|
||||
export const getScheduleNotificationResponseActionsService =
|
||||
({
|
||||
osqueryCreateAction,
|
||||
osqueryCreateActionService,
|
||||
endpointAppContextService,
|
||||
}: ScheduleNotificationResponseActionsService) =>
|
||||
({ signals, responseActions }: ScheduleNotificationActions) => {
|
||||
|
@ -48,8 +48,11 @@ export const getScheduleNotificationResponseActionsService =
|
|||
);
|
||||
|
||||
each(responseActions, (responseAction) => {
|
||||
if (responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY && osqueryCreateAction) {
|
||||
osqueryResponseAction(responseAction, osqueryCreateAction, {
|
||||
if (
|
||||
responseAction.actionTypeId === RESPONSE_ACTION_TYPES.OSQUERY &&
|
||||
osqueryCreateActionService
|
||||
) {
|
||||
osqueryResponseAction(responseAction, osqueryCreateActionService, {
|
||||
alerts,
|
||||
alertIds,
|
||||
agentIds,
|
||||
|
|
|
@ -57,7 +57,7 @@ import { registerEndpointRoutes } from './endpoint/routes/metadata';
|
|||
import { registerPolicyRoutes } from './endpoint/routes/policy';
|
||||
import { registerActionRoutes } from './endpoint/routes/actions';
|
||||
import { registerEndpointSuggestionsRoutes } from './endpoint/routes/suggestions';
|
||||
import { actionCreateService, EndpointArtifactClient, ManifestManager } from './endpoint/services';
|
||||
import { EndpointArtifactClient, ManifestManager } from './endpoint/services';
|
||||
import { EndpointAppContextService } from './endpoint/endpoint_app_context_services';
|
||||
import type { EndpointAppContext } from './endpoint/types';
|
||||
import { initUsageCollectors } from './usage';
|
||||
|
@ -101,6 +101,7 @@ import type {
|
|||
} from './plugin_contract';
|
||||
import { EndpointFleetServicesFactory } from './endpoint/services/fleet';
|
||||
import { featureUsageService } from './endpoint/services/feature_usage';
|
||||
import { actionCreateService } from './endpoint/services/actions';
|
||||
import { setIsElasticCloudDeployment } from './lib/telemetry/helpers';
|
||||
import { artifactService } from './lib/telemetry/artifact';
|
||||
import { endpointFieldsProvider } from './search_strategy/endpoint_fields';
|
||||
|
@ -246,7 +247,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = {
|
||||
scheduleNotificationResponseActionsService: getScheduleNotificationResponseActionsService({
|
||||
endpointAppContextService: this.endpointAppContextService,
|
||||
osqueryCreateAction: plugins.osquery.osqueryCreateAction,
|
||||
osqueryCreateActionService: plugins.osquery.createActionService,
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -505,8 +506,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
messageSigningService: plugins.fleet?.messageSigningService,
|
||||
actionCreateService: actionCreateService(
|
||||
core.elasticsearch.client.asInternalUser,
|
||||
this.endpointContext,
|
||||
licenseService
|
||||
this.endpointContext
|
||||
),
|
||||
});
|
||||
|
||||
|
|
|
@ -25807,7 +25807,6 @@
|
|||
"xpack.osquery.liveQueryActionResults.table.expiredStatusText": "expiré",
|
||||
"xpack.osquery.liveQueryActionResults.table.pendingStatusText": "en attente",
|
||||
"xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "Nombre de lignes de résultats",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedErrorText": "Cette requête n'a pas été appelée en raison du paramètre utilisé et de sa valeur non trouvée dans l'alerte.",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedStatusText": "ignoré",
|
||||
"xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "Statut",
|
||||
"xpack.osquery.liveQueryActionResults.table.successStatusText": "réussite",
|
||||
|
|
|
@ -25788,7 +25788,6 @@
|
|||
"xpack.osquery.liveQueryActionResults.table.expiredStatusText": "期限切れ",
|
||||
"xpack.osquery.liveQueryActionResults.table.pendingStatusText": "保留中",
|
||||
"xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "結果行数",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedErrorText": "このクエリは、使用されているパラメーターとその値がアラートで見つからないため、呼び出されていません。",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedStatusText": "スキップ済み",
|
||||
"xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "ステータス",
|
||||
"xpack.osquery.liveQueryActionResults.table.successStatusText": "成功",
|
||||
|
|
|
@ -25805,7 +25805,6 @@
|
|||
"xpack.osquery.liveQueryActionResults.table.expiredStatusText": "已过期",
|
||||
"xpack.osquery.liveQueryActionResults.table.pendingStatusText": "待处理",
|
||||
"xpack.osquery.liveQueryActionResults.table.resultRowsNumberColumnTitle": "结果行数",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedErrorText": "尚未调用此查询,因为在告警中找不到所使用的参数及其值。",
|
||||
"xpack.osquery.liveQueryActionResults.table.skippedStatusText": "已跳过",
|
||||
"xpack.osquery.liveQueryActionResults.table.statusColumnTitle": "状态",
|
||||
"xpack.osquery.liveQueryActionResults.table.successStatusText": "成功",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue