Skip to content

Commit d856cf9

Browse files
committed
Write tests for ingest callback and ensure policy is returned when errors occur
1 parent 53303ba commit d856cf9

5 files changed

Lines changed: 178 additions & 114 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { NewPackageConfig } from './types/models/package_config';
8+
9+
export const createNewPackageConfigMock = () => {
10+
return {
11+
name: 'endpoint-1',
12+
description: '',
13+
namespace: 'default',
14+
enabled: true,
15+
config_id: '93c46720-c217-11ea-9906-b5b8a21b268e',
16+
output_id: '',
17+
package: {
18+
name: 'endpoint',
19+
title: 'Elastic Endpoint',
20+
version: '0.9.0',
21+
},
22+
inputs: [],
23+
} as NewPackageConfig;
24+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
8+
import { loggerMock } from 'src/core/server/logging/logger.mock';
9+
import { createNewPackageConfigMock } from '../../../ingest_manager/common/mocks';
10+
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
11+
import { getManifestManagerMock } from './services/artifacts/manifest_manager/manifest_manager.mock';
12+
import { getPackageConfigCreateCallback } from './ingest_integration';
13+
14+
describe('ingest_integration tests ', () => {
15+
describe('ingest_integration sanity checks', () => {
16+
test('policy is updated with manifest', async () => {
17+
const logger = loggerMock.create();
18+
const manifestManager = getManifestManagerMock();
19+
const callback = getPackageConfigCreateCallback(logger, manifestManager);
20+
const policyConfig = createNewPackageConfigMock();
21+
const newPolicyConfig = await callback(policyConfig);
22+
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
23+
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
24+
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual({
25+
artifacts: {
26+
'endpoint-exceptionlist-linux-v1': {
27+
compression_algorithm: 'none',
28+
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
29+
decoded_size: 287,
30+
encoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
31+
encoded_size: 287,
32+
encryption_algorithm: 'none',
33+
relative_url:
34+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
35+
},
36+
},
37+
manifest_version: 'WzAsMF0=',
38+
schema_version: 'v1',
39+
});
40+
});
41+
42+
test('policy is returned even if error is encountered during artifact sync', async () => {
43+
const logger = loggerMock.create();
44+
const manifestManager = getManifestManagerMock();
45+
manifestManager.syncArtifacts = jest.fn().mockRejectedValue([new Error('error updating')]);
46+
const callback = getPackageConfigCreateCallback(logger, manifestManager);
47+
const policyConfig = createNewPackageConfigMock();
48+
const newPolicyConfig = await callback(policyConfig);
49+
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
50+
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
51+
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual({
52+
manifest_version: 'WzAsMF0=',
53+
schema_version: 'v1',
54+
artifacts: {},
55+
});
56+
});
57+
});
58+
});

x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { Logger } from '../../../../../src/core/server';
88
import { NewPackageConfig } from '../../../ingest_manager/common/types/models';
99
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
1010
import { NewPolicyData } from '../../common/endpoint/types';
11-
import { ManifestManager } from './services/artifacts';
12-
import { reportErrors } from './lib/artifacts/common';
11+
import { ManifestManager, ManifestSnapshot } from './services/artifacts';
12+
import { reportErrors, ManifestConstants } from './lib/artifacts/common';
13+
import { ManifestSchemaVersion } from '../../common/endpoint/schema/common';
1314

1415
/**
1516
* Callback to handle creation of PackageConfigs in Ingest Manager
@@ -30,43 +31,60 @@ export const getPackageConfigCreateCallback = (
3031
// follow the types/schema expected
3132
let updatedPackageConfig = newPackageConfig as NewPolicyData;
3233

33-
// get snapshot based on exception-list-agnostic SOs
34-
// with diffs from last dispatched manifest, if it exists
35-
const snapshot = await manifestManager.getSnapshot({ initialize: true });
34+
// get current manifest from SO (last dispatched)
35+
const manifest = (
36+
await manifestManager.getLastDispatchedManifest(ManifestConstants.SCHEMA_VERSION)
37+
)?.toEndpointFormat() ?? {
38+
manifest_version: 'default',
39+
schema_version: ManifestConstants.SCHEMA_VERSION as ManifestSchemaVersion,
40+
artifacts: {},
41+
};
3642

43+
// Until we get the Default Policy Configuration in the Endpoint package,
44+
// we will add it here manually at creation time.
45+
if (newPackageConfig.inputs.length === 0) {
46+
updatedPackageConfig = {
47+
...newPackageConfig,
48+
inputs: [
49+
{
50+
type: 'endpoint',
51+
enabled: true,
52+
streams: [],
53+
config: {
54+
artifact_manifest: {
55+
value: manifest,
56+
},
57+
policy: {
58+
value: policyConfigFactory(),
59+
},
60+
},
61+
},
62+
],
63+
};
64+
}
65+
66+
let snapshot: ManifestSnapshot | null = null;
3767
let success = true;
3868
try {
39-
if (snapshot && snapshot.diffs.length > 0) {
69+
// Try to get most up-to-date manifest data.
70+
71+
// get snapshot based on exception-list-agnostic SOs
72+
// with diffs from last dispatched manifest, if it exists
73+
snapshot = await manifestManager.getSnapshot({ initialize: true });
74+
75+
if (snapshot && snapshot.diffs.length) {
4076
// create new artifacts
4177
const errors = await manifestManager.syncArtifacts(snapshot, 'add');
4278
if (errors.length) {
4379
reportErrors(logger, errors);
4480
throw new Error('Error writing new artifacts.');
4581
}
82+
}
4683

47-
// Until we get the Default Policy Configuration in the Endpoint package,
48-
// we will add it here manually at creation time.
49-
// @ts-ignore
50-
if (newPackageConfig.inputs.length === 0) {
51-
updatedPackageConfig = {
52-
...newPackageConfig,
53-
inputs: [
54-
{
55-
type: 'endpoint',
56-
enabled: true,
57-
streams: [],
58-
config: {
59-
artifact_manifest: {
60-
value: snapshot.manifest.toEndpointFormat(),
61-
},
62-
policy: {
63-
value: policyConfigFactory(),
64-
},
65-
},
66-
},
67-
],
68-
};
69-
}
84+
if (snapshot) {
85+
updatedPackageConfig.inputs[0].config.artifact_manifest = {
86+
value: snapshot.manifest.toEndpointFormat(),
87+
};
7088
}
7189

7290
return updatedPackageConfig;

x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
// eslint-disable-next-line max-classes-per-file
87
import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks';
98
import { Logger } from 'src/core/server';
9+
import { PackageConfigServiceInterface } from '../../../../../../ingest_manager/server';
10+
import { createPackageConfigServiceMock } from '../../../../../../ingest_manager/server/mocks';
1011
import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
1112
import { listMock } from '../../../../../../lists/server/mocks';
1213
import {
@@ -21,40 +22,6 @@ import { getArtifactClientMock } from '../artifact_client.mock';
2122
import { getManifestClientMock } from '../manifest_client.mock';
2223
import { ManifestManager } from './manifest_manager';
2324

24-
function getMockPackageConfig() {
25-
return {
26-
id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
27-
inputs: [
28-
{
29-
config: {},
30-
},
31-
],
32-
revision: 1,
33-
version: 'abcd', // TODO: not yet implemented in ingest_manager (https://github.com/elastic/kibana/issues/69992)
34-
updated_at: '2020-06-25T16:03:38.159292',
35-
updated_by: 'kibana',
36-
created_at: '2020-06-25T16:03:38.159292',
37-
created_by: 'kibana',
38-
};
39-
}
40-
41-
class PackageConfigServiceMock {
42-
public create = jest.fn().mockResolvedValue(getMockPackageConfig());
43-
public get = jest.fn().mockResolvedValue(getMockPackageConfig());
44-
public getByIds = jest.fn().mockResolvedValue([getMockPackageConfig()]);
45-
public list = jest.fn().mockResolvedValue({
46-
items: [getMockPackageConfig()],
47-
total: 1,
48-
page: 1,
49-
perPage: 20,
50-
});
51-
public update = jest.fn().mockResolvedValue(getMockPackageConfig());
52-
}
53-
54-
export function getPackageConfigServiceMock() {
55-
return new PackageConfigServiceMock();
56-
}
57-
5825
async function mockBuildExceptionListArtifacts(
5926
os: string,
6027
schemaVersion: string
@@ -66,35 +33,33 @@ async function mockBuildExceptionListArtifacts(
6633
return [await buildArtifact(exceptions, os, schemaVersion)];
6734
}
6835

69-
// @ts-ignore
7036
export class ManifestManagerMock extends ManifestManager {
71-
// @ts-ignore
72-
private buildExceptionListArtifacts = async () => {
73-
return mockBuildExceptionListArtifacts('linux', 'v1');
74-
};
37+
protected buildExceptionListArtifacts = jest
38+
.fn()
39+
.mockResolvedValue(mockBuildExceptionListArtifacts('linux', 'v1'));
7540

76-
// @ts-ignore
77-
private getLastDispatchedManifest = jest
41+
public getLastDispatchedManifest = jest
7842
.fn()
7943
.mockResolvedValue(new Manifest(new Date(), 'v1', ManifestConstants.INITIAL_VERSION));
8044

81-
// @ts-ignore
82-
private getManifestClient = jest
45+
protected getManifestClient = jest
8346
.fn()
8447
.mockReturnValue(getManifestClientMock(this.savedObjectsClient));
48+
49+
public syncArtifacts = jest.fn().mockResolvedValue([]);
8550
}
8651

8752
export const getManifestManagerMock = (opts?: {
8853
cache?: ExceptionsCache;
89-
packageConfigService?: PackageConfigServiceMock;
54+
packageConfigService?: jest.Mocked<PackageConfigServiceInterface>;
9055
savedObjectsClient?: ReturnType<typeof savedObjectsClientMock.create>;
9156
}): ManifestManagerMock => {
9257
let cache = new ExceptionsCache(5);
9358
if (opts?.cache !== undefined) {
9459
cache = opts.cache;
9560
}
9661

97-
let packageConfigService = getPackageConfigServiceMock();
62+
let packageConfigService = createPackageConfigServiceMock();
9863
if (opts?.packageConfigService !== undefined) {
9964
packageConfigService = opts.packageConfigService;
10065
}
@@ -107,7 +72,6 @@ export const getManifestManagerMock = (opts?: {
10772
const manifestManager = new ManifestManagerMock({
10873
artifactClient: getArtifactClientMock(savedObjectsClient),
10974
cache,
110-
// @ts-ignore
11175
packageConfigService,
11276
exceptionListClient: listMock.getExceptionListClient(),
11377
logger: loggingSystemMock.create().get() as jest.Mocked<Logger>,

x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class ManifestManager {
6464
* @param schemaVersion The schema version of the manifest.
6565
* @returns {ManifestClient} A ManifestClient scoped to the provided schemaVersion.
6666
*/
67-
private getManifestClient(schemaVersion: string): ManifestClient {
67+
protected getManifestClient(schemaVersion: string): ManifestClient {
6868
return new ManifestClient(this.savedObjectsClient, schemaVersion as ManifestSchemaVersion);
6969
}
7070

@@ -76,7 +76,7 @@ export class ManifestManager {
7676
* @returns {Promise<InternalArtifactSchema[]>} An array of uncompressed artifacts built from exception-list-agnostic SOs.
7777
* @throws Throws/rejects if there are errors building the list.
7878
*/
79-
private async buildExceptionListArtifacts(
79+
protected async buildExceptionListArtifacts(
8080
schemaVersion: string
8181
): Promise<InternalArtifactSchema[]> {
8282
// TODO: should wrap in try/catch?
@@ -96,42 +96,6 @@ export class ManifestManager {
9696
);
9797
}
9898

99-
/**
100-
* Returns the last dispatched manifest based on the current state of the
101-
* user-artifact-manifest SO.
102-
*
103-
* @param schemaVersion The schema version of the manifest.
104-
* @returns {Promise<Manifest | null>} The last dispatched manifest, or null if does not exist.
105-
* @throws Throws/rejects if there is an unexpected error retrieving the manifest.
106-
*/
107-
private async getLastDispatchedManifest(schemaVersion: string): Promise<Manifest | null> {
108-
try {
109-
const manifestClient = this.getManifestClient(schemaVersion);
110-
const manifestSo = await manifestClient.getManifest();
111-
112-
if (manifestSo.version === undefined) {
113-
throw new Error('No version returned for manifest.');
114-
}
115-
116-
const manifest = new Manifest(
117-
new Date(manifestSo.attributes.created),
118-
schemaVersion,
119-
manifestSo.version
120-
);
121-
122-
for (const id of manifestSo.attributes.ids) {
123-
const artifactSo = await this.artifactClient.getArtifact(id);
124-
manifest.addEntry(artifactSo.attributes);
125-
}
126-
return manifest;
127-
} catch (err) {
128-
if (err.output.statusCode !== 404) {
129-
throw err;
130-
}
131-
return null;
132-
}
133-
}
134-
13599
/**
136100
* Writes new artifact SOs based on provided snapshot.
137101
*
@@ -186,6 +150,42 @@ export class ManifestManager {
186150
return errors;
187151
}
188152

153+
/**
154+
* Returns the last dispatched manifest based on the current state of the
155+
* user-artifact-manifest SO.
156+
*
157+
* @param schemaVersion The schema version of the manifest.
158+
* @returns {Promise<Manifest | null>} The last dispatched manifest, or null if does not exist.
159+
* @throws Throws/rejects if there is an unexpected error retrieving the manifest.
160+
*/
161+
public async getLastDispatchedManifest(schemaVersion: string): Promise<Manifest | null> {
162+
try {
163+
const manifestClient = this.getManifestClient(schemaVersion);
164+
const manifestSo = await manifestClient.getManifest();
165+
166+
if (manifestSo.version === undefined) {
167+
throw new Error('No version returned for manifest.');
168+
}
169+
170+
const manifest = new Manifest(
171+
new Date(manifestSo.attributes.created),
172+
schemaVersion,
173+
manifestSo.version
174+
);
175+
176+
for (const id of manifestSo.attributes.ids) {
177+
const artifactSo = await this.artifactClient.getArtifact(id);
178+
manifest.addEntry(artifactSo.attributes);
179+
}
180+
return manifest;
181+
} catch (err) {
182+
if (err.output.statusCode !== 404) {
183+
throw err;
184+
}
185+
return null;
186+
}
187+
}
188+
189189
/**
190190
* Snapshots a manifest based on current state of exception-list-agnostic SOs.
191191
*

0 commit comments

Comments
 (0)