Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,29 @@ export class TrustedAppGenerator extends BaseDataGenerator<TrustedApp> {
...(scopeType === 'policy' ? { policies: this.randomArray(5, () => this.randomUUID()) } : {}),
}) as EffectScope;

const os = this.randomOSFamily();
const pathEntry = this.randomChoice([
{
field: ConditionEntryField.PATH,
operator: 'included',
type: 'match',
value: os !== 'windows' ? '/one/two/three' : 'c:\\fol\\bin.exe',
},
{
field: ConditionEntryField.PATH,
operator: 'included',
type: 'wildcard',
value: os !== 'windows' ? '/one/t*/*re/three.app' : 'c:\\fol*\\*ub*\\bin.exe',
},
]);

// TS types are conditional when it comes to the combination of OS and ENTRIES
// @ts-expect-error TS2322
return merge(
{
description: `Generator says we trust ${name}`,
name,
os: this.randomOSFamily(),
os,
effectScope,
entries: [
{
Expand All @@ -82,12 +98,7 @@ export class TrustedAppGenerator extends BaseDataGenerator<TrustedApp> {
type: 'match',
value: '1234234659af249ddf3e40864e9fb241',
},
{
field: ConditionEntryField.PATH,
operator: 'included',
type: 'match',
value: '/one/two/three',
},
pathEntry,
],
},
overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { isPathValid } from './validations';
import { isPathValid, hasSimpleExecutableName } from './validations';
import { OperatingSystem, ConditionEntryField } from '../../types';

describe('Unacceptable Windows wildcard paths', () => {
Expand Down Expand Up @@ -504,3 +504,58 @@ describe('Unacceptable Mac/Linux exact paths', () => {
).toEqual(false);
});
});

describe('Executable filenames with wildcard PATHS', () => {
it('should return TRUE when MAC/LINUX wildcard paths have an executable name', () => {
expect(
hasSimpleExecutableName({
os: OperatingSystem.LINUX,
type: 'wildcard',
value: '/opt/*/app',
})
).toEqual(true);
expect(
hasSimpleExecutableName({
os: OperatingSystem.MAC,
type: 'wildcard',
value: '/op*/**/app.dmg',
})
).toEqual(true);
});

it('should return TRUE when WINDOWS wildcards paths have a executable name', () => {
expect(
hasSimpleExecutableName({
os: OperatingSystem.WINDOWS,
type: 'wildcard',
value: 'c:\\**\\path.exe',
})
).toEqual(true);
});

it('should return FALSE when MAC/LINUX wildcard paths have a wildcard in executable name', () => {
expect(
hasSimpleExecutableName({
os: OperatingSystem.LINUX,
type: 'wildcard',
value: '/op/*/*pp',
})
).toEqual(false);
expect(
hasSimpleExecutableName({
os: OperatingSystem.MAC,
type: 'wildcard',
value: '/op*/b**/ap.m**',
})
).toEqual(false);
});
it('should return FALSE when WINDOWS wildcards paths have a wildcard in executable name', () => {
expect(
hasSimpleExecutableName({
os: OperatingSystem.WINDOWS,
type: 'wildcard',
value: 'c:\\**\\pa*h.exe',
})
).toEqual(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,36 @@ export const getDuplicateFields = (entries: ConditionEntry[]) => {
.map((entry) => entry[0]);
};

const WIN_EXEC_PATH = /\\(\w+\.\w+)$/i;
const UNIX_EXEC_PATH = /\/\w+\.*\w*$/i;

export const getExecutableName = ({
os,
value,
}: {
os: OperatingSystem;
value: string;
}): string => {
const execName =
os === OperatingSystem.WINDOWS ? value.match(WIN_EXEC_PATH) : value.match(UNIX_EXEC_PATH);
return execName ? execName[0].replaceAll(/\/|\\/gi, '') : '';
};

export const hasSimpleExecutableName = ({
os,
type,
value,
}: {
os: OperatingSystem;
type: TrustedAppEntryTypes;
value: string;
}): boolean => {
if (type === 'wildcard') {
return os === OperatingSystem.WINDOWS ? WIN_EXEC_PATH.test(value) : UNIX_EXEC_PATH.test(value);
}
return true;
};

export const isPathValid = ({
os,
field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { ConditionEntryField, OperatingSystem, TrustedAppEntryTypes } from '../e

export const getPlaceholderText = () => ({
windows: {
wildcard: 'C:\\sample\\**\\*',
wildcard: 'C:\\sample\\**\\path.exe',
exact: 'C:\\sample\\path.exe',
},
others: {
wildcard: '/opt/**/*',
wildcard: '/opt/**/app',
exact: '/opt/bin',
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import {
isValidHash,
isPathValid,
hasSimpleExecutableName,
} from '../../../../../../common/endpoint/service/trusted_apps/validations';

import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
Expand Down Expand Up @@ -136,6 +137,13 @@ const validateFormValues = (values: MaybeImmutable<NewTrustedApp>): ValidationRe
);
} else {
values.entries.forEach((entry, index) => {
const isValidPathEntry = isPathValid({
os: values.os,
field: entry.field,
type: entry.type,
value: entry.value,
});

if (!entry.field || !entry.value.trim()) {
isValid = false;
addResultToValidation(
Expand All @@ -161,9 +169,7 @@ const validateFormValues = (values: MaybeImmutable<NewTrustedApp>): ValidationRe
values: { row: index + 1 },
})
);
} else if (
!isPathValid({ os: values.os, field: entry.field, type: entry.type, value: entry.value })
) {
} else if (!isValidPathEntry) {
addResultToValidation(
validation,
'entries',
Expand All @@ -173,6 +179,22 @@ const validateFormValues = (values: MaybeImmutable<NewTrustedApp>): ValidationRe
values: { row: index + 1 },
})
);
} else if (
isValidPathEntry &&
!hasSimpleExecutableName({ os: values.os, value: entry.value, type: entry.type })
) {
addResultToValidation(
validation,
'entries',
'warnings',
i18n.translate(
'xpack.securitySolution.trustedapps.create.conditionFieldDegradedPerformanceMsg',
{
defaultMessage: `[{row}] A wildcard in the filename will affect endpoint's performance`,
values: { row: index + 1 },
}
)
);
}
});
}
Expand Down
119 changes: 101 additions & 18 deletions x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ import {
ENDPOINT_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
import { OperatingSystem } from '../../../../common/endpoint/types';
import { ExceptionListClient } from '../../../../../lists/server';
import {
InternalArtifactCompleteSchema,
TranslatedEntry,
TranslatedPerformantEntries,
translatedPerformantEntries as translatedPerformantEntriesType,
translatedEntry as translatedEntryType,
translatedEntryMatchAnyMatcher,
TranslatedEntryMatcher,
translatedEntryMatchMatcher,
TranslatedEntryMatchWildcard,
TranslatedEntryMatchWildcardMatcher,
translatedEntryMatchWildcardMatcher,
TranslatedEntryNestedEntry,
Expand All @@ -35,6 +39,10 @@ import {
WrappedTranslatedExceptionList,
wrappedTranslatedExceptionList,
} from '../../schemas';
import {
hasSimpleExecutableName,
getExecutableName,
} from '../../../../common/endpoint/service/trusted_apps/validations';

export async function buildArtifact(
exceptions: WrappedTranslatedExceptionList,
Expand Down Expand Up @@ -217,31 +225,84 @@ function translateItem(
item: ExceptionListItemSchema
): TranslatedExceptionListItem {
const itemSet = new Set();
return {
type: item.type,
entries: item.entries.reduce<TranslatedEntry[]>((translatedEntries, entry) => {
const translatedEntry = translateEntry(schemaVersion, entry);
if (translatedEntry !== undefined && translatedEntryType.is(translatedEntry)) {
const itemHash = createHash('sha256').update(JSON.stringify(translatedEntry)).digest('hex');
if (!itemSet.has(itemHash)) {
translatedEntries.push(translatedEntry);
itemSet.add(itemHash);
const getEntries = (): TranslatedExceptionListItem['entries'] => {
return item.entries.reduce<TranslatedEntry[]>((translatedEntries, entry) => {
const translatedEntry = translateEntry(schemaVersion, entry, item.os_types[0]);

if (translatedEntry !== undefined) {
if (translatedEntryType.is(translatedEntry)) {
const itemHash = createHash('sha256')
.update(JSON.stringify(translatedEntry))
.digest('hex');
if (!itemSet.has(itemHash)) {
translatedEntries.push(translatedEntry);
itemSet.add(itemHash);
}
}
if (translatedPerformantEntriesType.is(translatedEntry)) {
translatedEntry.forEach((tpe) => {
const itemHash = createHash('sha256').update(JSON.stringify(tpe)).digest('hex');
if (!itemSet.has(itemHash)) {
translatedEntries.push(tpe);
itemSet.add(itemHash);
}
});
}
}

return translatedEntries;
}, []),
}, []);
};

return {
type: item.type,
entries: getEntries(),
};
}

function appendProcessNameEntry({
wildcardProcessEntry,
entry,
os,
}: {
wildcardProcessEntry: TranslatedEntryMatchWildcard;
entry: {
field: string;
operator: 'excluded' | 'included';
type: 'wildcard';
value: string;
};
os: ExceptionListItemSchema['os_types'][0];
}): TranslatedPerformantEntries {
const entries: TranslatedPerformantEntries = [
wildcardProcessEntry,
{
field: normalizeFieldName('process.name'),
operator: entry.operator,
type: (os === 'linux' ? 'exact_cased' : 'exact_caseless') as Extract<
TranslatedEntryMatcher,
'exact_caseless' | 'exact_cased'
>,
value: getExecutableName({ os: os as OperatingSystem, value: entry.value }),
},
].reduce<TranslatedPerformantEntries>((p, c) => {
p.push(c);
return p;
}, []);

return entries;
}

function translateEntry(
schemaVersion: string,
entry: Entry | EntryNested
): TranslatedEntry | undefined {
entry: Entry | EntryNested,
os: ExceptionListItemSchema['os_types'][0]
): TranslatedEntry | TranslatedPerformantEntries | undefined {
switch (entry.type) {
case 'nested': {
const nestedEntries = entry.entries.reduce<TranslatedEntryNestedEntry[]>(
(entries, nestedEntry) => {
const translatedEntry = translateEntry(schemaVersion, nestedEntry);
const translatedEntry = translateEntry(schemaVersion, nestedEntry, os);
if (nestedEntry !== undefined && translatedEntryNestedEntry.is(translatedEntry)) {
entries.push(translatedEntry);
}
Expand Down Expand Up @@ -278,15 +339,37 @@ function translateEntry(
: undefined;
}
case 'wildcard': {
const matcher = getMatcherWildcardFunction(entry.field);
return translatedEntryMatchWildcardMatcher.is(matcher)
? {
const wildcardMatcher = getMatcherWildcardFunction(entry.field);
const translatedEntryWildcardMatcher =
translatedEntryMatchWildcardMatcher.is(wildcardMatcher);

const buildEntries = () => {
if (translatedEntryWildcardMatcher) {
// default process.executable entry
const wildcardProcessEntry: TranslatedEntryMatchWildcard = {
field: normalizeFieldName(entry.field),
operator: entry.operator,
type: matcher,
type: wildcardMatcher,
value: entry.value,
};

const hasExecutableName = hasSimpleExecutableName({
os: os as OperatingSystem,
type: entry.type,
value: entry.value,
});
if (hasExecutableName) {
// when path has a full executable name
// append a process.name entry based on os
// `exact_cased` for linux and `exact_caseless` for others
return appendProcessNameEntry({ entry, os, wildcardProcessEntry });
} else {
return wildcardProcessEntry;
}
: undefined;
}
};

return buildEntries();
}
}
}
Loading