mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Defend Workflows][Trusted Apps Advanced Mode][API] Updates the validator to use the advanced mode schema (#221980)
## Summary - [x] Modifies the trusted apps validator to use an advanced mode "free-for-all" schema identical to what is allowed in the event filters validator if there is `form_mode:advanced` present in the exception item's `tags` field. - [x] Hides the api behind the feature flag: `trustedAppsAdvancedMode` - [x] Adds api functional tests --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
12e71e8bab
commit
8f76a4f706
5 changed files with 224 additions and 10 deletions
|
@ -263,6 +263,11 @@ export const allowedExperimentalValues = Object.freeze({
|
||||||
* Automatically installs the security AI prompts package
|
* Automatically installs the security AI prompts package
|
||||||
*/
|
*/
|
||||||
securityAIPromptsEnabled: false,
|
securityAIPromptsEnabled: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables advanced mode for Trusted Apps creation and update
|
||||||
|
*/
|
||||||
|
trustedAppsAdvancedMode: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||||
|
|
|
@ -187,6 +187,26 @@ const TrustedAppDataSchema = schema.object(
|
||||||
{ unknowns: 'ignore' }
|
{ unknowns: 'ignore' }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema to validate Trusted Apps in Advanced mode
|
||||||
|
*/
|
||||||
|
const TrustedAppAdvancedModeDataSchema = schema.object(
|
||||||
|
{
|
||||||
|
entries: schema.arrayOf(
|
||||||
|
schema.object(
|
||||||
|
{
|
||||||
|
field: schema.string(),
|
||||||
|
},
|
||||||
|
{ unknowns: 'ignore' }
|
||||||
|
),
|
||||||
|
{ minSize: 1 }
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unknowns: 'ignore',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export class TrustedAppValidator extends BaseValidator {
|
export class TrustedAppValidator extends BaseValidator {
|
||||||
static isTrustedApp(item: { listId: string }): boolean {
|
static isTrustedApp(item: { listId: string }): boolean {
|
||||||
return item.listId === ENDPOINT_ARTIFACT_LISTS.trustedApps.id;
|
return item.listId === ENDPOINT_ARTIFACT_LISTS.trustedApps.id;
|
||||||
|
@ -270,7 +290,16 @@ export class TrustedAppValidator extends BaseValidator {
|
||||||
await this.validateBasicData(item);
|
await this.validateBasicData(item);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const isTAAdvancedModeFeatureFlagEnabled =
|
||||||
|
this.endpointAppContext.experimentalFeatures.trustedAppsAdvancedMode;
|
||||||
|
const isAdvancedMode = item.tags.includes('form_mode:advanced');
|
||||||
|
if (!isTAAdvancedModeFeatureFlagEnabled && isAdvancedMode) {
|
||||||
|
throw new Error('Trusted apps advanced mode feature is not enabled');
|
||||||
|
} else if (isTAAdvancedModeFeatureFlagEnabled && isAdvancedMode) {
|
||||||
|
TrustedAppAdvancedModeDataSchema.validate(item);
|
||||||
|
} else {
|
||||||
TrustedAppDataSchema.validate(item, { os: item.osTypes[0] });
|
TrustedAppDataSchema.validate(item, { os: item.osTypes[0] });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new EndpointArtifactExceptionValidationError(error.message);
|
throw new EndpointArtifactExceptionValidationError(error.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FtrConfigProviderContext } from '@kbn/test';
|
import { FtrConfigProviderContext } from '@kbn/test';
|
||||||
|
import type { ExperimentalFeatures as SecuritySolutionExperimentalFeatures } from '@kbn/security-solution-plugin/common';
|
||||||
|
|
||||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
const functionalConfig = await readConfigFile(
|
const functionalConfig = await readConfigFile(
|
||||||
require.resolve('../../../../../config/ess/config.base.edr_workflows.trial')
|
require.resolve('../../../../../config/ess/config.base.edr_workflows.trial')
|
||||||
);
|
);
|
||||||
|
const securitySolutionEnableExperimental: Array<keyof SecuritySolutionExperimentalFeatures> = [
|
||||||
|
'trustedAppsAdvancedMode',
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...functionalConfig.getAll(),
|
...functionalConfig.getAll(),
|
||||||
|
@ -18,5 +22,19 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
junit: {
|
junit: {
|
||||||
reportName: 'EDR Workflows - Artifacts Integration Tests - ESS Env - Trial License',
|
reportName: 'EDR Workflows - Artifacts Integration Tests - ESS Env - Trial License',
|
||||||
},
|
},
|
||||||
|
kbnTestServer: {
|
||||||
|
...functionalConfig.get('kbnTestServer'),
|
||||||
|
serverArgs: [
|
||||||
|
...functionalConfig.get('kbnTestServer.serverArgs').filter(
|
||||||
|
// Exclude Security solution experimental features
|
||||||
|
// properties since we are overriding them here
|
||||||
|
(arg: string) => !arg.includes('xpack.securitySolution.enableExperimental')
|
||||||
|
),
|
||||||
|
// SECURITY SOLUTION: set any experimental feature flags for testing
|
||||||
|
`--xpack.securitySolution.enableExperimental=${JSON.stringify(
|
||||||
|
securitySolutionEnableExperimental
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FtrConfigProviderContext } from '@kbn/test';
|
import { FtrConfigProviderContext } from '@kbn/test';
|
||||||
|
import type { ExperimentalFeatures as SecuritySolutionExperimentalFeatures } from '@kbn/security-solution-plugin/common';
|
||||||
|
|
||||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
const functionalConfig = await readConfigFile(
|
const functionalConfig = await readConfigFile(
|
||||||
require.resolve('../../../../../config/serverless/config.base.edr_workflows')
|
require.resolve('../../../../../config/serverless/config.base.edr_workflows')
|
||||||
);
|
);
|
||||||
|
const securitySolutionEnableExperimental: Array<keyof SecuritySolutionExperimentalFeatures> = [
|
||||||
|
'trustedAppsAdvancedMode',
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...functionalConfig.getAll(),
|
...functionalConfig.getAll(),
|
||||||
|
@ -18,5 +22,19 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
junit: {
|
junit: {
|
||||||
reportName: 'EDR Workflows - Artifacts Integration Tests - Serverless Env - Complete',
|
reportName: 'EDR Workflows - Artifacts Integration Tests - Serverless Env - Complete',
|
||||||
},
|
},
|
||||||
|
kbnTestServer: {
|
||||||
|
...functionalConfig.get('kbnTestServer'),
|
||||||
|
serverArgs: [
|
||||||
|
...functionalConfig.get('kbnTestServer.serverArgs').filter(
|
||||||
|
// Exclude Security solution experimental features
|
||||||
|
// properties since we are overriding them here
|
||||||
|
(arg: string) => !arg.includes('xpack.securitySolution.enableExperimental')
|
||||||
|
),
|
||||||
|
// SECURITY SOLUTION: set any experimental feature flags for testing
|
||||||
|
`--xpack.securitySolution.enableExperimental=${JSON.stringify(
|
||||||
|
securitySolutionEnableExperimental
|
||||||
|
)}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,28 +241,35 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not error if signer is set for a windows os entry item', async () => {
|
it('should not error if signer is set for a windows os entry item', async () => {
|
||||||
const body = trustedAppApiCalls[0].getBody();
|
const body = trustedAppApiCall.getBody();
|
||||||
|
|
||||||
|
// Match request version with artifact version
|
||||||
|
if ('_version' in body) {
|
||||||
|
body._version = trustedAppData.artifact._version;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
|
||||||
body.os_types = ['windows'];
|
body.os_types = ['windows'];
|
||||||
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry();
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry();
|
||||||
|
|
||||||
await endpointPolicyManagerSupertest[trustedAppApiCalls[0].method](
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
trustedAppApiCalls[0].path
|
|
||||||
)
|
|
||||||
.set('kbn-xsrf', 'true')
|
.set('kbn-xsrf', 'true')
|
||||||
.send(body)
|
.send(body)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not error if signer is set for a mac os entry item', async () => {
|
it('should not error if signer is set for a mac os entry item', async () => {
|
||||||
const body = trustedAppApiCalls[0].getBody();
|
const body = trustedAppApiCall.getBody();
|
||||||
|
|
||||||
|
// Match request version with artifact version
|
||||||
|
if ('_version' in body) {
|
||||||
|
body._version = trustedAppData.artifact._version;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
|
||||||
body.os_types = ['macos'];
|
body.os_types = ['macos'];
|
||||||
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry('mac');
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry('mac');
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
await endpointPolicyManagerSupertest[trustedAppApiCalls[0].method](
|
|
||||||
trustedAppApiCalls[0].path
|
|
||||||
)
|
|
||||||
.set('kbn-xsrf', 'true')
|
.set('kbn-xsrf', 'true')
|
||||||
.send(body)
|
.send(body)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
@ -294,6 +301,143 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
.expect(anEndpointArtifactError)
|
.expect(anEndpointArtifactError)
|
||||||
.expect(anErrorMessageWith(/invalid policy ids/));
|
.expect(anErrorMessageWith(/invalid policy ids/));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when in advanced form mode', () => {
|
||||||
|
const getAdvancedModeBody = () => {
|
||||||
|
const body = trustedAppApiCall.getBody();
|
||||||
|
body.tags.push('form_mode:advanced');
|
||||||
|
|
||||||
|
// Match request version with artifact version
|
||||||
|
if ('_version' in body) {
|
||||||
|
body._version = trustedAppData.artifact._version;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method}] if invalid condition entry fields are used`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
body.entries[0].field = 'some.invalid.field';
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method}] if a condition entry field is used more than once`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
body.entries.push({ ...body.entries[0] });
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method}] if an invalid hash is used`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.entries = [
|
||||||
|
{
|
||||||
|
field: 'process.hash.md5',
|
||||||
|
operator: 'included',
|
||||||
|
type: 'match',
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method}] if signer is set for a non windows os entry item`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['linux'];
|
||||||
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry();
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method} if Mac signer field is used for Windows entry`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['windows'];
|
||||||
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry('mac');
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should NOT error on [${trustedAppApiCall.method} if Windows signer field is used for Mac entry`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['macos'];
|
||||||
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry();
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error if signer is set for a windows os entry item', async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['windows'];
|
||||||
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry();
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error if signer is set for a mac os entry item', async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['macos'];
|
||||||
|
body.entries = exceptionsGenerator.generateTrustedAppSignerEntry('mac');
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should error on [${trustedAppApiCall.method}] if more than one OS is set`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.os_types = ['linux', 'windows'];
|
||||||
|
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(400)
|
||||||
|
.expect(anEndpointArtifactError)
|
||||||
|
.expect(anErrorMessageWith(/\[osTypes\]: array size is \[2\]/));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should error on [${trustedAppApiCall.method}] if policy id is invalid`, async () => {
|
||||||
|
const body = getAdvancedModeBody();
|
||||||
|
|
||||||
|
body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`];
|
||||||
|
|
||||||
|
// Using superuser here as we need custom license for this action
|
||||||
|
await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.send(body)
|
||||||
|
.expect(400)
|
||||||
|
.expect(anEndpointArtifactError)
|
||||||
|
.expect(anErrorMessageWith(/invalid policy ids/));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for (const trustedAppApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) {
|
for (const trustedAppApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) {
|
||||||
it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => {
|
it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue