Skip to content

Commit

Permalink
[Security Solution] [Endpoint] Generate empty endpoint user artifacts…
Browse files Browse the repository at this point in the history
… depending on the PLI (#163602)

## Summary

Generates empty array when the PLI don't meet the requirement. It end up
having empty fleet artifacts for those cannot be generated.

It also adds new test cases

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
dasansol92 and kibanamachine authored Aug 21, 2023
1 parent 81a151e commit bc988f2
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { createEndpointArtifactClientMock, getManifestClientMock } from '../mock
import type { ManifestManagerContext } from './manifest_manager';
import { ManifestManager } from './manifest_manager';
import { parseExperimentalConfigValue } from '../../../../../common/experimental_features';
import { createAppFeaturesMock } from '../../../../lib/app_features/mocks';
import type { AppFeatureKeys } from '../../../../../common/types/app_features';
import type { AppFeatures } from '../../../../lib/app_features/app_features';

export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({
data,
Expand Down Expand Up @@ -68,24 +71,28 @@ export interface ManifestManagerMockOptions {
exceptionListClient: ExceptionListClient;
packagePolicyService: jest.Mocked<PackagePolicyClient>;
savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
appFeatures: AppFeatures;
}

export const buildManifestManagerMockOptions = (
opts: Partial<ManifestManagerMockOptions>
opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys
): ManifestManagerMockOptions => {
const savedObjectMock = savedObjectsClientMock.create();
return {
exceptionListClient: listMock.getExceptionListClient(savedObjectMock),
packagePolicyService: createPackagePolicyServiceMock(),
savedObjectsClient: savedObjectMock,
appFeatures: createAppFeaturesMock(customAppFeatures),
...opts,
};
};

export const buildManifestManagerContextMock = (
opts: Partial<ManifestManagerMockOptions>
opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys
): ManifestManagerContext => {
const fullOpts = buildManifestManagerMockOptions(opts);
const fullOpts = buildManifestManagerMockOptions(opts, customAppFeatures);

return {
...fullOpts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import type { EndpointArtifactClientInterface } from '../artifact_client';
import { InvalidInternalManifestError } from '../errors';
import { EndpointError } from '../../../../../common/endpoint/errors';
import type { Artifact } from '@kbn/fleet-plugin/server';
import { AppFeatureSecurityKey } from '../../../../../common/types/app_features';

const getArtifactObject = (artifact: InternalArtifactSchema) =>
JSON.parse(Buffer.from(artifact.body!, 'base64').toString());
Expand Down Expand Up @@ -599,6 +600,271 @@ describe('ManifestManager', () => {
});
});

describe('buildNewManifest when using app features', () => {
const SUPPORTED_ARTIFACT_NAMES = [
ARTIFACT_NAME_EXCEPTIONS_MACOS,
ARTIFACT_NAME_EXCEPTIONS_WINDOWS,
ARTIFACT_NAME_EXCEPTIONS_LINUX,
ARTIFACT_NAME_TRUSTED_APPS_MACOS,
ARTIFACT_NAME_TRUSTED_APPS_WINDOWS,
ARTIFACT_NAME_TRUSTED_APPS_LINUX,
ARTIFACT_NAME_EVENT_FILTERS_MACOS,
ARTIFACT_NAME_EVENT_FILTERS_WINDOWS,
ARTIFACT_NAME_EVENT_FILTERS_LINUX,
ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_MACOS,
ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_WINDOWS,
ARTIFACT_NAME_HOST_ISOLATION_EXCEPTIONS_LINUX,
ARTIFACT_NAME_BLOCKLISTS_MACOS,
ARTIFACT_NAME_BLOCKLISTS_WINDOWS,
ARTIFACT_NAME_BLOCKLISTS_LINUX,
];

const getArtifactIds = (artifacts: InternalArtifactSchema[]) => [
...new Set(artifacts.map((artifact) => artifact.identifier)).values(),
];

const mockPolicyListIdsResponse = (items: string[]) =>
jest.fn().mockResolvedValue({
items,
page: 1,
per_page: 100,
total: items.length,
});

test('when it has endpoint artifact management app feature it should not generate host isolation exceptions', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
const trustedAppListItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const eventFiltersListItem = getExceptionListItemSchemaMock({
os_types: ['windows'],
tags: ['policy:all'],
});
const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const blocklistsListItem = getExceptionListItemSchemaMock({
os_types: ['macos'],
tags: ['policy:all'],
});
const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement,
]);
const manifestManager = new ManifestManager(context);

context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
});
context.savedObjectsClient.create = jest
.fn()
.mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);

const manifest = await manifestManager.buildNewManifest();

expect(manifest?.getSchemaVersion()).toStrictEqual('v1');
expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0');
expect(manifest?.getSavedObjectVersion()).toBeUndefined();

const artifacts = manifest.getAllArtifacts();

expect(artifacts.length).toBe(15);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);

expect(getArtifactObject(artifacts[0])).toStrictEqual({
entries: translateToEndpointExceptions([exceptionListItem], 'v1'),
});
expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[5])).toStrictEqual({
entries: translateToEndpointExceptions([trustedAppListItem], 'v1'),
});
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({
entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'),
});
expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[11])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[14])).toStrictEqual({
entries: translateToEndpointExceptions([blocklistsListItem], 'v1'),
});

for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual(
new Set([TEST_POLICY_ID_1])
);
}
});

