mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[EDR Workflows][Osquery] Osquery space awareness (#221606)
## Summary This PR introduces Fleet’s agent space awareness to the Osquery plugin. The primary goal is to ensure that agents, policies, packs, and queries remain isolated to their respective Kibana spaces. Specifically: 1. Agents enrolled in policy A (belonging to Space A) are not accessible from Space B—live queries cannot be performed on them from other spaces. 2. Packs created in Space A are not visible in Space B. 3. Saved queries created in Space A are not visible in Space B. 4. Agent policies, agents, and space-exclusive operating systems from Space A are not visible from Space B. 5. Live query history is only visible in a space if it includes agents from that space. ## Implementation Details - All usages of the Saved Object Client have been updated to use the space-aware SO client, ensuring queries only return results from the current space. - When creating live queries, we now add a `space_id` field to `.logs-osquery_manager.actions-default` documents. This field stores the integration policy (osquery_manager) IDs of the agents involved in each query. - When reading live queries, searches are filtered to include only documents with `policy_ids` that are present in the current space. This ensures strict separation of Osquery data and actions across different spaces. ## Feature Flag Considerations At this stage, we believe no feature flag or gating is necessary. In all cases, we default to the `default` space ID. We have tested these changes with the feature flag turned off and observed the following: | Scenario | Result | |------------------------------------------------------|:-----------:| | Agents and policies are visible in the Live query UI | ✅ | | Users can query different agents/policies | ✅ | | Live queries are saved in the live query history | ✅ | | Users can create both packs and saved queries | ✅ | | Users can run packs and saved queries | ✅ | | Users can enable packs for interval execution | ✅ | Based on these results, we conclude that this PR can be safely merged without enabling the feature flag, as it does not impact current operations. **The index mappings for `.logs-osquery_manager.actions-default` will be updated to introduce the `policy_ids` field. This field will also be populated when actions are created. This change is fully backward compatible and does not introduce any breaking changes.** ### Enable feature flags ```yaml xpack.securitySolution.enableExperimental: - endpointManagementSpaceAwarenessEnabled xpack.fleet.enableExperimental: - useSpaceAwareness ``` ### Switch Fleet to Space aware ```http request POST /internal/fleet/enable_space_awareness Elastic-Api-Version: 1, ``` ## Manual Test Cases for Space Awareness (Feature Enabled) ### Setup - **Space A:** Policies `PolicyA1`, `PolicyA2`, `PolicyAB` (each with an enrolled agent) - **Space B:** Policies `PolicyB1`, `PolicyB2`, `PolicyAB` (each with an enrolled agent) - `PolicyAB` is present in both spaces --- ### 1. Agent Visibility | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, view the list of agents and policies | Only agents for `PolicyA1`, `PolicyA2`, `PolicyAB` are visible | ✅ | | In Space B, view the list of agents and policies | Only agents for `PolicyB1`, `PolicyB2`, `PolicyAB` are visible | ✅ | | `PolicyAB`'s agents are visible in both spaces | Agents enrolled in `PolicyAB` are visible in both spaces | ✅ | --- ### 2. Live Query Permissions | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, attempt a live query on an agent from `PolicyB1` or `PolicyB2`| Agent is not available for selection/query | ✅ | | In Space B, attempt a live query on an agent from `PolicyA1` or `PolicyA2`| Agent is not available for selection/query | ✅ | | In either space, perform a live query on an agent from `PolicyAB` | Query is allowed and succeeds | ✅ | | In Space A, attempt a live query on an agent from `PolicyA1` or `PolicyA2`| Query is allowed and succeeds | ✅ | | In Space B, attempt a live query on an agent from `PolicyB1` or `PolicyB2`| Query is allowed and succeeds | ✅ | --- ### 3. Packs and Saved Queries | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, create a pack and a saved query | Only visible in Space A | ✅ | | In Space B, create a pack and a saved query | Only visible in Space B | ✅ | | In Space A, check for packs/queries created in Space B | Not visible | ✅ | | In Space B, check for packs/queries created in Space A | Not visible | ✅ | | In Space A, live query a pack that has Agent A and Agent AB. | In space B user doesnt see results from Agent A | ✅ | --- ### 4. Live Query History | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, view live query history | Only queries involving agents from Space A (including `PolicyAB`) are visible | ✅ | | In Space B, view live query history | Only queries involving agents from Space B (including `PolicyAB`) are visible | ✅ | --- ### 5. Pack Execution | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, run a pack on agents from `PolicyA1`, `PolicyA2`, `PolicyAB` | Execution succeeds | ✅ | | In Space B, run a pack on agents from `PolicyB1`, `PolicyB2`, `PolicyAB` | Execution succeeds | ✅ | | Attempt to run a pack on an agent from another space | Not possible; agent not available for selection | ✅ | --- ### 6. Interval Execution of Packs | Test Step | Expected Result | Pass | |---------------------------------------------------------------------------|------------------------------------------------------|----------| | In Space A, enable interval execution for a pack | Only agents/policies in Space A are affected | ✅ | | In Space B, enable interval execution for a pack | Only agents/policies in Space B are affected | ✅ | closes https://github.com/elastic/kibana/pull/222935 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
3b6a0dcd1e
commit
b33c211b5b
54 changed files with 1790 additions and 255 deletions
|
@ -539,6 +539,8 @@ export const updateAgentPolicyHandler: FleetRequestHandler<
|
||||||
logger.debug(`updating policy [${request.params.agentPolicyId}] in space [${spaceId}]`);
|
logger.debug(`updating policy [${request.params.agentPolicyId}] in space [${spaceId}]`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const requestSpaceId = spaceId;
|
||||||
|
|
||||||
if (spaceIds?.length) {
|
if (spaceIds?.length) {
|
||||||
const authorizedSpaces = await checkAgentPoliciesAllPrivilegesForSpaces(
|
const authorizedSpaces = await checkAgentPoliciesAllPrivilegesForSpaces(
|
||||||
request,
|
request,
|
||||||
|
@ -564,7 +566,7 @@ export const updateAgentPolicyHandler: FleetRequestHandler<
|
||||||
esClient,
|
esClient,
|
||||||
request.params.agentPolicyId,
|
request.params.agentPolicyId,
|
||||||
data,
|
data,
|
||||||
{ force, bumpRevision, user, spaceId }
|
{ force, bumpRevision, user, spaceId, requestSpaceId }
|
||||||
);
|
);
|
||||||
|
|
||||||
let item: any = agentPolicy;
|
let item: any = agentPolicy;
|
||||||
|
|
|
@ -358,7 +358,8 @@ class AgentPolicyService {
|
||||||
|
|
||||||
public async runExternalCallbacks(
|
public async runExternalCallbacks(
|
||||||
externalCallbackType: ExternalCallback[0],
|
externalCallbackType: ExternalCallback[0],
|
||||||
agentPolicy: NewAgentPolicy | Partial<AgentPolicy> | AgentPolicy
|
agentPolicy: NewAgentPolicy | Partial<AgentPolicy> | AgentPolicy,
|
||||||
|
requestSpaceId?: string
|
||||||
): Promise<NewAgentPolicy | Partial<AgentPolicy> | AgentPolicy> {
|
): Promise<NewAgentPolicy | Partial<AgentPolicy> | AgentPolicy> {
|
||||||
const logger = this.getLogger('runExternalCallbacks');
|
const logger = this.getLogger('runExternalCallbacks');
|
||||||
try {
|
try {
|
||||||
|
@ -387,7 +388,8 @@ class AgentPolicyService {
|
||||||
}
|
}
|
||||||
if (externalCallbackType === 'agentPolicyPostUpdate') {
|
if (externalCallbackType === 'agentPolicyPostUpdate') {
|
||||||
result = await (callback as PostAgentPolicyPostUpdateCallback)(
|
result = await (callback as PostAgentPolicyPostUpdateCallback)(
|
||||||
newAgentPolicy as AgentPolicy
|
newAgentPolicy as AgentPolicy,
|
||||||
|
requestSpaceId
|
||||||
);
|
);
|
||||||
updatedNewAgentPolicy = result;
|
updatedNewAgentPolicy = result;
|
||||||
}
|
}
|
||||||
|
@ -828,6 +830,7 @@ class AgentPolicyService {
|
||||||
authorizationHeader?: HTTPAuthorizationHeader | null;
|
authorizationHeader?: HTTPAuthorizationHeader | null;
|
||||||
skipValidation?: boolean;
|
skipValidation?: boolean;
|
||||||
bumpRevision?: boolean;
|
bumpRevision?: boolean;
|
||||||
|
requestSpaceId?: string;
|
||||||
}
|
}
|
||||||
): Promise<AgentPolicy> {
|
): Promise<AgentPolicy> {
|
||||||
const logger = this.getLogger('update');
|
const logger = this.getLogger('update');
|
||||||
|
@ -906,7 +909,8 @@ class AgentPolicyService {
|
||||||
.then((updatedAgentPolicy) => {
|
.then((updatedAgentPolicy) => {
|
||||||
return this.runExternalCallbacks(
|
return this.runExternalCallbacks(
|
||||||
'agentPolicyPostUpdate',
|
'agentPolicyPostUpdate',
|
||||||
updatedAgentPolicy
|
updatedAgentPolicy,
|
||||||
|
options?.requestSpaceId ?? DEFAULT_SPACE_ID
|
||||||
) as unknown as AgentPolicy;
|
) as unknown as AgentPolicy;
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
|
|
@ -76,7 +76,10 @@ export interface PackageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PackageClient {
|
export interface PackageClient {
|
||||||
getInstallation(pkgName: string): Promise<Installation | undefined>;
|
getInstallation(
|
||||||
|
pkgName: string,
|
||||||
|
savedObjectsClient?: SavedObjectsClientContract
|
||||||
|
): Promise<Installation | undefined>;
|
||||||
|
|
||||||
ensureInstalledPackage(options: {
|
ensureInstalledPackage(options: {
|
||||||
pkgName: string;
|
pkgName: string;
|
||||||
|
@ -208,11 +211,14 @@ class PackageClientImpl implements PackageClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getInstallation(pkgName: string) {
|
public async getInstallation(
|
||||||
|
pkgName: string,
|
||||||
|
savedObjectsClient: SavedObjectsClientContract = this.internalSoClient
|
||||||
|
) {
|
||||||
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
await this.#runPreflight(READ_PACKAGE_INFO_AUTHZ);
|
||||||
return getInstallation({
|
return getInstallation({
|
||||||
pkgName,
|
pkgName,
|
||||||
savedObjectsClient: this.internalSoClient,
|
savedObjectsClient,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,10 @@ export type PostAgentPolicyUpdateCallback = (
|
||||||
agentPolicy: Partial<AgentPolicy>
|
agentPolicy: Partial<AgentPolicy>
|
||||||
) => Promise<Partial<AgentPolicy>>;
|
) => Promise<Partial<AgentPolicy>>;
|
||||||
|
|
||||||
export type PostAgentPolicyPostUpdateCallback = (agentPolicy: AgentPolicy) => Promise<AgentPolicy>;
|
export type PostAgentPolicyPostUpdateCallback = (
|
||||||
|
agentPolicy: AgentPolicy,
|
||||||
|
requestSpaceId?: string
|
||||||
|
) => Promise<AgentPolicy>;
|
||||||
|
|
||||||
export type ExternalCallbackCreate = ['packagePolicyCreate', PostPackagePolicyCreateCallback];
|
export type ExternalCallbackCreate = ['packagePolicyCreate', PostPackagePolicyCreateCallback];
|
||||||
export type ExternalCallbackPostCreate = [
|
export type ExternalCallbackPostCreate = [
|
||||||
|
|
|
@ -31,6 +31,7 @@ export interface ActionDetails {
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
pack_id?: string;
|
pack_id?: string;
|
||||||
pack_name?: string;
|
pack_name?: string;
|
||||||
|
space_id?: string;
|
||||||
pack_prebuilt?: boolean;
|
pack_prebuilt?: boolean;
|
||||||
status?: string;
|
status?: string;
|
||||||
queries?: Array<{
|
queries?: Array<{
|
||||||
|
@ -46,7 +47,9 @@ export interface ActionDetails {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ActionsRequestOptions = RequestOptionsPaginated;
|
export interface ActionsRequestOptions extends RequestOptionsPaginated {
|
||||||
|
spaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ActionDetailsStrategyResponse extends IEsSearchResponse {
|
export interface ActionDetailsStrategyResponse extends IEsSearchResponse {
|
||||||
actionDetails: estypes.SearchHit<ActionDetails>;
|
actionDetails: estypes.SearchHit<ActionDetails>;
|
||||||
|
@ -55,6 +58,7 @@ export interface ActionDetailsStrategyResponse extends IEsSearchResponse {
|
||||||
|
|
||||||
export interface ActionDetailsRequestOptions extends RequestOptions {
|
export interface ActionDetailsRequestOptions extends RequestOptions {
|
||||||
actionId: string;
|
actionId: string;
|
||||||
|
spaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionResultsStrategyResponse
|
export interface ActionResultsStrategyResponse
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
"home",
|
"home",
|
||||||
"lens",
|
"lens",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"cases"
|
"cases",
|
||||||
|
"spaces"
|
||||||
],
|
],
|
||||||
"requiredBundles": [
|
"requiredBundles": [
|
||||||
"esUiShared",
|
"esUiShared",
|
||||||
|
|
|
@ -23,6 +23,9 @@ export const actionsMapping: MappingTypeMapping = {
|
||||||
type: 'keyword',
|
type: 'keyword',
|
||||||
ignore_above: 1024,
|
ignore_above: 1024,
|
||||||
},
|
},
|
||||||
|
space_id: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
properties: {
|
properties: {
|
||||||
query: {
|
query: {
|
||||||
|
|
|
@ -0,0 +1,437 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { actionsMapping } from './actions_mapping';
|
||||||
|
import { actionResponsesMapping } from './action_responses_mapping';
|
||||||
|
import {
|
||||||
|
isSubsetMapping,
|
||||||
|
createIndexIfNotExists,
|
||||||
|
initializeTransformsIndices,
|
||||||
|
ACTIONS_INDEX_NAME,
|
||||||
|
ACTIONS_INDEX_DEFAULT_NS,
|
||||||
|
ACTION_RESPONSES_INDEX_NAME,
|
||||||
|
ACTION_RESPONSES_INDEX_DEFAULT_NS,
|
||||||
|
} from './create_transforms_indices';
|
||||||
|
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||||
|
|
||||||
|
interface IndexTemplateResponse {
|
||||||
|
index_templates: Array<{
|
||||||
|
name: string;
|
||||||
|
index_template: {
|
||||||
|
template: {
|
||||||
|
mappings: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MockEsClient {
|
||||||
|
indices: {
|
||||||
|
exists: jest.Mock<Promise<boolean>>;
|
||||||
|
putIndexTemplate: jest.Mock<Promise<{ acknowledged: boolean }>>;
|
||||||
|
create: jest.Mock<Promise<{ acknowledged: boolean }>>;
|
||||||
|
getIndexTemplate: jest.Mock<Promise<IndexTemplateResponse>>;
|
||||||
|
putMapping: jest.Mock<Promise<{ acknowledged: boolean }>>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EsIndexMappings {
|
||||||
|
properties: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMockEsClient = (): MockEsClient => ({
|
||||||
|
indices: {
|
||||||
|
exists: jest.fn().mockResolvedValue(false),
|
||||||
|
putIndexTemplate: jest.fn().mockResolvedValue({ acknowledged: true }),
|
||||||
|
create: jest.fn().mockResolvedValue({ acknowledged: true }),
|
||||||
|
getIndexTemplate: jest.fn().mockResolvedValue({
|
||||||
|
index_templates: [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
index_template: {
|
||||||
|
template: {
|
||||||
|
mappings: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
putMapping: jest.fn().mockResolvedValue({ acknowledged: true }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const logger: Logger = {
|
||||||
|
debug: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
get: jest.fn(() => logger),
|
||||||
|
} as Partial<Logger> as Logger;
|
||||||
|
|
||||||
|
let mockEsClient: ReturnType<typeof createMockEsClient>;
|
||||||
|
|
||||||
|
function makeEsIndexMappings(
|
||||||
|
overrides: Partial<EsIndexMappings['properties']> = {}
|
||||||
|
): EsIndexMappings {
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
pack_name: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
metadata: { type: 'object', enabled: false },
|
||||||
|
data: {
|
||||||
|
properties: {
|
||||||
|
query: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pack_id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
space_id: { type: 'keyword' },
|
||||||
|
input_type: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
pack_prebuilt: { type: 'boolean' },
|
||||||
|
type: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
queries: {
|
||||||
|
properties: {
|
||||||
|
action_id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
saved_query_id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
saved_query_prebuilt: { type: 'boolean' },
|
||||||
|
query: { type: 'text' },
|
||||||
|
id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
version: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
ecs_mapping: { type: 'object', enabled: false },
|
||||||
|
platform: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
agents: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
...((overrides.queries as any)?.properties || {}),
|
||||||
|
},
|
||||||
|
...((overrides.queries as any) || {}),
|
||||||
|
},
|
||||||
|
agents: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
'@timestamp': { type: 'date' },
|
||||||
|
action_id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
user_id: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
expiration: { type: 'date' },
|
||||||
|
event: {
|
||||||
|
properties: {
|
||||||
|
agent_id_status: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
ingested: {
|
||||||
|
format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis',
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
...((overrides.event as any)?.properties || {}),
|
||||||
|
},
|
||||||
|
...((overrides.event as any) || {}),
|
||||||
|
},
|
||||||
|
agent_ids: { ignore_above: 1024, type: 'keyword' },
|
||||||
|
...overrides,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('createTransformIndices', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
describe('isSubsetMapping', () => {
|
||||||
|
it('returns true for exact match', () => {
|
||||||
|
const mapping = { foo: { type: 'keyword' } };
|
||||||
|
expect(isSubsetMapping(mapping, mapping, logger)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if current has extra fields', () => {
|
||||||
|
const desired = { foo: { type: 'keyword' } };
|
||||||
|
const current = { foo: { type: 'keyword' }, bar: { type: 'text' } };
|
||||||
|
expect(isSubsetMapping(desired, current, logger)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if current is missing a field', () => {
|
||||||
|
const desired = { foo: { type: 'keyword' }, bar: { type: 'text' } };
|
||||||
|
const current = { foo: { type: 'keyword' } };
|
||||||
|
expect(isSubsetMapping(desired, current, logger)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a field has a different value', () => {
|
||||||
|
const desired = { foo: { type: 'keyword' } };
|
||||||
|
const current = { foo: { type: 'text' } };
|
||||||
|
expect(isSubsetMapping(desired, current, logger)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles nested properties correctly', () => {
|
||||||
|
const desired = {
|
||||||
|
properties: {
|
||||||
|
foo: { type: 'keyword' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const current = {
|
||||||
|
properties: {
|
||||||
|
foo: { type: 'keyword' },
|
||||||
|
bar: { type: 'text' },
|
||||||
|
},
|
||||||
|
extra: 'ignored',
|
||||||
|
};
|
||||||
|
expect(isSubsetMapping(desired, current, logger)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for missing nested property', () => {
|
||||||
|
const desired = {
|
||||||
|
properties: {
|
||||||
|
foo: { type: 'keyword' },
|
||||||
|
bar: { type: 'text' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const current = {
|
||||||
|
properties: {
|
||||||
|
foo: { type: 'keyword' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(isSubsetMapping(desired, current, logger)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for leaf value match', () => {
|
||||||
|
expect(isSubsetMapping({ foo: 'a' }, { foo: 'a' }, logger)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for leaf value mismatch', () => {
|
||||||
|
expect(isSubsetMapping({ foo: 'a' }, { foo: 'b' }, logger)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Using mocked data', () => {
|
||||||
|
it('returns true for real-world mapping vs index mappings', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings();
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a required field is missing in ES mapping', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings();
|
||||||
|
delete esIndexMappings.properties.space_id;
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a field type mismatches', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings({
|
||||||
|
action_id: { type: 'text' }, // should be 'keyword'
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true if ES mapping has extra fields', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings({ extra_field: { type: 'keyword' } });
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a nested property is missing', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings();
|
||||||
|
delete (esIndexMappings.properties.queries as { properties: Record<string, unknown> })
|
||||||
|
.properties.action_id;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false if a nested property type mismatches', () => {
|
||||||
|
const esIndexMappings = makeEsIndexMappings({
|
||||||
|
queries: {
|
||||||
|
properties: {
|
||||||
|
...(makeEsIndexMappings().properties.queries as any).properties,
|
||||||
|
action_id: { type: 'text' }, // should be 'keyword'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
isSubsetMapping(
|
||||||
|
actionsMapping as Record<string, unknown>,
|
||||||
|
esIndexMappings as unknown as Record<string, unknown>,
|
||||||
|
logger
|
||||||
|
)
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createIndexIfNotExists', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEsClient = createMockEsClient();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create index and template if they do not exist', async () => {
|
||||||
|
mockEsClient.indices.exists.mockResolvedValueOnce(false);
|
||||||
|
mockEsClient.indices.putIndexTemplate.mockResolvedValueOnce({ acknowledged: true } as any);
|
||||||
|
mockEsClient.indices.create.mockResolvedValueOnce({ acknowledged: true } as any);
|
||||||
|
|
||||||
|
await createIndexIfNotExists(
|
||||||
|
mockEsClient as unknown as ElasticsearchClient,
|
||||||
|
'test-index',
|
||||||
|
'.logs-test-index-default',
|
||||||
|
actionsMapping,
|
||||||
|
logger
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockEsClient.indices.exists).toHaveBeenCalledWith({
|
||||||
|
index: '.logs-test-index-default',
|
||||||
|
});
|
||||||
|
expect(mockEsClient.indices.putIndexTemplate).toHaveBeenCalledWith({
|
||||||
|
name: 'test-index',
|
||||||
|
index_patterns: '.logs-test-index-default',
|
||||||
|
template: { mappings: actionsMapping },
|
||||||
|
priority: 500,
|
||||||
|
});
|
||||||
|
expect(mockEsClient.indices.create).toHaveBeenCalledWith({
|
||||||
|
index: '.logs-test-index-default',
|
||||||
|
mappings: actionsMapping,
|
||||||
|
});
|
||||||
|
expect(logger.debug).toHaveBeenCalledWith('Index test-index does not exist, creating...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update index template if mappings are outdated', async () => {
|
||||||
|
const currentMappings = {
|
||||||
|
properties: {
|
||||||
|
...actionsMapping.properties,
|
||||||
|
// Remove a field to simulate outdated mappings
|
||||||
|
pack_name: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEsClient.indices.exists.mockResolvedValueOnce(true);
|
||||||
|
mockEsClient.indices.getIndexTemplate.mockResolvedValueOnce({
|
||||||
|
index_templates: [
|
||||||
|
{
|
||||||
|
name: 'test-index',
|
||||||
|
index_template: {
|
||||||
|
template: {
|
||||||
|
mappings: currentMappings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any);
|
||||||
|
mockEsClient.indices.putIndexTemplate.mockResolvedValueOnce({ acknowledged: true } as any);
|
||||||
|
mockEsClient.indices.putMapping.mockResolvedValueOnce({ acknowledged: true } as any);
|
||||||
|
|
||||||
|
await createIndexIfNotExists(
|
||||||
|
mockEsClient as unknown as ElasticsearchClient,
|
||||||
|
'test-index',
|
||||||
|
'.logs-test-index-default',
|
||||||
|
actionsMapping,
|
||||||
|
logger
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockEsClient.indices.putIndexTemplate).toHaveBeenCalledWith({
|
||||||
|
name: 'test-index',
|
||||||
|
index_patterns: '.logs-test-index-default',
|
||||||
|
template: { mappings: actionsMapping },
|
||||||
|
priority: 500,
|
||||||
|
});
|
||||||
|
expect(mockEsClient.indices.putMapping).toHaveBeenCalledWith({
|
||||||
|
index: '.logs-test-index-default',
|
||||||
|
body: { properties: actionsMapping.properties },
|
||||||
|
});
|
||||||
|
expect(logger.debug).toHaveBeenCalledWith(
|
||||||
|
'Index test-index already exists, checking template...'
|
||||||
|
);
|
||||||
|
expect(logger.debug).toHaveBeenCalledWith(
|
||||||
|
'Mappings for "test-index" are outdated. Updating mappings...'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log error if index creation fails', async () => {
|
||||||
|
const error = new Error('Failed to create index');
|
||||||
|
mockEsClient.indices.exists.mockRejectedValueOnce(error);
|
||||||
|
|
||||||
|
await createIndexIfNotExists(
|
||||||
|
mockEsClient as unknown as ElasticsearchClient,
|
||||||
|
'test-index',
|
||||||
|
'.logs-test-index-default',
|
||||||
|
actionsMapping,
|
||||||
|
logger
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalledWith('Failed to create the index template: test-index');
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Failed to create index'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initializeTransformsIndices', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEsClient = createMockEsClient();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize both actions and action responses indices', async () => {
|
||||||
|
mockEsClient.indices.exists.mockResolvedValue(false);
|
||||||
|
mockEsClient.indices.putIndexTemplate.mockResolvedValue({ acknowledged: true } as any);
|
||||||
|
mockEsClient.indices.create.mockResolvedValue({ acknowledged: true } as any);
|
||||||
|
|
||||||
|
await initializeTransformsIndices(mockEsClient as unknown as ElasticsearchClient, logger);
|
||||||
|
|
||||||
|
// Should call createIndexIfNotExists for both indices
|
||||||
|
expect(mockEsClient.indices.exists).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockEsClient.indices.putIndexTemplate).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockEsClient.indices.create).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
// Verify actions index creation
|
||||||
|
expect(mockEsClient.indices.putIndexTemplate).toHaveBeenNthCalledWith(1, {
|
||||||
|
name: ACTIONS_INDEX_NAME,
|
||||||
|
index_patterns: ACTIONS_INDEX_DEFAULT_NS,
|
||||||
|
template: { mappings: actionsMapping },
|
||||||
|
priority: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify action responses index creation
|
||||||
|
expect(mockEsClient.indices.putIndexTemplate).toHaveBeenNthCalledWith(2, {
|
||||||
|
name: ACTION_RESPONSES_INDEX_NAME,
|
||||||
|
index_patterns: ACTION_RESPONSES_INDEX_DEFAULT_NS,
|
||||||
|
template: { mappings: actionResponsesMapping },
|
||||||
|
priority: 500,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors during initialization', async () => {
|
||||||
|
const error = new Error('Failed to initialize');
|
||||||
|
mockEsClient.indices.exists.mockRejectedValueOnce(error);
|
||||||
|
|
||||||
|
// The second call to exists will resolve to false
|
||||||
|
mockEsClient.indices.exists.mockResolvedValueOnce(false);
|
||||||
|
mockEsClient.indices.putIndexTemplate.mockResolvedValue({ acknowledged: true } as any);
|
||||||
|
mockEsClient.indices.create.mockResolvedValue({ acknowledged: true } as any);
|
||||||
|
|
||||||
|
await initializeTransformsIndices(mockEsClient as unknown as ElasticsearchClient, logger);
|
||||||
|
|
||||||
|
// Should still attempt to create both indices even if one fails
|
||||||
|
expect(mockEsClient.indices.exists).toHaveBeenCalledTimes(2);
|
||||||
|
expect(logger.error).toHaveBeenCalledWith(
|
||||||
|
'Failed to create the index template: osquery_manager.actions'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,11 +5,21 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||||
import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
|
import type {
|
||||||
|
MappingTypeMapping,
|
||||||
|
IndicesPutMappingRequest,
|
||||||
|
} from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
|
||||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||||
import { actionsMapping } from './actions_mapping';
|
import { actionsMapping } from './actions_mapping';
|
||||||
import { actionResponsesMapping } from './action_responses_mapping';
|
import { actionResponsesMapping } from './action_responses_mapping';
|
||||||
|
|
||||||
|
interface ESMappingObject {
|
||||||
|
[key: string]: ESMappingValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ESMappingValue = string | number | boolean | null | ESMappingObject | undefined;
|
||||||
|
|
||||||
export const ACTIONS_INDEX_NAME = 'osquery_manager.actions';
|
export const ACTIONS_INDEX_NAME = 'osquery_manager.actions';
|
||||||
export const ACTIONS_INDEX_DEFAULT_NS = '.logs-' + ACTIONS_INDEX_NAME + '-default';
|
export const ACTIONS_INDEX_DEFAULT_NS = '.logs-' + ACTIONS_INDEX_NAME + '-default';
|
||||||
|
|
||||||
|
@ -42,26 +52,149 @@ export const createIndexIfNotExists = async (
|
||||||
mappings: MappingTypeMapping,
|
mappings: MappingTypeMapping,
|
||||||
logger: Logger
|
logger: Logger
|
||||||
) => {
|
) => {
|
||||||
|
const subLogger = logger.get('createIndexIfNotExists');
|
||||||
try {
|
try {
|
||||||
const isLatestIndexExists = await esClient.indices.exists({
|
const isLatestIndexExists = await esClient.indices.exists({
|
||||||
index: indexPattern,
|
index: indexPattern,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isLatestIndexExists) {
|
if (!isLatestIndexExists) {
|
||||||
|
subLogger.debug(`Index ${indexTemplateName} does not exist, creating...`);
|
||||||
|
|
||||||
await esClient.indices.putIndexTemplate({
|
await esClient.indices.putIndexTemplate({
|
||||||
name: indexTemplateName,
|
name: indexTemplateName,
|
||||||
index_patterns: indexPattern,
|
index_patterns: indexPattern,
|
||||||
template: { mappings },
|
template: { mappings },
|
||||||
priority: 500,
|
priority: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
await esClient.indices.create({
|
await esClient.indices.create({
|
||||||
index: indexPattern,
|
index: indexPattern,
|
||||||
mappings,
|
mappings,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
subLogger.debug(`Index ${indexTemplateName} already exists, checking template...`);
|
||||||
|
|
||||||
|
// pull the index template to check if mappings need to be updated
|
||||||
|
const indexTemplate = await esClient.indices.getIndexTemplate({
|
||||||
|
name: indexTemplateName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the template with the exact name match
|
||||||
|
const matchedTemplateObj = indexTemplate.index_templates.find(
|
||||||
|
(tpl: { name: string }) => tpl.name === indexTemplateName
|
||||||
|
);
|
||||||
|
const currentMappings = matchedTemplateObj?.index_template?.template?.mappings || {};
|
||||||
|
|
||||||
|
subLogger.debug(`Fetched current mappings for template "${indexTemplateName}"`);
|
||||||
|
|
||||||
|
if (isSubsetMapping(mappings, currentMappings as ESMappingObject, subLogger)) {
|
||||||
|
subLogger.debug(`Mappings for "${indexTemplateName}" are up to date. No update needed.`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subLogger.debug(`Mappings for "${indexTemplateName}" are outdated. Updating mappings...`);
|
||||||
|
|
||||||
|
if (mappings.properties) {
|
||||||
|
await esClient.indices.putIndexTemplate({
|
||||||
|
name: indexTemplateName,
|
||||||
|
index_patterns: indexPattern,
|
||||||
|
template: { mappings },
|
||||||
|
priority: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
await esClient.indices.putMapping({
|
||||||
|
index: indexPattern,
|
||||||
|
body: (mappings.properties
|
||||||
|
? { properties: mappings.properties }
|
||||||
|
: {}) as IndicesPutMappingRequest['body'],
|
||||||
|
});
|
||||||
|
|
||||||
|
subLogger.debug(`Mappings for "${indexTemplateName}" have been updated.`);
|
||||||
|
} else {
|
||||||
|
subLogger.error(`No properties found in mappings for "${indexTemplateName}"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = transformError(err);
|
const error = transformError(err);
|
||||||
logger.error(`Failed to create the index template: ${indexTemplateName}`);
|
subLogger.error(`Failed to create the index template: ${indexTemplateName}`);
|
||||||
logger.error(error.message);
|
subLogger.error(error.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively checks if all fields and configs from `desired` mapping exist in `current` mapping.
|
||||||
|
* Ignores extra fields in `current`. Logs each major step for traceability.
|
||||||
|
*
|
||||||
|
* @param desired - The mapping you want to enforce
|
||||||
|
* @param current - The mapping fetched from ESccc
|
||||||
|
* @param logger - Kibana logger instance
|
||||||
|
* @returns true if all fields/configs in desired are present in current
|
||||||
|
*/
|
||||||
|
export function isSubsetMapping(
|
||||||
|
desired: MappingTypeMapping | ESMappingObject,
|
||||||
|
current: MappingTypeMapping | ESMappingObject,
|
||||||
|
logger: Logger
|
||||||
|
): boolean {
|
||||||
|
const subLogger = logger.get('isSubsetMapping');
|
||||||
|
|
||||||
|
// Handle primitive types or null values
|
||||||
|
if (typeof desired !== 'object' || desired === null) {
|
||||||
|
const result = desired === current;
|
||||||
|
subLogger.debug(`Comparing leaf values: ${desired} === ${current} -> ${result}`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert both to ESMappingObject to safely access with string keys
|
||||||
|
const desiredObj = desired as ESMappingObject;
|
||||||
|
const currentObj = current as ESMappingObject;
|
||||||
|
|
||||||
|
for (const key of Object.keys(desiredObj)) {
|
||||||
|
if (!(key in currentObj)) {
|
||||||
|
subLogger.debug(`Key "${key}" missing in current mapping`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const desiredValue = desiredObj[key];
|
||||||
|
const currentValue = currentObj[key];
|
||||||
|
|
||||||
|
const bothAreNonNullObjects =
|
||||||
|
typeof desiredValue === 'object' &&
|
||||||
|
desiredValue !== null &&
|
||||||
|
typeof currentValue === 'object' &&
|
||||||
|
currentValue !== null;
|
||||||
|
|
||||||
|
if (key === 'properties') {
|
||||||
|
if (bothAreNonNullObjects) {
|
||||||
|
if (
|
||||||
|
!isSubsetMapping(desiredValue as ESMappingObject, currentValue as ESMappingObject, logger)
|
||||||
|
) {
|
||||||
|
subLogger.debug(`Nested properties mismatch for key "${key}"`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subLogger.debug(`Expected both desired and current to be objects for key "${key}"`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (bothAreNonNullObjects) {
|
||||||
|
if (
|
||||||
|
!isSubsetMapping(desiredValue as ESMappingObject, currentValue as ESMappingObject, logger)
|
||||||
|
) {
|
||||||
|
subLogger.debug(`Value mismatch for key "${key}"`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (desiredValue !== currentValue) {
|
||||||
|
subLogger.debug(`Value mismatch for key "${key}": ${desiredValue} !== ${currentValue}`);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -8,11 +8,10 @@
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { filter, isEmpty, isNumber, map, omit, pick, pickBy, some } from 'lodash';
|
import { filter, isEmpty, isNumber, map, omit, pick, pickBy, some } from 'lodash';
|
||||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
|
||||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
import type { CreateLiveQueryRequestBodySchema } from '../../../common/api';
|
import type { CreateLiveQueryRequestBodySchema } from '../../../common/api';
|
||||||
import { createDynamicQueries, replacedQueries } from './create_queries';
|
import { createDynamicQueries, replacedQueries } from './create_queries';
|
||||||
import { getInternalSavedObjectsClient } from '../../routes/utils';
|
|
||||||
import { parseAgentSelection } from '../../lib/parse_agent_groups';
|
import { parseAgentSelection } from '../../lib/parse_agent_groups';
|
||||||
import { packSavedObjectType } from '../../../common/types';
|
import { packSavedObjectType } from '../../../common/types';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
@ -21,13 +20,14 @@ import { ACTIONS_INDEX, QUERY_TIMEOUT } from '../../../common/constants';
|
||||||
import { TELEMETRY_EBT_LIVE_QUERY_EVENT } from '../../lib/telemetry/constants';
|
import { TELEMETRY_EBT_LIVE_QUERY_EVENT } from '../../lib/telemetry/constants';
|
||||||
import type { PackSavedObject } from '../../common/types';
|
import type { PackSavedObject } from '../../common/types';
|
||||||
import { CustomHttpRequestError } from '../../common/error';
|
import { CustomHttpRequestError } from '../../common/error';
|
||||||
|
import { getInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
|
|
||||||
interface Metadata {
|
interface Metadata {
|
||||||
currentUser: string | undefined;
|
currentUser: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateActionHandlerOptions {
|
interface CreateActionHandlerOptions {
|
||||||
soClient?: SavedObjectsClientContract;
|
space?: { id: string };
|
||||||
metadata?: Metadata;
|
metadata?: Metadata;
|
||||||
alertData?: ParsedTechnicalFields & { _index: string };
|
alertData?: ParsedTechnicalFields & { _index: string };
|
||||||
error?: string;
|
error?: string;
|
||||||
|
@ -40,24 +40,30 @@ export const createActionHandler = async (
|
||||||
) => {
|
) => {
|
||||||
const [coreStartServices] = await osqueryContext.getStartServices();
|
const [coreStartServices] = await osqueryContext.getStartServices();
|
||||||
const esClientInternal = coreStartServices.elasticsearch.client.asInternalUser;
|
const esClientInternal = coreStartServices.elasticsearch.client.asInternalUser;
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
|
||||||
osqueryContext.getStartServices
|
const spaceScopedInternalSavedObjectsClient = getInternalSavedObjectsClientForSpaceId(
|
||||||
|
coreStartServices,
|
||||||
|
options.space?.id ?? DEFAULT_SPACE_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
const { soClient, metadata, alertData, error } = options;
|
const { metadata, alertData, error } = options;
|
||||||
const savedObjectsClient = soClient ?? coreStartServices.savedObjects.createInternalRepository();
|
|
||||||
const elasticsearchClient = coreStartServices.elasticsearch.client.asInternalUser;
|
const elasticsearchClient = coreStartServices.elasticsearch.client.asInternalUser;
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
const {
|
||||||
const { agent_all, agent_ids, agent_platforms, agent_policy_ids } = params;
|
agent_all: agentAll,
|
||||||
|
agent_ids: agentIds,
|
||||||
|
agent_platforms: agentPlatforms,
|
||||||
|
agent_policy_ids: agentPolicyIds,
|
||||||
|
} = params;
|
||||||
const selectedAgents = await parseAgentSelection(
|
const selectedAgents = await parseAgentSelection(
|
||||||
internalSavedObjectsClient,
|
spaceScopedInternalSavedObjectsClient,
|
||||||
elasticsearchClient,
|
elasticsearchClient,
|
||||||
osqueryContext,
|
osqueryContext,
|
||||||
{
|
{
|
||||||
agents: agent_ids,
|
agents: agentIds,
|
||||||
allAgentsSelected: !!agent_all,
|
allAgentsSelected: !!agentAll,
|
||||||
platformsSelected: agent_platforms,
|
platformsSelected: agentPlatforms,
|
||||||
policiesSelected: agent_policy_ids,
|
policiesSelected: agentPolicyIds,
|
||||||
|
spaceId: options.space?.id ?? DEFAULT_SPACE_ID,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -68,7 +74,10 @@ export const createActionHandler = async (
|
||||||
let packSO;
|
let packSO;
|
||||||
|
|
||||||
if (params.pack_id) {
|
if (params.pack_id) {
|
||||||
packSO = await savedObjectsClient.get<PackSavedObject>(packSavedObjectType, params.pack_id);
|
packSO = await spaceScopedInternalSavedObjectsClient.get<PackSavedObject>(
|
||||||
|
packSavedObjectType,
|
||||||
|
params.pack_id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const osqueryAction = {
|
const osqueryAction = {
|
||||||
|
@ -92,6 +101,7 @@ export const createActionHandler = async (
|
||||||
pack_prebuilt: params.pack_id
|
pack_prebuilt: params.pack_id
|
||||||
? some(packSO?.references, ['type', 'osquery-pack-asset'])
|
? some(packSO?.references, ['type', 'osquery-pack-asset'])
|
||||||
: undefined,
|
: undefined,
|
||||||
|
space_id: options.space?.id ?? DEFAULT_SPACE_ID,
|
||||||
queries: packSO
|
queries: packSO
|
||||||
? map(convertSOQueriesToPack(packSO.attributes.queries), (packQuery, packQueryId) => {
|
? map(convertSOQueriesToPack(packSO.attributes.queries), (packQuery, packQueryId) => {
|
||||||
const replacedQuery = replacedQueries(packQuery.query, alertData);
|
const replacedQuery = replacedQueries(packQuery.query, alertData);
|
||||||
|
@ -117,6 +127,8 @@ export const createActionHandler = async (
|
||||||
agents: selectedAgents,
|
agents: selectedAgents,
|
||||||
osqueryContext,
|
osqueryContext,
|
||||||
error,
|
error,
|
||||||
|
spaceId: options.space?.id ?? DEFAULT_SPACE_ID,
|
||||||
|
spaceScopedClient: spaceScopedInternalSavedObjectsClient,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,11 @@ import type { OsqueryActiveLicenses } from './validate_license';
|
||||||
import { validateLicense } from './validate_license';
|
import { validateLicense } from './validate_license';
|
||||||
import { createActionHandler } from './create_action_handler';
|
import { createActionHandler } from './create_action_handler';
|
||||||
|
|
||||||
|
export interface CreateActionOptions {
|
||||||
|
alertData?: ParsedTechnicalFields & { _index: string };
|
||||||
|
space?: { id: string };
|
||||||
|
}
|
||||||
|
|
||||||
export const createActionService = (osqueryContext: OsqueryAppContext) => {
|
export const createActionService = (osqueryContext: OsqueryAppContext) => {
|
||||||
let licenseSubscription: Subscription | null = null;
|
let licenseSubscription: Subscription | null = null;
|
||||||
const licenses: OsqueryActiveLicenses = { isActivePlatinumLicense: false };
|
const licenses: OsqueryActiveLicenses = { isActivePlatinumLicense: false };
|
||||||
|
@ -21,13 +26,19 @@ export const createActionService = (osqueryContext: OsqueryAppContext) => {
|
||||||
licenses.isActivePlatinumLicense = license.isActive && license.hasAtLeast('platinum');
|
licenses.isActivePlatinumLicense = license.isActive && license.hasAtLeast('platinum');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const logger = osqueryContext.logFactory.get('createActionService');
|
||||||
|
|
||||||
const create = async (
|
const create = async (
|
||||||
params: CreateLiveQueryRequestBodySchema,
|
params: CreateLiveQueryRequestBodySchema,
|
||||||
alertData?: ParsedTechnicalFields & { _index: string }
|
options?: CreateActionOptions
|
||||||
) => {
|
) => {
|
||||||
const error = validateLicense(licenses);
|
const error = validateLicense(licenses);
|
||||||
|
|
||||||
return createActionHandler(osqueryContext, params, { alertData, error });
|
return createActionHandler(osqueryContext, params, {
|
||||||
|
alertData: options?.alertData,
|
||||||
|
space: options?.space,
|
||||||
|
error,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
|
@ -39,5 +50,6 @@ export const createActionService = (osqueryContext: OsqueryAppContext) => {
|
||||||
return {
|
return {
|
||||||
create,
|
create,
|
||||||
stop,
|
stop,
|
||||||
|
logger,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,11 @@ import { createDynamicQueries } from './create_queries';
|
||||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
||||||
|
import type { SavedObjectsClient } from '@kbn/core/server';
|
||||||
|
|
||||||
describe('create queries', () => {
|
describe('create queries', () => {
|
||||||
|
const spaceId = 'default';
|
||||||
|
const mockSavedObjectsClient = {} as unknown as SavedObjectsClient;
|
||||||
const defualtQueryParams = {
|
const defualtQueryParams = {
|
||||||
interval: 3600,
|
interval: 3600,
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
|
@ -62,6 +65,8 @@ describe('create queries', () => {
|
||||||
},
|
},
|
||||||
} as unknown as ParsedTechnicalFields & { _index: string },
|
} as unknown as ParsedTechnicalFields & { _index: string },
|
||||||
osqueryContext: {} as OsqueryAppContext,
|
osqueryContext: {} as OsqueryAppContext,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient: mockSavedObjectsClient,
|
||||||
});
|
});
|
||||||
expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`);
|
expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`);
|
||||||
expect(queries[0].error).toBe(undefined);
|
expect(queries[0].error).toBe(undefined);
|
||||||
|
@ -79,6 +84,8 @@ describe('create queries', () => {
|
||||||
process: { pid },
|
process: { pid },
|
||||||
} as unknown as ParsedTechnicalFields & { _index: string },
|
} as unknown as ParsedTechnicalFields & { _index: string },
|
||||||
osqueryContext: {} as OsqueryAppContext,
|
osqueryContext: {} as OsqueryAppContext,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient: mockSavedObjectsClient,
|
||||||
});
|
});
|
||||||
expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`);
|
expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`);
|
||||||
expect(queries[0].error).toBe(undefined);
|
expect(queries[0].error).toBe(undefined);
|
||||||
|
@ -91,6 +98,8 @@ describe('create queries', () => {
|
||||||
process: {},
|
process: {},
|
||||||
} as unknown as ParsedTechnicalFields & { _index: string },
|
} as unknown as ParsedTechnicalFields & { _index: string },
|
||||||
osqueryContext: {} as OsqueryAppContext,
|
osqueryContext: {} as OsqueryAppContext,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient: mockSavedObjectsClient,
|
||||||
});
|
});
|
||||||
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
||||||
expect(queries[0].error).toBe(PARAMETER_NOT_FOUND);
|
expect(queries[0].error).toBe(PARAMETER_NOT_FOUND);
|
||||||
|
@ -100,6 +109,8 @@ describe('create queries', () => {
|
||||||
params: mockedQueriesParams,
|
params: mockedQueriesParams,
|
||||||
agents: [TEST_AGENT],
|
agents: [TEST_AGENT],
|
||||||
osqueryContext: {} as OsqueryAppContext,
|
osqueryContext: {} as OsqueryAppContext,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient: mockSavedObjectsClient,
|
||||||
});
|
});
|
||||||
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
||||||
expect(queries[0].agents).toContain(TEST_AGENT);
|
expect(queries[0].agents).toContain(TEST_AGENT);
|
||||||
|
@ -114,6 +125,8 @@ describe('create queries', () => {
|
||||||
params: mockedSingleQueryParams,
|
params: mockedSingleQueryParams,
|
||||||
agents: [TEST_AGENT],
|
agents: [TEST_AGENT],
|
||||||
osqueryContext: {} as OsqueryAppContext,
|
osqueryContext: {} as OsqueryAppContext,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient: mockSavedObjectsClient,
|
||||||
});
|
});
|
||||||
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};');
|
||||||
expect(queries[0].agents).toContain(TEST_AGENT);
|
expect(queries[0].agents).toContain(TEST_AGENT);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { isEmpty, isNumber, map, pickBy } from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||||
|
import type { SavedObjectsClient } from '@kbn/core-saved-objects-api-server-internal';
|
||||||
import type { CreateLiveQueryRequestBodySchema } from '../../../common/api';
|
import type { CreateLiveQueryRequestBodySchema } from '../../../common/api';
|
||||||
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
import { PARAMETER_NOT_FOUND } from '../../../common/translations/errors';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
@ -21,6 +22,8 @@ interface CreateDynamicQueriesParams {
|
||||||
agents: string[];
|
agents: string[];
|
||||||
osqueryContext: OsqueryAppContext;
|
osqueryContext: OsqueryAppContext;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
spaceId: string;
|
||||||
|
spaceScopedClient: SavedObjectsClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createDynamicQueries = async ({
|
export const createDynamicQueries = async ({
|
||||||
|
@ -29,6 +32,8 @@ export const createDynamicQueries = async ({
|
||||||
agents,
|
agents,
|
||||||
osqueryContext,
|
osqueryContext,
|
||||||
error,
|
error,
|
||||||
|
spaceId,
|
||||||
|
spaceScopedClient,
|
||||||
}: CreateDynamicQueriesParams) =>
|
}: CreateDynamicQueriesParams) =>
|
||||||
params.queries?.length
|
params.queries?.length
|
||||||
? map(params.queries, ({ query, ...restQuery }) => {
|
? map(params.queries, ({ query, ...restQuery }) => {
|
||||||
|
@ -56,7 +61,9 @@ export const createDynamicQueries = async ({
|
||||||
saved_query_prebuilt: params.saved_query_id
|
saved_query_prebuilt: params.saved_query_id
|
||||||
? await isSavedQueryPrebuilt(
|
? await isSavedQueryPrebuilt(
|
||||||
osqueryContext.service.getPackageService()?.asInternalUser,
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
params.saved_query_id
|
params.saved_query_id,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
ecs_mapping: params.ecs_mapping,
|
ecs_mapping: params.ecs_mapping,
|
||||||
|
|
|
@ -5,12 +5,75 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SavedObjectReference, SavedObjectsClient } from '@kbn/core/server';
|
import type { CoreStart, SavedObjectReference, SavedObjectsClient } from '@kbn/core/server';
|
||||||
import { filter, map } from 'lodash';
|
import { filter, map } from 'lodash';
|
||||||
import type { PostPackagePolicyPostDeleteCallback } from '@kbn/fleet-plugin/server';
|
import type { PostPackagePolicyPostDeleteCallback } from '@kbn/fleet-plugin/server';
|
||||||
|
import type {
|
||||||
|
PackagePolicy,
|
||||||
|
PostAgentPolicyPostUpdateCallback,
|
||||||
|
} from '@kbn/fleet-plugin/server/types';
|
||||||
|
|
||||||
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import { packSavedObjectType } from '../../common/types';
|
import { packSavedObjectType } from '../../common/types';
|
||||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||||
|
import { getInternalSavedObjectsClientForSpaceId } from '../utils/get_internal_saved_object_client';
|
||||||
|
|
||||||
|
export const getAgentPolicyPostUpdateCallback =
|
||||||
|
(core: CoreStart): PostAgentPolicyPostUpdateCallback =>
|
||||||
|
async (updatedAgentPolicy, requestSpaceId) => {
|
||||||
|
const hasOsqueryIntegration =
|
||||||
|
Array.isArray(updatedAgentPolicy.package_policies) &&
|
||||||
|
updatedAgentPolicy.package_policies.some(
|
||||||
|
(pkg: PackagePolicy) => pkg?.package?.name === OSQUERY_INTEGRATION_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasOsqueryIntegration) {
|
||||||
|
return updatedAgentPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestSpaceId) {
|
||||||
|
if (!(updatedAgentPolicy.space_ids ?? []).includes(requestSpaceId)) {
|
||||||
|
const spaceScopedSavedObjectClient = getInternalSavedObjectsClientForSpaceId(
|
||||||
|
core,
|
||||||
|
requestSpaceId
|
||||||
|
);
|
||||||
|
const foundPacks = await spaceScopedSavedObjectClient.find({
|
||||||
|
type: packSavedObjectType,
|
||||||
|
hasReference: {
|
||||||
|
type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||||
|
id: updatedAgentPolicy.id,
|
||||||
|
},
|
||||||
|
perPage: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundPacks.saved_objects.length) {
|
||||||
|
const policyId = updatedAgentPolicy.id;
|
||||||
|
await Promise.all(
|
||||||
|
map(
|
||||||
|
foundPacks.saved_objects,
|
||||||
|
(pack: {
|
||||||
|
id: string;
|
||||||
|
references: SavedObjectReference[];
|
||||||
|
attributes: { shards: Array<{ key: string; value: string }> };
|
||||||
|
}) =>
|
||||||
|
spaceScopedSavedObjectClient.update(
|
||||||
|
packSavedObjectType,
|
||||||
|
pack.id,
|
||||||
|
{
|
||||||
|
shards: filter(pack.attributes.shards, (shard) => shard.key !== policyId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
references: filter(pack.references, (reference) => reference.id !== policyId),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedAgentPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
export const getPackagePolicyDeleteCallback =
|
export const getPackagePolicyDeleteCallback =
|
||||||
(packsClient: SavedObjectsClient): PostPackagePolicyPostDeleteCallback =>
|
(packsClient: SavedObjectsClient): PostPackagePolicyPostDeleteCallback =>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CoreSetup, Logger, LoggerFactory } from '@kbn/core/server';
|
import type { CoreSetup, KibanaRequest, Logger, LoggerFactory } from '@kbn/core/server';
|
||||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||||
import type {
|
import type {
|
||||||
AgentService,
|
AgentService,
|
||||||
|
@ -17,6 +17,7 @@ import type {
|
||||||
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
||||||
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||||
import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions';
|
import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions';
|
||||||
|
import type { Space, SpacesServiceStart } from '@kbn/spaces-plugin/server';
|
||||||
import type { ConfigType } from '../../common/config';
|
import type { ConfigType } from '../../common/config';
|
||||||
import type { TelemetryEventsSender } from './telemetry/sender';
|
import type { TelemetryEventsSender } from './telemetry/sender';
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ export type OsqueryAppContextServiceStartContract = Partial<
|
||||||
config: ConfigType;
|
config: ConfigType;
|
||||||
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
|
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
|
||||||
ruleRegistryService?: RuleRegistryPluginStartContract;
|
ruleRegistryService?: RuleRegistryPluginStartContract;
|
||||||
|
spacesService: SpacesServiceStart | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,6 +49,7 @@ export class OsqueryAppContextService {
|
||||||
private agentPolicyService: AgentPolicyServiceInterface | undefined;
|
private agentPolicyService: AgentPolicyServiceInterface | undefined;
|
||||||
private ruleRegistryService: RuleRegistryPluginStartContract | undefined;
|
private ruleRegistryService: RuleRegistryPluginStartContract | undefined;
|
||||||
private fleetActionsClient: FleetActionsClientInterface | undefined;
|
private fleetActionsClient: FleetActionsClientInterface | undefined;
|
||||||
|
private spacesService: SpacesServiceStart | undefined;
|
||||||
|
|
||||||
public start(dependencies: OsqueryAppContextServiceStartContract) {
|
public start(dependencies: OsqueryAppContextServiceStartContract) {
|
||||||
this.agentService = dependencies.agentService;
|
this.agentService = dependencies.agentService;
|
||||||
|
@ -55,6 +58,7 @@ export class OsqueryAppContextService {
|
||||||
this.agentPolicyService = dependencies.agentPolicyService;
|
this.agentPolicyService = dependencies.agentPolicyService;
|
||||||
this.ruleRegistryService = dependencies.ruleRegistryService;
|
this.ruleRegistryService = dependencies.ruleRegistryService;
|
||||||
this.fleetActionsClient = dependencies.createFleetActionsClient?.('osquery');
|
this.fleetActionsClient = dependencies.createFleetActionsClient?.('osquery');
|
||||||
|
this.spacesService = dependencies.spacesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
@ -83,6 +87,9 @@ export class OsqueryAppContextService {
|
||||||
public getFleetActionsClient(): FleetActionsClientInterface | undefined {
|
public getFleetActionsClient(): FleetActionsClientInterface | undefined {
|
||||||
return this.fleetActionsClient;
|
return this.fleetActionsClient;
|
||||||
}
|
}
|
||||||
|
public getActiveSpace(httpRequest: KibanaRequest): Promise<Space> | undefined {
|
||||||
|
return this.spacesService?.getActiveSpace(httpRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,6 +17,7 @@ export interface AgentSelection {
|
||||||
allAgentsSelected?: boolean;
|
allAgentsSelected?: boolean;
|
||||||
platformsSelected?: string[];
|
platformsSelected?: string[];
|
||||||
policiesSelected?: string[];
|
policiesSelected?: string[];
|
||||||
|
spaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PER_PAGE = 9000;
|
const PER_PAGE = 9000;
|
||||||
|
@ -90,7 +91,9 @@ export const parseAgentSelection = async (
|
||||||
policiesSelected = [],
|
policiesSelected = [],
|
||||||
agents = [],
|
agents = [],
|
||||||
} = agentSelection;
|
} = agentSelection;
|
||||||
const agentService = context.service.getAgentService()?.asInternalUser;
|
const agentService = context.service
|
||||||
|
.getAgentService()
|
||||||
|
?.asInternalScopedUser(agentSelection.spaceId);
|
||||||
const packagePolicyService = context.service.getPackagePolicyService();
|
const packagePolicyService = context.service.getPackagePolicyService();
|
||||||
const kueryFragments = ['status:online'];
|
const kueryFragments = ['status:online'];
|
||||||
|
|
||||||
|
@ -172,5 +175,16 @@ export const parseAgentSelection = async (
|
||||||
|
|
||||||
agents.forEach(addAgent);
|
agents.forEach(addAgent);
|
||||||
|
|
||||||
return Array.from(selectedAgents);
|
const selectedAgentsArray = Array.from(selectedAgents);
|
||||||
|
|
||||||
|
// validate if all selected agents are in current space. If not, getByIds will throw an error, caught by caller
|
||||||
|
try {
|
||||||
|
await agentService?.getByIds(selectedAgentsArray, { ignoreMissing: false });
|
||||||
|
|
||||||
|
return selectedAgentsArray;
|
||||||
|
} catch (error) {
|
||||||
|
context.logFactory.get().error(error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -101,6 +101,7 @@ export class TelemetryEventsSender {
|
||||||
pack_id: { type: 'keyword', _meta: { description: '', optional: true } },
|
pack_id: { type: 'keyword', _meta: { description: '', optional: true } },
|
||||||
pack_name: { type: 'keyword', _meta: { description: '', optional: true } },
|
pack_name: { type: 'keyword', _meta: { description: '', optional: true } },
|
||||||
pack_prebuilt: { type: 'boolean', _meta: { description: '', optional: true } },
|
pack_prebuilt: { type: 'boolean', _meta: { description: '', optional: true } },
|
||||||
|
space_id: { type: 'keyword', _meta: { description: '', optional: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,10 @@ import type { OsqueryAppContext } from './lib/osquery_app_context_services';
|
||||||
import { OsqueryAppContextService } from './lib/osquery_app_context_services';
|
import { OsqueryAppContextService } from './lib/osquery_app_context_services';
|
||||||
import type { ConfigType } from '../common/config';
|
import type { ConfigType } from '../common/config';
|
||||||
import { OSQUERY_INTEGRATION_NAME } from '../common';
|
import { OSQUERY_INTEGRATION_NAME } from '../common';
|
||||||
import { getPackagePolicyDeleteCallback } from './lib/fleet_integration';
|
import {
|
||||||
|
getPackagePolicyDeleteCallback,
|
||||||
|
getAgentPolicyPostUpdateCallback,
|
||||||
|
} from './lib/fleet_integration';
|
||||||
import { TelemetryEventsSender } from './lib/telemetry/sender';
|
import { TelemetryEventsSender } from './lib/telemetry/sender';
|
||||||
import { TelemetryReceiver } from './lib/telemetry/receiver';
|
import { TelemetryReceiver } from './lib/telemetry/receiver';
|
||||||
import { initializeTransformsIndices } from './create_indices/create_transforms_indices';
|
import { initializeTransformsIndices } from './create_indices/create_transforms_indices';
|
||||||
|
@ -82,6 +85,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
||||||
|
|
||||||
initSavedObjects(core.savedObjects);
|
initSavedObjects(core.savedObjects);
|
||||||
|
|
||||||
|
// TODO: We do not pass so client here.
|
||||||
this.createActionService = createActionService(osqueryContext);
|
this.createActionService = createActionService(osqueryContext);
|
||||||
|
|
||||||
core
|
core
|
||||||
|
@ -119,6 +123,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
||||||
config: this.config!,
|
config: this.config!,
|
||||||
logger: this.logger,
|
logger: this.logger,
|
||||||
registerIngestCallback,
|
registerIngestCallback,
|
||||||
|
spacesService: plugins.spaces?.spacesService,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.telemetryReceiver.start(core, this.osqueryAppContextService);
|
this.telemetryReceiver.start(core, this.osqueryAppContextService);
|
||||||
|
@ -192,6 +197,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
||||||
);
|
);
|
||||||
|
|
||||||
registerIngestCallback('packagePolicyPostDelete', getPackagePolicyDeleteCallback(client));
|
registerIngestCallback('packagePolicyPostDelete', getPackagePolicyDeleteCallback(client));
|
||||||
|
registerIngestCallback('agentPolicyPostUpdate', getAgentPolicyPostUpdateCallback(core));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { PLUGIN_ID } from '../../../common';
|
import { PLUGIN_ID } from '../../../common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
@ -33,12 +34,15 @@ export const getAgentDetailsRoute = (router: IRouter, osqueryContext: OsqueryApp
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
|
||||||
let agent;
|
let agent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
agent = await osqueryContext.service
|
agent = await osqueryContext.service
|
||||||
.getAgentService()
|
.getAgentService()
|
||||||
?.asInternalUser?.getAgent(request.params.id);
|
?.asInternalScopedUser(space?.id ?? DEFAULT_SPACE_ID)
|
||||||
|
?.getAgent(request.params.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return response.notFound();
|
return response.notFound();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,11 @@ import { satisfies } from 'semver';
|
||||||
import type { GetAgentPoliciesResponseItem, PackagePolicy } from '@kbn/fleet-plugin/common';
|
import type { GetAgentPoliciesResponseItem, PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common';
|
import { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
|
||||||
|
|
||||||
export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
|
@ -33,35 +34,33 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
validate: {},
|
validate: {},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
|
||||||
const agentService = osqueryContext.service.getAgentService();
|
const agentService = osqueryContext.service.getAgentService();
|
||||||
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
|
|
||||||
const { items: packagePolicies } = (await packagePolicyService?.list(
|
const { items: packagePolicies } = (await packagePolicyService?.list(spaceScopedClient, {
|
||||||
internalSavedObjectsClient,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
{
|
perPage: 1000,
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
page: 1,
|
||||||
perPage: 1000,
|
})) ?? { items: [] as PackagePolicy[] };
|
||||||
page: 1,
|
|
||||||
}
|
|
||||||
)) ?? { items: [] as PackagePolicy[] };
|
|
||||||
const supportedPackagePolicyIds = filter(packagePolicies, (packagePolicy) =>
|
const supportedPackagePolicyIds = filter(packagePolicies, (packagePolicy) =>
|
||||||
satisfies(packagePolicy.package?.version ?? '', '>=0.6.0')
|
satisfies(packagePolicy.package?.version ?? '', '>=0.6.0')
|
||||||
);
|
);
|
||||||
const agentPolicyIds = uniq(flatMap(supportedPackagePolicyIds, 'policy_ids'));
|
const agentPolicyIds = uniq(flatMap(supportedPackagePolicyIds, 'policy_ids'));
|
||||||
const agentPolicies = await agentPolicyService?.getByIds(
|
const agentPolicies = await agentPolicyService?.getByIds(spaceScopedClient, agentPolicyIds);
|
||||||
internalSavedObjectsClient,
|
|
||||||
agentPolicyIds
|
|
||||||
);
|
|
||||||
|
|
||||||
if (agentPolicies?.length) {
|
if (agentPolicies?.length) {
|
||||||
await pMap(
|
await pMap(
|
||||||
agentPolicies,
|
agentPolicies,
|
||||||
(agentPolicy: GetAgentPoliciesResponseItem) =>
|
(agentPolicy: GetAgentPoliciesResponseItem) =>
|
||||||
agentService?.asInternalUser
|
agentService
|
||||||
|
?.asInternalScopedUser(space?.id ?? DEFAULT_SPACE_ID)
|
||||||
.getAgentStatusForAgentPolicy(agentPolicy.id)
|
.getAgentStatusForAgentPolicy(agentPolicy.id)
|
||||||
.then(({ active: agentTotal }) => (agentPolicy.agents = agentTotal)),
|
.then(({ active: agentTotal }) => (agentPolicy.agents = agentTotal)),
|
||||||
{ concurrency: 10 }
|
{ concurrency: 10 }
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
import { buildRouteValidationWithZod } from '@kbn/zod-helpers';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { PLUGIN_ID } from '../../../common';
|
import { PLUGIN_ID } from '../../../common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
|
||||||
import { GetAgentPolicyRequestParams } from '../../../common/api';
|
import { GetAgentPolicyRequestParams } from '../../../common/api';
|
||||||
|
|
||||||
export const getAgentPolicyRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
export const getAgentPolicyRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
|
@ -34,12 +34,13 @@ export const getAgentPolicyRoute = (router: IRouter, osqueryContext: OsqueryAppC
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
const packageInfo = await osqueryContext.service
|
const packageInfo = await osqueryContext.service
|
||||||
.getAgentPolicyService()
|
.getAgentPolicyService()
|
||||||
?.get(internalSavedObjectsClient, request.params.id);
|
?.get(spaceScopedClient, request.params.id);
|
||||||
|
|
||||||
return response.ok({ body: { item: packageInfo } });
|
return response.ok({ body: { item: packageInfo } });
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import type { GetAgentStatusResponse } from '@kbn/fleet-plugin/common';
|
import type { GetAgentStatusResponse } from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
import type {
|
import type {
|
||||||
GetAgentStatusForAgentPolicyRequestParamsSchema,
|
GetAgentStatusForAgentPolicyRequestParamsSchema,
|
||||||
GetAgentStatusForAgentPolicyRequestQuerySchema,
|
GetAgentStatusForAgentPolicyRequestQuerySchema,
|
||||||
|
@ -51,9 +52,10 @@ export const getAgentStatusForAgentPolicyRoute = (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
const results = await osqueryContext.service
|
const results = await osqueryContext.service
|
||||||
.getAgentService()
|
.getAgentService()
|
||||||
?.asScoped(request)
|
?.asInternalScopedUser(space?.id ?? DEFAULT_SPACE_ID)
|
||||||
.getAgentStatusForAgentPolicy(request.query.policyId, request.query.kuery);
|
.getAgentStatusForAgentPolicy(request.query.policyId, request.query.kuery);
|
||||||
|
|
||||||
if (!results) {
|
if (!results) {
|
||||||
|
|
|
@ -12,8 +12,9 @@ import { filter, flatMap, mapKeys, uniq } from 'lodash';
|
||||||
import type { PackagePolicy } from '@kbn/fleet-plugin/server/types';
|
import type { PackagePolicy } from '@kbn/fleet-plugin/server/types';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
import type { SortResults } from '@elastic/elasticsearch/lib/api/types';
|
import type { SortResults } from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { processAggregations } from '../../../common/utils/aggregations';
|
import { processAggregations } from '../../../common/utils/aggregations';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
|
||||||
import { getAgentsRequestQuerySchema } from '../../../common/api';
|
import { getAgentsRequestQuerySchema } from '../../../common/api';
|
||||||
import type { GetAgentsRequestQuerySchema } from '../../../common/api';
|
import type { GetAgentsRequestQuerySchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
|
@ -54,29 +55,26 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex
|
||||||
getStatusSummary?: boolean;
|
getStatusSummary?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
||||||
|
|
||||||
const { items: packagePolicies } = (await packagePolicyService?.list(
|
const { items: packagePolicies } = (await packagePolicyService?.list(spaceScopedClient, {
|
||||||
internalSavedObjectsClient,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
{
|
perPage: 1000,
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
page: 1,
|
||||||
perPage: 1000,
|
})) ?? { items: [] as PackagePolicy[] };
|
||||||
page: 1,
|
|
||||||
}
|
|
||||||
)) ?? { items: [] as PackagePolicy[] };
|
|
||||||
const supportedPackagePolicyIds = filter(packagePolicies, (packagePolicy) =>
|
const supportedPackagePolicyIds = filter(packagePolicies, (packagePolicy) =>
|
||||||
satisfies(packagePolicy.package?.version ?? '', '>=0.6.0')
|
satisfies(packagePolicy.package?.version ?? '', '>=0.6.0')
|
||||||
);
|
);
|
||||||
const agentPolicyIds = uniq(flatMap(supportedPackagePolicyIds, 'policy_ids'));
|
const agentPolicyIds = uniq(flatMap(supportedPackagePolicyIds, 'policy_ids'));
|
||||||
|
|
||||||
const agentPolicies = await agentPolicyService?.getByIds(
|
const agentPolicies = await agentPolicyService?.getByIds(spaceScopedClient, agentPolicyIds);
|
||||||
internalSavedObjectsClient,
|
|
||||||
agentPolicyIds
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIND agents by policy_name
|
// FIND agents by policy_name
|
||||||
const policyNamePattern = /policy_name:([^ ]+)/;
|
const policyNamePattern = /policy_name:([^ ]+)/;
|
||||||
|
@ -101,32 +99,35 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex
|
||||||
const agentPolicyById = mapKeys(agentPolicies, 'id');
|
const agentPolicyById = mapKeys(agentPolicies, 'id');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
esAgents = await osqueryContext.service.getAgentService()?.asInternalUser.listAgents({
|
esAgents = await osqueryContext.service
|
||||||
page: query.page,
|
.getAgentService()
|
||||||
perPage: query.perPage,
|
?.asInternalScopedUser(space?.id ?? DEFAULT_SPACE_ID)
|
||||||
sortField: query.sortField,
|
.listAgents({
|
||||||
sortOrder: query.sortOrder,
|
page: query.page,
|
||||||
showUpgradeable: query.showUpgradeable,
|
perPage: query.perPage,
|
||||||
getStatusSummary: query.getStatusSummary,
|
sortField: query.sortField,
|
||||||
pitId: query.pitId,
|
sortOrder: query.sortOrder,
|
||||||
searchAfter: query.searchAfter,
|
showUpgradeable: query.showUpgradeable,
|
||||||
kuery,
|
getStatusSummary: query.getStatusSummary,
|
||||||
showAgentless: query.showAgentless,
|
pitId: query.pitId,
|
||||||
showInactive: query.showInactive,
|
searchAfter: query.searchAfter,
|
||||||
aggregations: {
|
kuery,
|
||||||
platforms: {
|
showAgentless: query.showAgentless,
|
||||||
terms: {
|
showInactive: query.showInactive,
|
||||||
field: 'local_metadata.os.platform',
|
aggregations: {
|
||||||
|
platforms: {
|
||||||
|
terms: {
|
||||||
|
field: 'local_metadata.os.platform',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
policies: {
|
||||||
|
terms: {
|
||||||
|
field: 'policy_id',
|
||||||
|
size: 2000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
policies: {
|
});
|
||||||
terms: {
|
|
||||||
field: 'policy_id',
|
|
||||||
size: 2000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return response.badRequest({ body: error });
|
return response.badRequest({ body: error });
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
|
||||||
|
|
||||||
export const getPackagePoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
export const getPackagePoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
|
@ -29,12 +29,14 @@ export const getPackagePoliciesRoute = (router: IRouter, osqueryContext: Osquery
|
||||||
validate: {},
|
validate: {},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
const kuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: ${OSQUERY_INTEGRATION_NAME}`;
|
const kuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: ${OSQUERY_INTEGRATION_NAME}`;
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
const policies = await packagePolicyService?.list(internalSavedObjectsClient, {
|
const policies = await packagePolicyService?.list(spaceScopedClient, {
|
||||||
kuery,
|
kuery,
|
||||||
perPage: 1000,
|
perPage: 1000,
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,7 +50,6 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const [coreStartServices] = await osqueryContext.getStartServices();
|
const [coreStartServices] = await osqueryContext.getStartServices();
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const soClient = coreContext.savedObjects.client;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
osquery: { writeLiveQueries, runSavedQueries },
|
osquery: { writeLiveQueries, runSavedQueries },
|
||||||
|
@ -115,13 +114,14 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
const { response: osqueryAction, fleetActionsCount } = await createActionHandler(
|
const { response: osqueryAction, fleetActionsCount } = await createActionHandler(
|
||||||
osqueryContext,
|
osqueryContext,
|
||||||
request.body,
|
request.body,
|
||||||
{
|
{
|
||||||
soClient,
|
|
||||||
metadata: { currentUser },
|
metadata: { currentUser },
|
||||||
alertData,
|
alertData,
|
||||||
|
space,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!fleetActionsCount) {
|
if (!fleetActionsCount) {
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { omit } from 'lodash';
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import type { FindLiveQueryRequestQuerySchema } from '../../../common/api';
|
import type { FindLiveQueryRequestQuerySchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
|
@ -24,7 +26,10 @@ import { OsqueryQueries } from '../../../common/search_strategy';
|
||||||
import { findLiveQueryRequestQuerySchema } from '../../../common/api';
|
import { findLiveQueryRequestQuerySchema } from '../../../common/api';
|
||||||
import { generateTablePaginationOptions } from '../../../common/utils/build_query';
|
import { generateTablePaginationOptions } from '../../../common/utils/build_query';
|
||||||
|
|
||||||
export const findLiveQueryRoute = (router: IRouter<DataRequestHandlerContext>) => {
|
export const findLiveQueryRoute = (
|
||||||
|
router: IRouter<DataRequestHandlerContext>,
|
||||||
|
osqueryContext: OsqueryAppContext
|
||||||
|
) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
.get({
|
.get({
|
||||||
access: 'public',
|
access: 'public',
|
||||||
|
@ -52,6 +57,10 @@ export const findLiveQueryRoute = (router: IRouter<DataRequestHandlerContext>) =
|
||||||
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const spaceId = osqueryContext?.service?.getActiveSpace
|
||||||
|
? (await osqueryContext.service.getActiveSpace(request))?.id || DEFAULT_SPACE_ID
|
||||||
|
: DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const search = await context.search;
|
const search = await context.search;
|
||||||
const res = await lastValueFrom(
|
const res = await lastValueFrom(
|
||||||
search.search<ActionsRequestOptions, ActionsStrategyResponse>(
|
search.search<ActionsRequestOptions, ActionsStrategyResponse>(
|
||||||
|
@ -66,6 +75,7 @@ export const findLiveQueryRoute = (router: IRouter<DataRequestHandlerContext>) =
|
||||||
direction: (request.query.sortOrder ?? 'desc') as Direction,
|
direction: (request.query.sortOrder ?? 'desc') as Direction,
|
||||||
field: request.query.sort ?? 'created_at',
|
field: request.query.sort ?? 'created_at',
|
||||||
},
|
},
|
||||||
|
spaceId,
|
||||||
},
|
},
|
||||||
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { every, map, mapKeys, pick, reduce } from 'lodash';
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
import { lastValueFrom, zip } from 'rxjs';
|
import { lastValueFrom, zip } from 'rxjs';
|
||||||
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
import type {
|
import type {
|
||||||
GetLiveQueryDetailsRequestParamsSchema,
|
GetLiveQueryDetailsRequestParamsSchema,
|
||||||
GetLiveQueryDetailsRequestQuerySchema,
|
GetLiveQueryDetailsRequestQuerySchema,
|
||||||
|
@ -28,8 +29,12 @@ import {
|
||||||
getLiveQueryDetailsRequestParamsSchema,
|
getLiveQueryDetailsRequestParamsSchema,
|
||||||
getLiveQueryDetailsRequestQuerySchema,
|
getLiveQueryDetailsRequestQuerySchema,
|
||||||
} from '../../../common/api';
|
} from '../../../common/api';
|
||||||
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
|
||||||
export const getLiveQueryDetailsRoute = (router: IRouter<DataRequestHandlerContext>) => {
|
export const getLiveQueryDetailsRoute = (
|
||||||
|
router: IRouter<DataRequestHandlerContext>,
|
||||||
|
osqueryContext: OsqueryAppContext
|
||||||
|
) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
.get({
|
.get({
|
||||||
access: 'public',
|
access: 'public',
|
||||||
|
@ -60,12 +65,17 @@ export const getLiveQueryDetailsRoute = (router: IRouter<DataRequestHandlerConte
|
||||||
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const spaceId = osqueryContext?.service?.getActiveSpace
|
||||||
|
? (await osqueryContext.service.getActiveSpace(request))?.id || DEFAULT_SPACE_ID
|
||||||
|
: DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const search = await context.search;
|
const search = await context.search;
|
||||||
const { actionDetails } = await lastValueFrom(
|
const { actionDetails } = await lastValueFrom(
|
||||||
search.search<ActionDetailsRequestOptions, ActionDetailsStrategyResponse>(
|
search.search<ActionDetailsRequestOptions, ActionDetailsStrategyResponse>(
|
||||||
{
|
{
|
||||||
actionId: request.params.id,
|
actionId: request.params.id,
|
||||||
factoryQueryType: OsqueryQueries.actionDetails,
|
factoryQueryType: OsqueryQueries.actionDetails,
|
||||||
|
spaceId,
|
||||||
},
|
},
|
||||||
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { map } from 'lodash';
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
import { lastValueFrom, zip } from 'rxjs';
|
import { lastValueFrom, zip } from 'rxjs';
|
||||||
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
import type { DataRequestHandlerContext } from '@kbn/data-plugin/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
import type {
|
import type {
|
||||||
GetLiveQueryResultsRequestQuerySchema,
|
GetLiveQueryResultsRequestQuerySchema,
|
||||||
GetLiveQueryResultsRequestParamsSchema,
|
GetLiveQueryResultsRequestParamsSchema,
|
||||||
|
@ -30,8 +31,12 @@ import {
|
||||||
getLiveQueryResultsRequestParamsSchema,
|
getLiveQueryResultsRequestParamsSchema,
|
||||||
getLiveQueryResultsRequestQuerySchema,
|
getLiveQueryResultsRequestQuerySchema,
|
||||||
} from '../../../common/api';
|
} from '../../../common/api';
|
||||||
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
|
||||||
export const getLiveQueryResultsRoute = (router: IRouter<DataRequestHandlerContext>) => {
|
export const getLiveQueryResultsRoute = (
|
||||||
|
router: IRouter<DataRequestHandlerContext>,
|
||||||
|
osqueryContext: OsqueryAppContext
|
||||||
|
) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
.get({
|
.get({
|
||||||
access: 'public',
|
access: 'public',
|
||||||
|
@ -62,6 +67,10 @@ export const getLiveQueryResultsRoute = (router: IRouter<DataRequestHandlerConte
|
||||||
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const spaceId = osqueryContext?.service?.getActiveSpace
|
||||||
|
? (await osqueryContext.service.getActiveSpace(request))?.id || DEFAULT_SPACE_ID
|
||||||
|
: DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const search = await context.search;
|
const search = await context.search;
|
||||||
const { actionDetails } = await lastValueFrom(
|
const { actionDetails } = await lastValueFrom(
|
||||||
search.search<ActionDetailsRequestOptions, ActionDetailsStrategyResponse>(
|
search.search<ActionDetailsRequestOptions, ActionDetailsStrategyResponse>(
|
||||||
|
@ -69,11 +78,16 @@ export const getLiveQueryResultsRoute = (router: IRouter<DataRequestHandlerConte
|
||||||
actionId: request.params.id,
|
actionId: request.params.id,
|
||||||
kuery: request.query.kuery,
|
kuery: request.query.kuery,
|
||||||
factoryQueryType: OsqueryQueries.actionDetails,
|
factoryQueryType: OsqueryQueries.actionDetails,
|
||||||
|
spaceId,
|
||||||
},
|
},
|
||||||
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
{ abortSignal, strategy: 'osquerySearchStrategy' }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!actionDetails) {
|
||||||
|
return response.notFound({ body: { message: 'Action not found' } });
|
||||||
|
}
|
||||||
|
|
||||||
const queries = actionDetails?._source?.queries;
|
const queries = actionDetails?._source?.queries;
|
||||||
|
|
||||||
await lastValueFrom(
|
await lastValueFrom(
|
||||||
|
|
|
@ -17,8 +17,8 @@ export const initLiveQueryRoutes = (
|
||||||
router: IRouter<DataRequestHandlerContext>,
|
router: IRouter<DataRequestHandlerContext>,
|
||||||
context: OsqueryAppContext
|
context: OsqueryAppContext
|
||||||
) => {
|
) => {
|
||||||
findLiveQueryRoute(router);
|
findLiveQueryRoute(router, context);
|
||||||
createLiveQueryRoute(router, context);
|
createLiveQueryRoute(router, context);
|
||||||
getLiveQueryDetailsRoute(router);
|
getLiveQueryDetailsRoute(router, context);
|
||||||
getLiveQueryResultsRoute(router);
|
getLiveQueryResultsRoute(router, context);
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,8 @@ import {
|
||||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||||
} from '@kbn/fleet-plugin/common';
|
} from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import type { CreatePackRequestBodySchema } from '../../../common/api';
|
import type { CreatePackRequestBodySchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
|
@ -28,7 +30,7 @@ import {
|
||||||
findMatchingShards,
|
findMatchingShards,
|
||||||
getInitialPolicies,
|
getInitialPolicies,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { convertShardsToArray, getInternalSavedObjectsClient } from '../utils';
|
import { convertShardsToArray } from '../utils';
|
||||||
import type { PackSavedObject } from '../../common/types';
|
import type { PackSavedObject } from '../../common/types';
|
||||||
import type { PackResponseData } from './types';
|
import type { PackResponseData } from './types';
|
||||||
import { createPackRequestBodySchema } from '../../../common/api';
|
import { createPackRequestBodySchema } from '../../../common/api';
|
||||||
|
@ -61,10 +63,12 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
||||||
|
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
|
@ -72,7 +76,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const { name, description, queries, enabled, policy_ids, shards = {} } = request.body;
|
const { name, description, queries, enabled, policy_ids, shards = {} } = request.body;
|
||||||
const conflictingEntries = await savedObjectsClient.find({
|
const conflictingEntries = await spaceScopedClient.find({
|
||||||
type: packSavedObjectType,
|
type: packSavedObjectType,
|
||||||
filter: `${packSavedObjectType}.attributes.name: "${name}"`,
|
filter: `${packSavedObjectType}.attributes.name: "${name}"`,
|
||||||
});
|
});
|
||||||
|
@ -84,14 +88,11 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
return response.conflict({ body: `Pack with name "${name}" already exists.` });
|
return response.conflict({ body: `Pack with name "${name}" already exists.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { items: packagePolicies } = (await packagePolicyService?.list(
|
const { items: packagePolicies } = (await packagePolicyService?.list(spaceScopedClient, {
|
||||||
internalSavedObjectsClient,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
{
|
perPage: 1000,
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
page: 1,
|
||||||
perPage: 1000,
|
})) ?? { items: [] };
|
||||||
page: 1,
|
|
||||||
}
|
|
||||||
)) ?? { items: [] };
|
|
||||||
|
|
||||||
const { policiesList, invalidPolicies } = getInitialPolicies(
|
const { policiesList, invalidPolicies } = getInitialPolicies(
|
||||||
packagePolicies,
|
packagePolicies,
|
||||||
|
@ -104,10 +105,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentPolicies = await agentPolicyService?.getByIds(
|
const agentPolicies = await agentPolicyService?.getByIds(spaceScopedClient, policiesList);
|
||||||
internalSavedObjectsClient,
|
|
||||||
policiesList
|
|
||||||
);
|
|
||||||
|
|
||||||
const policyShards = findMatchingShards(agentPolicies, shards);
|
const policyShards = findMatchingShards(agentPolicies, shards);
|
||||||
|
|
||||||
|
@ -119,7 +117,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
|
type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const packSO = await savedObjectsClient.create<PackSavedObjectLimited>(
|
const packSO = await spaceScopedClient.create<PackSavedObjectLimited>(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
@ -146,7 +144,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
);
|
);
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { has, filter, unset } from 'lodash';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
|
||||||
import type { DeletePacksRequestParamsSchema } from '../../../common/api';
|
import type { DeletePacksRequestParamsSchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
|
@ -19,6 +19,7 @@ import { PLUGIN_ID } from '../../../common';
|
||||||
import { packSavedObjectType } from '../../../common/types';
|
import { packSavedObjectType } from '../../../common/types';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { deletePacksRequestParamsSchema } from '../../../common/api';
|
import { deletePacksRequestParamsSchema } from '../../../common/api';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
|
|
||||||
export const deletePackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
export const deletePackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
|
@ -46,22 +47,23 @@ export const deletePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
osqueryContext,
|
||||||
osqueryContext.getStartServices
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
|
|
||||||
const currentPackSO = await savedObjectsClient.get<{ name: string }>(
|
const currentPackSO = await spaceScopedClient.get<{ name: string }>(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
request.params.id
|
request.params.id
|
||||||
);
|
);
|
||||||
|
|
||||||
await savedObjectsClient.delete(packSavedObjectType, request.params.id, {
|
await spaceScopedClient.delete(packSavedObjectType, request.params.id, {
|
||||||
refresh: 'wait_for',
|
refresh: 'wait_for',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { items: packagePolicies } = (await packagePolicyService?.list(savedObjectsClient, {
|
const { items: packagePolicies } = (await packagePolicyService?.list(spaceScopedClient, {
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
perPage: 1000,
|
perPage: 1000,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -76,7 +78,7 @@ export const deletePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
currentPackagePolicies.map((packagePolicy) =>
|
currentPackagePolicies.map((packagePolicy) =>
|
||||||
packagePolicyService?.update(
|
packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce(packagePolicy, (draft) => {
|
produce(packagePolicy, (draft) => {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { filter, map, omit } from 'lodash';
|
||||||
|
|
||||||
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import type { FindPacksRequestQuerySchema } from '../../../common/api';
|
import type { FindPacksRequestQuerySchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
|
@ -17,8 +19,9 @@ import { PLUGIN_ID } from '../../../common';
|
||||||
import type { PackSavedObject } from '../../common/types';
|
import type { PackSavedObject } from '../../common/types';
|
||||||
import type { PackResponseData } from './types';
|
import type { PackResponseData } from './types';
|
||||||
import { findPacksRequestQuerySchema } from '../../../common/api';
|
import { findPacksRequestQuerySchema } from '../../../common/api';
|
||||||
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
|
||||||
export const findPackRoute = (router: IRouter) => {
|
export const findPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
.get({
|
.get({
|
||||||
access: 'public',
|
access: 'public',
|
||||||
|
@ -42,10 +45,12 @@ export const findPackRoute = (router: IRouter) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
const soClientResponse = await savedObjectsClient.find<PackSavedObject>({
|
const soClientResponse = await spaceScopedClient.find<PackSavedObject>({
|
||||||
type: packSavedObjectType,
|
type: packSavedObjectType,
|
||||||
page: request.query.page ?? 1,
|
page: request.query.page ?? 1,
|
||||||
perPage: request.query.pageSize ?? 20,
|
perPage: request.query.pageSize ?? 20,
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { updatePackRoute } from './update_pack_route';
|
||||||
export const initPackRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
export const initPackRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||||
createPackRoute(router, context);
|
createPackRoute(router, context);
|
||||||
deletePackRoute(router, context);
|
deletePackRoute(router, context);
|
||||||
findPackRoute(router);
|
findPackRoute(router, context);
|
||||||
readPackRoute(router);
|
readPackRoute(router, context);
|
||||||
updatePackRoute(router, context);
|
updatePackRoute(router, context);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import { filter, map } from 'lodash';
|
import { filter, map } from 'lodash';
|
||||||
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import type { ReadPacksRequestParamsSchema } from '../../../common/api';
|
import type { ReadPacksRequestParamsSchema } from '../../../common/api';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
|
@ -19,8 +21,9 @@ import { convertSOQueriesToPack } from './utils';
|
||||||
import { convertShardsToObject } from '../utils';
|
import { convertShardsToObject } from '../utils';
|
||||||
import type { ReadPackResponseData } from './types';
|
import type { ReadPackResponseData } from './types';
|
||||||
import { readPacksRequestParamsSchema } from '../../../common/api';
|
import { readPacksRequestParamsSchema } from '../../../common/api';
|
||||||
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
|
|
||||||
export const readPackRoute = (router: IRouter) => {
|
export const readPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
.get({
|
.get({
|
||||||
access: 'public',
|
access: 'public',
|
||||||
|
@ -44,11 +47,13 @@ export const readPackRoute = (router: IRouter) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
const { attributes, references, id, ...rest } =
|
const { attributes, references, id, ...rest } =
|
||||||
await savedObjectsClient.get<PackSavedObject>(packSavedObjectType, request.params.id);
|
await spaceScopedClient.get<PackSavedObject>(packSavedObjectType, request.params.id);
|
||||||
|
|
||||||
const policyIds = map(
|
const policyIds = map(
|
||||||
filter(references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]),
|
filter(references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]),
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
} from '@kbn/fleet-plugin/common';
|
} from '@kbn/fleet-plugin/common';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import type {
|
import type {
|
||||||
UpdatePacksRequestParamsSchema,
|
UpdatePacksRequestParamsSchema,
|
||||||
UpdatePacksRequestBodySchema,
|
UpdatePacksRequestBodySchema,
|
||||||
|
@ -34,7 +35,7 @@ import {
|
||||||
findMatchingShards,
|
findMatchingShards,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
import { convertShardsToArray, getInternalSavedObjectsClient } from '../utils';
|
import { convertShardsToArray } from '../utils';
|
||||||
import type { PackSavedObject } from '../../common/types';
|
import type { PackSavedObject } from '../../common/types';
|
||||||
import type { PackResponseData } from './types';
|
import type { PackResponseData } from './types';
|
||||||
import { updatePacksRequestBodySchema, updatePacksRequestParamsSchema } from '../../../common/api';
|
import { updatePacksRequestBodySchema, updatePacksRequestParamsSchema } from '../../../common/api';
|
||||||
|
@ -69,10 +70,12 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
const esClient = coreContext.elasticsearch.client.asCurrentUser;
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
||||||
|
@ -80,13 +83,13 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const { name, description, queries, enabled, policy_ids, shards = {} } = request.body;
|
const { name, description, queries, enabled, policy_ids, shards = {} } = request.body;
|
||||||
|
|
||||||
const currentPackSO = await savedObjectsClient.get<{ name: string; enabled: boolean }>(
|
const currentPackSO = await spaceScopedClient.get<{ name: string; enabled: boolean }>(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
request.params.id
|
request.params.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
const conflictingEntries = await savedObjectsClient.find<PackSavedObject>({
|
const conflictingEntries = await spaceScopedClient.find<PackSavedObject>({
|
||||||
type: packSavedObjectType,
|
type: packSavedObjectType,
|
||||||
filter: `${packSavedObjectType}.attributes.name: "${name}"`,
|
filter: `${packSavedObjectType}.attributes.name: "${name}"`,
|
||||||
});
|
});
|
||||||
|
@ -101,14 +104,11 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { items: packagePolicies } = (await packagePolicyService?.list(
|
const { items: packagePolicies } = (await packagePolicyService?.list(spaceScopedClient, {
|
||||||
internalSavedObjectsClient,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
{
|
perPage: 1000,
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
page: 1,
|
||||||
perPage: 1000,
|
})) ?? { items: [] };
|
||||||
page: 1,
|
|
||||||
}
|
|
||||||
)) ?? { items: [] };
|
|
||||||
const currentPackagePolicies = filter(packagePolicies, (packagePolicy) =>
|
const currentPackagePolicies = filter(packagePolicies, (packagePolicy) =>
|
||||||
has(
|
has(
|
||||||
packagePolicy,
|
packagePolicy,
|
||||||
|
@ -128,10 +128,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentPolicies = await agentPolicyService?.getByIds(
|
const agentPolicies = await agentPolicyService?.getByIds(spaceScopedClient, policiesList);
|
||||||
internalSavedObjectsClient,
|
|
||||||
policiesList
|
|
||||||
);
|
|
||||||
|
|
||||||
const policyShards = findMatchingShards(agentPolicies, shards);
|
const policyShards = findMatchingShards(agentPolicies, shards);
|
||||||
|
|
||||||
|
@ -158,7 +155,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
|
|
||||||
const references = getUpdatedReferences();
|
const references = getUpdatedReferences();
|
||||||
|
|
||||||
await savedObjectsClient.update<PackSavedObject>(
|
await spaceScopedClient.update<PackSavedObject>(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
request.params.id,
|
request.params.id,
|
||||||
{
|
{
|
||||||
|
@ -180,7 +177,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
filter(currentPackSO.references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]),
|
filter(currentPackSO.references, ['type', LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]),
|
||||||
'id'
|
'id'
|
||||||
);
|
);
|
||||||
const updatedPackSO = await savedObjectsClient.get<PackSavedObject>(
|
const updatedPackSO = await spaceScopedClient.get<PackSavedObject>(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
request.params.id
|
request.params.id
|
||||||
);
|
);
|
||||||
|
@ -203,7 +200,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
);
|
);
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
@ -235,7 +232,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
if (!packagePolicy) return;
|
if (!packagePolicy) return;
|
||||||
|
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
@ -266,7 +263,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
);
|
);
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
@ -290,7 +287,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
);
|
);
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
@ -326,7 +323,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte
|
||||||
|
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce<PackagePolicy>(packagePolicy, (draft) => {
|
produce<PackagePolicy>(packagePolicy, (draft) => {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import { isEmpty, pickBy, some, isBoolean, isNumber } from 'lodash';
|
import { isEmpty, pickBy, some, isBoolean, isNumber } from 'lodash';
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import type { CreateSavedQueryRequestSchemaDecoded } from '../../../common/api';
|
import type { CreateSavedQueryRequestSchemaDecoded } from '../../../common/api';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import type { SavedQueryResponse } from './types';
|
import type { SavedQueryResponse } from './types';
|
||||||
|
@ -43,7 +44,10 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
@ -61,7 +65,7 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
|
|
||||||
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
||||||
|
|
||||||
const conflictingEntries = await savedObjectsClient.find<SavedQuerySavedObject>({
|
const conflictingEntries = await spaceScopedClient.find<SavedQuerySavedObject>({
|
||||||
type: savedQuerySavedObjectType,
|
type: savedQuerySavedObjectType,
|
||||||
filter: `${savedQuerySavedObjectType}.attributes.id: "${id}"`,
|
filter: `${savedQuerySavedObjectType}.attributes.id: "${id}"`,
|
||||||
});
|
});
|
||||||
|
@ -73,7 +77,7 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
return response.conflict({ body: `Saved query with id "${id}" already exists.` });
|
return response.conflict({ body: `Saved query with id "${id}" already exists.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedQuerySO = await savedObjectsClient.create(
|
const savedQuerySO = await spaceScopedClient.create(
|
||||||
savedQuerySavedObjectType,
|
savedQuerySavedObjectType,
|
||||||
pickBy(
|
pickBy(
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { PLUGIN_ID } from '../../../common';
|
import { PLUGIN_ID } from '../../../common';
|
||||||
|
@ -39,18 +41,25 @@ export const deleteSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
const spaceId = space?.id ?? DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const isPrebuilt = await isSavedQueryPrebuilt(
|
const isPrebuilt = await isSavedQueryPrebuilt(
|
||||||
osqueryContext.service.getPackageService()?.asInternalUser,
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
request.params.id
|
request.params.id,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
);
|
);
|
||||||
if (isPrebuilt) {
|
if (isPrebuilt) {
|
||||||
return response.conflict({ body: `Elastic prebuilt Saved query cannot be deleted.` });
|
return response.conflict({ body: `Elastic prebuilt Saved query cannot be deleted.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
await savedObjectsClient.delete(savedQuerySavedObjectType, request.params.id, {
|
await spaceScopedClient.delete(savedQuerySavedObjectType, request.params.id, {
|
||||||
refresh: 'wait_for',
|
refresh: 'wait_for',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import type { SavedQueryResponse } from './types';
|
import type { SavedQueryResponse } from './types';
|
||||||
|
@ -44,11 +46,16 @@ export const findSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
const spaceId = space?.id ?? DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedQueries = await savedObjectsClient.find<SavedQuerySavedObject>({
|
const savedQueries = await spaceScopedClient.find<SavedQuerySavedObject>({
|
||||||
type: savedQuerySavedObjectType,
|
type: savedQuerySavedObjectType,
|
||||||
page: request.query.page || 1,
|
page: request.query.page || 1,
|
||||||
perPage: request.query.pageSize,
|
perPage: request.query.pageSize,
|
||||||
|
@ -57,14 +64,22 @@ export const findSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC
|
||||||
});
|
});
|
||||||
|
|
||||||
const prebuiltSavedQueriesMap = await getInstalledSavedQueriesMap(
|
const prebuiltSavedQueriesMap = await getInstalledSavedQueriesMap(
|
||||||
osqueryContext.service.getPackageService()?.asInternalUser
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
);
|
);
|
||||||
const savedObjects: SavedQueryResponse[] = savedQueries.saved_objects.map(
|
const savedObjects: SavedQueryResponse[] = savedQueries.saved_objects.map(
|
||||||
(savedObject) => {
|
(savedObject) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const ecs_mapping = savedObject.attributes.ecs_mapping;
|
const ecs_mapping = savedObject.attributes.ecs_mapping;
|
||||||
|
|
||||||
savedObject.attributes.prebuilt = !!prebuiltSavedQueriesMap[savedObject.id];
|
const prebuiltById = savedObject.id && prebuiltSavedQueriesMap[savedObject.id];
|
||||||
|
const prebuiltByOriginId =
|
||||||
|
!prebuiltById && savedObject.originId
|
||||||
|
? prebuiltSavedQueriesMap[savedObject.originId]
|
||||||
|
: false;
|
||||||
|
|
||||||
|
savedObject.attributes.prebuilt = !!(prebuiltById || prebuiltByOriginId);
|
||||||
|
|
||||||
if (ecs_mapping) {
|
if (ecs_mapping) {
|
||||||
// @ts-expect-error update types
|
// @ts-expect-error update types
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import type { SavedQueryResponse } from './types';
|
import type { SavedQueryResponse } from './types';
|
||||||
|
@ -42,10 +44,15 @@ export const readSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
const savedQuery = await savedObjectsClient.get<SavedQuerySavedObject>(
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
const spaceId = space?.id ?? DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
|
const savedQuery = await spaceScopedClient.get<SavedQuerySavedObject>(
|
||||||
savedQuerySavedObjectType,
|
savedQuerySavedObjectType,
|
||||||
request.params.id
|
request.params.id
|
||||||
);
|
);
|
||||||
|
@ -57,11 +64,25 @@ export const readSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
savedQuery.attributes.prebuilt = await isSavedQueryPrebuilt(
|
const prebuiltById = await isSavedQueryPrebuilt(
|
||||||
osqueryContext.service.getPackageService()?.asInternalUser,
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
savedQuery.id
|
savedQuery.id,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const prebuiltByOriginId =
|
||||||
|
!prebuiltById && savedQuery.originId
|
||||||
|
? await isSavedQueryPrebuilt(
|
||||||
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
|
savedQuery.originId,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
savedQuery.attributes.prebuilt = prebuiltById || prebuiltByOriginId;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
created_at: createdAt,
|
created_at: createdAt,
|
||||||
created_by: createdBy,
|
created_by: createdBy,
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
import { filter, some } from 'lodash';
|
import { filter, some } from 'lodash';
|
||||||
|
|
||||||
import type { IRouter } from '@kbn/core/server';
|
import type { IRouter } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
import { buildRouteValidation } from '../../utils/build_validation/route_validation';
|
||||||
import { API_VERSIONS } from '../../../common/constants';
|
import { API_VERSIONS } from '../../../common/constants';
|
||||||
import { isSavedQueryPrebuilt } from './utils';
|
import { isSavedQueryPrebuilt } from './utils';
|
||||||
|
@ -54,7 +56,14 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
},
|
},
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const savedObjectsClient = coreContext.savedObjects.client;
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
|
osqueryContext,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
const spaceId = space?.id ?? DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
const currentUser = coreContext.security.authc.getCurrentUser()?.username;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -73,14 +82,16 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
|
|
||||||
const isPrebuilt = await isSavedQueryPrebuilt(
|
const isPrebuilt = await isSavedQueryPrebuilt(
|
||||||
osqueryContext.service.getPackageService()?.asInternalUser,
|
osqueryContext.service.getPackageService()?.asInternalUser,
|
||||||
request.params.id
|
request.params.id,
|
||||||
|
spaceScopedClient,
|
||||||
|
spaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isPrebuilt) {
|
if (isPrebuilt) {
|
||||||
return response.conflict({ body: `Elastic prebuilt Saved query cannot be updated.` });
|
return response.conflict({ body: `Elastic prebuilt Saved query cannot be updated.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const conflictingEntries = await savedObjectsClient.find<{ id: string }>({
|
const conflictingEntries = await spaceScopedClient.find<{ id: string }>({
|
||||||
type: savedQuerySavedObjectType,
|
type: savedQuerySavedObjectType,
|
||||||
filter: `${savedQuerySavedObjectType}.attributes.id: "${id}"`,
|
filter: `${savedQuerySavedObjectType}.attributes.id: "${id}"`,
|
||||||
});
|
});
|
||||||
|
@ -97,7 +108,7 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp
|
||||||
return response.conflict({ body: `Saved query with id "${id}" already exists.` });
|
return response.conflict({ body: `Saved query with id "${id}" already exists.` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedSavedQuerySO = await savedObjectsClient.update(
|
const updatedSavedQuerySO = await spaceScopedClient.update(
|
||||||
savedQuerySavedObjectType,
|
savedQuerySavedObjectType,
|
||||||
request.params.id,
|
request.params.id,
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,374 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||||
|
import type { PackageClient } from '@kbn/fleet-plugin/server';
|
||||||
|
import type { KibanaAssetReference, KibanaSavedObjectType } from '@kbn/fleet-plugin/common';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import {
|
||||||
|
getInstalledSavedQueriesMap,
|
||||||
|
getPrebuiltSavedQueryIds,
|
||||||
|
isSavedQueryPrebuilt,
|
||||||
|
} from './utils';
|
||||||
|
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||||
|
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||||
|
|
||||||
|
describe('saved query utils', () => {
|
||||||
|
const mockPackageService = {
|
||||||
|
getInstallation: jest.fn(),
|
||||||
|
} as unknown as PackageClient;
|
||||||
|
|
||||||
|
const mockSavedObjectsClient = {} as SavedObjectsClientContract;
|
||||||
|
|
||||||
|
const mockSavedQueryAsset: KibanaAssetReference = {
|
||||||
|
id: 'saved-query-1',
|
||||||
|
type: savedQuerySavedObjectType as KibanaSavedObjectType,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockOtherAsset: KibanaAssetReference = {
|
||||||
|
id: 'other-asset-1',
|
||||||
|
type: 'other-type' as KibanaSavedObjectType,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getInstalledSavedQueriesMap', () => {
|
||||||
|
it('should return empty object when no installation found', async () => {
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({});
|
||||||
|
expect(mockPackageService.getInstallation).toHaveBeenCalledWith(
|
||||||
|
OSQUERY_INTEGRATION_NAME,
|
||||||
|
mockSavedObjectsClient
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty object when packageService is undefined', async () => {
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
undefined,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return saved queries map for default space', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: DEFAULT_SPACE_ID,
|
||||||
|
installed_kibana: [mockSavedQueryAsset, mockOtherAsset],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
'saved-query-1': mockSavedQueryAsset,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return saved queries map for default space when installed_kibana_space_id is undefined', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana: [mockSavedQueryAsset, mockOtherAsset],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
'saved-query-1': mockSavedQueryAsset,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return saved queries map for additional space', async () => {
|
||||||
|
const customSpaceId = 'custom-space';
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: 'other-space',
|
||||||
|
installed_kibana: [mockOtherAsset],
|
||||||
|
additional_spaces_installed_kibana: {
|
||||||
|
[customSpaceId]: [mockSavedQueryAsset],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
customSpaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
'saved-query-1': mockSavedQueryAsset,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty object when space is not found in additional spaces', async () => {
|
||||||
|
const customSpaceId = 'custom-space';
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: 'yet-another-space',
|
||||||
|
installed_kibana: [mockSavedQueryAsset],
|
||||||
|
additional_spaces_installed_kibana: {
|
||||||
|
'different-space': [mockSavedQueryAsset],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
customSpaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out non-saved-query assets', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: DEFAULT_SPACE_ID,
|
||||||
|
installed_kibana: [
|
||||||
|
mockSavedQueryAsset,
|
||||||
|
mockOtherAsset,
|
||||||
|
{ id: 'saved-query-2', type: savedQuerySavedObjectType },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getInstalledSavedQueriesMap(
|
||||||
|
mockPackageService,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
'saved-query-1': mockSavedQueryAsset,
|
||||||
|
'saved-query-2': { id: 'saved-query-2', type: savedQuerySavedObjectType },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPrebuiltSavedQueryIds', () => {
|
||||||
|
it('should return empty array when no installation found', async () => {
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await getPrebuiltSavedQueryIds(mockPackageService);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
expect(mockPackageService.getInstallation).toHaveBeenCalledWith(OSQUERY_INTEGRATION_NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array when packageService is undefined', async () => {
|
||||||
|
const result = await getPrebuiltSavedQueryIds(undefined);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return saved query IDs from installation', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana: [
|
||||||
|
mockSavedQueryAsset,
|
||||||
|
mockOtherAsset,
|
||||||
|
{ id: 'saved-query-2', type: savedQuerySavedObjectType },
|
||||||
|
{ id: 'saved-query-3', type: savedQuerySavedObjectType },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getPrebuiltSavedQueryIds(mockPackageService);
|
||||||
|
|
||||||
|
expect(result).toEqual(['saved-query-1', 'saved-query-2', 'saved-query-3']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out non-saved-query assets', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana: [
|
||||||
|
mockOtherAsset,
|
||||||
|
{ id: 'another-asset', type: 'another-type' },
|
||||||
|
mockSavedQueryAsset,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await getPrebuiltSavedQueryIds(mockPackageService);
|
||||||
|
|
||||||
|
expect(result).toEqual(['saved-query-1']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSavedQueryPrebuilt', () => {
|
||||||
|
const savedQueryId = 'test-saved-query';
|
||||||
|
|
||||||
|
it('should return false when no installation found', async () => {
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(mockPackageService.getInstallation).toHaveBeenCalledWith(
|
||||||
|
OSQUERY_INTEGRATION_NAME,
|
||||||
|
mockSavedObjectsClient
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when packageService is undefined', async () => {
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
undefined,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
'default'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when saved query is found in default space', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: DEFAULT_SPACE_ID,
|
||||||
|
installed_kibana: [{ id: savedQueryId, type: savedQuerySavedObjectType }, mockOtherAsset],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when saved query is found in default space and installed_kibana_space_id is undefined', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana: [{ id: savedQueryId, type: savedQuerySavedObjectType }, mockOtherAsset],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when saved query is found in additional space', async () => {
|
||||||
|
const customSpaceId = 'custom-space';
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: 'other-space',
|
||||||
|
installed_kibana: [mockOtherAsset],
|
||||||
|
additional_spaces_installed_kibana: {
|
||||||
|
[customSpaceId]: [{ id: savedQueryId, type: savedQuerySavedObjectType }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
customSpaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when saved query is not found in space', async () => {
|
||||||
|
const customSpaceId = 'custom-space';
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: 'yet-another-space',
|
||||||
|
installed_kibana: [{ id: 'different-query', type: savedQuerySavedObjectType }],
|
||||||
|
additional_spaces_installed_kibana: {
|
||||||
|
'different-space': [{ id: 'another-query', type: savedQuerySavedObjectType }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
customSpaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when saved query has wrong type', async () => {
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: DEFAULT_SPACE_ID,
|
||||||
|
installed_kibana: [{ id: savedQueryId, type: 'wrong-type' }, mockOtherAsset],
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
DEFAULT_SPACE_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when space is not found in additional spaces', async () => {
|
||||||
|
const customSpaceId = 'custom-space';
|
||||||
|
const mockInstallation = {
|
||||||
|
installed_kibana_space_id: 'other-space',
|
||||||
|
installed_kibana: [mockOtherAsset],
|
||||||
|
additional_spaces_installed_kibana: {
|
||||||
|
'different-space': [{ id: savedQueryId, type: savedQuerySavedObjectType }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(mockPackageService.getInstallation as jest.Mock).mockResolvedValue(mockInstallation);
|
||||||
|
|
||||||
|
const result = await isSavedQueryPrebuilt(
|
||||||
|
mockPackageService,
|
||||||
|
savedQueryId,
|
||||||
|
mockSavedObjectsClient,
|
||||||
|
customSpaceId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,15 +9,32 @@ import { find, filter, map, reduce } from 'lodash';
|
||||||
import type { KibanaAssetReference } from '@kbn/fleet-plugin/common';
|
import type { KibanaAssetReference } from '@kbn/fleet-plugin/common';
|
||||||
|
|
||||||
import type { PackageClient } from '@kbn/fleet-plugin/server';
|
import type { PackageClient } from '@kbn/fleet-plugin/server';
|
||||||
|
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||||
|
|
||||||
export const getInstalledSavedQueriesMap = async (packageService: PackageClient | undefined) => {
|
export const getInstalledSavedQueriesMap = async (
|
||||||
const installation = await packageService?.getInstallation(OSQUERY_INTEGRATION_NAME);
|
packageService: PackageClient | undefined,
|
||||||
|
savedObjectsClient: SavedObjectsClientContract,
|
||||||
|
spaceId: string
|
||||||
|
) => {
|
||||||
|
const installation = await packageService?.getInstallation(
|
||||||
|
OSQUERY_INTEGRATION_NAME,
|
||||||
|
savedObjectsClient
|
||||||
|
);
|
||||||
|
|
||||||
if (installation) {
|
if (installation) {
|
||||||
|
let installedSavedQueries: KibanaAssetReference[] = [];
|
||||||
|
|
||||||
|
if ((installation.installed_kibana_space_id ?? DEFAULT_SPACE_ID) === spaceId) {
|
||||||
|
installedSavedQueries = installation.installed_kibana;
|
||||||
|
} else if (installation.additional_spaces_installed_kibana?.[spaceId]) {
|
||||||
|
installedSavedQueries = installation.additional_spaces_installed_kibana[spaceId];
|
||||||
|
}
|
||||||
|
|
||||||
return reduce<KibanaAssetReference, Record<string, KibanaAssetReference>>(
|
return reduce<KibanaAssetReference, Record<string, KibanaAssetReference>>(
|
||||||
installation.installed_kibana,
|
installedSavedQueries,
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
if (item.type === savedQuerySavedObjectType) {
|
if (item.type === savedQuerySavedObjectType) {
|
||||||
return { ...acc, [item.id]: item };
|
return { ...acc, [item.id]: item };
|
||||||
|
@ -49,13 +66,26 @@ export const getPrebuiltSavedQueryIds = async (packageService: PackageClient | u
|
||||||
|
|
||||||
export const isSavedQueryPrebuilt = async (
|
export const isSavedQueryPrebuilt = async (
|
||||||
packageService: PackageClient | undefined,
|
packageService: PackageClient | undefined,
|
||||||
savedQueryId: string
|
savedQueryId: string,
|
||||||
|
savedObjectsClient: SavedObjectsClientContract,
|
||||||
|
spaceId: string
|
||||||
) => {
|
) => {
|
||||||
const installation = await packageService?.getInstallation(OSQUERY_INTEGRATION_NAME);
|
const installation = await packageService?.getInstallation(
|
||||||
|
OSQUERY_INTEGRATION_NAME,
|
||||||
|
savedObjectsClient
|
||||||
|
);
|
||||||
|
|
||||||
if (installation) {
|
if (installation) {
|
||||||
|
let installedSavedQueries: KibanaAssetReference[] = [];
|
||||||
|
|
||||||
|
if ((installation.installed_kibana_space_id ?? DEFAULT_SPACE_ID) === spaceId) {
|
||||||
|
installedSavedQueries = installation.installed_kibana;
|
||||||
|
} else if (installation.additional_spaces_installed_kibana?.[spaceId]) {
|
||||||
|
installedSavedQueries = installation.additional_spaces_installed_kibana[spaceId];
|
||||||
|
}
|
||||||
|
|
||||||
const installationSavedQueries = find(
|
const installationSavedQueries = find(
|
||||||
installation.installed_kibana,
|
installedSavedQueries,
|
||||||
(item) => item.type === savedQuerySavedObjectType && item.id === savedQueryId
|
(item) => item.type === savedQuerySavedObjectType && item.id === savedQueryId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ import { packSavedObjectType } from '../../../common/types';
|
||||||
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||||
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
import type { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||||
import { convertPackQueriesToSO } from '../pack/utils';
|
import { convertPackQueriesToSO } from '../pack/utils';
|
||||||
import { getInternalSavedObjectsClient } from '../utils';
|
import { createInternalSavedObjectsClientForSpaceId } from '../../utils/get_internal_saved_object_client';
|
||||||
|
import { fetchOsqueryPackagePolicyIds } from '../utils';
|
||||||
|
|
||||||
export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||||
router.versioned
|
router.versioned
|
||||||
|
@ -41,18 +42,34 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
||||||
async (context, request, response) => {
|
async (context, request, response) => {
|
||||||
const coreContext = await context.core;
|
const coreContext = await context.core;
|
||||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
const spaceScopedClient = await createInternalSavedObjectsClientForSpaceId(
|
||||||
osqueryContext.getStartServices
|
osqueryContext,
|
||||||
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
const packageService = osqueryContext.service.getPackageService()?.asInternalUser;
|
const packageService = osqueryContext.service.getPackageService()?.asInternalUser;
|
||||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
const agentPolicyService = osqueryContext.service.getAgentPolicyService();
|
||||||
|
|
||||||
const packageInfo = await packageService?.getInstallation(OSQUERY_INTEGRATION_NAME);
|
const packageInfo = await packageService?.getInstallation(
|
||||||
|
OSQUERY_INTEGRATION_NAME,
|
||||||
|
spaceScopedClient
|
||||||
|
);
|
||||||
|
|
||||||
|
const osqueryPackagePolicyIdsWithinCurrentSpace = await fetchOsqueryPackagePolicyIds(
|
||||||
|
spaceScopedClient,
|
||||||
|
osqueryContext
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!osqueryPackagePolicyIdsWithinCurrentSpace.length) {
|
||||||
|
return response.ok({
|
||||||
|
body: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (packageInfo?.install_version && satisfies(packageInfo?.install_version, '<0.6.0')) {
|
if (packageInfo?.install_version && satisfies(packageInfo?.install_version, '<0.6.0')) {
|
||||||
try {
|
try {
|
||||||
const policyPackages = await packagePolicyService?.list(internalSavedObjectsClient, {
|
const policyPackages = await packagePolicyService?.list(spaceScopedClient, {
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||||
perPage: 10000,
|
perPage: 10000,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -128,13 +145,13 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
||||||
|
|
||||||
const agentPolicyIds = uniq(flatMap(policyPackages?.items, 'policy_ids'));
|
const agentPolicyIds = uniq(flatMap(policyPackages?.items, 'policy_ids'));
|
||||||
const agentPolicies = mapKeys(
|
const agentPolicies = mapKeys(
|
||||||
await agentPolicyService?.getByIds(internalSavedObjectsClient, agentPolicyIds),
|
await agentPolicyService?.getByIds(spaceScopedClient, agentPolicyIds),
|
||||||
'id'
|
'id'
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
map(migrationObject.packs, async (packObject) => {
|
map(migrationObject.packs, async (packObject) => {
|
||||||
await internalSavedObjectsClient.create(
|
await spaceScopedClient.create(
|
||||||
packSavedObjectType,
|
packSavedObjectType,
|
||||||
{
|
{
|
||||||
name: packObject.name,
|
name: packObject.name,
|
||||||
|
@ -160,7 +177,7 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
||||||
|
|
||||||
// delete unnecessary package policies
|
// delete unnecessary package policies
|
||||||
await packagePolicyService?.delete(
|
await packagePolicyService?.delete(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
migrationObject.packagePoliciesToDelete
|
migrationObject.packagePoliciesToDelete
|
||||||
);
|
);
|
||||||
|
@ -171,15 +188,12 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
||||||
const agentPacks = filter(migrationObject.packs, (pack) =>
|
const agentPacks = filter(migrationObject.packs, (pack) =>
|
||||||
pack.policy_ids.includes(key)
|
pack.policy_ids.includes(key)
|
||||||
);
|
);
|
||||||
await packagePolicyService?.upgrade(internalSavedObjectsClient, esClient, value);
|
await packagePolicyService?.upgrade(spaceScopedClient, esClient, value);
|
||||||
const packagePolicy = await packagePolicyService?.get(
|
const packagePolicy = await packagePolicyService?.get(spaceScopedClient, value);
|
||||||
internalSavedObjectsClient,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
|
|
||||||
if (packagePolicy) {
|
if (packagePolicy) {
|
||||||
return packagePolicyService?.update(
|
return packagePolicyService?.update(
|
||||||
internalSavedObjectsClient,
|
spaceScopedClient,
|
||||||
esClient,
|
esClient,
|
||||||
packagePolicy.id,
|
packagePolicy.id,
|
||||||
produce(packagePolicy, (draft) => {
|
produce(packagePolicy, (draft) => {
|
||||||
|
|
|
@ -8,8 +8,12 @@
|
||||||
import type { CoreSetup } from '@kbn/core/server';
|
import type { CoreSetup } from '@kbn/core/server';
|
||||||
import { SavedObjectsClient } from '@kbn/core/server';
|
import { SavedObjectsClient } from '@kbn/core/server';
|
||||||
import { reduce } from 'lodash';
|
import { reduce } from 'lodash';
|
||||||
|
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||||
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||||
import type { Shard } from '../../common/utils/converters';
|
import type { Shard } from '../../common/utils/converters';
|
||||||
import type { SOShard } from '../common/types';
|
import type { SOShard } from '../common/types';
|
||||||
|
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||||
|
import type { OsqueryAppContext } from '../lib/osquery_app_context_services';
|
||||||
|
|
||||||
export const convertECSMappingToArray = (ecsMapping: Record<string, object> | undefined) =>
|
export const convertECSMappingToArray = (ecsMapping: Record<string, object> | undefined) =>
|
||||||
ecsMapping
|
ecsMapping
|
||||||
|
@ -56,3 +60,34 @@ export const getInternalSavedObjectsClient = async (
|
||||||
|
|
||||||
return new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
return new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all package policy IDs for osquery_manager integration in the current space.
|
||||||
|
* @param packagePolicyService - Fleet's package policy service
|
||||||
|
* @param soClient - Saved objects client
|
||||||
|
* @returns Array of package policy IDs
|
||||||
|
*/
|
||||||
|
export const fetchOsqueryPackagePolicyIds = async (
|
||||||
|
soClient: SavedObjectsClientContract,
|
||||||
|
osqueryContext: OsqueryAppContext
|
||||||
|
): Promise<string[]> => {
|
||||||
|
const logger = osqueryContext.logFactory.get('fetchOsqueryPackagePolicyIds');
|
||||||
|
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||||
|
|
||||||
|
if (!packagePolicyService) {
|
||||||
|
throw new Error('Package policy service is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Fetching osquery package policy IDs');
|
||||||
|
|
||||||
|
const kuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`;
|
||||||
|
const idIterable = await packagePolicyService.fetchAllItemIds(soClient, { kuery });
|
||||||
|
const ids: string[] = [];
|
||||||
|
for await (const batch of idIterable) {
|
||||||
|
ids.push(...batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(`Fetched ${ids.length} osquery package policy IDs`);
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
|
@ -9,27 +9,50 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
import type { ISearchRequestParams } from '@kbn/search-types';
|
import type { ISearchRequestParams } from '@kbn/search-types';
|
||||||
import { AGENT_ACTIONS_INDEX } from '@kbn/fleet-plugin/common';
|
import { AGENT_ACTIONS_INDEX } from '@kbn/fleet-plugin/common';
|
||||||
import type { AgentsRequestOptions } from '../../../../../../common/search_strategy/osquery/agents';
|
|
||||||
import { getQueryFilter } from '../../../../../utils/build_query';
|
import { getQueryFilter } from '../../../../../utils/build_query';
|
||||||
import { ACTIONS_INDEX } from '../../../../../../common/constants';
|
import { ACTIONS_INDEX } from '../../../../../../common/constants';
|
||||||
|
|
||||||
|
import type { ActionsRequestOptions } from '../../../../../../common/search_strategy/osquery/actions';
|
||||||
|
|
||||||
export const buildActionsQuery = ({
|
export const buildActionsQuery = ({
|
||||||
kuery = '',
|
kuery = '',
|
||||||
sort,
|
sort,
|
||||||
pagination: { cursorStart, querySize },
|
pagination: { cursorStart, querySize },
|
||||||
componentTemplateExists,
|
componentTemplateExists,
|
||||||
}: AgentsRequestOptions): ISearchRequestParams => {
|
spaceId,
|
||||||
|
}: ActionsRequestOptions): ISearchRequestParams => {
|
||||||
const {
|
const {
|
||||||
bool: { filter },
|
bool: { filter },
|
||||||
} = getQueryFilter({ filter: kuery });
|
} = getQueryFilter({ filter: kuery });
|
||||||
|
|
||||||
|
let extendedFilter = filter;
|
||||||
|
|
||||||
|
if (spaceId === 'default') {
|
||||||
|
// For default space, include docs where space_id matches 'default' OR where space_id field does not exist
|
||||||
|
extendedFilter = [
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ term: { space_id: 'default' } },
|
||||||
|
{ bool: { must_not: { exists: { field: 'space_id' } } } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...filter,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// For other spaces, only include docs where space_id matches the current spaceId
|
||||||
|
extendedFilter = [...filter, { term: { space_id: spaceId } }];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allow_no_indices: true,
|
allow_no_indices: true,
|
||||||
index: componentTemplateExists ? `${ACTIONS_INDEX}*` : AGENT_ACTIONS_INDEX,
|
index: componentTemplateExists ? `${ACTIONS_INDEX}*` : AGENT_ACTIONS_INDEX,
|
||||||
ignore_unavailable: true,
|
ignore_unavailable: true,
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
filter,
|
filter: extendedFilter,
|
||||||
must: [
|
must: [
|
||||||
{
|
{
|
||||||
term: {
|
term: {
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const buildActionDetailsQuery = ({
|
||||||
actionId,
|
actionId,
|
||||||
kuery,
|
kuery,
|
||||||
componentTemplateExists,
|
componentTemplateExists,
|
||||||
|
spaceId,
|
||||||
}: ActionDetailsRequestOptions): ISearchRequestParams => {
|
}: ActionDetailsRequestOptions): ISearchRequestParams => {
|
||||||
const actionIdQuery = `action_id: ${actionId}`;
|
const actionIdQuery = `action_id: ${actionId}`;
|
||||||
let filter = actionIdQuery;
|
let filter = actionIdQuery;
|
||||||
|
@ -23,13 +24,35 @@ export const buildActionDetailsQuery = ({
|
||||||
filter = filter + ` AND ${kuery}`;
|
filter = filter + ` AND ${kuery}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterQuery = getQueryFilter({ filter });
|
const {
|
||||||
|
bool: { filter: baseFilter },
|
||||||
|
} = getQueryFilter({ filter });
|
||||||
|
|
||||||
|
let extendedFilter = baseFilter;
|
||||||
|
|
||||||
|
if (spaceId === 'default') {
|
||||||
|
// For default space, include docs where space_id matches 'default' OR where space_id field does not exist
|
||||||
|
extendedFilter = [
|
||||||
|
{
|
||||||
|
bool: {
|
||||||
|
should: [
|
||||||
|
{ term: { space_id: 'default' } },
|
||||||
|
{ bool: { must_not: { exists: { field: 'space_id' } } } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...baseFilter,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// For other spaces, only include docs where space_id matches the current spaceId
|
||||||
|
extendedFilter = [...baseFilter, { term: { space_id: spaceId } }];
|
||||||
|
}
|
||||||
|
|
||||||
const dslQuery = {
|
const dslQuery = {
|
||||||
allow_no_indices: true,
|
allow_no_indices: true,
|
||||||
index: componentTemplateExists ? `${ACTIONS_INDEX}*` : AGENT_ACTIONS_INDEX,
|
index: componentTemplateExists ? `${ACTIONS_INDEX}*` : AGENT_ACTIONS_INDEX,
|
||||||
ignore_unavailable: true,
|
ignore_unavailable: true,
|
||||||
query: { bool: { filter: filterQuery } },
|
query: { bool: { filter: extendedFilter } },
|
||||||
size: 1,
|
size: 1,
|
||||||
fields: ['*'],
|
fields: ['*'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,12 +53,15 @@ export const osquerySearchStrategyProvider = <T extends FactoryQueryTypes>(
|
||||||
...('actionId' in request ? { actionId: request.actionId } : {}),
|
...('actionId' in request ? { actionId: request.actionId } : {}),
|
||||||
...('startDate' in request ? { startDate: request.startDate } : {}),
|
...('startDate' in request ? { startDate: request.startDate } : {}),
|
||||||
...('agentId' in request ? { agentId: request.agentId } : {}),
|
...('agentId' in request ? { agentId: request.agentId } : {}),
|
||||||
|
...('policyIds' in request ? { policyIds: request.policyIds } : {}),
|
||||||
|
...('spaceId' in request ? { spaceId: request.spaceId } : {}),
|
||||||
} as StrategyRequestType<T>;
|
} as StrategyRequestType<T>;
|
||||||
|
|
||||||
const dsl = queryFactory.buildDsl({
|
const dsl = queryFactory.buildDsl({
|
||||||
...strictRequest,
|
...strictRequest,
|
||||||
componentTemplateExists: actionsIndexExists,
|
componentTemplateExists: actionsIndexExists,
|
||||||
} as StrategyRequestType<T>);
|
} as StrategyRequestType<T>);
|
||||||
|
|
||||||
// use internal user for searching .fleet* indices
|
// use internal user for searching .fleet* indices
|
||||||
es =
|
es =
|
||||||
dsl.index?.includes('fleet') || dsl.index?.includes('logs-osquery_manager.action')
|
dsl.index?.includes('fleet') || dsl.index?.includes('logs-osquery_manager.action')
|
||||||
|
|
|
@ -23,6 +23,7 @@ import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin
|
||||||
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
||||||
import type { CasesServerSetup } from '@kbn/cases-plugin/server';
|
import type { CasesServerSetup } from '@kbn/cases-plugin/server';
|
||||||
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||||
|
import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server';
|
||||||
import type { createActionService } from './handlers/action/create_action_service';
|
import type { createActionService } from './handlers/action/create_action_service';
|
||||||
|
|
||||||
export interface OsqueryPluginSetup {
|
export interface OsqueryPluginSetup {
|
||||||
|
@ -41,6 +42,7 @@ export interface SetupPlugins {
|
||||||
taskManager?: TaskManagerPluginSetup;
|
taskManager?: TaskManagerPluginSetup;
|
||||||
telemetry?: TelemetryPluginSetup;
|
telemetry?: TelemetryPluginSetup;
|
||||||
licensing: LicensingPluginSetup;
|
licensing: LicensingPluginSetup;
|
||||||
|
spaces?: SpacesPluginSetup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StartPlugins {
|
export interface StartPlugins {
|
||||||
|
@ -51,4 +53,5 @@ export interface StartPlugins {
|
||||||
taskManager?: TaskManagerPluginStart;
|
taskManager?: TaskManagerPluginStart;
|
||||||
telemetry?: TelemetryPluginStart;
|
telemetry?: TelemetryPluginStart;
|
||||||
ruleRegistry?: RuleRegistryPluginStartContract;
|
ruleRegistry?: RuleRegistryPluginStartContract;
|
||||||
|
spaces?: SpacesPluginStart;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { SavedObjectsClient } from '@kbn/core-saved-objects-api-server-internal'
|
||||||
import { kibanaRequestFactory } from '@kbn/core-http-server-utils';
|
import { kibanaRequestFactory } from '@kbn/core-http-server-utils';
|
||||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
||||||
|
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||||
|
|
||||||
export async function getInternalSavedObjectsClient(coreStart: CoreStart) {
|
export async function getInternalSavedObjectsClient(coreStart: CoreStart) {
|
||||||
return new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
return new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
||||||
|
@ -36,3 +37,13 @@ export function getInternalSavedObjectsClientForSpaceId(
|
||||||
excludedExtensions: [SECURITY_EXTENSION_ID],
|
excludedExtensions: [SECURITY_EXTENSION_ID],
|
||||||
}) as SavedObjectsClient;
|
}) as SavedObjectsClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createInternalSavedObjectsClientForSpaceId(
|
||||||
|
osqueryContext: { service: { getActiveSpace: Function }; getStartServices: Function },
|
||||||
|
request: KibanaRequest
|
||||||
|
): Promise<SavedObjectsClient> {
|
||||||
|
const space = await osqueryContext.service.getActiveSpace(request);
|
||||||
|
const [core] = await osqueryContext.getStartServices();
|
||||||
|
|
||||||
|
return getInternalSavedObjectsClientForSpaceId(core, space?.id ?? DEFAULT_SPACE_ID);
|
||||||
|
}
|
||||||
|
|
|
@ -80,6 +80,8 @@
|
||||||
"@kbn/zod",
|
"@kbn/zod",
|
||||||
"@kbn/core-lifecycle-server",
|
"@kbn/core-lifecycle-server",
|
||||||
"@kbn/core-saved-objects-api-server-internal",
|
"@kbn/core-saved-objects-api-server-internal",
|
||||||
"@kbn/core-http-server-utils"
|
"@kbn/core-http-server-utils",
|
||||||
|
"@kbn/spaces-utils",
|
||||||
|
"@kbn/core-http-server"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,21 @@
|
||||||
import { each, map, some, uniq } from 'lodash';
|
import { each, map, some, uniq } from 'lodash';
|
||||||
import { containsDynamicQuery } from '@kbn/osquery-plugin/common/utils/replace_params_query';
|
import { containsDynamicQuery } from '@kbn/osquery-plugin/common/utils/replace_params_query';
|
||||||
import { requiredOptional } from '@kbn/zod-helpers';
|
import { requiredOptional } from '@kbn/zod-helpers';
|
||||||
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-utils';
|
||||||
|
import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
|
||||||
import type { ResponseActionAlerts } from './types';
|
import type { ResponseActionAlerts } from './types';
|
||||||
import type { SetupPlugins } from '../../../plugin_contract';
|
import type { SetupPlugins } from '../../../plugin_contract';
|
||||||
import type { RuleResponseOsqueryAction } from '../../../../common/api/detection_engine/model/rule_response_actions';
|
import type { RuleResponseOsqueryAction } from '../../../../common/api/detection_engine/model/rule_response_actions';
|
||||||
|
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
|
||||||
|
|
||||||
export const osqueryResponseAction = (
|
export const osqueryResponseAction = (
|
||||||
responseAction: RuleResponseOsqueryAction,
|
responseAction: RuleResponseOsqueryAction,
|
||||||
osqueryCreateActionService: SetupPlugins['osquery']['createActionService'],
|
osqueryCreateActionService: SetupPlugins['osquery']['createActionService'],
|
||||||
|
endpointAppContextService: EndpointAppContextService,
|
||||||
{ alerts }: ResponseActionAlerts
|
{ alerts }: ResponseActionAlerts
|
||||||
) => {
|
) => {
|
||||||
|
const logger = osqueryCreateActionService.logger;
|
||||||
|
|
||||||
const temporaryQueries = responseAction.params.queries?.length
|
const temporaryQueries = responseAction.params.queries?.length
|
||||||
? responseAction.params.queries
|
? responseAction.params.queries
|
||||||
: [{ query: responseAction.params.query }];
|
: [{ query: responseAction.params.query }];
|
||||||
|
@ -26,31 +32,75 @@ export const osqueryResponseAction = (
|
||||||
);
|
);
|
||||||
|
|
||||||
const { savedQueryId, packId, queries, ecsMapping, ...rest } = responseAction.params;
|
const { savedQueryId, packId, queries, ecsMapping, ...rest } = responseAction.params;
|
||||||
|
// Extract space information from the first alert (all alerts should be from the same space)
|
||||||
|
let spaceId = alerts[0]?.kibana?.space_ids?.[0];
|
||||||
|
|
||||||
|
const processResponseActionClientError = (err: Error, endpointIds: string[]): Promise<void> => {
|
||||||
|
logger.error(
|
||||||
|
`attempt to run osquery queries on host IDs [${endpointIds.join(', ')}] returned error: ${
|
||||||
|
err.message
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (endpointAppContextService.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||||
|
if (!spaceId) {
|
||||||
|
const ruleId = alerts[0].kibana.alert?.rule.uuid;
|
||||||
|
const ruleName = alerts[0].kibana.alert?.rule.name;
|
||||||
|
logger.error(
|
||||||
|
`Unable to identify the space ID from alert data ('kibana.space_ids') for rule [${ruleName}][${ruleId}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// force the space to `default` when space awareness is not enabled
|
||||||
|
spaceId = DEFAULT_SPACE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
if (!containsDynamicQueries) {
|
if (!containsDynamicQueries) {
|
||||||
const agentIds = uniq(map(alerts, 'agent.id'));
|
const agentIds = uniq(map(alerts, 'agent.id'));
|
||||||
const alertIds = map(alerts, '_id');
|
const alertIds = map(alerts, '_id');
|
||||||
|
|
||||||
return osqueryCreateActionService.create({
|
return osqueryCreateActionService
|
||||||
...rest,
|
.create(
|
||||||
queries: requiredOptional(queries),
|
{
|
||||||
ecs_mapping: ecsMapping,
|
...rest,
|
||||||
saved_query_id: savedQueryId,
|
queries: requiredOptional(queries),
|
||||||
agent_ids: agentIds,
|
ecs_mapping: ecsMapping,
|
||||||
alert_ids: alertIds,
|
saved_query_id: savedQueryId,
|
||||||
});
|
agent_ids: agentIds,
|
||||||
|
alert_ids: alertIds,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
space: { id: spaceId },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
return processResponseActionClientError(err, agentIds);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
each(alerts, (alert) => {
|
each(alerts, (alert) => {
|
||||||
return osqueryCreateActionService.create(
|
const agentIds = alert.agent?.id ? [alert.agent.id] : [];
|
||||||
{
|
|
||||||
...rest,
|
return osqueryCreateActionService
|
||||||
queries: requiredOptional(queries),
|
.create(
|
||||||
ecs_mapping: ecsMapping,
|
{
|
||||||
saved_query_id: savedQueryId,
|
...rest,
|
||||||
agent_ids: alert.agent?.id ? [alert.agent.id] : [],
|
queries: requiredOptional(queries),
|
||||||
alert_ids: [(alert as unknown as { _id: string })._id],
|
ecs_mapping: ecsMapping,
|
||||||
},
|
saved_query_id: savedQueryId,
|
||||||
alert
|
agent_ids: agentIds,
|
||||||
);
|
alert_ids: [(alert as unknown as { _id: string })._id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
alertData: alert as ParsedTechnicalFields & { _index: string },
|
||||||
|
space: { id: spaceId as string },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
return processResponseActionClientError(err, agentIds);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { ALERT_RULE_NAME, ALERT_RULE_UUID, SPACE_IDS } from '@kbn/rule-data-util
|
||||||
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
|
||||||
import { responseActionsClientMock } from '../../../endpoint/services/actions/clients/mocks';
|
import { responseActionsClientMock } from '../../../endpoint/services/actions/clients/mocks';
|
||||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||||
|
import type { Logger } from '@kbn/logging';
|
||||||
describe('ScheduleNotificationResponseActions', () => {
|
describe('ScheduleNotificationResponseActions', () => {
|
||||||
const getSignals = () => [
|
const getSignals = () => [
|
||||||
{
|
{
|
||||||
|
@ -30,9 +30,13 @@ describe('ScheduleNotificationResponseActions', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const osqueryActionMock = {
|
const osqueryActionMock = {
|
||||||
create: jest.fn(),
|
create: jest.fn().mockResolvedValue({}),
|
||||||
stop: jest.fn(),
|
stop: jest.fn(),
|
||||||
|
logger: {
|
||||||
|
error: jest.fn(),
|
||||||
|
} as unknown as Logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mockedResponseActionsClient = responseActionsClientMock.create();
|
let mockedResponseActionsClient = responseActionsClientMock.create();
|
||||||
const endpointServiceMock = createMockEndpointAppContextService();
|
const endpointServiceMock = createMockEndpointAppContextService();
|
||||||
(endpointServiceMock.getInternalResponseActionsClient as jest.Mock).mockImplementation(() => {
|
(endpointServiceMock.getInternalResponseActionsClient as jest.Mock).mockImplementation(() => {
|
||||||
|
@ -47,6 +51,11 @@ describe('ScheduleNotificationResponseActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Osquery', () => {
|
describe('Osquery', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
// @ts-expect-error assignment to readonly property
|
||||||
|
endpointServiceMock.experimentalFeatures.endpointManagementSpaceAwarenessEnabled = true;
|
||||||
|
});
|
||||||
const simpleQuery = 'select * from uptime';
|
const simpleQuery = 'select * from uptime';
|
||||||
const defaultQueryParams = {
|
const defaultQueryParams = {
|
||||||
ecsMapping: { testField: { field: 'testField', value: 'testValue' } },
|
ecsMapping: { testField: { field: 'testField', value: 'testValue' } },
|
||||||
|
@ -73,15 +82,115 @@ describe('ScheduleNotificationResponseActions', () => {
|
||||||
const defaultResultParams = {
|
const defaultResultParams = {
|
||||||
agent_ids: ['agent-id-1', 'agent-id-2'],
|
agent_ids: ['agent-id-1', 'agent-id-2'],
|
||||||
alert_ids: ['alert-id-1', 'alert-id-2'],
|
alert_ids: ['alert-id-1', 'alert-id-2'],
|
||||||
};
|
|
||||||
const defaultQueryResultParams = {
|
|
||||||
...defaultResultParams,
|
|
||||||
ecs_mapping: { testField: { field: 'testField', value: 'testValue' } },
|
ecs_mapping: { testField: { field: 'testField', value: 'testValue' } },
|
||||||
ecsMapping: undefined,
|
|
||||||
saved_query_id: 'testSavedQueryId',
|
saved_query_id: 'testSavedQueryId',
|
||||||
savedQueryId: undefined,
|
|
||||||
queries: [],
|
queries: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
it('should pass correct space id from alert.kibana.space_ids[0] when space awareness is enabled', () => {
|
||||||
|
const signals = getSignals();
|
||||||
|
scheduleNotificationResponseActions({
|
||||||
|
signals,
|
||||||
|
signalsCount: signals.length,
|
||||||
|
responseActions: [
|
||||||
|
{
|
||||||
|
actionTypeId: ResponseActionTypesEnum['.osquery'],
|
||||||
|
params: { ...defaultQueryParams, queries: [{ id: 'query-1', query: simpleQuery }] },
|
||||||
|
} as RuleResponseAction,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(osqueryActionMock.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
queries: expect.any(Array),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
space: { id: DEFAULT_SPACE_ID },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use default space id when space awareness is disabled', () => {
|
||||||
|
// @ts-expect-error assignment to readonly property
|
||||||
|
endpointServiceMock.experimentalFeatures.endpointManagementSpaceAwarenessEnabled = false;
|
||||||
|
const signals = getSignals();
|
||||||
|
scheduleNotificationResponseActions({
|
||||||
|
signals,
|
||||||
|
signalsCount: signals.length,
|
||||||
|
responseActions: [
|
||||||
|
{
|
||||||
|
actionTypeId: ResponseActionTypesEnum['.osquery'],
|
||||||
|
params: { ...defaultQueryParams, queries: [{ id: 'query-1', query: simpleQuery }] },
|
||||||
|
} as RuleResponseAction,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(osqueryActionMock.create).toHaveBeenCalledWith(
|
||||||
|
expect.any(Object),
|
||||||
|
expect.objectContaining({
|
||||||
|
space: { id: DEFAULT_SPACE_ID },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log error if space awareness is enabled and space id is missing', () => {
|
||||||
|
const signals = [{ ...getSignals()[0], [SPACE_IDS]: undefined }];
|
||||||
|
scheduleNotificationResponseActions({
|
||||||
|
signals,
|
||||||
|
signalsCount: signals.length,
|
||||||
|
responseActions: [
|
||||||
|
{
|
||||||
|
actionTypeId: ResponseActionTypesEnum['.osquery'],
|
||||||
|
params: { ...defaultQueryParams, queries: [{ id: 'query-1', query: simpleQuery }] },
|
||||||
|
} as RuleResponseAction,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(osqueryActionMock.logger.error).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Unable to identify the space ID')
|
||||||
|
);
|
||||||
|
expect(osqueryActionMock.create).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle errors from osqueryActionMock.create and log them', async () => {
|
||||||
|
const signals = getSignals();
|
||||||
|
const testError = new Error('Simulated create failure');
|
||||||
|
osqueryActionMock.create.mockRejectedValueOnce(testError);
|
||||||
|
|
||||||
|
await scheduleNotificationResponseActions({
|
||||||
|
signals,
|
||||||
|
signalsCount: signals.length,
|
||||||
|
responseActions: [
|
||||||
|
{
|
||||||
|
actionTypeId: ResponseActionTypesEnum['.osquery'],
|
||||||
|
params: { ...defaultQueryParams, queries: [{ id: 'query-1', query: simpleQuery }] },
|
||||||
|
} as RuleResponseAction,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(osqueryActionMock.logger.error).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('Simulated create failure')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass alertData when dynamic queries are present', () => {
|
||||||
|
const dynamicQuery = 'select * from uptime where id = {{host.id}}';
|
||||||
|
const signals = getSignals();
|
||||||
|
scheduleNotificationResponseActions({
|
||||||
|
signals,
|
||||||
|
signalsCount: signals.length,
|
||||||
|
responseActions: [
|
||||||
|
{
|
||||||
|
actionTypeId: ResponseActionTypesEnum['.osquery'],
|
||||||
|
params: { ...defaultQueryParams, queries: [{ id: 'query-1', query: dynamicQuery }] },
|
||||||
|
} as RuleResponseAction,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(osqueryActionMock.create).toHaveBeenCalledWith(
|
||||||
|
expect.any(Object),
|
||||||
|
expect.objectContaining({
|
||||||
|
alertData: expect.objectContaining({ _id: signals[0]._id }),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const defaultPackResultParams = {
|
const defaultPackResultParams = {
|
||||||
...defaultResultParams,
|
...defaultResultParams,
|
||||||
query: undefined,
|
query: undefined,
|
||||||
|
@ -106,10 +215,19 @@ describe('ScheduleNotificationResponseActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response).not.toBeUndefined();
|
expect(response).not.toBeUndefined();
|
||||||
expect(osqueryActionMock.create).toHaveBeenCalledWith({
|
expect(osqueryActionMock.create).toHaveBeenCalledWith(
|
||||||
...defaultQueryResultParams,
|
expect.objectContaining({
|
||||||
query: simpleQuery,
|
agent_ids: ['agent-id-1', 'agent-id-2'],
|
||||||
});
|
alert_ids: ['alert-id-1', 'alert-id-2'],
|
||||||
|
ecs_mapping: { testField: { field: 'testField', value: 'testValue' } },
|
||||||
|
queries: [],
|
||||||
|
query: simpleQuery,
|
||||||
|
saved_query_id: 'testSavedQueryId',
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
space: { id: DEFAULT_SPACE_ID },
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle osquery response actions with packs', async () => {
|
it('should handle osquery response actions with packs', async () => {
|
||||||
|
@ -138,10 +256,15 @@ describe('ScheduleNotificationResponseActions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response).not.toBeUndefined();
|
expect(response).not.toBeUndefined();
|
||||||
expect(osqueryActionMock.create).toHaveBeenCalledWith({
|
expect(osqueryActionMock.create).toHaveBeenCalledWith(
|
||||||
...defaultPackResultParams,
|
expect.objectContaining({
|
||||||
queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }],
|
...defaultPackResultParams,
|
||||||
});
|
queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
space: { id: DEFAULT_SPACE_ID },
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Endpoint', () => {
|
describe('Endpoint', () => {
|
||||||
|
|
|
@ -44,9 +44,14 @@ export const getScheduleNotificationResponseActionsService =
|
||||||
responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] &&
|
responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] &&
|
||||||
osqueryCreateActionService
|
osqueryCreateActionService
|
||||||
) {
|
) {
|
||||||
await osqueryResponseAction(responseAction, osqueryCreateActionService, {
|
await osqueryResponseAction(
|
||||||
alerts,
|
responseAction,
|
||||||
});
|
osqueryCreateActionService,
|
||||||
|
endpointAppContextService,
|
||||||
|
{
|
||||||
|
alerts,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) {
|
if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) {
|
||||||
// We currently support only automated response actions for Elastic Defend. This will
|
// We currently support only automated response actions for Elastic Defend. This will
|
||||||
|
|
|
@ -254,5 +254,6 @@
|
||||||
"@kbn/triggers-actions-ui-types",
|
"@kbn/triggers-actions-ui-types",
|
||||||
"@kbn/unified-histogram",
|
"@kbn/unified-histogram",
|
||||||
"@kbn/react-kibana-context-theme",
|
"@kbn/react-kibana-context-theme",
|
||||||
|
"@kbn/spaces-utils",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue