Skip to content

Commit f499daf

Browse files
authored
[Threat Intel 10/11] Remaining adapters, dispatcher, and fetch_source step (#287206)
1 parent 2e4d5e7 commit f499daf

20 files changed

Lines changed: 2149 additions & 17 deletions

File tree

x-pack/solutions/security/plugins/security_solution/common/threat_intel/workflows/step_types/fetch_source/fetch_source_common.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ export const fetchSourceInputSchema = z.object({
3636
source: z.union([z.string(), sourceHitSchema]),
3737
});
3838

39+
/** Must match the `extracted.iocs` nested mapping in setup/index_templates.ts. */
40+
export const iocEntrySchema = z.object({
41+
type: z.string(),
42+
value: z.string(),
43+
defanged: z.string().optional(),
44+
tier: z.string(),
45+
tier_heuristic: z.string(),
46+
tier_basis: z.string(),
47+
port: z.number().optional(),
48+
reference: z.string().optional(),
49+
block_index: z.number().optional(),
50+
});
51+
52+
export type IocEntry = z.infer<typeof iocEntrySchema>;
53+
3954
/** Must match `.kibana-threat-reports` strict mapping in setup/index_templates.ts. */
4055
export const normalizedReportSchema = z.object({
4156
'@timestamp': z.string(),
@@ -69,21 +84,7 @@ export const normalizedReportSchema = z.object({
6984
}),
7085
extracted: z
7186
.object({
72-
iocs: z
73-
.array(
74-
z.object({
75-
type: z.string(),
76-
value: z.string(),
77-
defanged: z.string().optional(),
78-
tier: z.string(),
79-
tier_heuristic: z.string(),
80-
tier_basis: z.string(),
81-
port: z.number().optional(),
82-
reference: z.string().optional(),
83-
block_index: z.number().optional(),
84-
})
85-
)
86-
.optional(),
87+
iocs: z.array(iocEntrySchema).optional(),
8788
categories: z.array(z.string()).optional(),
8889
vulnerability: z
8990
.object({
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
module.exports = {
9+
preset: '@kbn/test',
10+
rootDir: '../../../../../../..',
11+
roots: ['<rootDir>/x-pack/solutions/security/plugins/security_solution/public/threat_intel'],
12+
coverageDirectory:
13+
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/security/plugins/security_solution/public/threat_intel',
14+
coverageReporters: ['text', 'html'],
15+
collectCoverageFrom: [
16+
'<rootDir>/x-pack/solutions/security/plugins/security_solution/public/threat_intel/**/*.{ts,tsx}',
17+
],
18+
moduleNameMapper: require('../../server/__mocks__/module_name_map'),
19+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import type { PublicStepDefinition } from '@kbn/workflows-extensions/public';
10+
import { fetchSourceStepCommonDefinition } from '../../../../../common/threat_intel/workflows/step_types/fetch_source/fetch_source_common';
11+
12+
/** YAML editor schema for threat_intel.fetch_source (handler is server-side). */
13+
export const fetchSourceStepDefinition: PublicStepDefinition = {
14+
...fetchSourceStepCommonDefinition,
15+
icon: React.lazy(() =>
16+
import('@elastic/eui/es/components/icon/assets/download').then(({ icon }) => ({
17+
default: icon,
18+
}))
19+
),
20+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { fetchSourceStepDefinition } from './fetch_source_step';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { registerThreatIntelWorkflowSteps } from './register_workflow_steps';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { PublicStepDefinition } from '@kbn/workflows-extensions/public';
9+
import { workflowsExtensionsMock } from '@kbn/workflows-extensions/public/mocks';
10+
import { registerThreatIntelWorkflowSteps } from './register_workflow_steps';
11+
import { fetchSourceStepDefinition } from './fetch_source';
12+
import { FETCH_SOURCE_STEP_TYPE } from '../../../../common/threat_intel/workflows/step_types/fetch_source/fetch_source_common';
13+
14+
type StepLoader = () => Promise<PublicStepDefinition | undefined>;
15+
16+
describe('registerThreatIntelWorkflowSteps (public)', () => {
17+
it('registers exactly one step definition for threat_intel.fetch_source', () => {
18+
const workflowsExtensions = workflowsExtensionsMock.createSetup();
19+
20+
registerThreatIntelWorkflowSteps(workflowsExtensions);
21+
22+
expect(workflowsExtensions.registerStepDefinition).toHaveBeenCalledTimes(1);
23+
});
24+
25+
it('async loader resolves to the fetch_source step definition', async () => {
26+
const workflowsExtensions = workflowsExtensionsMock.createSetup();
27+
28+
registerThreatIntelWorkflowSteps(workflowsExtensions);
29+
30+
const [loader] = workflowsExtensions.registerStepDefinition.mock.calls.map(
31+
([arg]) => arg as StepLoader
32+
);
33+
34+
const definition = await loader();
35+
expect(definition).toBe(fetchSourceStepDefinition);
36+
// Sanity-check the id matches the YAML-side step type so the editor
37+
// can find the schema for `type: threat_intel.fetch_source`.
38+
expect(definition?.id).toBe(FETCH_SOURCE_STEP_TYPE);
39+
});
40+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { WorkflowsExtensionsPublicPluginSetup } from '@kbn/workflows-extensions/public';
9+
10+
/**
11+
* Register threat-intel-owned workflow step types with the public
12+
* `workflowsExtensions` setup contract so the YAML editor's strict-schema
13+
* validator (see
14+
* `workflows_management/public/features/validate_workflow_yaml/model/use_workflow_json_schema.ts`)
15+
* picks them up.
16+
*
17+
* Mirrors the server-side `registerThreatIntelWorkflowSteps` in
18+
* `server/threat_intel/workflows/step_types/index.ts`. Steps are
19+
* loaded lazily via async loaders to keep the heavy
20+
* `fetch_source_common` Zod schemas off the critical-path bundle until the
21+
* editor actually requests them.
22+
*
23+
* The caller is expected to invoke this only when the optional
24+
* `workflowsExtensions` plugin is present and the
25+
* `threatIntelSupplyEnabled` experimental feature is on, matching
26+
* the gating policy applied server-side. Without that gate, dark-flagged
27+
* deployments would advertise a step type whose handler is never
28+
* registered.
29+
*/
30+
export const registerThreatIntelWorkflowSteps = (
31+
workflowsExtensions: WorkflowsExtensionsPublicPluginSetup
32+
): void => {
33+
workflowsExtensions.registerStepDefinition(async () =>
34+
import('./fetch_source').then((m) => m.fetchSourceStepDefinition)
35+
);
36+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export { runAdapter, UnknownAdapterError } from './run_adapter';
9+
export type { AdapterRunContext, FetchAdapter, NormalizedReport, SourceHit } from './types';

0 commit comments

Comments
 (0)