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

[SecuritySolution][Endpoint] Add scan response action API #184437

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2ccc4e5
Add `scan` action request API
ashokaditya May 29, 2024
db07eb3
fix types
ashokaditya May 30, 2024
b27d2a8
fix tests
ashokaditya May 30, 2024
e5f4b8c
route behind feature flag
ashokaditya May 30, 2024
9e62e59
fix type
ashokaditya May 30, 2024
ae882df
fix tests
ashokaditya May 30, 2024
4fdc8a3
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya May 30, 2024
42c4454
remove redundant schema field
ashokaditya May 30, 2024
3b6cf46
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya May 31, 2024
3ae76aa
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya May 31, 2024
8fa21e2
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya May 31, 2024
ea349a0
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
8cb121a
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Jun 3, 2024
881a8bb
fix mock creation
ashokaditya Jun 3, 2024
c44b3e6
Update actions.test.ts
ashokaditya Jun 3, 2024
471539e
update generator action errors for `scan`
ashokaditya Jun 3, 2024
e72d14c
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
022377b
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
a548ffc
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
3db5e62
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
23e73ef
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
63d93c5
Merge branch 'main' into task/dw-scan-response-action-api
ashokaditya Jun 3, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { schema } from '@kbn/config-schema';
import { UploadActionRequestSchema } from '../..';
import { ExecuteActionRequestSchema } from '../execute_route';
import { EndpointActionGetFileSchema } from '../get_file_route';
import { ScanActionRequestSchema } from '../scan_route';
import { KillOrSuspendProcessRequestSchema, NoParametersRequestSchema } from './base';