test('when it has endpoint artifact management and response actions app features it should generate all exceptions', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
const trustedAppListItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const eventFiltersListItem = getExceptionListItemSchemaMock({
os_types: ['windows'],
tags: ['policy:all'],
});
const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const blocklistsListItem = getExceptionListItemSchemaMock({
os_types: ['macos'],
tags: ['policy:all'],
});
const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement,
AppFeatureSecurityKey.endpointResponseActions,
]);
const manifestManager = new ManifestManager(context);

context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
});
context.savedObjectsClient.create = jest
.fn()
.mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);

const manifest = await manifestManager.buildNewManifest();

expect(manifest?.getSchemaVersion()).toStrictEqual('v1');
expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0');
expect(manifest?.getSavedObjectVersion()).toBeUndefined();

const artifacts = manifest.getAllArtifacts();

expect(artifacts.length).toBe(15);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);

expect(getArtifactObject(artifacts[0])).toStrictEqual({
entries: translateToEndpointExceptions([exceptionListItem], 'v1'),
});
expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[5])).toStrictEqual({
entries: translateToEndpointExceptions([trustedAppListItem], 'v1'),
});
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({
entries: translateToEndpointExceptions([eventFiltersListItem], 'v1'),
});
expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[11])).toStrictEqual({
entries: translateToEndpointExceptions([hostIsolationExceptionsItem], 'v1'),
});
expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[14])).toStrictEqual({
entries: translateToEndpointExceptions([blocklistsListItem], 'v1'),
});

for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual(
new Set([TEST_POLICY_ID_1])
);
}
});

test('when does not have right app features, should not generate any exception', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
const trustedAppListItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const eventFiltersListItem = getExceptionListItemSchemaMock({
os_types: ['windows'],
tags: ['policy:all'],
});
const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({
os_types: ['linux'],
tags: ['policy:all'],
});
const blocklistsListItem = getExceptionListItemSchemaMock({
os_types: ['macos'],
tags: ['policy:all'],
});
const context = buildManifestManagerContextMock({}, []);
const manifestManager = new ManifestManager(context);

context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
});
context.savedObjectsClient.create = jest
.fn()
.mockImplementation((_type: string, object: InternalManifestSchema) => ({
attributes: object,
}));
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);

const manifest = await manifestManager.buildNewManifest();

expect(manifest?.getSchemaVersion()).toStrictEqual('v1');
expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0');
expect(manifest?.getSavedObjectVersion()).toBeUndefined();

const artifacts = manifest.getAllArtifacts();

expect(artifacts.length).toBe(15);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);

expect(getArtifactObject(artifacts[0])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[1])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[2])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[3])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[4])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[5])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[10])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[11])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[12])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[13])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[14])).toStrictEqual({ entries: [] });

for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual(
new Set([TEST_POLICY_ID_1])
);
}
});
});

describe('deleteArtifacts', () => {
test('Successfully invokes saved objects client', async () => {
const context = buildManifestManagerContextMock({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type { ListResult, PackagePolicy } from '@kbn/fleet-plugin/common';
import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { AppFeatureKey } from '../../../../../common/types/app_features';
import type { AppFeatures } from '../../../../lib/app_features';
import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common';
import type { ManifestSchema } from '../../../../../common/endpoint/schema/manifest';
import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/manifest';
Expand Down Expand Up @@ -97,6 +99,7 @@ export interface ManifestManagerContext {
experimentalFeatures: ExperimentalFeatures;
packagerTaskPackagePolicyUpdateBatchSize: number;
esClient: ElasticsearchClient;
appFeatures: AppFeatures;
}

const getArtifactIds = (manifest: ManifestSchema) =>
Expand All @@ -118,6 +121,7 @@ export class ManifestManager {
protected cachedExceptionsListsByOs: Map<string, ExceptionListItemSchema[]>;
protected packagerTaskPackagePolicyUpdateBatchSize: number;
protected esClient: ElasticsearchClient;
protected appFeatures: AppFeatures;

constructor(context: ManifestManagerContext) {
this.artifactClient = context.artifactClient;
Expand All @@ -131,6 +135,7 @@ export class ManifestManager {
this.packagerTaskPackagePolicyUpdateBatchSize =
context.packagerTaskPackagePolicyUpdateBatchSize;
this.esClient = context.esClient;
this.appFeatures = context.appFeatures;
}

/**
Expand Down Expand Up @@ -159,11 +164,19 @@ export class ManifestManager {
schemaVersion: string;
}): Promise<WrappedTranslatedExceptionList> {
if (!this.cachedExceptionsListsByOs.has(`${listId}-${os}`)) {
const itemsByListId = await getAllItemsFromEndpointExceptionList({
elClient,
os,
listId,
});
let itemsByListId: ExceptionListItemSchema[] = [];
if (
(listId === ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID &&
this.appFeatures.isEnabled(AppFeatureKey.endpointResponseActions)) ||
(listId !== ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID &&
this.appFeatures.isEnabled(AppFeatureKey.endpointArtifactManagement))
) {
itemsByListId = await getAllItemsFromEndpointExceptionList({
elClient,
os,
listId,
});
}
this.cachedExceptionsListsByOs.set(`${listId}-${os}`, itemsByListId);
}

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ export class Plugin implements ISecuritySolutionPlugin {
experimentalFeatures: config.experimentalFeatures,
packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize,
esClient: core.elasticsearch.client.asInternalUser,
appFeatures: this.appFeatures,
});

// Migrate artifacts to fleet and then start the manifest task after that is done
Expand Down

0 comments on commit bc988f2

Please sign in to comment.