[Security solution][Endpoint] Removes zip compression when creating artifacts (#101379)

* Removes zlib compression when creating artifacts. Also fixes related unit tests and removes old code

* Replaces artifact in new manifest using the ones from fleet client with zlip compression

* Fixes create_policy_artifact_manifest pushArtifacts missing new manifest. Also fixes unit tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
David Sánchez 2021-06-14 12:25:42 +02:00 committed by GitHub
parent 06aaa529d4
commit ec2ec6ab40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 158 additions and 193 deletions

View file

@ -15,6 +15,7 @@ export type CompressionAlgorithm = t.TypeOf<typeof compressionAlgorithm>;
export const compressionAlgorithmDispatch = t.keyof({
zlib: null,
none: null,
});
export type CompressionAlgorithmDispatch = t.TypeOf<typeof compressionAlgorithmDispatch>;

View file

@ -6,7 +6,6 @@
*/
import { createHash } from 'crypto';
import { deflate } from 'zlib';
import type {
Entry,
EntryNested,
@ -21,9 +20,7 @@ import {
} from '@kbn/securitysolution-list-constants';
import { ExceptionListClient } from '../../../../../lists/server';
import {
internalArtifactCompleteSchema,
InternalArtifactCompleteSchema,
InternalArtifactSchema,
TranslatedEntry,
translatedEntry as translatedEntryType,
translatedEntryMatchAnyMatcher,
@ -60,28 +57,6 @@ export async function buildArtifact(
};
}
export async function maybeCompressArtifact(
uncompressedArtifact: InternalArtifactSchema
): Promise<InternalArtifactSchema> {
const compressedArtifact = { ...uncompressedArtifact };
if (internalArtifactCompleteSchema.is(uncompressedArtifact)) {
const compressedArtifactBody = await compressExceptionList(
Buffer.from(uncompressedArtifact.body, 'base64')
);
compressedArtifact.body = compressedArtifactBody.toString('base64');
compressedArtifact.encodedSize = compressedArtifactBody.byteLength;
compressedArtifact.compressionAlgorithm = 'zlib';
compressedArtifact.encodedSha256 = createHash('sha256')
.update(compressedArtifactBody)
.digest('hex');
}
return compressedArtifact;
}
export function isCompressed(artifact: InternalArtifactSchema) {
return artifact.compressionAlgorithm === 'zlib';
}
export async function getFilteredEndpointExceptionList(
eClient: ExceptionListClient,
schemaVersion: string,
@ -297,15 +272,3 @@ function translateEntry(
}
}
}
export async function compressExceptionList(buffer: Buffer): Promise<Buffer> {
return new Promise((resolve, reject) => {
deflate(buffer, function (err, buf) {
if (err) {
reject(err);
} else {
resolve(buf);
}
});
});
}

View file

@ -37,8 +37,8 @@ describe('manifest', () => {
let ARTIFACT_COPY_TRUSTED_APPS_WINDOWS: InternalArtifactCompleteSchema;
beforeAll(async () => {
ARTIFACTS = await getMockArtifacts({ compress: true });
ARTIFACTS_COPY = await getMockArtifacts({ compress: true });
ARTIFACTS = await getMockArtifacts();
ARTIFACTS_COPY = await getMockArtifacts();
ARTIFACT_EXCEPTIONS_MACOS = ARTIFACTS[0];
ARTIFACT_EXCEPTIONS_WINDOWS = ARTIFACTS[1];
ARTIFACT_EXCEPTIONS_LINUX = ARTIFACTS[2];

View file

@ -12,6 +12,7 @@ import {
InternalArtifactSchema,
InternalManifestSchema,
InternalManifestEntrySchema,
InternalArtifactCompleteSchema,
} from '../../schemas/artifacts';
import {
ManifestSchemaVersion,
@ -139,6 +140,27 @@ export class Manifest {
return this.allEntries.get(getArtifactId(artifact))?.specificTargetPolicies;
}
/**
* Replaces an artifact from all the collections.
*
* @param artifact An InternalArtifactCompleteSchema representing the artifact.
*/
public replaceArtifact(artifact: InternalArtifactCompleteSchema) {
const existingEntry = this.allEntries.get(getArtifactId(artifact));
if (existingEntry) {
existingEntry.entry = new ManifestEntry(artifact);
this.allEntries.set(getArtifactId(artifact), existingEntry);
this.defaultEntries.set(getArtifactId(artifact), existingEntry.entry);
existingEntry.specificTargetPolicies.forEach((policyId) => {
const entries = this.policySpecificEntries.get(policyId) || new Map();
entries.set(existingEntry.entry.getDocId(), existingEntry.entry);
this.policySpecificEntries.set(policyId, entries);
});
}
}
public diff(manifest: Manifest): ManifestDiff {
const diff: ManifestDiff = {
additions: [],

View file

@ -15,7 +15,7 @@ describe('manifest_entry', () => {
let manifestEntry: ManifestEntry;
beforeAll(async () => {
artifact = await getInternalArtifactMock('windows', 'v1', { compress: true });
artifact = await getInternalArtifactMock('windows', 'v1');
manifestEntry = new ManifestEntry(artifact);
});
@ -35,7 +35,7 @@ describe('manifest_entry', () => {
test('Correct sha256 is returned', () => {
expect(manifestEntry.getEncodedSha256()).toEqual(
'975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e'
'96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3'
);
expect(manifestEntry.getDecodedSha256()).toEqual(
'96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3'
@ -43,7 +43,7 @@ describe('manifest_entry', () => {
});
test('Correct size is returned', () => {
expect(manifestEntry.getEncodedSize()).toEqual(147);
expect(manifestEntry.getEncodedSize()).toEqual(432);
expect(manifestEntry.getDecodedSize()).toEqual(432);
});
@ -59,12 +59,12 @@ describe('manifest_entry', () => {
test('Correct record is returned', () => {
expect(manifestEntry.getRecord()).toEqual({
compression_algorithm: 'zlib',
compression_algorithm: 'none',
encryption_algorithm: 'none',
decoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
encoded_sha256: '975382ab55d019cbab0bbac207a54e2a7d489fad6e8f6de34fc6402e5ef37b1e',
encoded_sha256: '96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
decoded_size: 432,
encoded_size: 147,
encoded_size: 432,
relative_url:
'/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/96b76a1a911662053a1562ac14c4ff1e87c2ff550d6fe52e1e0b3790526597d3',
});

View file

@ -49,7 +49,7 @@ describe('When migrating artifacts to fleet', () => {
type: '',
id: 'abc123',
references: [],
attributes: await getInternalArtifactMock('windows', 'v1', { compress: true }),
attributes: await getInternalArtifactMock('windows', 'v1'),
},
])
);

View file

@ -17,49 +17,49 @@ import {
import { ArtifactConstants } from './common';
import { Manifest } from './manifest';
export const getMockArtifacts = async (opts?: { compress: boolean }) => {
export const getMockArtifacts = async () => {
return Promise.all([
// Exceptions items
...ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map<Promise<InternalArtifactCompleteSchema>>(
async (os) => {
return getInternalArtifactMock(os, 'v1', opts);
return getInternalArtifactMock(os, 'v1');
}
),
// Trusted Apps items
...ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS.map<
Promise<InternalArtifactCompleteSchema>
>(async (os) => {
return getInternalArtifactMock(os, 'v1', opts, ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME);
return getInternalArtifactMock(os, 'v1', ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME);
}),
]);
};
export const getMockArtifactsWithDiff = async (opts?: { compress: boolean }) => {
export const getMockArtifactsWithDiff = async () => {
return Promise.all(
ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map<Promise<InternalArtifactCompleteSchema>>(
async (os) => {
if (os === 'macos') {
return getInternalArtifactMockWithDiffs(os, 'v1');
}
return getInternalArtifactMock(os, 'v1', opts);
return getInternalArtifactMock(os, 'v1');
}
)
);
};
export const getEmptyMockArtifacts = async (opts?: { compress: boolean }) => {
export const getEmptyMockArtifacts = async () => {
return Promise.all(
ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS.map<Promise<InternalArtifactCompleteSchema>>(
async (os) => {
return getEmptyInternalArtifactMock(os, 'v1', opts);
return getEmptyInternalArtifactMock(os, 'v1');
}
)
);
};
export const getMockManifest = async (opts?: { compress: boolean }) => {
export const getMockManifest = async () => {
const manifest = new Manifest();
const artifacts = await getMockArtifacts(opts);
const artifacts = await getMockArtifacts();
artifacts.forEach((artifact) => manifest.addEntry(artifact));
return manifest;
};

View file

@ -106,7 +106,7 @@ describe('task', () => {
let ARTIFACT_TRUSTED_APPS_MACOS: InternalArtifactCompleteSchema;
beforeAll(async () => {
const artifacts = await getMockArtifacts({ compress: true });
const artifacts = await getMockArtifacts();
ARTIFACT_EXCEPTIONS_MACOS = artifacts[0];
ARTIFACT_EXCEPTIONS_WINDOWS = artifacts[1];
ARTIFACT_TRUSTED_APPS_MACOS = artifacts[2];
@ -167,7 +167,7 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([], newManifest);
expect(manifestManager.commit).not.toHaveBeenCalled();
expect(manifestManager.tryDispatch).toHaveBeenCalledWith(newManifest);
expect(manifestManager.deleteArtifacts).toHaveBeenCalledWith([]);
@ -192,10 +192,10 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_TRUSTED_APPS_MACOS,
]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_TRUSTED_APPS_MACOS],
newManifest
);
expect(manifestManager.commit).not.toHaveBeenCalled();
expect(manifestManager.tryDispatch).not.toHaveBeenCalled();
expect(manifestManager.deleteArtifacts).not.toHaveBeenCalled();
@ -221,10 +221,10 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_TRUSTED_APPS_MACOS,
]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_TRUSTED_APPS_MACOS],
newManifest
);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
expect(manifestManager.tryDispatch).not.toHaveBeenCalled();
expect(manifestManager.deleteArtifacts).not.toHaveBeenCalled();
@ -251,10 +251,10 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_TRUSTED_APPS_MACOS,
]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_TRUSTED_APPS_MACOS],
newManifest
);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
expect(manifestManager.tryDispatch).toHaveBeenCalledWith(newManifest);
expect(manifestManager.deleteArtifacts).not.toHaveBeenCalled();
@ -284,7 +284,10 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([ARTIFACT_TRUSTED_APPS_MACOS]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_TRUSTED_APPS_MACOS],
newManifest
);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
expect(manifestManager.tryDispatch).toHaveBeenCalledWith(newManifest);
expect(manifestManager.deleteArtifacts).toHaveBeenCalledWith([ARTIFACT_ID_1]);
@ -314,7 +317,7 @@ describe('task', () => {
expect(manifestManager.getLastComputedManifest).toHaveBeenCalled();
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith(lastManifest);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([], newManifest);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
expect(manifestManager.tryDispatch).toHaveBeenCalledWith(newManifest);
expect(manifestManager.deleteArtifacts).toHaveBeenCalledWith([]);

View file

@ -130,7 +130,8 @@ export class ManifestTask {
const diff = newManifest.diff(oldManifest);
const persistErrors = await manifestManager.pushArtifacts(
diff.additions as InternalArtifactCompleteSchema[]
diff.additions as InternalArtifactCompleteSchema[],
newManifest
);
if (persistErrors.length) {
reportErrors(this.logger, persistErrors);

View file

@ -5,33 +5,13 @@
* 2.0.
*/
import {
buildArtifact,
maybeCompressArtifact,
isCompressed,
ArtifactConstants,
} from '../../lib/artifacts';
import { buildArtifact, ArtifactConstants } from '../../lib/artifacts';
import { getTranslatedExceptionListMock } from './lists.mock';
import {
InternalManifestSchema,
internalArtifactCompleteSchema,
InternalArtifactCompleteSchema,
} from './saved_objects';
const compressArtifact = async (artifact: InternalArtifactCompleteSchema) => {
const compressedArtifact = await maybeCompressArtifact(artifact);
if (!isCompressed(compressedArtifact)) {
throw new Error(`Unable to compress artifact: ${artifact.identifier}`);
} else if (!internalArtifactCompleteSchema.is(compressedArtifact)) {
throw new Error(`Incomplete artifact detected: ${artifact.identifier}`);
}
return compressedArtifact;
};
import { InternalManifestSchema, InternalArtifactCompleteSchema } from './saved_objects';
export const getInternalArtifactMock = async (
os: string,
schemaVersion: string,
opts?: { compress: boolean },
artifactName: string = ArtifactConstants.GLOBAL_ALLOWLIST_NAME
): Promise<InternalArtifactCompleteSchema> => {
const artifact = await buildArtifact(
@ -40,23 +20,21 @@ export const getInternalArtifactMock = async (
os,
artifactName
);
return opts?.compress ? compressArtifact(artifact) : artifact;
return artifact;
};
export const getEmptyInternalArtifactMock = async (
os: string,
schemaVersion: string,
opts?: { compress: boolean },
artifactName: string = ArtifactConstants.GLOBAL_ALLOWLIST_NAME
): Promise<InternalArtifactCompleteSchema> => {
const artifact = await buildArtifact({ entries: [] }, schemaVersion, os, artifactName);
return opts?.compress ? compressArtifact(artifact) : artifact;
return artifact;
};
export const getInternalArtifactMockWithDiffs = async (
os: string,
schemaVersion: string,
opts?: { compress: boolean }
schemaVersion: string
): Promise<InternalArtifactCompleteSchema> => {
const mock = getTranslatedExceptionListMock();
mock.entries.pop();
@ -66,7 +44,7 @@ export const getInternalArtifactMockWithDiffs = async (
os,
ArtifactConstants.GLOBAL_ALLOWLIST_NAME
);
return opts?.compress ? compressArtifact(artifact) : artifact;
return artifact;
};
export const getInternalManifestMock = (): InternalManifestSchema => ({

View file

@ -29,7 +29,7 @@ describe('artifact_client', () => {
});
test('can create artifact', async () => {
const artifact = await getInternalArtifactMock('linux', 'v1', { compress: true });
const artifact = await getInternalArtifactMock('linux', 'v1');
await artifactClient.createArtifact(artifact);
expect(fleetArtifactClient.createArtifact).toHaveBeenCalledWith({
identifier: artifact.identifier,

View file

@ -5,13 +5,9 @@
* 2.0.
*/
import { inflate as _inflate } from 'zlib';
import { promisify } from 'util';
import { InternalArtifactCompleteSchema } from '../../schemas/artifacts';
import { Artifact, ArtifactsClientInterface } from '../../../../../fleet/server';
const inflateAsync = promisify(_inflate);
export interface EndpointArtifactClientInterface {
getArtifact(id: string): Promise<InternalArtifactCompleteSchema | undefined>;
@ -56,12 +52,8 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface {
async createArtifact(
artifact: InternalArtifactCompleteSchema
): Promise<InternalArtifactCompleteSchema> {
// FIXME:PT refactor to make this more efficient by passing through the uncompressed artifact content
// Artifact `.body` is compressed/encoded. We need it decoded and as a string
const artifactContent = await inflateAsync(Buffer.from(artifact.body, 'base64'));
const createdArtifact = await this.fleetArtifacts.createArtifact({
content: artifactContent.toString(),
content: Buffer.from(artifact.body, 'base64').toString(),
identifier: artifact.identifier,
type: this.parseArtifactId(artifact.identifier).type,
});

View file

@ -140,7 +140,7 @@ export const getManifestManagerMock = (
case ManifestManagerMockType.InitialSystemState:
return null;
case ManifestManagerMockType.NormalFlow:
return getMockManifest({ compress: true });
return getMockManifest();
}
});

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { inflateSync } from 'zlib';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import {
ENDPOINT_LIST_ID,
@ -27,7 +26,6 @@ import {
import {
ManifestConstants,
getArtifactId,
isCompressed,
translateToEndpointExceptions,
Manifest,
} from '../../../lib/artifacts';
@ -40,10 +38,8 @@ import {
import { ManifestManager } from './manifest_manager';
import { EndpointArtifactClientInterface } from '../artifact_client';
const uncompressData = async (data: Buffer) => JSON.parse(await inflateSync(data).toString());
const uncompressArtifact = async (artifact: InternalArtifactSchema) =>
uncompressData(Buffer.from(artifact.body!, 'base64'));
const getArtifactObject = (artifact: InternalArtifactSchema) =>
JSON.parse(Buffer.from(artifact.body!, 'base64').toString());
describe('ManifestManager', () => {
const TEST_POLICY_ID_1 = 'c6d16e42-c32d-4dce-8a88-113cfe276ad1';
@ -77,7 +73,7 @@ describe('ManifestManager', () => {
let ARTIFACT_TRUSTED_APPS_WINDOWS: InternalArtifactCompleteSchema;
beforeAll(async () => {
ARTIFACTS = await getMockArtifacts({ compress: true });
ARTIFACTS = await getMockArtifacts();
ARTIFACTS_BY_ID = {
[ARTIFACT_ID_EXCEPTIONS_MACOS]: ARTIFACTS[0],
[ARTIFACT_ID_EXCEPTIONS_WINDOWS]: ARTIFACTS[1],
@ -270,10 +266,9 @@ describe('ManifestManager', () => {
expect(artifacts.length).toBe(9);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(artifacts.every(isCompressed)).toBe(true);
for (const artifact of artifacts) {
expect(await uncompressArtifact(artifact)).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifact)).toStrictEqual({ entries: [] });
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual(
new Set([TEST_POLICY_ID_1])
@ -308,21 +303,20 @@ describe('ManifestManager', () => {
expect(artifacts.length).toBe(9);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(artifacts.every(isCompressed)).toBe(true);
expect(await uncompressArtifact(artifacts[0])).toStrictEqual({
expect(getArtifactObject(artifacts[0])).toStrictEqual({
entries: translateToEndpointExceptions([exceptionListItem], 'v1'),
});
expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[5])).toStrictEqual({
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(await uncompressArtifact(artifacts[6])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({ entries: [] });
for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
@ -364,19 +358,18 @@ describe('ManifestManager', () => {
expect(artifacts.length).toBe(9);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(artifacts.every(isCompressed)).toBe(true);
expect(artifacts[0]).toStrictEqual(oldManifest.getAllArtifacts()[0]);
expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[5])).toStrictEqual({
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(await uncompressArtifact(artifacts[6])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[6])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({ entries: [] });
for (const artifact of artifacts) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
@ -426,27 +419,26 @@ describe('ManifestManager', () => {
expect(artifacts.length).toBe(10);
expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES);
expect(artifacts.every(isCompressed)).toBe(true);
expect(await uncompressArtifact(artifacts[0])).toStrictEqual({
expect(getArtifactObject(artifacts[0])).toStrictEqual({
entries: translateToEndpointExceptions([exceptionListItem], 'v1'),
});
expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[5])).toStrictEqual({
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(await uncompressArtifact(artifacts[6])).toStrictEqual({
expect(getArtifactObject(artifacts[6])).toStrictEqual({
entries: translateToEndpointExceptions(
[trustedAppListItem, trustedAppListItemPolicy2],
'v1'
),
});
expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] });
expect(await uncompressArtifact(artifacts[9])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[7])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[8])).toStrictEqual({ entries: [] });
expect(getArtifactObject(artifacts[9])).toStrictEqual({ entries: [] });
for (const artifact of artifacts.slice(0, 5)) {
expect(manifest.isDefaultArtifact(artifact)).toBe(true);
@ -520,9 +512,13 @@ describe('ManifestManager', () => {
const context = buildManifestManagerContextMock({});
const artifactClient = context.artifactClient as jest.Mocked<EndpointArtifactClientInterface>;
const manifestManager = new ManifestManager(context);
const newManifest = ManifestManager.createDefaultManifest();
await expect(
manifestManager.pushArtifacts([ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_EXCEPTIONS_WINDOWS])
manifestManager.pushArtifacts(
[ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_EXCEPTIONS_WINDOWS],
newManifest
)
).resolves.toStrictEqual([]);
expect(artifactClient.createArtifact).toHaveBeenCalledTimes(2);
@ -533,17 +529,18 @@ describe('ManifestManager', () => {
...ARTIFACT_EXCEPTIONS_WINDOWS,
});
expect(
await uncompressData(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!)
).toStrictEqual(await uncompressArtifact(ARTIFACT_EXCEPTIONS_MACOS));
JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!.toString())
).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_MACOS));
expect(
await uncompressData(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))!)
).toStrictEqual(await uncompressArtifact(ARTIFACT_EXCEPTIONS_WINDOWS));
JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))!.toString())
).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_WINDOWS));
});
test('Returns errors for partial failures', async () => {
const context = buildManifestManagerContextMock({});
const artifactClient = context.artifactClient as jest.Mocked<EndpointArtifactClientInterface>;
const manifestManager = new ManifestManager(context);
const newManifest = ManifestManager.createDefaultManifest();
const error = new Error();
const { body, ...incompleteArtifact } = ARTIFACT_TRUSTED_APPS_MACOS;
@ -558,11 +555,14 @@ describe('ManifestManager', () => {
);
await expect(
manifestManager.pushArtifacts([
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_EXCEPTIONS_WINDOWS,
incompleteArtifact as InternalArtifactCompleteSchema,
])
manifestManager.pushArtifacts(
[
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_EXCEPTIONS_WINDOWS,
incompleteArtifact as InternalArtifactCompleteSchema,
],
newManifest
)
).resolves.toStrictEqual([
error,
new Error(`Incomplete artifact: ${ARTIFACT_ID_TRUSTED_APPS_MACOS}`),
@ -573,8 +573,8 @@ describe('ManifestManager', () => {
...ARTIFACT_EXCEPTIONS_MACOS,
});
expect(
await uncompressData(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!)
).toStrictEqual(await uncompressArtifact(ARTIFACT_EXCEPTIONS_MACOS));
JSON.parse(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_MACOS))!.toString())
).toStrictEqual(getArtifactObject(ARTIFACT_EXCEPTIONS_MACOS));
expect(context.cache.get(getArtifactId(ARTIFACT_EXCEPTIONS_WINDOWS))).toBeUndefined();
});
});
@ -843,10 +843,7 @@ describe('ManifestManager', () => {
artifacts: toArtifactRecords({
[ARTIFACT_NAME_EXCEPTIONS_MACOS]: await getEmptyInternalArtifactMock(
'macos',
'v1',
{
compress: true,
}
'v1'
),
}),
manifest_version: '1.0.0',

View file

@ -25,9 +25,7 @@ import {
getEndpointEventFiltersList,
getEndpointExceptionList,
getEndpointTrustedAppsList,
isCompressed,
Manifest,
maybeCompressArtifact,
} from '../../../lib/artifacts';
import {
InternalArtifactCompleteSchema,
@ -239,13 +237,16 @@ export class ManifestManager {
* Writes new artifact SO.
*
* @param artifact An InternalArtifactCompleteSchema representing the artifact.
* @returns {Promise<Error | null>} An error, if encountered, or null.
* @returns {Promise<[Error | null, InternalArtifactCompleteSchema | undefined]>} An array with the error if encountered or null and the generated artifact or null.
*/
protected async pushArtifact(artifact: InternalArtifactCompleteSchema): Promise<Error | null> {
protected async pushArtifact(
artifact: InternalArtifactCompleteSchema
): Promise<[Error | null, InternalArtifactCompleteSchema | undefined]> {
const artifactId = getArtifactId(artifact);
let fleetArtifact;
try {
// Write the artifact SO
await this.artifactClient.createArtifact(artifact);
fleetArtifact = await this.artifactClient.createArtifact(artifact);
// Cache the compressed body of the artifact
this.cache.set(artifactId, Buffer.from(artifact.body, 'base64'));
@ -253,26 +254,32 @@ export class ManifestManager {
if (this.savedObjectsClient.errors.isConflictError(err)) {
this.logger.debug(`Tried to create artifact ${artifactId}, but it already exists.`);
} else {
return err;
return [err, undefined];
}
}
return null;
return [null, fleetArtifact];
}
/**
* Writes new artifact SOs.
*
* @param artifacts An InternalArtifactCompleteSchema array representing the artifacts.
* @param newManifest A Manifest representing the new manifest
* @returns {Promise<Error[]>} Any errors encountered.
*/
public async pushArtifacts(artifacts: InternalArtifactCompleteSchema[]): Promise<Error[]> {
public async pushArtifacts(
artifacts: InternalArtifactCompleteSchema[],
newManifest: Manifest
): Promise<Error[]> {
const errors: Error[] = [];
for (const artifact of artifacts) {
if (internalArtifactCompleteSchema.is(artifact)) {
const err = await this.pushArtifact(artifact);
const [err, fleetArtifact] = await this.pushArtifact(artifact);
if (err) {
errors.push(err);
} else if (fleetArtifact) {
newManifest.replaceArtifact(fleetArtifact);
}
} else {
errors.push(new Error(`Incomplete artifact: ${getArtifactId(artifact)}`));
@ -372,16 +379,10 @@ export class ManifestManager {
for (const result of results) {
await iterateArtifactsBuildResult(result, async (artifact, policyId) => {
let artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact;
if (!isCompressed(artifactToAdd)) {
artifactToAdd = await maybeCompressArtifact(artifactToAdd);
if (!isCompressed(artifactToAdd)) {
throw new Error(`Unable to compress artifact: ${getArtifactId(artifactToAdd)}`);
} else if (!internalArtifactCompleteSchema.is(artifactToAdd)) {
throw new Error(`Incomplete artifact detected: ${getArtifactId(artifactToAdd)}`);
}
const artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact;
artifactToAdd.compressionAlgorithm = 'none';
if (!internalArtifactCompleteSchema.is(artifactToAdd)) {
throw new Error(`Incomplete artifact detected: ${getArtifactId(artifactToAdd)}`);
}
manifest.addEntry(artifactToAdd, policyId);

View file

@ -100,7 +100,7 @@ describe('ingest_integration tests ', () => {
let ARTIFACT_TRUSTED_APPS_WINDOWS: InternalArtifactCompleteSchema;
beforeAll(async () => {
const artifacts = await getMockArtifacts({ compress: true });
const artifacts = await getMockArtifacts();
ARTIFACT_EXCEPTIONS_MACOS = artifacts[0];
ARTIFACT_EXCEPTIONS_WINDOWS = artifacts[1];
ARTIFACT_TRUSTED_APPS_MACOS = artifacts[3];
@ -147,7 +147,10 @@ describe('ingest_integration tests ', () => {
);
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith();
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([ARTIFACT_EXCEPTIONS_MACOS]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS],
newManifest
);
expect(manifestManager.commit).not.toHaveBeenCalled();
});
@ -170,7 +173,10 @@ describe('ingest_integration tests ', () => {
);
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith();
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([ARTIFACT_EXCEPTIONS_MACOS]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS],
newManifest
);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
});
@ -197,10 +203,10 @@ describe('ingest_integration tests ', () => {
);
expect(manifestManager.buildNewManifest).toHaveBeenCalledWith();
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith([
ARTIFACT_EXCEPTIONS_MACOS,
ARTIFACT_TRUSTED_APPS_MACOS,
]);
expect(manifestManager.pushArtifacts).toHaveBeenCalledWith(
[ARTIFACT_EXCEPTIONS_MACOS, ARTIFACT_TRUSTED_APPS_MACOS],
newManifest
);
expect(manifestManager.commit).toHaveBeenCalledWith(newManifest);
});

View file

@ -25,7 +25,8 @@ const getManifest = async (logger: Logger, manifestManager: ManifestManager): Pr
// Persist new artifacts
const persistErrors = await manifestManager.pushArtifacts(
newManifest.getAllArtifacts() as InternalArtifactCompleteSchema[]
newManifest.getAllArtifacts() as InternalArtifactCompleteSchema[],
newManifest
);
if (persistErrors.length) {
reportErrors(logger, persistErrors);