export const ResponseActionBodySchema = schema.oneOf([
NoParametersRequestSchema.body,
KillOrSuspendProcessRequestSchema.body,
EndpointActionGetFileSchema.body,
ExecuteActionRequestSchema.body,
ScanActionRequestSchema.body,
UploadActionRequestSchema.body,
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ components:
- type: object
required:
- parameters
- file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this removed on purpose?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. The file param is redundant. The schema needs a parameters only.

properties:
parameters:
required:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { z } from 'zod';

/*
* NOTICE: Do not edit this file manually.
* This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator.
*
* info:
* title: Scan Schema
* version: 2023-10-31
*/

import { BaseActionSchema } from '../model/schema/common.gen';

export type ScanActionRequestBody = z.infer<typeof ScanActionRequestBody>;
export const ScanActionRequestBody = BaseActionSchema.merge(
z.object({
parameters: z.object({
path: z.string(),
}),
})
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
openapi: 3.0.0
info:
title: Scan Schema
version: '2023-10-31'
paths:
/api/endpoint/action/scan:
post:
summary: Scan Action
operationId: EndpointScanAction
x-codegen-enabled: false
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ScanActionRequestBody'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse'

components:
schemas:
ScanActionRequestBody:
allOf:
- $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema'
- type: object
required:
- parameters
properties:
parameters:
required:
- path
type: object
properties:
path:
type: string

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { TypeOf } from '@kbn/config-schema';
import { schema } from '@kbn/config-schema';
import { BaseActionRequestSchema } from './common/base';

export const ScanActionRequestSchema = {
body: schema.object({
...BaseActionRequestSchema,

parameters: schema.object({
path: schema.string({
minLength: 1,
validate: (value) => {
if (!value.trim().length) {
return 'path cannot be an empty string';
}
},
}),
}),
}),
};

export type ScanActionRequestBody = TypeOf<typeof ScanActionRequestSchema.body>;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './actions/suspend_process_route';
export * from './actions/get_processes_route';
export * from './actions/get_file_route';
export * from './actions/execute_route';
export * from './actions/scan_route';
export * from './actions/common/base';
export * from './actions/common/response_actions';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const SUSPEND_PROCESS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/suspend_proc
export const GET_FILE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/get_file`;
export const EXECUTE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/execute`;
export const UPLOAD_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/upload`;
export const SCAN_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/scan`;

/** Endpoint Actions Routes */
export const ENDPOINT_ACTION_LOG_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_log/{agent_id}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import type {
ResponseActionUploadParameters,
EndpointActionResponseDataOutput,
WithAllKeys,
ResponseActionScanOutputContent,
ResponseActionsScanParameters,
} from '../types';
import { ActivityLogItemTypes } from '../types';
import {
Expand Down Expand Up @@ -102,9 +104,13 @@ export class EndpointActionGenerator extends BaseDataGenerator {

const command = overrides?.EndpointActions?.data?.command ?? this.randomResponseActionCommand();
let output: ActionResponseOutput<
ResponseActionGetFileOutputContent | ResponseActionExecuteOutputContent
| ResponseActionGetFileOutputContent
| ResponseActionExecuteOutputContent
| ResponseActionScanOutputContent
> = overrides?.EndpointActions?.data?.output as unknown as ActionResponseOutput<
ResponseActionGetFileOutputContent | ResponseActionExecuteOutputContent
| ResponseActionGetFileOutputContent
| ResponseActionExecuteOutputContent
| ResponseActionScanOutputContent
>;

if (command === 'get-file') {
Expand All @@ -128,6 +134,17 @@ export class EndpointActionGenerator extends BaseDataGenerator {
}
}

if (command === 'scan') {
if (!output) {
output = {
type: 'json',
content: {
code: 'ra_scan_success_done',
},
};
}
}

if (command === 'execute') {
if (!output) {
output = this.generateExecuteActionResponseOutput();
Expand Down Expand Up @@ -269,6 +286,35 @@ export class EndpointActionGenerator extends BaseDataGenerator {
}
}

if (command === 'scan') {
if (!details.parameters) {
(
details as unknown as ActionDetails<
ResponseActionScanOutputContent,
ResponseActionsScanParameters
>
).parameters = {
path: '/some/file.txt',
};
}

if (!details.outputs || Object.keys(details.outputs).length === 0) {
(
details as unknown as ActionDetails<
ResponseActionScanOutputContent,
ResponseActionsScanParameters
>
).outputs = {
[details.agents[0]]: {
type: 'json',
content: {
code: 'ra_scan_success_done',
},
},
};
}
}

if (command === 'execute') {
if (!details.parameters) {
(
Expand Down Expand Up @@ -347,6 +393,14 @@ export class EndpointActionGenerator extends BaseDataGenerator {
]);
}

randomScanFailureCode(): string {
return this.randomChoice([
'ra_scan_error_scan-invalid-input',
'ra_scan_error_not-found',
'ra_scan_error_scan-queue-quota',
]);
}

generateActivityLogAction(
overrides: DeepPartial<EndpointActivityLogAction>
): EndpointActivityLogAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
NoParametersRequestSchema,
} from '../../api/endpoint/actions/common/base';
import { ExecuteActionRequestSchema } from '../../api/endpoint/actions/execute_route';
import { ScanActionRequestSchema } from '../../api/endpoint/actions/scan_route';

// NOTE: Even though schemas are kept in common/api/endpoint - we keep tests here, because common/api should import from outside
describe('actions schemas', () => {
Expand Down Expand Up @@ -759,4 +760,33 @@ describe('actions schemas', () => {
}).toThrow('[file]: expected value of type [Stream] but got [Object]');
});
});

describe('ScanActionRequestSchema', () => {
it('should not accept empty string as path', () => {
expect(() => {
ScanActionRequestSchema.body.validate({
endpoint_ids: ['endpoint_id'],
parameters: { path: ' ' },
});
}).toThrowError('path cannot be an empty string');
});

it('should not accept when payload does not match', () => {
expect(() => {
ScanActionRequestSchema.body.validate({
endpoint_ids: ['endpoint_id'],
path: 'some/path',
});
}).toThrowError('[parameters.path]: expected value of type [string] but got [undefined]');
});

it('should accept path in payload if not empty', () => {
expect(() => {
ScanActionRequestSchema.body.validate({
endpoint_ids: ['endpoint_id'],
parameters: { path: 'some/path' },
});
}).not.toThrow();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ export const RESPONSE_ACTION_API_COMMANDS_NAMES = [
'get-file',
'execute',
'upload',
// TODO: for API changes in a subsequent PR
// 'scan',
'scan',
] as const;

export type ResponseActionsApiCommandNames = typeof RESPONSE_ACTION_API_COMMANDS_NAMES[number];
Expand All @@ -54,8 +53,7 @@ export const ENDPOINT_CAPABILITIES = [
'get_file',
'execute',
'upload_file',
// TODO: for API changes in a subsequent PR
// 'scan',
'scan',
] as const;

export type EndpointCapabilities = typeof ENDPOINT_CAPABILITIES[number];
Expand All @@ -73,8 +71,7 @@ export const CONSOLE_RESPONSE_ACTION_COMMANDS = [
'get-file',
'execute',
'upload',
// TODO: for API changes in a subsequent PR
// 'scan',
'scan',
] as const;

export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMANDS[number];
Expand Down Expand Up @@ -102,8 +99,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL: Record<
'get-file': 'writeFileOperations',
execute: 'writeExecuteOperations',
upload: 'writeFileOperations',
// TODO: for API changes in a subsequent PR
// scan: 'writeScanOperations',
scan: 'writeScanOperations',
});

export const RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP = Object.freeze<
Expand All @@ -117,8 +113,7 @@ export const RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP = Object.freeze<
'kill-process': 'kill-process',
'suspend-process': 'suspend-process',
upload: 'upload',
// TODO: for API changes in a subsequent PR
// scan: 'scan',
scan: 'scan',
});

export const RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP = Object.freeze<
Expand All @@ -132,8 +127,7 @@ export const RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP = Object.freeze<
'kill-process': 'kill-process',
'suspend-process': 'suspend-process',
upload: 'upload',
// TODO: for API changes in a subsequent PR
// scan: 'scan',
scan: 'scan',
});

export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY = Object.freeze<
Expand All @@ -147,8 +141,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY = Object.fr
'kill-process': 'kill_process',
'suspend-process': 'suspend_process',
upload: 'upload_file',
// TODO: for API changes in a subsequent PR
// scan: 'scan',
scan: 'scan',
});

/**
Expand All @@ -165,8 +158,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ = Object.freeze<
processes: 'canGetRunningProcesses',
'kill-process': 'canKillProcess',
'suspend-process': 'canSuspendProcess',
// TODO: for API changes in a subsequent PR
// scan: 'canWriteScanOperations',
scan: 'canWriteScanOperations',
});

// 4 hrs in seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,18 @@ const RESPONSE_ACTIONS_SUPPORT_MAP: SupportMap = {
crowdstrike: false,
},
},
scan: {
automated: {
endpoint: false,
sentinel_one: false,
crowdstrike: false,
},
manual: {
endpoint: true,
sentinel_one: false,
crowdstrike: false,
},
},
};

/**
Expand Down
Loading