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

[Security Solution][Endpoint] Add ability for users to release an isolated host in serverless tiers where Response Actions are not available #163616

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
835f427
add `writeHostIsolationRelease` privilege to Endpoint List `all`
paul-tavares Aug 9, 2023
fd00892
Update endpoint authz to remove `release` from the checks done for `c…
paul-tavares Aug 9, 2023
cbe5984
Fix bug: `Take Action` menu does not show Release on downgrade scenarios
paul-tavares Aug 9, 2023
9e0d797
Move Host Isolation sub-feature to the Security Solution base set of …
paul-tavares Aug 10, 2023
30a8c09
Merge remote-tracking branch 'upstream/main' into task/olm-fix-server…
paul-tavares Aug 11, 2023
9a88e46
add some jsdocs around privileges for isolation
paul-tavares Aug 11, 2023
5bfb543
Fix tests for Authz
paul-tavares Aug 11, 2023
983ae60
fix cypress tests
paul-tavares Aug 11, 2023
58d3121
centralized definition of the valid combinations for serverless PLI t…
paul-tavares Aug 11, 2023
9f61fac
Renamed test file + added `indexEndpointHosts()` task
paul-tavares Aug 11, 2023
9b45976
Serverless cypress tests for endpoint list in security essentials
paul-tavares Aug 11, 2023
4b30fb5
Revert "centralized definition of the valid combinations for serverle…
paul-tavares Aug 11, 2023
842da17
fix spelling
paul-tavares Aug 11, 2023
f012ad1
Fix test for endpoint list
paul-tavares Aug 11, 2023
7389212
remove endpointManagement and policyManagement features from endpoint…
paul-tavares Aug 11, 2023
dac3c55
fix test
paul-tavares Aug 11, 2023
5b21022
Merge remote-tracking branch 'upstream/main' into task/olm-fix-server…
paul-tavares Aug 14, 2023
b599f28
fix types
paul-tavares Aug 14, 2023
abcfb85
Merge remote-tracking branch 'upstream/main' into task/olm-fix-server…
paul-tavares Aug 14, 2023
5ad3ce3
correct spellings
paul-tavares Aug 14, 2023
8f1c936
small adjustments from code review (ash)
paul-tavares Aug 14, 2023
9277f99
Merge remote-tracking branch 'upstream/main' into task/olm-fix-server…
paul-tavares Aug 14, 2023
ecb8f4c
Merge branch 'main' into task/olm-fix-serverless-access-to-isolate-re…
paul-tavares Aug 14, 2023
e759009
Merge branch 'main' into task/olm-fix-serverless-access-to-isolate-re…
kibanamachine Aug 14, 2023
3989f83
Merge branch 'main' into task/olm-fix-serverless-access-to-isolate-re…
kibanamachine Aug 15, 2023
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 @@ -235,7 +235,15 @@ describe('Endpoint Authz service', () => {
].executePackageAction = true;

const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles);
expect(authz.canAccessResponseConsole).toBe(true);

// Having ONLY host isolation Release response action can only be true in a
// downgrade scenario, where we allow the user to continue to release isolated
// hosts. In that scenario, we don't show access to the response console
if (responseConsolePrivilege === 'writeHostIsolationRelease') {
expect(authz.canAccessResponseConsole).toBe(false);
} else {
expect(authz.canAccessResponseConsole).toBe(true);
}
}
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import type { ENDPOINT_PRIVILEGES, FleetAuthz } from '@kbn/fleet-plugin/common';

import { omit } from 'lodash';
import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ } from '../response_actions/constants';
import type { LicenseService } from '../../../license';
import type { EndpointAuthz } from '../../types/authz';
import type { MaybeImmutable } from '../../types';
Expand Down Expand Up @@ -82,7 +84,7 @@ export const calculateEndpointAuthz = (

const canWriteExecuteOperations = hasKibanaPrivilege(fleetAuthz, 'writeExecuteOperations');

return {
const authz: EndpointAuthz = {
canWriteSecuritySolution,
canReadSecuritySolution,
canAccessFleet: fleetAuthz?.fleet.all ?? false,
Expand All @@ -95,22 +97,22 @@ export const calculateEndpointAuthz = (
canWriteActionsLogManagement,
canReadActionsLogManagement: canReadActionsLogManagement && isEnterpriseLicense,
canAccessEndpointActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,

// ---------------------------------------------------------
// Response Actions
// ---------------------------------------------------------
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
canUnIsolateHost,
canKillProcess: canWriteProcessOperations && isEnterpriseLicense,
canSuspendProcess: canWriteProcessOperations && isEnterpriseLicense,
canGetRunningProcesses: canWriteProcessOperations && isEnterpriseLicense,
canAccessResponseConsole:
isEnterpriseLicense &&
(canIsolateHost ||
canUnIsolateHost ||
canWriteProcessOperations ||
canWriteFileOperations ||
canWriteExecuteOperations),
canAccessResponseConsole: false, // set further below
canWriteExecuteOperations: canWriteExecuteOperations && isEnterpriseLicense,
canWriteFileOperations: canWriteFileOperations && isEnterpriseLicense,

// ---------------------------------------------------------
// artifacts
// ---------------------------------------------------------
canWriteTrustedApplications,
canReadTrustedApplications,
canWriteHostIsolationExceptions: canWriteHostIsolationExceptions && isPlatinumPlusLicense,
Expand All @@ -122,6 +124,20 @@ export const calculateEndpointAuthz = (
canWriteEventFilters,
canReadEventFilters,
};

// Response console is only accessible when is license is Enterprise and user has access to any
Copy link
Member

Choose a reason for hiding this comment

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

nit: ..when license is...

// of the response actions with exception of `release`. Sole access to `release` is something
Copy link
Member

Choose a reason for hiding this comment

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

nit: ... with the exception to ...

// that is supported for a user in a license downgrade scenario, and in that case we don't want
Copy link
Member

Choose a reason for hiding this comment

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

nit: ...in that case, we...

// to allow access to Response Console.
authz.canAccessResponseConsole =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

enables Response Console access if the user has Authz to any response action with the exception of un-isolate. Users that have only permission to unisolate occurs when the Kibana license is downgraded or in serverless when running a non Endpoint Complete tier. It will allow users to continue to release their isolated hosts if any.

Also - this change here will ensure that as we add more response actions, that access to Response console will continue to pick those up and enable this option.

isEnterpriseLicense &&
Object.values(omit(RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ, 'release')).some(
(responseActionAuthzKey) => {
return authz[responseActionAuthzKey];
}
);
Comment on lines +134 to +138
Copy link
Member

Choose a reason for hiding this comment

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

🔥


return authz;
};

export const getEndpointAuthzInitialState = (): EndpointAuthz => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export const useHostIsolationAction = ({
detailsData,
isHostIsolationPanelOpen,
onAddIsolationStatusClick,
}: UseHostIsolationActionProps) => {
}: UseHostIsolationActionProps): AlertTableContextMenuItem[] => {
const { canIsolateHost, canUnIsolateHost } = useUserPrivileges().endpointPrivileges;

const isEndpointAlert = useMemo(() => {
return isAlertFromEndpointEvent({ data: detailsData || [] });
}, [detailsData]);
Expand All @@ -49,14 +51,14 @@ export const useHostIsolationAction = ({

const {
loading: loadingHostIsolationStatus,
isIsolated: isolationStatus,
isIsolated: isHostIsolated,
agentStatus,
capabilities,
} = useHostIsolationStatus({
agentId,
});

const isolationSupported = useMemo(() => {
const doesHostSupportIsolation = useMemo(() => {
Copy link
Member

Choose a reason for hiding this comment

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

🔥

return isEndpointAlert
? isIsolationSupported({
osName: hostOsFamily,
Expand All @@ -66,46 +68,45 @@ export const useHostIsolationAction = ({
: false;
}, [agentVersion, capabilities, hostOsFamily, isEndpointAlert]);

const isIsolationAllowed = useUserPrivileges().endpointPrivileges.canIsolateHost;

const isolateHostHandler = useCallback(() => {
closePopover();
if (isolationStatus === false) {
if (isHostIsolated === false) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: !isHostIsolated

onAddIsolationStatusClick('isolateHost');
} else {
onAddIsolationStatusClick('unisolateHost');
}
}, [closePopover, isolationStatus, onAddIsolationStatusClick]);
}, [closePopover, isHostIsolated, onAddIsolationStatusClick]);

return useMemo(() => {
if (
!isEndpointAlert ||
!doesHostSupportIsolation ||
loadingHostIsolationStatus ||
isHostIsolationPanelOpen
) {
return [];
}

const isolateHostTitle = isolationStatus === false ? ISOLATE_HOST : UNISOLATE_HOST;
const menuItems = [
{
key: 'isolate-host-action-item',
'data-test-subj': 'isolate-host-action-item',
disabled: agentStatus === HostStatus.UNENROLLED,
onClick: isolateHostHandler,
name: isHostIsolated ? UNISOLATE_HOST : ISOLATE_HOST,
},
];

const hostIsolationAction: AlertTableContextMenuItem[] = useMemo(
() =>
isIsolationAllowed &&
isEndpointAlert &&
isolationSupported &&
isHostIsolationPanelOpen === false &&
loadingHostIsolationStatus === false
? [
{
key: 'isolate-host-action-item',
'data-test-subj': 'isolate-host-action-item',
disabled: agentStatus === HostStatus.UNENROLLED,
onClick: isolateHostHandler,
name: isolateHostTitle,
},
]
: [],
[
agentStatus,
isEndpointAlert,
isHostIsolationPanelOpen,
isIsolationAllowed,
isolateHostHandler,
isolateHostTitle,
isolationSupported,
loadingHostIsolationStatus,
]
);
return hostIsolationAction;
return canIsolateHost || (isHostIsolated && canUnIsolateHost) ? menuItems : [];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fixes a bug were before it was not showing release in a downgrade scenario.

}, [
isEndpointAlert,
doesHostSupportIsolation,
loadingHostIsolationStatus,
isHostIsolationPanelOpen,
agentStatus,
isolateHostHandler,
canIsolateHost,
isHostIsolated,
canUnIsolateHost,
]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,14 @@ export const getSecurityBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({
},
});

/**
* Returns the list of Security SubFeature IDs that should be loaded and available in
* kibana regardless of PLI or License level.
* @param _
*/
export const getSecurityBaseKibanaSubFeatureIds = (
_: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use
): SecuritySubFeatureId[] => [];
): SecuritySubFeatureId[] => [SecuritySubFeatureId.hostIsolation];

/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
Expand Down Expand Up @@ -214,12 +219,13 @@ export const getSecurityAppFeaturesConfig = (
SecuritySubFeatureId.hostIsolationExceptions,

SecuritySubFeatureId.responseActionsHistory,
SecuritySubFeatureId.hostIsolation,
SecuritySubFeatureId.processOperations,
SecuritySubFeatureId.fileOperations,
SecuritySubFeatureId.executeAction,
],
subFeaturesPrivileges: [
// Adds the privilege to Isolate hosts to the already loaded `host_isolation_all`
// sub-feature (always loaded), which included the `release` privilege already
{
id: 'host_isolation_all',
api: [`${APP_ID}-writeHostIsolation`],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,14 +396,18 @@ const hostIsolationSubFeature: SubFeatureConfig = {
groupType: 'mutually_exclusive',
privileges: [
{
api: [`${APP_ID}-writeHostIsolationRelease`],
id: 'host_isolation_all',
includeIn: 'none',
name: 'All',
savedObject: {
all: [],
read: [],
},
// FYI: The current set of values below (`api`, `ui`) cover only `release` response action.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm hopping that tidbits of info like this can help along understanding how the framework around loading different privileges into existing Sub-Features

// There is a second set of values for API and UI that are added later if `endpointResponseActions`
// appFeature is enabled. Needed to ensure that in a downgrade of license condition,
// users are still able to un-isolate a host machine.
api: [`${APP_ID}-writeHostIsolationRelease`],
ui: ['writeHostIsolationRelease'],
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@ export const PLI_APP_FEATURES: PliAppFeatures = {
],
},
endpoint: {
essentials: [
AppFeatureKey.endpointHostManagement,
AppFeatureKey.endpointPolicyManagement,
AppFeatureKey.endpointPolicyProtections,
AppFeatureKey.endpointArtifactManagement,
],
essentials: [AppFeatureKey.endpointPolicyProtections, AppFeatureKey.endpointArtifactManagement],
complete: [
AppFeatureKey.endpointResponseActions,
AppFeatureKey.osqueryAutomatedResponseActions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { login } from '../../tasks/login';
import {
getConsoleActionMenuItem,
getUnIsolateActionMenuItem,
openRowActionMenu,
visitEndpointList,
} from '../../screens/endpoint_management';
import {
CyIndexEndpointHosts,
indexEndpointHosts,
} from '../../tasks/endpoint_management/index_endpoint_hosts';

describe(
'When on the Endpoint List in Security Essentials PLI',
{
env: {
ftrConfig: {
productTypes: [{ product_line: 'security', product_tier: 'essentials' }],
},
},
},
() => {
describe('and Isolated hosts exist', () => {
let indexedEndpointData: CyIndexEndpointHosts;

before(() => {
indexEndpointHosts({ isolation: true }).then((response) => {
indexedEndpointData = response;
});
});

after(() => {
if (indexedEndpointData) {
indexedEndpointData.cleanup();
}
});

beforeEach(() => {
login();
visitEndpointList();
openRowActionMenu();
});

it('should display `release` options in host row actions', () => {
getUnIsolateActionMenuItem().should('exist');
});

it('should NOT display access to response console', () => {
getConsoleActionMenuItem().should('not.exist');
});
});
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getEndpointManagementPageList } from '../../../screens/endpoint_managem
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';

describe(
'App Features for Complete PLI',
'App Features for Security Complete PLI',
{
env: {
ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'complete' }] },
Expand Down Expand Up @@ -50,10 +50,17 @@ describe(
});
}

for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) {
// No access to response actions (except `unisolate`)
for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter(
(apiName) => apiName !== 'unisolate'
)) {
it(`should not allow access to Response Action: ${actionName}`, () => {
ensureResponseActionAuthzAccess('none', actionName, username, password);
});
}

it('should have access to `unisoalte` api', () => {
Copy link
Member

Choose a reason for hiding this comment

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

typo: unisolate

ensureResponseActionAuthzAccess('all', 'unisolate', username, password);
});
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getEndpointManagementPageList } from '../../../screens/endpoint_managem
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';

describe(
'App Features for Complete PLI with Endpoint Complete',
'App Features for Security Complete PLI with Endpoint Complete Addon',
{
env: {
ftrConfig: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_managem
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';

describe(
'App Features for Essential PLI',
'App Features for Security Essential PLI',
{
env: {
ftrConfig: {
Expand Down Expand Up @@ -52,10 +52,17 @@ describe(
});
}

for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) {
it(`should NOT allow access to Response Action: ${actionName}`, () => {
// No access to response actions (except `unisolate`)
for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES.filter(
(apiName) => apiName !== 'unisolate'
)) {
it(`should not allow access to Response Action: ${actionName}`, () => {
ensureResponseActionAuthzAccess('none', actionName, username, password);
});
}

it('should have access to `unisoalte` api', () => {
Copy link
Member

Choose a reason for hiding this comment

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

copy/paste typo

ensureResponseActionAuthzAccess('all', 'unisolate', username, password);
});
}
);
Loading