Skip to content

Commit 1adeb7c

Browse files
committed
[Security Solution][Endpoint][Exceptions] Only write manifest to policy when there are changes (#72000)
* Refactor security_solution policy creation callback - part 1 * Fix manifest dispatch * Change how dispatches are performed * simplify manifest types * Remove unused mock * Fix tests * one place to construct artifact ids * fixing linter exceptions * Add tests for stable hashes * Additional testing and type cleanup * Remove unnecessary log * Minor fixup * jsdoc * type fixup * Additional type adjustments
1 parent d7168a0 commit 1adeb7c

26 files changed

Lines changed: 1154 additions & 583 deletions

x-pack/plugins/ingest_manager/common/mocks.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,90 @@ export const createPackageConfigMock = (): PackageConfig => {
4444
],
4545
};
4646
};
47+
48+
export const createPackageConfigWithInitialManifestMock = (): PackageConfig => {
49+
const packageConfig = createPackageConfigMock();
50+
packageConfig.inputs[0].config!.artifact_manifest = {
51+
value: {
52+
artifacts: {
53+
'endpoint-exceptionlist-linux-v1': {
54+
compression_algorithm: 'zlib',
55+
encryption_algorithm: 'none',
56+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
57+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
58+
decoded_size: 14,
59+
encoded_size: 22,
60+
relative_url:
61+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
62+
},
63+
'endpoint-exceptionlist-macos-v1': {
64+
compression_algorithm: 'zlib',
65+
encryption_algorithm: 'none',
66+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
67+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
68+
decoded_size: 14,
69+
encoded_size: 22,
70+
relative_url:
71+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
72+
},
73+
'endpoint-exceptionlist-windows-v1': {
74+
compression_algorithm: 'zlib',
75+
encryption_algorithm: 'none',
76+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
77+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
78+
decoded_size: 14,
79+
encoded_size: 22,
80+
relative_url:
81+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
82+
},
83+
},
84+
manifest_version: 'a9b7ef358a363f327f479e31efc4f228b2277a7fb4d1914ca9b4e7ca9ffcf537',
85+
schema_version: 'v1',
86+
},
87+
};
88+
return packageConfig;
89+
};
90+
91+
export const createPackageConfigWithManifestMock = (): PackageConfig => {
92+
const packageConfig = createPackageConfigMock();
93+
packageConfig.inputs[0].config!.artifact_manifest = {
94+
value: {
95+
artifacts: {
96+
'endpoint-exceptionlist-linux-v1': {
97+
compression_algorithm: 'zlib',
98+
encryption_algorithm: 'none',
99+
decoded_sha256: '0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8',
100+
encoded_sha256: '57941169bb2c5416f9bd7224776c8462cb9a2be0fe8b87e6213e77a1d29be824',
101+
decoded_size: 292,
102+
encoded_size: 131,
103+
relative_url:
104+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8',
105+
},
106+
'endpoint-exceptionlist-macos-v1': {
107+
compression_algorithm: 'zlib',
108+
encryption_algorithm: 'none',
109+
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
110+
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
111+
decoded_size: 432,
112+
encoded_size: 147,
113+
relative_url:
114+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
115+
},
116+
'endpoint-exceptionlist-windows-v1': {
117+
compression_algorithm: 'zlib',
118+
encryption_algorithm: 'none',
119+
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
120+
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
121+
decoded_size: 432,
122+
encoded_size: 147,
123+
relative_url:
124+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
125+
},
126+
},
127+
manifest_version: '520f6cf88b3f36a065c6ca81058d5f8690aadadf6fe857f8dec4cc37589e7283',
128+
schema_version: 'v1',
129+
},
130+
};
131+
132+
return packageConfig;
133+
};

x-pack/plugins/security_solution/common/endpoint/schema/common.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export const compressionAlgorithm = t.keyof({
1212
});
1313
export type CompressionAlgorithm = t.TypeOf<typeof compressionAlgorithm>;
1414

15+
export const compressionAlgorithmDispatch = t.keyof({
16+
zlib: null,
17+
});
18+
export type CompressionAlgorithmDispatch = t.TypeOf<typeof compressionAlgorithmDispatch>;
19+
1520
export const encryptionAlgorithm = t.keyof({
1621
none: null,
1722
});

