mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[security solution][endpoint] Add new experimental feature flag for get-file
and use it hide/display get-file
response action (#145042)
## Summary - Adds new experimental feature flag that controls the availability of the `get-file` response action - UI updated to remove `get-file` from the console if FF is `false` - Server APIs updated to not register `get-file` related APIs if FF is `false` - Hides the "File Operation" kibana feature privilege
This commit is contained in:
parent
843eefa7a7
commit
9fda59f512
16 changed files with 105 additions and 24 deletions
|
@ -81,6 +81,11 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
* Enables the alert details page currently only accessible via the alert details flyout and alert table context menu
|
||||
*/
|
||||
alertDetailsPageEnabled: false,
|
||||
|
||||
/**
|
||||
* Enables the `get-file` endpoint response action
|
||||
*/
|
||||
responseActionGetFileEnabled: false,
|
||||
});
|
||||
|
||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { ExperimentalFeatures } from '../../../common/experimental_features';
|
||||
import { allowedExperimentalValues } from '../../../common/experimental_features';
|
||||
|
||||
const ExperimentalFeaturesServiceMock = {
|
||||
init: jest.fn(),
|
||||
|
||||
get: jest.fn(() => {
|
||||
const ff: ExperimentalFeatures = {
|
||||
...allowedExperimentalValues,
|
||||
|
||||
responseActionGetFileEnabled: true,
|
||||
};
|
||||
|
||||
return ff;
|
||||
}),
|
||||
};
|
||||
|
||||
export { ExperimentalFeaturesServiceMock as ExperimentalFeaturesService };
|
|
@ -30,6 +30,7 @@ import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser';
|
|||
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
|
||||
|
||||
jest.mock('../../../../../common/components/user_privileges');
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using get-file action from response actions console', () => {
|
||||
let render: (
|
||||
|
|
|
@ -20,6 +20,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
|
|||
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using processes action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: EndpointCapabilities[]
|
||||
|
|
|
@ -21,6 +21,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
|
|||
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using isolate action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: EndpointCapabilities[]
|
||||
|
|
|
@ -25,6 +25,8 @@ import type {
|
|||
} from '../../../../../../common/endpoint/types';
|
||||
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
|
||||
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using the kill-process action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: EndpointCapabilities[]
|
||||
|
|
|
@ -21,6 +21,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
|
|||
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using the release action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: EndpointCapabilities[]
|
||||
|
|
|
@ -25,6 +25,8 @@ import type {
|
|||
} from '../../../../../../common/endpoint/types';
|
||||
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
|
||||
|
||||
jest.mock('../../../../../common/experimental_features_service');
|
||||
|
||||
describe('When using the suspend-process action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: EndpointCapabilities[]
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ExperimentalFeaturesService } from '../../../../common/experimental_features_service';
|
||||
import type {
|
||||
EndpointCapabilities,
|
||||
ConsoleResponseActionCommands,
|
||||
|
@ -134,6 +135,8 @@ export const getEndpointConsoleCommands = ({
|
|||
endpointCapabilities: ImmutableArray<string>;
|
||||
endpointPrivileges: EndpointPrivileges;
|
||||
}): CommandDefinition[] => {
|
||||
const isGetFileEnabled = ExperimentalFeaturesService.get().responseActionGetFileEnabled;
|
||||
|
||||
const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => {
|
||||
const responderCapability = commandToCapabilitiesMap.get(commandName);
|
||||
if (responderCapability) {
|
||||
|
@ -142,7 +145,7 @@ export const getEndpointConsoleCommands = ({
|
|||
return false;
|
||||
};
|
||||
|
||||
return [
|
||||
const consoleCommands: CommandDefinition[] = [
|
||||
{
|
||||
name: 'isolate',
|
||||
about: getCommandAboutInfo({
|
||||
|
@ -365,7 +368,11 @@ export const getEndpointConsoleCommands = ({
|
|||
helpDisabled: doesEndpointSupportCommand('processes') === false,
|
||||
helpHidden: !getRbacControl({ commandName: 'processes', privileges: endpointPrivileges }),
|
||||
},
|
||||
{
|
||||
];
|
||||
|
||||
// `get-file` is currently behind feature flag
|
||||
if (isGetFileEnabled) {
|
||||
consoleCommands.push({
|
||||
name: 'get-file',
|
||||
about: getCommandAboutInfo({
|
||||
aboutInfo: i18n.translate('xpack.securitySolution.endpointConsoleCommands.getFile.about', {
|
||||
|
@ -411,6 +418,8 @@ export const getEndpointConsoleCommands = ({
|
|||
commandName: 'get-file',
|
||||
privileges: endpointPrivileges,
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return consoleCommands;
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
DurationRange,
|
||||
OnRefreshChangeProps,
|
||||
} from '@elastic/eui/src/components/date_picker/types';
|
||||
import { ExperimentalFeaturesService } from '../../../../common/experimental_features_service';
|
||||
import type {
|
||||
ConsoleResponseActionCommands,
|
||||
ResponseActionsApiCommandNames,
|
||||
|
@ -232,7 +233,17 @@ export const useActionsLogFilter = ({
|
|||
}))
|
||||
: isHostsFilter
|
||||
? []
|
||||
: RESPONSE_ACTION_API_COMMANDS_NAMES.map((commandName) => ({
|
||||
: RESPONSE_ACTION_API_COMMANDS_NAMES.filter((commandName) => {
|
||||
// `get-file` is currently behind FF
|
||||
if (
|
||||
commandName === 'get-file' &&
|
||||
!ExperimentalFeaturesService.get().responseActionGetFileEnabled
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).map((commandName) => ({
|
||||
key: commandName,
|
||||
label: getUiCommand(commandName),
|
||||
checked:
|
||||
|
|
|
@ -119,6 +119,8 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
|
|||
|
||||
jest.mock('../../hooks/endpoint/use_get_endpoints_list');
|
||||
|
||||
jest.mock('../../../common/experimental_features_service');
|
||||
|
||||
jest.mock('../../../common/components/user_privileges');
|
||||
|
||||
let mockUseGetFileInfo: {
|
||||
|
|
|
@ -20,6 +20,8 @@ import { MANAGEMENT_PATH } from '../../../../../common/constants';
|
|||
import { getActionListMock } from '../../../components/endpoint_response_actions_list/mocks';
|
||||
import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list';
|
||||
|
||||
jest.mock('../../../../common/experimental_features_service');
|
||||
|
||||
let mockUseGetEndpointActionList: {
|
||||
isFetched?: boolean;
|
||||
isFetching?: boolean;
|
||||
|
|
|
@ -11,7 +11,10 @@ import { parseExperimentalConfigValue } from '../common/experimental_features';
|
|||
import type { ConfigType } from './config';
|
||||
|
||||
export const createMockConfig = (): ConfigType => {
|
||||
const enableExperimental: string[] = [];
|
||||
const enableExperimental: Array<keyof ExperimentalFeatures> = [
|
||||
// Remove property below once `get-file` FF is enabled or removed
|
||||
'responseActionGetFileEnabled',
|
||||
];
|
||||
|
||||
return {
|
||||
[SIGNALS_INDEX_KEY]: DEFAULT_SIGNALS_INDEX,
|
||||
|
|
|
@ -25,7 +25,11 @@ export function registerActionRoutes(
|
|||
registerActionAuditLogRoutes(router, endpointContext);
|
||||
registerActionListRoutes(router, endpointContext);
|
||||
registerActionDetailsRoutes(router, endpointContext);
|
||||
registerActionFileDownloadRoutes(router, endpointContext);
|
||||
registerResponseActionRoutes(router, endpointContext);
|
||||
registerActionFileInfoRoute(router, endpointContext);
|
||||
|
||||
// APIs specific to `get-file` are behind FF
|
||||
if (endpointContext.experimentalFeatures.responseActionGetFileEnabled) {
|
||||
registerActionFileDownloadRoutes(router, endpointContext);
|
||||
registerActionFileInfoRoute(router, endpointContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,18 +160,21 @@ export function registerResponseActionRoutes(
|
|||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: GET_FILE_ROUTE,
|
||||
validate: EndpointActionGetFileSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canWriteFileOperations'] },
|
||||
logger,
|
||||
responseActionRequestHandler(endpointContext, 'get-file')
|
||||
)
|
||||
);
|
||||
// `get-file` currently behind FF
|
||||
if (endpointContext.experimentalFeatures.responseActionGetFileEnabled) {
|
||||
router.post(
|
||||
{
|
||||
path: GET_FILE_ROUTE,
|
||||
validate: EndpointActionGetFileSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canWriteFileOperations'] },
|
||||
logger,
|
||||
responseActionRequestHandler(endpointContext, 'get-file')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const commandToFeatureKeyMap = new Map<ResponseActionsApiCommandNames, FeatureKeys>([
|
||||
|
|
|
@ -495,15 +495,21 @@ const subFeatures: SubFeatureConfig[] = [
|
|||
];
|
||||
|
||||
function getSubFeatures(experimentalFeatures: ConfigType['experimentalFeatures']) {
|
||||
let filteredSubFeatures: SubFeatureConfig[] = [];
|
||||
|
||||
if (experimentalFeatures.endpointRbacEnabled) {
|
||||
return subFeatures;
|
||||
filteredSubFeatures = subFeatures;
|
||||
} else if (experimentalFeatures.endpointRbacV1Enabled) {
|
||||
filteredSubFeatures = responseActionSubFeatures;
|
||||
}
|
||||
|
||||
if (experimentalFeatures.endpointRbacV1Enabled) {
|
||||
return responseActionSubFeatures;
|
||||
if (!experimentalFeatures.responseActionGetFileEnabled) {
|
||||
filteredSubFeatures = filteredSubFeatures.filter((subFeat) => {
|
||||
return subFeat.name !== 'File Operations';
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
return filteredSubFeatures;
|
||||
}
|
||||
|
||||
export const getKibanaPrivilegesFeaturePrivileges = (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue