[SECURITY_SOLUTION][ENDPOINT] Generate Trusted Apps artifacts and Manifest entries (#74988)

* Generate Trusted Apps artifacts + manifest entries
* Artifacts mocks support for generating trusted apps entries
* Adjusted Manifest + Artifacts tests to account for trusted apps
This commit is contained in:
Paul Tavares 2020-09-01 08:07:39 -04:00 committed by GitHub
parent bf7b4782e6
commit 97c8c941f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 430 additions and 47 deletions

View file

@ -0,0 +1,18 @@
{
"list_id": "endpoint_trusted_apps",
"item_id": "endpoint_trusted_apps_item",
"_tags": ["endpoint", "os:linux", "os:windows", "os:macos", "trusted-app"],
"tags": ["user added string for a tag", "malware"],
"type": "simple",
"description": "This is a sample agnostic endpoint trusted app entry",
"name": "Sample Endpoint Trusted App Entry",
"namespace_type": "agnostic",
"entries": [
{
"field": "actingProcess.file.signer",
"operator": "included",
"type": "match",
"value": "Elastic, N.V."
}
]
}

View file

@ -49,6 +49,36 @@ describe('ingest_integration tests ', () => {
relative_url:
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
},
'endpoint-trustlist-linux-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
'endpoint-trustlist-macos-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
'endpoint-trustlist-windows-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
},
manifest_version: '1.0.0',
schema_version: 'v1',

View file

@ -14,6 +14,8 @@ export const ArtifactConstants = {
GLOBAL_ALLOWLIST_NAME: 'endpoint-exceptionlist',
SAVED_OBJECT_TYPE: 'endpoint:user-artifact',
SUPPORTED_OPERATING_SYSTEMS: ['macos', 'windows'],
SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'],
GLOBAL_TRUSTED_APPS_NAME: 'endpoint-trustlist',
};
export const ManifestConstants = {

View file

@ -11,6 +11,8 @@ import { getExceptionListItemSchemaMock } from '../../../../../lists/common/sche
import { EntriesArray, EntryList } from '../../../../../lists/common/schemas/types';
import { buildArtifact, getFullEndpointExceptionList } from './lists';
import { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts';
import { ArtifactConstants } from './common';
import { ENDPOINT_LIST_ID } from '../../../../../lists/common';
describe('buildEventTypeSignal', () => {
let mockExceptionClient: ExceptionListClient;
@ -47,7 +49,12 @@ describe('buildEventTypeSignal', () => {
const first = getFoundExceptionListItemSchemaMock();
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -88,7 +95,12 @@ describe('buildEventTypeSignal', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -134,7 +146,12 @@ describe('buildEventTypeSignal', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -182,7 +199,12 @@ describe('buildEventTypeSignal', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -229,7 +251,12 @@ describe('buildEventTypeSignal', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -267,7 +294,12 @@ describe('buildEventTypeSignal', () => {
first.data[1].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -305,7 +337,12 @@ describe('buildEventTypeSignal', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp).toEqual({
entries: [expectedEndpointExceptions],
});
@ -329,7 +366,12 @@ describe('buildEventTypeSignal', () => {
.mockReturnValueOnce(first)
.mockReturnValueOnce(second);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
// Expect 2 exceptions, the first two calls returned the same exception list items
expect(resp.entries.length).toEqual(2);
@ -340,7 +382,12 @@ describe('buildEventTypeSignal', () => {
exceptionsResponse.data = [];
exceptionsResponse.total = 0;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionsResponse);
const resp = await getFullEndpointExceptionList(mockExceptionClient, 'linux', 'v1');
const resp = await getFullEndpointExceptionList(
mockExceptionClient,
'linux',
'v1',
ENDPOINT_LIST_ID
);
expect(resp.entries.length).toEqual(0);
});
@ -385,8 +432,18 @@ describe('buildEventTypeSignal', () => {
],
};
const artifact1 = await buildArtifact(translatedExceptionList, 'linux', 'v1');
const artifact2 = await buildArtifact(translatedExceptionListReversed, 'linux', 'v1');
const artifact1 = await buildArtifact(
translatedExceptionList,
'linux',
'v1',
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
const artifact2 = await buildArtifact(
translatedExceptionListReversed,
'linux',
'v1',
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256);
});
@ -430,8 +487,18 @@ describe('buildEventTypeSignal', () => {
entries: translatedItems.reverse(),
};
const artifact1 = await buildArtifact(translatedExceptionList, 'linux', 'v1');
const artifact2 = await buildArtifact(translatedExceptionListReversed, 'linux', 'v1');
const artifact1 = await buildArtifact(
translatedExceptionList,
'linux',
'v1',
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
const artifact2 = await buildArtifact(
translatedExceptionListReversed,
'linux',
'v1',
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256);
});
});

View file

@ -28,19 +28,20 @@ import {
internalArtifactCompleteSchema,
InternalArtifactCompleteSchema,
} from '../../schemas';
import { ArtifactConstants } from './common';
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants';
export async function buildArtifact(
exceptions: WrappedTranslatedExceptionList,
os: string,
schemaVersion: string
schemaVersion: string,
name: string
): Promise<InternalArtifactCompleteSchema> {
const exceptionsBuffer = Buffer.from(JSON.stringify(exceptions));
const sha256 = createHash('sha256').update(exceptionsBuffer.toString()).digest('hex');
// Keep compression info empty in case its a duplicate. Lazily compress before committing if needed.
return {
identifier: `${ArtifactConstants.GLOBAL_ALLOWLIST_NAME}-${os}-${schemaVersion}`,
identifier: `${name}-${os}-${schemaVersion}`,
compressionAlgorithm: 'none',
encryptionAlgorithm: 'none',
decodedSha256: sha256,
@ -76,7 +77,8 @@ export function isCompressed(artifact: InternalArtifactSchema) {
export async function getFullEndpointExceptionList(
eClient: ExceptionListClient,
os: string,
schemaVersion: string
schemaVersion: string,
listId: typeof ENDPOINT_LIST_ID | typeof ENDPOINT_TRUSTED_APPS_LIST_ID
): Promise<WrappedTranslatedExceptionList> {
const exceptions: WrappedTranslatedExceptionList = { entries: [] };
let page = 1;
@ -84,7 +86,7 @@ export async function getFullEndpointExceptionList(
while (paging) {
const response = await eClient.findExceptionListItem({
listId: ENDPOINT_LIST_ID,
listId,
namespaceType: 'agnostic',
filter: `exception-list-agnostic.attributes._tags:\"os:${os}\"`,
perPage: 100,

View file

@ -94,6 +94,36 @@ describe('manifest', () => {
relative_url:
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
},
'endpoint-trustlist-linux-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
decoded_size: 432,
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
encoded_size: 147,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
},
'endpoint-trustlist-macos-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
decoded_size: 432,
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
encoded_size: 147,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
},
'endpoint-trustlist-windows-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
decoded_size: 432,
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
encoded_size: 147,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
},
},
manifest_version: '1.0.0',
schema_version: 'v1',
@ -107,6 +137,9 @@ describe('manifest', () => {
ids: [
'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-exceptionlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
],
});
});
@ -119,6 +152,21 @@ describe('manifest', () => {
'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8',
@ -139,6 +187,9 @@ describe('manifest', () => {
expect(keys).toEqual([
'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-exceptionlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
]);
});

View file

@ -16,13 +16,20 @@ import { ArtifactConstants } from './common';
import { Manifest } from './manifest';
export const getMockArtifacts = async (opts?: { compress: boolean }) => {
return Promise.all(
ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map<Promise<InternalArtifactCompleteSchema>>(
return Promise.all([
// Exceptions items
...ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map<Promise<InternalArtifactCompleteSchema>>(
async (os) => {
return getInternalArtifactMock(os, 'v1', opts);
}
)
);
),
// Trusted Apps items
...ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS.map<
Promise<InternalArtifactCompleteSchema>
>(async (os) => {
return getInternalArtifactMock(os, 'v1', opts, ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME);
}),
]);
};
export const getMockArtifactsWithDiff = async (opts?: { compress: boolean }) => {

View file

@ -4,7 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { buildArtifact, maybeCompressArtifact, isCompressed } from '../../lib/artifacts';
import {
buildArtifact,
maybeCompressArtifact,
isCompressed,
ArtifactConstants,
} from '../../lib/artifacts';
import { getTranslatedExceptionListMock } from './lists.mock';
import {
InternalManifestSchema,
@ -25,9 +30,15 @@ const compressArtifact = async (artifact: InternalArtifactCompleteSchema) => {
export const getInternalArtifactMock = async (
os: string,
schemaVersion: string,
opts?: { compress: boolean }
opts?: { compress: boolean },
artifactName: string = ArtifactConstants.GLOBAL_ALLOWLIST_NAME
): Promise<InternalArtifactCompleteSchema> => {
const artifact = await buildArtifact(getTranslatedExceptionListMock(), os, schemaVersion);
const artifact = await buildArtifact(
getTranslatedExceptionListMock(),
os,
schemaVersion,
artifactName
);
return opts?.compress ? compressArtifact(artifact) : artifact;
};
@ -36,7 +47,12 @@ export const getEmptyInternalArtifactMock = async (
schemaVersion: string,
opts?: { compress: boolean }
): Promise<InternalArtifactCompleteSchema> => {
const artifact = await buildArtifact({ entries: [] }, os, schemaVersion);
const artifact = await buildArtifact(
{ entries: [] },
os,
schemaVersion,
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
return opts?.compress ? compressArtifact(artifact) : artifact;
};
@ -47,7 +63,12 @@ export const getInternalArtifactMockWithDiffs = async (
): Promise<InternalArtifactCompleteSchema> => {
const mock = getTranslatedExceptionListMock();
mock.entries.pop();
const artifact = await buildArtifact(mock, os, schemaVersion);
const artifact = await buildArtifact(
mock,
os,
schemaVersion,
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
return opts?.compress ? compressArtifact(artifact) : artifact;
};

View file

@ -24,11 +24,41 @@ describe('manifest_manager', () => {
'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8',
type: 'add',
},
{
id:
'endpoint-trustlist-macos-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
{
id:
'endpoint-trustlist-windows-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
{
id:
'endpoint-trustlist-linux-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
]);
});
@ -44,16 +74,53 @@ describe('manifest_manager', () => {
'endpoint-exceptionlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-macos-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-windows-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-trustlist-linux-v1-96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
type: 'delete',
},
{
id:
'endpoint-exceptionlist-macos-v1-0a5a2013a79f9e60682472284a1be45ab1ff68b9b43426d00d665016612c15c8',
type: 'add',
},
{
id:
'endpoint-trustlist-macos-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
{
id:
'endpoint-trustlist-windows-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
{
id:
'endpoint-trustlist-linux-v1-1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
type: 'add',
},
]);
const newArtifactId = diffs[1].id;
await newManifest.compressArtifact(newArtifactId);
const artifact = newManifest.getArtifact(newArtifactId)!;
const firstNewArtifactId = diffs.find((diff) => diff.type === 'add')!.id;
// Compress all `add` artifacts
for (const artifactDiff of diffs) {
if (artifactDiff.type === 'add') {
await newManifest.compressArtifact(artifactDiff.id);
}
}
const artifact = newManifest.getArtifact(firstNewArtifactId)!;
if (isCompleteArtifact(artifact)) {
await manifestManager.pushArtifacts([artifact]); // caches the artifact
@ -61,7 +128,7 @@ describe('manifest_manager', () => {
throw new Error('Artifact is missing a body.');
}
const entry = JSON.parse(inflateSync(cache.get(newArtifactId)! as Buffer).toString());
const entry = JSON.parse(inflateSync(cache.get(firstNewArtifactId)! as Buffer).toString());
expect(entry).toEqual({
entries: [
{
@ -107,8 +174,12 @@ describe('manifest_manager', () => {
const oldManifest = await manifestManager.getLastComputedManifest();
const newManifest = await manifestManager.buildNewManifest(oldManifest!);
const diffs = newManifest.diff(oldManifest!);
const newArtifactId = diffs[1].id;
await newManifest.compressArtifact(newArtifactId);
for (const artifactDiff of diffs) {
if (artifactDiff.type === 'add') {
await newManifest.compressArtifact(artifactDiff.id);
}
}
newManifest.bumpSemanticVersion();
@ -145,6 +216,36 @@ describe('manifest_manager', () => {
relative_url:
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
},
'endpoint-trustlist-linux-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
'endpoint-trustlist-macos-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
'endpoint-trustlist-windows-v1': {
compression_algorithm: 'zlib',
decoded_sha256: '1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
decoded_size: 287,
encoded_sha256: 'c3dec543df1177561ab2aa74a37997ea3c1d748d532a597884f5a5c16670d56c',
encoded_size: 133,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/1a8295e6ccb93022c6f5ceb8997b29f2912389b3b38f52a8f5a2ff7b0154b1bc',
},
},
});
});
@ -155,8 +256,12 @@ describe('manifest_manager', () => {
const oldManifest = await manifestManager.getLastComputedManifest();
const newManifest = await manifestManager.buildNewManifest(oldManifest!);
const diffs = newManifest.diff(oldManifest!);
const newArtifactId = diffs[1].id;
await newManifest.compressArtifact(newArtifactId);
for (const artifactDiff of diffs) {
if (artifactDiff.type === 'add') {
await newManifest.compressArtifact(artifactDiff.id);
}
}
newManifest.bumpSemanticVersion();
@ -174,11 +279,17 @@ describe('manifest_manager', () => {
const oldManifest = await manifestManager.getLastComputedManifest();
const newManifest = await manifestManager.buildNewManifest(oldManifest!);
const diffs = newManifest.diff(oldManifest!);
const oldArtifactId = diffs[0].id;
const newArtifactId = diffs[1].id;
await newManifest.compressArtifact(newArtifactId);
const firstOldArtifactId = diffs.find((diff) => diff.type === 'delete')!.id;
const FirstNewArtifactId = diffs.find((diff) => diff.type === 'add')!.id;
const artifact = newManifest.getArtifact(newArtifactId)!;
// Compress all new artifacts
for (const artifactDiff of diffs) {
if (artifactDiff.type === 'add') {
await newManifest.compressArtifact(artifactDiff.id);
}
}
const artifact = newManifest.getArtifact(FirstNewArtifactId)!;
if (isCompleteArtifact(artifact)) {
await manifestManager.pushArtifacts([artifact]);
} else {
@ -186,7 +297,7 @@ describe('manifest_manager', () => {
}
await manifestManager.commit(newManifest);
await manifestManager.deleteArtifacts([oldArtifactId]);
await manifestManager.deleteArtifacts([firstOldArtifactId]);
// created new artifact
expect(savedObjectsClient.create.mock.calls[0][0]).toEqual(
@ -201,7 +312,7 @@ describe('manifest_manager', () => {
// deleted old artifact
expect(savedObjectsClient.delete).toHaveBeenCalledWith(
ArtifactConstants.SAVED_OBJECT_TYPE,
oldArtifactId
firstOldArtifactId
);
});

View file

@ -13,11 +13,11 @@ import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/ma
import {
ArtifactConstants,
Manifest,
buildArtifact,
getFullEndpointExceptionList,
ManifestDiff,
getArtifactId,
getFullEndpointExceptionList,
Manifest,
ManifestDiff,
} from '../../../lib/artifacts';
import {
InternalArtifactCompleteSchema,
@ -25,6 +25,8 @@ import {
} from '../../../schemas/artifacts';
import { ArtifactClient } from '../artifact_client';
import { ManifestClient } from '../manifest_client';
import { ENDPOINT_LIST_ID } from '../../../../../../lists/common';
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common/constants';
export interface ManifestManagerContext {
savedObjectsClient: SavedObjectsClientContract;
@ -87,9 +89,43 @@ export class ManifestManager {
const exceptionList = await getFullEndpointExceptionList(
this.exceptionListClient,
os,
artifactSchemaVersion ?? 'v1'
artifactSchemaVersion ?? 'v1',
ENDPOINT_LIST_ID
);
const artifact = await buildArtifact(
exceptionList,
os,
artifactSchemaVersion ?? 'v1',
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
artifacts.push(artifact);
}
return artifacts;
}
/**
* Builds an array of artifacts (one per supported OS) based on the current state of the
* Trusted Apps list (which uses the `exception-list-agnostic` SO type)
* @param artifactSchemaVersion
*/
protected async buildTrustedAppsArtifacts(
artifactSchemaVersion?: string
): Promise<InternalArtifactCompleteSchema[]> {
const artifacts: InternalArtifactCompleteSchema[] = [];
for (const os of ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS) {
const trustedApps = await getFullEndpointExceptionList(
this.exceptionListClient,
os,
artifactSchemaVersion ?? 'v1',
ENDPOINT_TRUSTED_APPS_LIST_ID
);
const artifact = await buildArtifact(
trustedApps,
os,
'v1',
ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME
);
const artifact = await buildArtifact(exceptionList, os, artifactSchemaVersion ?? 'v1');
artifacts.push(artifact);
}
return artifacts;
@ -205,7 +241,9 @@ export class ManifestManager {
*/
public async buildNewManifest(baselineManifest?: Manifest): Promise<Manifest> {
// Build new exception list artifacts
const artifacts = await this.buildExceptionListArtifacts();
const artifacts = (
await Promise.all([this.buildExceptionListArtifacts(), this.buildTrustedAppsArtifacts()])
).flat();
// Build new manifest
const manifest = Manifest.fromArtifacts(

View file

@ -142,6 +142,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
relative_url:
'/api/endpoint/artifacts/download/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
},
'endpoint-trustlist-linux-v1': {
compression_algorithm: 'zlib',
decoded_sha256:
'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
decoded_size: 14,
encoded_sha256:
'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
encoded_size: 22,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
},
'endpoint-trustlist-macos-v1': {
compression_algorithm: 'zlib',
decoded_sha256:
'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
decoded_size: 14,
encoded_sha256:
'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
encoded_size: 22,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
},
'endpoint-trustlist-windows-v1': {
compression_algorithm: 'zlib',
decoded_sha256:
'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
decoded_size: 14,
encoded_sha256:
'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda',
encoded_size: 22,
encryption_algorithm: 'none',
relative_url:
'/api/endpoint/artifacts/download/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658',
},
},
// The manifest version could have changed when the Policy was updated because the
// policy details page ensures that a save action applies the udpated policy on top