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] Adds a version and OS check for Host Isolation #103026

Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class BaseDataGenerator<GeneratedDoc extends {} = {}> {
}

protected randomVersion(): string {
return [6, ...this.randomNGenerator(10, 2)].map((x) => x.toString()).join('.');
return [7, ...this.randomNGenerator(20, 2)].map((x) => x.toString()).join('.');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make the version generator possible to be 7.14.0+ to ensure fake docs will pass the version check.

}

protected randomChoice<T>(choices: T[]): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const ANCESTRY_LIMIT: number = 2;

const Windows: OSFields[] = [
{
name: 'windows 10.0',
name: 'Windows',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

full: 'Windows 10',
version: '10.0',
platform: 'Windows',
Expand All @@ -61,7 +61,7 @@ const Windows: OSFields[] = [
},
},
{
name: 'windows 10.0',
name: 'Windows',
full: 'Windows Server 2016',
version: '10.0',
platform: 'Windows',
Expand All @@ -71,7 +71,7 @@ const Windows: OSFields[] = [
},
},
{
name: 'windows 6.2',
name: 'Windows',
full: 'Windows Server 2012',
version: '6.2',
platform: 'Windows',
Expand All @@ -81,7 +81,7 @@ const Windows: OSFields[] = [
},
},
{
name: 'windows 6.3',
name: 'Windows',
full: 'Windows Server 2012R2',
version: '6.3',
platform: 'Windows',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 { isVersionSupported, isOsSupported, isIsolationSupported } from './utils';

describe('Host Isolation utils isVersionSupported', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

not necessary to take action:

Jest support table tests which makes these kind of repetitive tests easier to read and write.

https://jestjs.io/docs/api#testeachtablename-fn-timeout

Copy link
Contributor

Choose a reason for hiding this comment

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

Example:

test.each`
  a           | b           | expected
  ${'8.14.9'} | ${'7.13.0'} | ${true}
  ${'8.14.9'} | ${'9.14.0'} | ${false}
`('should validate that version $a is equal($expected) to $b', ({ a, b, expected }) => {
  expect(
    isVersionSupported({
      currentVersion: a,
      minVersionRequired: b,
    })
  ).toEqual(expected);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for the tip, I didn't know that! I've refactored my tests to reflect it.

test.each`
a | b | expected
${'8.14.0'} | ${'7.13.0'} | ${true}
${'7.14.0'} | ${'7.13.0'} | ${true}
${'7.14.1'} | ${'7.14.0'} | ${true}
${'8.14.0'} | ${'9.14.0'} | ${false}
${'7.13.0'} | ${'7.14.0'} | ${false}
${'7.14.0'} | ${'7.14.1'} | ${false}
${'7.14.0'} | ${'7.14.0'} | ${true}
${'7.14.0-SNAPSHOT'} | ${'7.14.0'} | ${true}
${'7.14.0-SNAPSHOT-beta'} | ${'7.14.0'} | ${true}
${'7.14.0-alpha'} | ${'7.14.0'} | ${true}
`('should validate that version $a is compatible($expected) to $b', ({ a, b, expected }) => {
expect(
isVersionSupported({
currentVersion: a,
minVersionRequired: b,
})
).toEqual(expected);
});
});

describe('Host Isolation utils isOsSupported', () => {
test.each`
a | b | expected
${'linux'} | ${['macos', 'linux']} | ${true}
${'linux'} | ${['macos', 'windows']} | ${false}
`('should validate that os $a is compatible($expected) to $b', ({ a, b, expected }) => {
expect(
isOsSupported({
currentOs: a,
supportedOss: b,
})
).toEqual(expected);
});
});

describe('Host Isolation utils isIsolationSupported', () => {
test.each`
a | b | expected
${'windows'} | ${'7.14.0'} | ${true}
${'linux'} | ${'7.13.0'} | ${false}
${'linux'} | ${'7.14.0'} | ${false}
${'macos'} | ${'7.13.0'} | ${false}
`(
'should validate that os $a and version $b supports hostIsolation($expected)',
({ a, b, expected }) => {
expect(
isIsolationSupported({
osName: a,
version: b,
})
).toEqual(expected);
}
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.
*/

export const isVersionSupported = ({
currentVersion,
minVersionRequired,
}: {
currentVersion: string;
minVersionRequired: string;
}) => {
const parsedCurrentVersion = currentVersion.includes('-SNAPSHOT')
? currentVersion.substring(0, currentVersion.indexOf('-'))
: currentVersion;
const tokenizedCurrent = parsedCurrentVersion
.split('.')
.map((token: string) => parseInt(token, 10));
const tokenizedMin = minVersionRequired.split('.').map((token: string) => parseInt(token, 10));

const versionNotSupported = tokenizedCurrent.some((token: number, index: number) => {
return token < tokenizedMin[index];
});

return !versionNotSupported;
};

export const isOsSupported = ({
currentOs,
supportedOss,
}: {
currentOs: string;
supportedOss: string[];
}) => {
return supportedOss.some((os) => currentOs === os);
};

export const isIsolationSupported = ({ osName, version }: { osName: string; version: string }) => {
const normalizedOs = osName.toLowerCase();
return (
isOsSupported({ currentOs: normalizedOs, supportedOss: ['macos', 'windows'] }) &&
isVersionSupported({ currentVersion: version, minVersionRequired: '7.14.0' })
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ describe('When using the Endpoint Details Actions Menu', () => {
// Safe to mutate this mocked data
// @ts-ignore
endpointHost.metadata.Endpoint.state.isolation = isolation;
// @ts-ignore
endpointHost.metadata.host.os.name = 'Windows';
// @ts-ignore
endpointHost.metadata.agent.version = '7.14.0';
httpMocks.responseProvider.metadataDetails.mockReturnValue(endpointHost);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer';
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
import { useLicense } from '../../../../../common/hooks/use_license';
import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils';

/**
* Returns a list (array) of actions for an individual endpoint
Expand All @@ -37,6 +38,10 @@ export const useEndpointActionItems = (
const endpointPolicyId = endpointMetadata.Endpoint.policy.applied.id;
const endpointHostName = endpointMetadata.host.hostname;
const fleetAgentId = endpointMetadata.elastic.agent.id;
const isolationSupported = isIsolationSupported({
osName: endpointMetadata.host.os.name,
version: endpointMetadata.agent.version,
});
const {
show,
selected_endpoint: _selectedEndpoint,
Expand Down Expand Up @@ -73,7 +78,7 @@ export const useEndpointActionItems = (
/>
),
});
} else if (isPlatinumPlus) {
} else if (isPlatinumPlus && isolationSupported) {
// For Platinum++ licenses, users also have ability to isolate
isolationActions.push({
'data-test-subj': 'isolateLink',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,17 @@ describe('when on the endpoint list page', () => {
isolation: false,
},
},
host: {
...hosts[0].metadata.host,
os: {
...hosts[0].metadata.host.os,
name: 'Windows',
},
},
agent: {
...hosts[0].metadata.agent,
version: '7.14.0',
},
},
query_strategy_version: queryStrategyVersion,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from '../../../../detections/components/host_isolation/translations';
import { ALERT_DETAILS } from './translations';
import { useIsolationPrivileges } from '../../../../common/hooks/endpoint/use_isolate_privileges';
import { isIsolationSupported } from '../../../../../common/endpoint/service/host_isolation/utils';
import { endpointAlertCheck } from '../../../../common/utils/endpoint_alert_check';
import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context';

Expand Down Expand Up @@ -102,6 +103,22 @@ const EventDetailsPanelComponent: React.FC<EventDetailsPanelProps> = ({
return findAgentId ? findAgentId[0] : '';
}, [detailsData]);

const hostOsFamily = useMemo(() => {
const findOsName = find({ category: 'host', field: 'host.os.name' }, detailsData)?.values;
return findOsName ? findOsName[0] : '';
}, [detailsData]);

const agentVersion = useMemo(() => {
const findAgentVersion = find({ category: 'agent', field: 'agent.version' }, detailsData)
?.values;
return findAgentVersion ? findAgentVersion[0] : '';
}, [detailsData]);

const isolationSupported = isIsolationSupported({
osName: hostOsFamily,
version: agentVersion,
});

const backToAlertDetailsLink = useMemo(() => {
return (
<>
Expand Down Expand Up @@ -164,15 +181,18 @@ const EventDetailsPanelComponent: React.FC<EventDetailsPanelProps> = ({
/>
)}
</StyledEuiFlyoutBody>
{isIsolationAllowed && isEndpointAlert && isHostIsolationPanelOpen === false && (
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<TakeActionDropdown onChange={showHostIsolationPanel} agentId={agentId} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
)}
{isIsolationAllowed &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a concern about hiding the TakeActionDropdown in its entirety. Are we planning to use this dropdown to add more actions later other than isolate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@academo we will add new actions in the future, but currently there is just the one. We would revisit this check when the time comes

isEndpointAlert &&
isolationSupported &&
isHostIsolationPanelOpen === false && (
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<TakeActionDropdown onChange={showHostIsolationPanel} agentId={agentId} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
)}
</>
) : (
<>
Expand Down