x-pack/plugins/security_solution/common/endpoint/schema/manifest.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import * as t from 'io-ts';
88
import {
99
compressionAlgorithm,
10+
compressionAlgorithmDispatch,
1011
encryptionAlgorithm,
1112
identifier,
1213
manifestSchemaVersion,
@@ -16,25 +17,60 @@ import {
1617
size,
1718
} from './common';
1819

19-
export const manifestEntrySchema = t.exact(
20+
export const manifestEntryBaseSchema = t.exact(
2021
t.type({
2122
relative_url: relativeUrl,
2223
decoded_sha256: sha256,
2324
decoded_size: size,
2425
encoded_sha256: sha256,
2526
encoded_size: size,
26-
compression_algorithm: compressionAlgorithm,
2727
encryption_algorithm: encryptionAlgorithm,
2828
})
2929
);
3030

31-
export const manifestSchema = t.exact(
31+
export const manifestEntrySchema = t.intersection([
32+
manifestEntryBaseSchema,
33+
t.exact(
34+
t.type({
35+
compression_algorithm: compressionAlgorithm,
36+
})
37+
),
38+
]);
39+
export type ManifestEntrySchema = t.TypeOf<typeof manifestEntrySchema>;
40+
41+
export const manifestEntryDispatchSchema = t.intersection([
42+
manifestEntryBaseSchema,
43+
t.exact(
44+
t.type({
45+
compression_algorithm: compressionAlgorithmDispatch,
46+
})
47+
),
48+
]);
49+
export type ManifestEntryDispatchSchema = t.TypeOf<typeof manifestEntryDispatchSchema>;
50+
51+
export const manifestBaseSchema = t.exact(
3252
t.type({
3353
manifest_version: manifestVersion,
3454
schema_version: manifestSchemaVersion,
35-
artifacts: t.record(identifier, manifestEntrySchema),
3655
})
3756
);
3857

39-
export type ManifestEntrySchema = t.TypeOf<typeof manifestEntrySchema>;
58+
export const manifestSchema = t.intersection([
59+
manifestBaseSchema,
60+
t.exact(
61+
t.type({
62+
artifacts: t.record(identifier, manifestEntrySchema),
63+
})
64+
),
65+
]);
4066
export type ManifestSchema = t.TypeOf<typeof manifestSchema>;
67+
68+
export const manifestDispatchSchema = t.intersection([
69+
manifestBaseSchema,
70+
t.exact(
71+
t.type({
72+
artifacts: t.record(identifier, manifestEntryDispatchSchema),
73+
})
74+
),
75+
]);
76+
export type ManifestDispatchSchema = t.TypeOf<typeof manifestDispatchSchema>;

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

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

7-
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
8-
import { loggerMock } from 'src/core/server/logging/logger.mock';
7+
import { loggingSystemMock } from 'src/core/server/mocks';
98
import { createNewPackageConfigMock } from '../../../ingest_manager/common/mocks';
109
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
11-
import { getManifestManagerMock } from './services/artifacts/manifest_manager/manifest_manager.mock';
10+
import {
11+
getManifestManagerMock,
12+
ManifestManagerMockType,
13+
} from './services/artifacts/manifest_manager/manifest_manager.mock';
1214
import { getPackageConfigCreateCallback } from './ingest_integration';
15+
import { ManifestConstants } from './lib/artifacts';
1316

1417
describe('ingest_integration tests ', () => {
1518
describe('ingest_integration sanity checks', () => {
16-
test('policy is updated with manifest', async () => {
17-
const logger = loggerMock.create();
18-
const manifestManager = getManifestManagerMock();
19+
test('policy is updated with initial manifest', async () => {
20+
const logger = loggingSystemMock.create().get('ingest_integration.test');
21+
const manifestManager = getManifestManagerMock({
22+
mockType: ManifestManagerMockType.InitialSystemState,
23+
});
24+
1925
const callback = getPackageConfigCreateCallback(logger, manifestManager);
20-
const policyConfig = createNewPackageConfigMock();
21-
const newPolicyConfig = await callback(policyConfig);
26+
const policyConfig = createNewPackageConfigMock(); // policy config without manifest
27+
const newPolicyConfig = await callback(policyConfig); // policy config WITH manifest
28+
2229
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
2330
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
2431
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual({
2532
artifacts: {
2633
'endpoint-exceptionlist-linux-v1': {
2734
compression_algorithm: 'zlib',
28-
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
29-
decoded_size: 287,
30-
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
31-
encoded_size: 133,
35+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
36+
decoded_size: 14,
37+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
38+
encoded_size: 22,
39+
encryption_algorithm: 'none',
40+
relative_url:
41+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
42+
},
43+
'endpoint-exceptionlist-macos-v1': {
44+
compression_algorithm: 'zlib',
45+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
46+
decoded_size: 14,
47+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
48+
encoded_size: 22,
49+
encryption_algorithm: 'none',
50+
relative_url:
51+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
52+
},
53+
'endpoint-exceptionlist-windows-v1': {
54+
compression_algorithm: 'zlib',
55+
decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
56+
decoded_size: 14,
57+
encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
58+
encoded_size: 22,
3259
encryption_algorithm: 'none',
3360
relative_url:
34-
'/api/endpoint/artifacts/download/endpoint-exceptionlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
61+
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
3562
},
3663
},
37-
manifest_version: 'WzAsMF0=',
64+
manifest_version: 'a9b7ef358a363f327f479e31efc4f228b2277a7fb4d1914ca9b4e7ca9ffcf537',
3865
schema_version: 'v1',
3966
});
4067
});
4168

42-
test('policy is returned even if error is encountered during artifact sync', async () => {
43-
const logger = loggerMock.create();
69+
test('policy is returned even if error is encountered during artifact creation', async () => {
70+
const logger = loggingSystemMock.create().get('ingest_integration.test');
4471
const manifestManager = getManifestManagerMock();
45-
manifestManager.syncArtifacts = jest.fn().mockRejectedValue([new Error('error updating')]);
46-
const lastDispatched = await manifestManager.getLastDispatchedManifest();
72+
manifestManager.pushArtifacts = jest.fn().mockResolvedValue([new Error('error updating')]);
73+
const lastComputed = await manifestManager.getLastComputedManifest(
74+
ManifestConstants.SCHEMA_VERSION
75+
);
76+
4777
const callback = getPackageConfigCreateCallback(logger, manifestManager);
4878
const policyConfig = createNewPackageConfigMock();
4979
const newPolicyConfig = await callback(policyConfig);
80+
5081
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
5182
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
5283
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
53-
lastDispatched.toEndpointFormat()
84+
lastComputed!.toEndpointFormat()
5485
);
5586
});
5687

57-
test('initial policy creation succeeds if snapshot retrieval fails', async () => {
58-
const logger = loggerMock.create();
59-
const manifestManager = getManifestManagerMock();
60-
const lastDispatched = await manifestManager.getLastDispatchedManifest();
61-
manifestManager.getSnapshot = jest.fn().mockResolvedValue(null);
88+
test('initial policy creation succeeds if manifest retrieval fails', async () => {
89+
const logger = loggingSystemMock.create().get('ingest_integration.test');
90+
const manifestManager = getManifestManagerMock({
91+
mockType: ManifestManagerMockType.InitialSystemState,
92+
});
93+
const lastComputed = await manifestManager.getLastComputedManifest(
94+
ManifestConstants.SCHEMA_VERSION
95+
);
96+
expect(lastComputed).toEqual(null);
97+
98+
manifestManager.buildNewManifest = jest.fn().mockRejectedValue(new Error('abcd'));
6299
const callback = getPackageConfigCreateCallback(logger, manifestManager);
63100
const policyConfig = createNewPackageConfigMock();
64101
const newPolicyConfig = await callback(policyConfig);
102+
65103
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
66104
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
67-
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
68-
lastDispatched.toEndpointFormat()
69-
);
70105
});
71106

72107
test('subsequent policy creations succeed', async () => {
73-
const logger = loggerMock.create();
108+
const logger = loggingSystemMock.create().get('ingest_integration.test');
74109
const manifestManager = getManifestManagerMock();
75-
const snapshot = await manifestManager.getSnapshot();
76-
manifestManager.getLastDispatchedManifest = jest.fn().mockResolvedValue(snapshot!.manifest);
77-
manifestManager.getSnapshot = jest.fn().mockResolvedValue({
78-
manifest: snapshot!.manifest,
79-
diffs: [],
80-
});
110+
const lastComputed = await manifestManager.getLastComputedManifest(
111+
ManifestConstants.SCHEMA_VERSION
112+
);
113+
114+
manifestManager.buildNewManifest = jest.fn().mockResolvedValue(lastComputed); // no diffs
81115
const callback = getPackageConfigCreateCallback(logger, manifestManager);
82116
const policyConfig = createNewPackageConfigMock();
83117
const newPolicyConfig = await callback(policyConfig);
118+
84119
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
85120
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
86121
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
87-
snapshot!.manifest.toEndpointFormat()
122+
lastComputed!.toEndpointFormat()
88123
);
89124
});
90125
});

0 commit comments

Comments
 (0)