[Security Solution][Endpoint] Updates to Endpoint artifacts Find API in support of spaces (#212696)

## Summary

Changes in support of Spaces, which is currently behind feature flag
`endpointManagementSpaceAwarenessEnabled`:

- Artifacts `find` API (via `lists` plugin extension points) were
updated to append additional filtering criteria to the request that
ensure only data scoped to the active space is returned. The Find API
will return:
    - global artifacts
- per-policy artifacts currently assigned to policies that are visible
in the current space
- per-policy artifacts currently NOT assigned to any policies that have
an owner space id that matches the active space (dangling artifats)
- Artifacts `get`-one API was updated to validate that user can read
artifact in active space. Read of single artifact will be allowed:
    - artifact is global
- If per-space artifact is assigned to policies and at least one policy
is visible in active space
- if per-space artifact is not assigned to any policies but its owner
space matches the active space
    - if user has the global artifact management privilege
- In addition to API integration tests for the `find`/`get` APIs, tests
were also added for endpoint exceptions
- Consolidated endpoint exceptions generator
(`endpoint_exceptions_generator.ts`) into `EndpointListItemGenerator`
for better reuse
This commit is contained in:
Paul Tavares 2025-03-10 11:00:19 -04:00 committed by GitHub
parent 4a67b8b3af
commit df78cbbedd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 986 additions and 281 deletions

View file

@ -1,85 +0,0 @@
/*
* 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 {
CreateExceptionListItemSchema,
ExceptionListItemSchema,
ListOperatorType,
} from '@kbn/securitysolution-io-ts-list-types';
import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import { LIST_ITEM_ENTRY_OPERATOR_TYPES } from './common/artifact_list_item_entry_values';
import { exceptionItemToCreateExceptionItem } from './exceptions_list_item_generator';
import { BaseDataGenerator } from './base_data_generator';
import { GLOBAL_ARTIFACT_TAG } from '../service/artifacts';
import { ENDPOINT_EVENTS_LOG_INDEX_FIELDS } from './common/alerts_ecs_fields';
export class EndpointExceptionsGenerator extends BaseDataGenerator<ExceptionListItemSchema> {
generate(overrides: Partial<ExceptionListItemSchema> = {}): ExceptionListItemSchema {
return {
name: `Generated Exception (${this.randomString(5)})`,
comments: [],
description: 'created by EndpointExceptionsGenerator',
id: this.seededUUIDv4(),
item_id: this.seededUUIDv4(),
list_id: ENDPOINT_LIST_ID,
tags: [GLOBAL_ARTIFACT_TAG],
entries: this.randomEndpointExceptionEntries(1),
meta: undefined,
namespace_type: 'agnostic',
os_types: [this.randomOSFamily()] as ExceptionListItemSchema['os_types'],
created_at: this.randomPastDate(),
created_by: this.randomUser(),
updated_at: '2020-04-20T15:25:31.830Z',
expire_time: undefined,
updated_by: this.randomUser(),
_version: this.randomString(5),
type: 'simple',
tie_breaker_id: this.seededUUIDv4(),
...overrides,
};
}
generateEndpointExceptionForCreate(
overrides: Partial<CreateExceptionListItemSchema> = {}
): CreateExceptionListItemSchema {
return {
...exceptionItemToCreateExceptionItem(this.generate()),
...overrides,
};
}
protected randomEndpointExceptionEntries(
count: number = this.randomN(5)
): ExceptionListItemSchema['entries'] {
const operatorTypes = LIST_ITEM_ENTRY_OPERATOR_TYPES.filter(
(item) =>
!(
[
ListOperatorTypeEnum.LIST,
ListOperatorTypeEnum.NESTED,
ListOperatorTypeEnum.EXISTS,
] as ListOperatorType[]
).includes(item)
);
const fieldList = ENDPOINT_EVENTS_LOG_INDEX_FIELDS.filter((field) => field.endsWith('.text'));
return Array.from({ length: count || 1 }, () => {
const operatorType = this.randomChoice(operatorTypes);
return {
field: this.randomChoice(fieldList),
operator: 'included',
type: operatorType,
value:
operatorType === ListOperatorTypeEnum.MATCH_ANY
? [this.randomString(10), this.randomString(10)]
: this.randomString(10),
};
}) as ExceptionListItemSchema['entries'];
}
}

View file

@ -11,10 +11,16 @@ import type {
UpdateExceptionListItemSchema,
EntriesArray,
} from '@kbn/securitysolution-io-ts-list-types';
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
import {
ListOperatorTypeEnum,
type ListOperatorType,
} from '@kbn/securitysolution-io-ts-list-types';
import { ENDPOINT_ARTIFACT_LISTS, ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import { ConditionEntryField } from '@kbn/securitysolution-utils';
import { LIST_ITEM_ENTRY_OPERATOR_TYPES } from './common/artifact_list_item_entry_values';
import { BaseDataGenerator } from './base_data_generator';
import { BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG } from '../service/artifacts/constants';
import { ENDPOINT_EVENTS_LOG_INDEX_FIELDS } from './common/alerts_ecs_fields';
/** Utility that removes null and undefined from a Type's property value */
type NonNullableTypeProperties<T> = {
@ -143,6 +149,57 @@ export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionList
return Object.assign(exceptionItemToCreateExceptionItem(this.generate()), overrides);
}
generateEndpointException(
overrides: Partial<ExceptionListItemSchema> = {}
): ExceptionListItemSchema {
return this.generate({
name: `Endpoint exception (${this.randomString(5)})`,
list_id: ENDPOINT_LIST_ID,
entries: this.randomEndpointExceptionEntries(1),
tags: [],
...overrides,
});
}
generateEndpointExceptionForCreate(
overrides: Partial<CreateExceptionListItemSchema> = {}
): CreateExceptionListItemSchema {
return {
...exceptionItemToCreateExceptionItem(this.generateEndpointException()),
...overrides,
};
}
protected randomEndpointExceptionEntries(
count: number = this.randomN(5)
): ExceptionListItemSchema['entries'] {
const operatorTypes = LIST_ITEM_ENTRY_OPERATOR_TYPES.filter(
(item) =>
!(
[
ListOperatorTypeEnum.LIST,
ListOperatorTypeEnum.NESTED,
ListOperatorTypeEnum.EXISTS,
] as ListOperatorType[]
).includes(item)
);
const fieldList = ENDPOINT_EVENTS_LOG_INDEX_FIELDS.filter((field) => field.endsWith('.text'));
return Array.from({ length: count || 1 }, () => {
const operatorType = this.randomChoice(operatorTypes);
return {
field: this.randomChoice(fieldList),
operator: 'included',
type: operatorType,
value:
operatorType === ListOperatorTypeEnum.MATCH_ANY
? [this.randomString(10), this.randomString(10)]
: this.randomString(10),
};
}) as ExceptionListItemSchema['entries'];
}
generateTrustedApp(overrides: Partial<ExceptionListItemSchema> = {}): ExceptionListItemSchema {
return this.generate({
name: `Trusted app (${this.randomString(5)})`,

View file

@ -10,7 +10,6 @@ import type { ToolingLog } from '@kbn/tooling-log';
import { BaseDataGenerator } from '../../../../common/endpoint/data_generators/base_data_generator';
import type { NewTrustedApp } from '../../../../common/endpoint/types';
import { stringify } from '../../../../server/endpoint/utils/stringify';
import { EndpointExceptionsGenerator } from '../../../../common/endpoint/data_generators/endpoint_exceptions_generator';
import {
BY_POLICY_ARTIFACT_TAG_PREFIX,
GLOBAL_ARTIFACT_TAG,
@ -276,7 +275,7 @@ export const createEndpointExceptions = async ({
reportProgress,
throttler,
}: ArtifactCreationOptions): Promise<void> => {
const generate = new EndpointExceptionsGenerator();
const generate = new ExceptionsListItemGenerator();
let doneCount = 0;
let errorCount = 0;

View file

@ -9,6 +9,7 @@ import type {
CreateExceptionListItemOptions,
ExceptionsListPreCreateItemServerExtension,
} from '@kbn/lists-plugin/server';
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import {
BlocklistValidator,
@ -17,63 +18,80 @@ import {
HostIsolationExceptionsValidator,
TrustedAppValidator,
} from '../validators';
import { setArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
type ValidatorCallback = ExceptionsListPreCreateItemServerExtension['callback'];
export const getExceptionsPreCreateItemHandler = (
endpointAppContext: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreCreateItemServerExtension['callback'] => {
return async function ({ data, context: { request } }): Promise<CreateExceptionListItemOptions> {
if (data.namespaceType !== 'agnostic') {
return data;
}
let isEndpointArtifact = false;
let validatedItem = data;
// Validate trusted apps
if (TrustedAppValidator.isTrustedApp(data)) {
isEndpointArtifact = true;
const trustedAppValidator = new TrustedAppValidator(endpointAppContext, request);
const validatedItem = await trustedAppValidator.validatePreCreateItem(data);
validatedItem = await trustedAppValidator.validatePreCreateItem(data);
trustedAppValidator.notifyFeatureUsage(data, 'TRUSTED_APP_BY_POLICY');
return validatedItem;
}
// Validate event filter
if (EventFilterValidator.isEventFilter(data)) {
isEndpointArtifact = true;
const eventFilterValidator = new EventFilterValidator(endpointAppContext, request);
const validatedItem = await eventFilterValidator.validatePreCreateItem(data);
validatedItem = await eventFilterValidator.validatePreCreateItem(data);
eventFilterValidator.notifyFeatureUsage(data, 'EVENT_FILTERS_BY_POLICY');
return validatedItem;
}
// Validate host isolation
if (HostIsolationExceptionsValidator.isHostIsolationException(data)) {
isEndpointArtifact = true;
const hostIsolationExceptionsValidator = new HostIsolationExceptionsValidator(
endpointAppContext,
request
);
const validatedItem = await hostIsolationExceptionsValidator.validatePreCreateItem(data);
validatedItem = await hostIsolationExceptionsValidator.validatePreCreateItem(data);
hostIsolationExceptionsValidator.notifyFeatureUsage(
data,
'HOST_ISOLATION_EXCEPTION_BY_POLICY'
);
hostIsolationExceptionsValidator.notifyFeatureUsage(data, 'HOST_ISOLATION_EXCEPTION');
return validatedItem;
}
// Validate blocklists
if (BlocklistValidator.isBlocklist(data)) {
isEndpointArtifact = true;
const blocklistValidator = new BlocklistValidator(endpointAppContext, request);
const validatedItem = await blocklistValidator.validatePreCreateItem(data);
validatedItem = await blocklistValidator.validatePreCreateItem(data);
blocklistValidator.notifyFeatureUsage(data, 'BLOCKLIST_BY_POLICY');
return validatedItem;
}
// validate endpoint exceptions
if (EndpointExceptionsValidator.isEndpointException(data)) {
isEndpointArtifact = true;
const endpointExceptionValidator = new EndpointExceptionsValidator(
endpointAppContext,
request
);
const validatedItem = await endpointExceptionValidator.validatePreCreateItem(data);
validatedItem = await endpointExceptionValidator.validatePreCreateItem(data);
endpointExceptionValidator.notifyFeatureUsage(data, 'ENDPOINT_EXCEPTIONS');
}
if (
isEndpointArtifact &&
endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled
) {
if (!request) {
throw new EndpointArtifactExceptionValidationError(`Missing HTTP Request object`);
}
const spaceId = (await endpointAppContext.getActiveSpace(request)).id;
setArtifactOwnerSpaceId(validatedItem, spaceId);
return validatedItem;
}

View file

@ -16,10 +16,9 @@ import {
TrustedAppValidator,
} from '../validators';
type ValidatorCallback = ExceptionsListPreDeleteItemServerExtension['callback'];
export const getExceptionsPreDeleteItemHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreDeleteItemServerExtension['callback'] => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;

View file

@ -15,10 +15,9 @@ import {
TrustedAppValidator,
} from '../validators';
type ValidatorCallback = ExceptionsListPreExportServerExtension['callback'];
export const getExceptionsPreExportHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreExportServerExtension['callback'] => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;

View file

@ -16,10 +16,9 @@ import {
TrustedAppValidator,
} from '../validators';
type ValidatorCallback = ExceptionsListPreGetOneItemServerExtension['callback'];
export const getExceptionsPreGetOneHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreGetOneItemServerExtension['callback'] => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;
@ -40,7 +39,9 @@ export const getExceptionsPreGetOneHandler = (
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreGetOneItem();
await new TrustedAppValidator(endpointAppContextService, request).validatePreGetOneItem(
exceptionItem
);
return data;
}
@ -49,19 +50,23 @@ export const getExceptionsPreGetOneHandler = (
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreGetOneItem();
).validatePreGetOneItem(exceptionItem);
return data;
}
// Event Filters Exception
if (EventFilterValidator.isEventFilter({ listId })) {
await new EventFilterValidator(endpointAppContextService, request).validatePreGetOneItem();
await new EventFilterValidator(endpointAppContextService, request).validatePreGetOneItem(
exceptionItem
);
return data;
}
// Validate Blocklists
if (BlocklistValidator.isBlocklist({ listId })) {
await new BlocklistValidator(endpointAppContextService, request).validatePreGetOneItem();
await new BlocklistValidator(endpointAppContextService, request).validatePreGetOneItem(
exceptionItem
);
return data;
}
@ -70,7 +75,7 @@ export const getExceptionsPreGetOneHandler = (
await new EndpointExceptionsValidator(
endpointAppContextService,
request
).validatePreGetOneItem();
).validatePreGetOneItem(exceptionItem);
return data;
}

View file

@ -9,23 +9,23 @@ import type { ExceptionsListPreImportServerExtension } from '@kbn/lists-plugin/s
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants';
type ValidatorCallback = ExceptionsListPreImportServerExtension['callback'];
export const getExceptionsPreImportHandler = (): ValidatorCallback => {
return async ({ data }) => {
const hasEndpointArtifactListOrListItems = [...data.lists, ...data.items].some((item) => {
if ('list_id' in item) {
return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id);
export const getExceptionsPreImportHandler =
(): ExceptionsListPreImportServerExtension['callback'] => {
return async ({ data }) => {
const hasEndpointArtifactListOrListItems = [...data.lists, ...data.items].some((item) => {
if ('list_id' in item) {
return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(item.list_id);
}
return false;
});
if (hasEndpointArtifactListOrListItems) {
throw new EndpointArtifactExceptionValidationError(
'Import is not supported for Endpoint artifact exceptions'
);
}
return false;
});
if (hasEndpointArtifactListOrListItems) {
throw new EndpointArtifactExceptionValidationError(
'Import is not supported for Endpoint artifact exceptions'
);
}
return data;
return data;
};
};
};

View file

@ -6,6 +6,7 @@
*/
import type { ExceptionsListPreMultiListFindServerExtension } from '@kbn/lists-plugin/server';
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import {
BlocklistValidator,
@ -14,54 +15,58 @@ import {
HostIsolationExceptionsValidator,
TrustedAppValidator,
} from '../validators';
import { setFindRequestFilterScopeToActiveSpace } from '../utils';
type ValidatorCallback = ExceptionsListPreMultiListFindServerExtension['callback'];
export const getExceptionsPreMultiListFindHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreMultiListFindServerExtension['callback'] => {
return async function ({ data, context: { request } }) {
if (!data.namespaceType.includes('agnostic')) {
return data;
}
// validate Trusted application
if (data.listId.some((id) => TrustedAppValidator.isTrustedApp({ listId: id }))) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreMultiListFind();
return data;
}
let isEndpointArtifact = false;
// Validate Host Isolation Exceptions
if (
if (data.listId.some((id) => TrustedAppValidator.isTrustedApp({ listId: id }))) {
// validate Trusted application
isEndpointArtifact = true;
await new TrustedAppValidator(endpointAppContextService, request).validatePreMultiListFind();
} else if (
data.listId.some((listId) =>
HostIsolationExceptionsValidator.isHostIsolationException({ listId })
)
) {
// Validate Host Isolation Exceptions
isEndpointArtifact = true;
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreMultiListFind();
return data;
}
// Event Filters Exceptions
if (data.listId.some((listId) => EventFilterValidator.isEventFilter({ listId }))) {
} else if (data.listId.some((listId) => EventFilterValidator.isEventFilter({ listId }))) {
// Event Filters Exceptions
isEndpointArtifact = true;
await new EventFilterValidator(endpointAppContextService, request).validatePreMultiListFind();
return data;
}
// validate Blocklist
if (data.listId.some((id) => BlocklistValidator.isBlocklist({ listId: id }))) {
} else if (data.listId.some((id) => BlocklistValidator.isBlocklist({ listId: id }))) {
// validate Blocklist
isEndpointArtifact = true;
await new BlocklistValidator(endpointAppContextService, request).validatePreMultiListFind();
return data;
}
// Validate Endpoint Exceptions
if (data.listId.some((id) => EndpointExceptionsValidator.isEndpointException({ listId: id }))) {
} else if (
data.listId.some((id) => EndpointExceptionsValidator.isEndpointException({ listId: id }))
) {
// Validate Endpoint Exceptions
isEndpointArtifact = true;
await new EndpointExceptionsValidator(
endpointAppContextService,
request
).validatePreMultiListFind();
return data;
}
if (isEndpointArtifact) {
if (!request) {
throw new EndpointArtifactExceptionValidationError(`Missing HTTP Request object`);
}
await setFindRequestFilterScopeToActiveSpace(endpointAppContextService, request, data);
}
return data;

View file

@ -6,6 +6,8 @@
*/
import type { ExceptionsListPreSingleListFindServerExtension } from '@kbn/lists-plugin/server';
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import { setFindRequestFilterScopeToActiveSpace } from '../utils';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import {
BlocklistValidator,
@ -15,54 +17,63 @@ import {
TrustedAppValidator,
} from '../validators';
type ValidatorCallback = ExceptionsListPreSingleListFindServerExtension['callback'];
export const getExceptionsPreSingleListFindHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreSingleListFindServerExtension['callback'] => {
return async function ({ data, context: { request } }) {
if (data.namespaceType !== 'agnostic') {
return data;
}
let isEndpointArtifact = false;
const { listId } = data;
// Validate Trusted applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
// Validate Trusted applications
isEndpointArtifact = true;
await new TrustedAppValidator(endpointAppContextService, request).validatePreSingleListFind();
return data;
}
// Host Isolation Exceptions
if (HostIsolationExceptionsValidator.isHostIsolationException({ listId })) {
} else if (HostIsolationExceptionsValidator.isHostIsolationException({ listId })) {
// Host Isolation Exceptions
isEndpointArtifact = true;
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreSingleListFind();
return data;
}
// Event Filters Exceptions
if (EventFilterValidator.isEventFilter({ listId })) {
} else if (EventFilterValidator.isEventFilter({ listId })) {
// Event Filters Exceptions
isEndpointArtifact = true;
await new EventFilterValidator(
endpointAppContextService,
request
).validatePreSingleListFind();
return data;
}
// Validate Blocklists
if (BlocklistValidator.isBlocklist({ listId })) {
await new BlocklistValidator(endpointAppContextService, request).validatePreSingleListFind();
return data;
}
// Validate Endpoint Exceptions
if (EndpointExceptionsValidator.isEndpointException({ listId })) {
} else if (EndpointExceptionsValidator.isEndpointException({ listId })) {
// Validate Endpoint Exceptions
isEndpointArtifact = true;
await new EndpointExceptionsValidator(
endpointAppContextService,
request
).validatePreSingleListFind();
return data;
} else if (EndpointExceptionsValidator.isEndpointException({ listId })) {
// Validate Endpoint Exceptions
isEndpointArtifact = true;
await new EndpointExceptionsValidator(
endpointAppContextService,
request
).validatePreSingleListFind();
}
if (isEndpointArtifact) {
if (!request) {
throw new EndpointArtifactExceptionValidationError(`Missing HTTP Request object`);
}
await setFindRequestFilterScopeToActiveSpace(endpointAppContextService, request, data);
}
return data;

View file

@ -15,10 +15,9 @@ import {
TrustedAppValidator,
} from '../validators';
type ValidatorCallback = ExceptionsListPreSummaryServerExtension['callback'];
export const getExceptionsPreSummaryHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreSummaryServerExtension['callback'] => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;

View file

@ -9,6 +9,7 @@ import type {
ExceptionsListPreUpdateItemServerExtension,
UpdateExceptionListItemOptions,
} from '@kbn/lists-plugin/server';
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import type { ExceptionItemLikeOptions } from '../types';
import {
@ -18,11 +19,14 @@ import {
HostIsolationExceptionsValidator,
TrustedAppValidator,
} from '../validators';
import {
hasArtifactOwnerSpaceId,
setArtifactOwnerSpaceId,
} from '../../../../common/endpoint/service/artifacts/utils';
type ValidatorCallback = ExceptionsListPreUpdateItemServerExtension['callback'];
export const getExceptionsPreUpdateItemHandler = (
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
): ExceptionsListPreUpdateItemServerExtension['callback'] => {
return async function ({
data,
context: { request, exceptionListClient },
@ -31,6 +35,8 @@ export const getExceptionsPreUpdateItemHandler = (
return data;
}
let isEndpointArtifact = false;
let validatedItem = data;
const currentSavedItem = await exceptionListClient.getExceptionListItem({
id: data.id,
itemId: data.itemId,
@ -47,36 +53,34 @@ export const getExceptionsPreUpdateItemHandler = (
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
isEndpointArtifact = true;
const trustedAppValidator = new TrustedAppValidator(endpointAppContextService, request);
const validatedItem = await trustedAppValidator.validatePreUpdateItem(data, currentSavedItem);
validatedItem = await trustedAppValidator.validatePreUpdateItem(data, currentSavedItem);
trustedAppValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'TRUSTED_APP_BY_POLICY'
);
return validatedItem;
}
// Validate Event Filters
if (EventFilterValidator.isEventFilter({ listId })) {
isEndpointArtifact = true;
const eventFilterValidator = new EventFilterValidator(endpointAppContextService, request);
const validatedItem = await eventFilterValidator.validatePreUpdateItem(
data,
currentSavedItem
);
validatedItem = await eventFilterValidator.validatePreUpdateItem(data, currentSavedItem);
eventFilterValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'EVENT_FILTERS_BY_POLICY'
);
return validatedItem;
}
// Validate host isolation
if (HostIsolationExceptionsValidator.isHostIsolationException({ listId })) {
isEndpointArtifact = true;
const hostIsolationExceptionValidator = new HostIsolationExceptionsValidator(
endpointAppContextService,
request
);
const validatedItem = await hostIsolationExceptionValidator.validatePreUpdateItem(
validatedItem = await hostIsolationExceptionValidator.validatePreUpdateItem(
data,
currentSavedItem
);
@ -88,27 +92,27 @@ export const getExceptionsPreUpdateItemHandler = (
data as ExceptionItemLikeOptions,
'HOST_ISOLATION_EXCEPTION'
);
return validatedItem;
}
// Validate Blocklists
if (BlocklistValidator.isBlocklist({ listId })) {
isEndpointArtifact = true;
const blocklistValidator = new BlocklistValidator(endpointAppContextService, request);
const validatedItem = await blocklistValidator.validatePreUpdateItem(data, currentSavedItem);
validatedItem = await blocklistValidator.validatePreUpdateItem(data, currentSavedItem);
blocklistValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'BLOCKLIST_BY_POLICY'
);
return validatedItem;
}
// Validate Endpoint Exceptions
if (EndpointExceptionsValidator.isEndpointException({ listId })) {
isEndpointArtifact = true;
const endpointExceptionValidator = new EndpointExceptionsValidator(
endpointAppContextService,
request
);
const validatedItem = await endpointExceptionValidator.validatePreUpdateItem(
validatedItem = await endpointExceptionValidator.validatePreUpdateItem(
data,
currentSavedItem
);
@ -116,6 +120,20 @@ export const getExceptionsPreUpdateItemHandler = (
data as ExceptionItemLikeOptions,
'ENDPOINT_EXCEPTIONS'
);
}
if (
isEndpointArtifact &&
endpointAppContextService.experimentalFeatures.endpointManagementSpaceAwarenessEnabled
) {
if (!hasArtifactOwnerSpaceId(validatedItem)) {
if (!request) {
throw new EndpointArtifactExceptionValidationError(`Missing HTTP Request object`);
}
const spaceId = (await endpointAppContextService.getActiveSpace(request)).id;
setArtifactOwnerSpaceId(validatedItem, spaceId);
}
return validatedItem;
}

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export * from './set_find_request_filter_scope_to_active_space';

View file

@ -0,0 +1,126 @@
/*
* 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 {
FindExceptionListItemOptions,
FindExceptionListsItemOptions,
} from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client_types';
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
import { setFindRequestFilterScopeToActiveSpace } from './set_find_request_filter_scope_to_active_space';
import { createMockEndpointAppContextService } from '../../../endpoint/mocks';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { httpServerMock } from '@kbn/core-http-server-mocks';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
describe('Artifacts: setFindRequestFilterScopeToActiveSpace()', () => {
let endpointAppContextServices: EndpointAppContextService;
let kibanaRequest: ReturnType<typeof httpServerMock.createKibanaRequest>;
let findOptionsMock: FindExceptionListItemOptions | FindExceptionListsItemOptions;
beforeEach(() => {
endpointAppContextServices = createMockEndpointAppContextService();
kibanaRequest = httpServerMock.createKibanaRequest();
(
endpointAppContextServices.getInternalFleetServices()
.packagePolicy as jest.Mocked<PackagePolicyClient>
).listIds.mockResolvedValue({
items: ['policy-1', 'policy-2'],
total: 2,
page: 1,
perPage: 20,
});
findOptionsMock = {
listId: ENDPOINT_ARTIFACT_LISTS.trustedApps.id,
namespaceType: 'agnostic',
filter: undefined,
perPage: 20,
page: 1,
sortField: undefined,
sortOrder: undefined,
};
// @ts-expect-error updating a readonly field
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled = true;
});
it('should nothing if feature flag is disabled', async () => {
// @ts-expect-error updating a readonly field
endpointAppContextServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled = false;
await setFindRequestFilterScopeToActiveSpace(
endpointAppContextServices,
kibanaRequest,
findOptionsMock
);
expect(findOptionsMock.filter).toBeUndefined();
});
it('should inject additional filtering into find request', async () => {
await setFindRequestFilterScopeToActiveSpace(
endpointAppContextServices,
kibanaRequest,
findOptionsMock
);
expect(findOptionsMock.filter).toEqual(`
(
(
exception-list-agnostic.attributes.tags:("policy:all" OR "policy:policy-1" OR "policy:policy-2"
)
)
OR
(
NOT exception-list-agnostic.attributes.tags:"policy:*"
AND
exception-list-agnostic.attributes.tags:"ownerSpaceId:default"
)
)`);
});
it('should inject additional filtering into find request when it already has a filter value', async () => {
findOptionsMock.filter = 'somevalue:match-this';
await setFindRequestFilterScopeToActiveSpace(
endpointAppContextServices,
kibanaRequest,
findOptionsMock
);
expect(findOptionsMock.filter).toEqual(`
(
(
exception-list-agnostic.attributes.tags:("policy:all" OR "policy:policy-1" OR "policy:policy-2"
)
)
OR
(
NOT exception-list-agnostic.attributes.tags:"policy:*"
AND
exception-list-agnostic.attributes.tags:"ownerSpaceId:default"
)
) AND (somevalue:match-this)`);
});
it('should inject additional filtering when using multi-list search format', async () => {
findOptionsMock.listId = [
ENDPOINT_ARTIFACT_LISTS.trustedApps.id,
ENDPOINT_ARTIFACT_LISTS.eventFilters.id,
];
findOptionsMock.filter = ['', 'somevalue:match-this'];
await setFindRequestFilterScopeToActiveSpace(
endpointAppContextServices,
kibanaRequest,
findOptionsMock
);
expect(findOptionsMock.filter).toEqual([
'\n (\n (\n exception-list-agnostic.attributes.tags:("policy:all" OR "policy:policy-1" OR "policy:policy-2"\n )\n )\n OR\n (\n NOT exception-list-agnostic.attributes.tags:"policy:*"\n AND\n exception-list-agnostic.attributes.tags:"ownerSpaceId:default"\n )\n )',
'\n (\n (\n exception-list-agnostic.attributes.tags:("policy:all" OR "policy:policy-1" OR "policy:policy-2"\n )\n )\n OR\n (\n NOT exception-list-agnostic.attributes.tags:"policy:*"\n AND\n exception-list-agnostic.attributes.tags:"ownerSpaceId:default"\n )\n ) AND (somevalue:match-this)',
]);
});
});

View file

@ -0,0 +1,110 @@
/*
* 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 {
FindExceptionListItemOptions,
FindExceptionListsItemOptions,
} from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client_types';
import type { KibanaRequest } from '@kbn/core-http-server';
import { stringify } from '../../../endpoint/utils/stringify';
import { GLOBAL_ARTIFACT_TAG } from '../../../../common/endpoint/service/artifacts';
import {
buildPerPolicyTag,
buildSpaceOwnerIdTag,
} from '../../../../common/endpoint/service/artifacts/utils';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
/**
* Mutates the Find options provided on input to include a filter that will scope the search
* down to only data that should be visible in active space
* @param endpointServices
* @param httpRequest
* @param findOptions
*/
export const setFindRequestFilterScopeToActiveSpace = async (
endpointServices: EndpointAppContextService,
httpRequest: KibanaRequest,
findOptions: FindExceptionListItemOptions | FindExceptionListsItemOptions
): Promise<void> => {
if (endpointServices.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
const logger = endpointServices.createLogger('setFindRequestFilterScopeToActiveSpace');
logger.debug(() => `Find options prior to adjusting filter:\n${stringify(findOptions)}`);
const spaceId = (await endpointServices.getActiveSpace(httpRequest)).id;
const fleetServices = endpointServices.getInternalFleetServices(spaceId);
const soScopedClient = fleetServices.savedObjects.createInternalScopedSoClient({ spaceId });
const { items: allEndpointPolicyIds } = await fleetServices.packagePolicy.listIds(
soScopedClient,
{ kuery: fleetServices.endpointPolicyKuery, perPage: 10_000 }
);
logger.debug(
() =>
`policies currently visible in space ID [${spaceId}]:\n${stringify(allEndpointPolicyIds)}`
);
// Filter to scope down the data visible in active space id by appending to the Find options the following filter:
// (
// All global artifacts
// -OR-
// All per-policy artifacts assigned to a policy visible in active space
// )
// -OR-
// (
// Artifacts NOT containing a `policy:` tag ("dangling" per-policy artifacts)
// -AND-
// having an owner space ID value that matches active space
// )
//
const spaceVisibleDataFilter = `
(
(
exception-list-agnostic.attributes.tags:("${GLOBAL_ARTIFACT_TAG}"${
allEndpointPolicyIds.length === 0
? ')'
: ` OR ${allEndpointPolicyIds
.map((policyId) => `"${buildPerPolicyTag(policyId)}"`)
.join(' OR ')}
)
)
OR
(
NOT exception-list-agnostic.attributes.tags:"${buildPerPolicyTag('*')}"
AND
exception-list-agnostic.attributes.tags:"${buildSpaceOwnerIdTag(spaceId)}"
)
)`
}`;
if (isSingleListFindOptions(findOptions)) {
findOptions.filter = `${spaceVisibleDataFilter}${
findOptions.filter ? ` AND (${findOptions.filter})` : ''
}`;
} else {
if (!findOptions.filter) {
findOptions.filter = [];
}
// Add the filter for every list that was defined in the options
findOptions.listId.forEach((listId, index) => {
const userFilter = findOptions.filter[index];
findOptions.filter[index] = `${spaceVisibleDataFilter}${
userFilter ? ` AND (${userFilter})` : ''
}`;
});
}
logger.debug(() => `Find options updated with active space filter:\n${stringify(findOptions)}`);
}
};
const isSingleListFindOptions = (
findOptions: FindExceptionListItemOptions | FindExceptionListsItemOptions
): findOptions is FindExceptionListItemOptions => {
return !Array.isArray(findOptions.listId);
};

View file

@ -19,7 +19,7 @@ import {
import { EndpointArtifactExceptionValidationError } from './errors';
import { httpServerMock } from '@kbn/core/server/mocks';
import type { PackagePolicy } from '@kbn/fleet-plugin/common';
import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks';
import { createFleetAuthzMock, createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { ExceptionItemLikeOptions } from '../types';
import {
@ -227,6 +227,18 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
(endpointAppContextServices.getEndpointAuthz as jest.Mock).mockResolvedValue(authzMock);
setArtifactOwnerSpaceId(exceptionLikeItem, DEFAULT_SPACE_ID);
validator = new BaseValidatorMock(endpointAppContextServices, kibanaRequest);
packagePolicyService = endpointAppContextServices.getInternalFleetServices()
.packagePolicy as jest.Mocked<PackagePolicyClient>;
packagePolicyService.listIds.mockResolvedValue({
items: ['policy-1', 'policy-2'],
total: 2,
page: 1,
perPage: 20,
});
packagePolicyService.getByIDs.mockResolvedValue([
Object.assign(createPackagePolicyMock(), { id: 'policy-1' }),
Object.assign(createPackagePolicyMock(), { id: 'policy-2' }),
]);
});
describe('#validateCreateOnwerSpaceIds()', () => {
@ -452,5 +464,85 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
).resolves.toBeUndefined();
});
});
describe('#validateCanReadItemInActiveSpace()', () => {
const itemNotFoundInSpaceErrorMessage =
'EndpointExceptionsError: Item not found in space [default]';
let savedExceptionItem: ExceptionListItemSchema;
beforeEach(async () => {
authzMock.canManageGlobalArtifacts = false;
savedExceptionItem = createExceptionListItemMock({
// Saved item is owned by different space id
tags: [
buildPerPolicyTag('some-other-policy'),
buildPerPolicyTag('policy-1'),
buildSpaceOwnerIdTag('foo'),
],
});
});
it('should do nothing if feature flag is disabled', async () => {
setSpaceAwarenessFeatureFlag('disabled');
savedExceptionItem.tags = [buildSpaceOwnerIdTag('foo')];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).resolves.toBeUndefined();
});
it('should allow read if item is global', async () => {
savedExceptionItem.tags = [GLOBAL_ARTIFACT_TAG, buildSpaceOwnerIdTag('foo')];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).resolves.toBeUndefined();
});
it('should allow read if user has global artifact privilege', async () => {
authzMock.canManageGlobalArtifacts = true;
savedExceptionItem.tags = [
buildPerPolicyTag('policy-999-not-visible-in-space'),
buildSpaceOwnerIdTag('foo'),
];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).resolves.toBeUndefined();
});
it('should allow read if item is per-policy with no policies assigned and space owner matches active space', async () => {
savedExceptionItem.tags = [buildSpaceOwnerIdTag('default')];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).resolves.toBeUndefined();
});
it('should error if item is per-policy with no policies assigned but space owner is NOT the active space', async () => {
savedExceptionItem.tags = [buildSpaceOwnerIdTag('foo')];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).rejects.toThrowError(itemNotFoundInSpaceErrorMessage);
});
it('should allow read if per-policy item has at least one policy that is visible in active space', async () => {
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).resolves.toBeUndefined();
});
it('should error if per-policy item does not have at least 1 policy id that is visible in active space', async () => {
savedExceptionItem.tags = [
buildPerPolicyTag('some-other-policy'),
buildSpaceOwnerIdTag('default'),
];
await expect(
validator._validateCanReadItemInActiveSpace(savedExceptionItem)
).rejects.toThrowError(itemNotFoundInSpaceErrorMessage);
});
});
});
});

View file

@ -12,10 +12,13 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t
import { OperatingSystem } from '@kbn/securitysolution-utils';
import { i18n } from '@kbn/i18n';
import {} from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client_types';
import { groupBy } from 'lodash';
import { stringify } from '../../../endpoint/utils/stringify';
import { ENDPOINT_AUTHZ_ERROR_MESSAGE } from '../../../endpoint/errors';
import {
getArtifactOwnerSpaceIds,
setArtifactOwnerSpaceId,
isArtifactGlobal,
} from '../../../../common/endpoint/service/artifacts/utils';
import type { FeatureKeys } from '../../../endpoint/services';
import type { EndpointAuthz } from '../../../../common/endpoint/types/authz';
@ -174,9 +177,14 @@ export class BaseValidator {
*/
protected async validateByPolicyItem(item: ExceptionItemLikeOptions): Promise<void> {
if (this.isItemByPolicy(item)) {
const { packagePolicy, savedObjects } = this.endpointAppContext.getInternalFleetServices();
const spaceId = this.endpointAppContext.experimentalFeatures
.endpointManagementSpaceAwarenessEnabled
? await this.getActiveSpaceId()
: undefined;
const { packagePolicy, savedObjects } =
this.endpointAppContext.getInternalFleetServices(spaceId);
const policyIds = getPolicyIdsFromArtifact(item);
const soClient = savedObjects.createInternalScopedSoClient();
const soClient = savedObjects.createInternalScopedSoClient({ spaceId });
if (policyIds.length === 0) {
return;
@ -186,6 +194,19 @@ export class BaseValidator {
ignoreMissing: true,
});
this.logger.debug(
() =>
`Lookup of policy ids:\n[${policyIds.join(
' | '
)}] for space [${spaceId}] returned:\n${stringify(
(policiesFromFleet ?? []).map((policy) => ({
id: policy.id,
name: policy.name,
spaceIds: policy.spaceIds,
}))
)}`
);
if (!policiesFromFleet) {
throw new EndpointArtifactExceptionValidationError(
`invalid policy ids: ${policyIds.join(', ')}`
@ -290,19 +311,6 @@ export class BaseValidator {
return (await this.endpointAppContext.getActiveSpace(this.request)).id;
}
/**
* Update the artifact item (if necessary) with a `ownerSpaceId` tag using the HTTP request's active space
* @param item
* @protected
*/
protected async setOwnerSpaceId(
item: Partial<Pick<ExceptionListItemSchema, 'tags'>>
): Promise<void> {
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
setArtifactOwnerSpaceId(item, await this.getActiveSpaceId());
}
}
protected async validateCanCreateGlobalArtifacts(item: ExceptionItemLikeOptions): Promise<void> {
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
if (
@ -376,4 +384,72 @@ export class BaseValidator {
}
}
}
protected async validateCanReadItemInActiveSpace(
currentSavedItem: ExceptionListItemSchema
): Promise<void> {
if (this.endpointAppContext.experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
this.logger.debug(
() => `Validating if can read single item:\n${stringify(currentSavedItem)}`
);
// Everyone can read global artifacts and those with global artifact management privilege can do it all
if (
isArtifactGlobal(currentSavedItem) ||
(await this.endpointAuthzPromise).canManageGlobalArtifacts
) {
return;
}
const activeSpaceId = await this.getActiveSpaceId();
const ownerSpaceIds = getArtifactOwnerSpaceIds(currentSavedItem);
const policyIds = getPolicyIdsFromArtifact(currentSavedItem);
// If per-policy item is not assigned to any policy (dangling artifact) and this artifact
// is owned by the active space, then allow read.
if (policyIds.length === 0 && ownerSpaceIds.includes(activeSpaceId)) {
return;
}
// if at least one policy is visible in active space, then allow read
if (policyIds.length > 0) {
const { packagePolicy, savedObjects } =
this.endpointAppContext.getInternalFleetServices(activeSpaceId);
const soClient = savedObjects.createInternalScopedSoClient({ spaceId: activeSpaceId });
const policiesFromFleet = await packagePolicy
.getByIDs(soClient, policyIds, {
ignoreMissing: true,
})
.then((packagePolicies) => {
this.logger.debug(
() =>
`Lookup of policy ids:[${policyIds.join(
' | '
)}]\nvia fleet for space ID [${activeSpaceId}] returned:\n${stringify(
(packagePolicies ?? []).map((policy) => ({
id: policy.id,
name: policy.name,
spaceIds: policy.spaceIds,
}))
)}`
);
return groupBy(packagePolicies ?? [], 'id');
});
if (policyIds.some((policyId) => Boolean(policiesFromFleet[policyId]))) {
return;
}
}
this.logger.debug(
() => `item can not be read from space [${activeSpaceId}]:\n${stringify(currentSavedItem)}`
);
throw new EndpointExceptionsValidationError(
`Item not found in space [${activeSpaceId}]`,
404
);
}
}
}

View file

@ -15,7 +15,6 @@ import type {
UpdateExceptionListItemOptions,
} from '@kbn/lists-plugin/server';
import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
import { BaseValidator } from './base_validator';
import type { ExceptionItemLikeOptions } from '../types';
import { isValidHash } from '../../../../common/endpoint/service/artifacts/validations';
@ -245,8 +244,6 @@ export class BlocklistValidator extends BaseValidator {
await this.validateByPolicyItem(item);
await this.validateCreateOwnerSpaceIds(item);
await this.setOwnerSpaceId(item);
return item;
}
@ -255,8 +252,9 @@ export class BlocklistValidator extends BaseValidator {
await this.validateCanDeleteItemInActiveSpace(currentItem);
}
async validatePreGetOneItem(): Promise<void> {
async validatePreGetOneItem(currentItem: ExceptionListItemSchema): Promise<void> {
await this.validateHasReadPrivilege();
await this.validateCanReadItemInActiveSpace(currentItem);
}
async validatePreMultiListFind(): Promise<void> {
@ -304,10 +302,6 @@ export class BlocklistValidator extends BaseValidator {
await this.validateUpdateOwnerSpaceIds(updatedItem, currentItem);
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
await this.setOwnerSpaceId(_updatedItem);
}
return _updatedItem;
}

View file

@ -12,7 +12,6 @@ import type {
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { EndpointExceptionsValidationError } from './endpoint_exception_errors';
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
import { BaseValidator, GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE } from './base_validator';
export class EndpointExceptionsValidator extends BaseValidator {
@ -46,8 +45,6 @@ export class EndpointExceptionsValidator extends BaseValidator {
await this.validateHasWritePrivilege();
await this.validateCreateOwnerSpaceIds(item);
await this.setOwnerSpaceId(item);
return item;
}
@ -58,10 +55,6 @@ export class EndpointExceptionsValidator extends BaseValidator {
await this.validateHasWritePrivilege();
await this.validateUpdateOwnerSpaceIds(item, currentItem);
if (!hasArtifactOwnerSpaceId(item)) {
await this.setOwnerSpaceId(item);
}
return item;
}
@ -70,8 +63,9 @@ export class EndpointExceptionsValidator extends BaseValidator {
await this.validateCanDeleteItemInActiveSpace(currentItem);
}
async validatePreGetOneItem(): Promise<void> {
async validatePreGetOneItem(currentItem: ExceptionListItemSchema): Promise<void> {
await this.validateHasReadPrivilege();
await this.validateCanReadItemInActiveSpace(currentItem);
}
async validatePreMultiListFind(): Promise<void> {

View file

@ -14,7 +14,6 @@ import type {
UpdateExceptionListItemOptions,
} from '@kbn/lists-plugin/server';
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
import type { ExceptionItemLikeOptions } from '../types';
import { BaseValidator } from './base_validator';
@ -62,8 +61,6 @@ export class EventFilterValidator extends BaseValidator {
await this.validateCreateOwnerSpaceIds(item);
await this.setOwnerSpaceId(item);
return item;
}
@ -91,10 +88,6 @@ export class EventFilterValidator extends BaseValidator {
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
await this.setOwnerSpaceId(_updatedItem);
}
return _updatedItem;
}
@ -108,8 +101,9 @@ export class EventFilterValidator extends BaseValidator {
}
}
async validatePreGetOneItem(): Promise<void> {
async validatePreGetOneItem(currentItem: ExceptionListItemSchema): Promise<void> {
await this.validateHasReadPrivilege();
await this.validateCanReadItemInActiveSpace(currentItem);
}
async validatePreSummary(): Promise<void> {

View file

@ -13,7 +13,6 @@ import type {
UpdateExceptionListItemOptions,
} from '@kbn/lists-plugin/server';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
import { BaseValidator, BasicEndpointExceptionDataSchema } from './base_validator';
import { EndpointArtifactExceptionValidationError } from './errors';
import type { ExceptionItemLikeOptions } from '../types';
@ -82,8 +81,6 @@ export class HostIsolationExceptionsValidator extends BaseValidator {
await this.validateByPolicyItem(item);
await this.validateCreateOwnerSpaceIds(item);
await this.setOwnerSpaceId(item);
return item;
}
@ -99,15 +96,12 @@ export class HostIsolationExceptionsValidator extends BaseValidator {
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
await this.setOwnerSpaceId(_updatedItem);
}
return _updatedItem;
}
async validatePreGetOneItem(): Promise<void> {
async validatePreGetOneItem(currentItem: ExceptionListItemSchema): Promise<void> {
await this.validateHasReadPrivilege();
await this.validateCanReadItemInActiveSpace(currentItem);
}
async validatePreSummary(): Promise<void> {

View file

@ -76,6 +76,10 @@ export class BaseValidatorMock extends BaseValidator {
_validateCanDeleteItemInActiveSpace(currentSavedItem: ExceptionListItemSchema): Promise<void> {
return this.validateCanDeleteItemInActiveSpace(currentSavedItem);
}
_validateCanReadItemInActiveSpace(currentSavedItem: ExceptionListItemSchema): Promise<void> {
return this.validateCanReadItemInActiveSpace(currentSavedItem);
}
}
export const createExceptionItemLikeOptionsMock = (

View file

@ -15,7 +15,7 @@ import type {
CreateExceptionListItemOptions,
UpdateExceptionListItemOptions,
} from '@kbn/lists-plugin/server';
import { hasArtifactOwnerSpaceId } from '../../../../common/endpoint/service/artifacts/utils';
import {} from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client_types';
import { BaseValidator } from './base_validator';
import type { ExceptionItemLikeOptions } from '../types';
import type { TrustedAppConditionEntry as ConditionEntry } from '../../../../common/endpoint/types';
@ -210,8 +210,6 @@ export class TrustedAppValidator extends BaseValidator {
await this.validateCreateOwnerSpaceIds(item);
await this.validateCanCreateGlobalArtifacts(item);
await this.setOwnerSpaceId(item);
return item;
}
@ -220,8 +218,9 @@ export class TrustedAppValidator extends BaseValidator {
await this.validateCanDeleteItemInActiveSpace(currentItem);
}
async validatePreGetOneItem(): Promise<void> {
async validatePreGetOneItem(currentItem: ExceptionListItemSchema): Promise<void> {
await this.validateHasReadPrivilege();
await this.validateCanReadItemInActiveSpace(currentItem);
}
async validatePreMultiListFind(): Promise<void> {
@ -264,10 +263,6 @@ export class TrustedAppValidator extends BaseValidator {
await this.validateUpdateOwnerSpaceIds(_updatedItem, currentItem);
await this.validateCanUpdateItemInActiveSpace(_updatedItem, currentItem);
if (!hasArtifactOwnerSpaceId(_updatedItem)) {
await this.setOwnerSpaceId(_updatedItem);
}
return _updatedItem;
}

View file

@ -18,7 +18,10 @@ import {
} from '@kbn/security-solution-plugin/common/endpoint/service/artifacts/utils';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
import { exceptionItemToCreateExceptionItem } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import type {
ExceptionListItemSchema,
FoundExceptionListItemSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { Role } from '@kbn/security-plugin-types-common';
import { GLOBAL_ARTIFACT_TAG } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts';
import { PolicyTestResourceInfo } from '../../../../../security_solution_endpoint/services/endpoint_policy';
@ -37,6 +40,7 @@ export default function ({ getService }: FtrProviderContext) {
// @skipInServerless: due to the fact that the serverless builtin roles are not yet updated with new privilege
// and tests below are currently creating a new role/user
describe('@ess @skipInServerless, @skipInServerlessMKI Endpoint Artifacts space awareness support', function () {
const afterEachDataCleanup: Array<Pick<ArtifactTestData, 'cleanup'>> = [];
const spaceOneId = 'space_one';
const spaceTwoId = 'space_two';
@ -45,6 +49,7 @@ export default function ({ getService }: FtrProviderContext) {
let supertestArtifactManager: TestAgent;
let supertestGlobalArtifactManager: TestAgent;
let spaceOnePolicy: PolicyTestResourceInfo;
let spaceTwoPolicy: PolicyTestResourceInfo;
before(async () => {
// For testing, we're using the `t3_analyst` role which already has All privileges
@ -93,12 +98,21 @@ export default function ({ getService }: FtrProviderContext) {
ensureSpaceIdExists(kbnServer, spaceTwoId, { log }),
]);
spaceOnePolicy = await policyTestResources.createPolicy();
spaceOnePolicy = await policyTestResources.createPolicy({ options: { spaceId: spaceOneId } });
spaceTwoPolicy = await policyTestResources.createPolicy({ options: { spaceId: spaceTwoId } });
});
// the endpoint uses data streams and es archiver does not support deleting them at the moment so we need
// to do it manually
after(async () => {
await spaceOnePolicy.cleanup();
// @ts-expect-error
spaceOnePolicy = undefined;
await spaceTwoPolicy.cleanup();
// @ts-expect-error
spaceTwoPolicy = undefined;
if (artifactManagerRole) {
await rolesUsersProvider.loader.delete(artifactManagerRole.name);
// @ts-expect-error
@ -110,12 +124,10 @@ export default function ({ getService }: FtrProviderContext) {
// @ts-expect-error
globalArtifactManagerRole = undefined;
}
});
if (spaceOnePolicy) {
await spaceOnePolicy.cleanup();
// @ts-expect-error
spaceOnePolicy = undefined;
}
afterEach(async () => {
await Promise.allSettled(afterEachDataCleanup.splice(0).map((data) => data.cleanup()));
});
const artifactLists = Object.keys(ENDPOINT_ARTIFACT_LISTS);
@ -127,27 +139,50 @@ export default function ({ getService }: FtrProviderContext) {
describe(`for ${listInfo.name}`, () => {
let spaceOnePerPolicyArtifact: ArtifactTestData;
let spaceOneGlobalArtifact: ArtifactTestData;
let spaceTwoPerPolicyArtifact: ArtifactTestData;
let spaceTwoGlobalArtifact: ArtifactTestData;
beforeEach(async () => {
// SPACE 1 ARTIFACTS
spaceOnePerPolicyArtifact = await endpointArtifactTestResources.createArtifact(
listInfo.id,
{ tags: [buildPerPolicyTag(spaceOnePolicy.packagePolicy.id)] },
{ supertest: supertestArtifactManager, spaceId: spaceOneId }
);
afterEachDataCleanup.push(spaceOnePerPolicyArtifact);
spaceOneGlobalArtifact = await endpointArtifactTestResources.createArtifact(
listInfo.id,
{ tags: [GLOBAL_ARTIFACT_TAG] },
{ supertest: supertestGlobalArtifactManager, spaceId: spaceOneId }
);
afterEachDataCleanup.push(spaceOneGlobalArtifact);
// SPACE 2 ARTIFACTS
spaceTwoPerPolicyArtifact = await endpointArtifactTestResources.createArtifact(
listInfo.id,
{ tags: [buildPerPolicyTag(spaceTwoPolicy.packagePolicy.id)] },
{ supertest: supertestGlobalArtifactManager, spaceId: spaceTwoId }
);
afterEachDataCleanup.push(spaceTwoPerPolicyArtifact);
spaceTwoGlobalArtifact = await endpointArtifactTestResources.createArtifact(
listInfo.id,
{ tags: [GLOBAL_ARTIFACT_TAG] },
{ supertest: supertestGlobalArtifactManager, spaceId: spaceTwoId }
);
afterEachDataCleanup.push(spaceTwoGlobalArtifact);
});
afterEach(async () => {
if (spaceOnePerPolicyArtifact) {
await spaceOnePerPolicyArtifact.cleanup();
// @ts-expect-error assigning `undefined`
spaceOnePerPolicyArtifact = undefined;
}
// @ts-expect-error assigning `undefined`
spaceOnePerPolicyArtifact = undefined;
// @ts-expect-error assigning `undefined`
spaceOneGlobalArtifact = undefined;
// @ts-expect-error assigning `undefined`
spaceTwoPerPolicyArtifact = undefined;
// @ts-expect-error assigning `undefined`
spaceTwoGlobalArtifact = undefined;
});
it('should add owner space id when item is created', async () => {
@ -176,6 +211,55 @@ export default function ({ getService }: FtrProviderContext) {
);
});
it('should return only artifacts in active space when sending a find request', async () => {
const response = await supertestArtifactManager
.get(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL + '/_find'))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log))
.query({
page: 1,
per_page: 100,
list_id: listInfo.id,
namespace_type: 'agnostic',
})
.send()
.expect(200);
const body = response.body as FoundExceptionListItemSchema;
log.info(`find results:\n${JSON.stringify(body)}, null, 2`);
expect(body.total).to.eql(3);
expect(body.data.map((item) => item.item_id).sort()).to.eql(
[
spaceOnePerPolicyArtifact.artifact.item_id,
spaceOneGlobalArtifact.artifact.item_id,
spaceTwoGlobalArtifact.artifact.item_id,
].sort()
);
});
it('should get single global artifact regardless of space', async () => {
const response = await supertestArtifactManager
.get(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log))
.query({
item_id: spaceTwoGlobalArtifact.artifact.item_id,
namespace_type: 'agnostic',
})
.send()
.expect(200);
expect((response.body as ExceptionListItemSchema).item_id).to.equal(
spaceTwoGlobalArtifact.artifact.item_id
);
});
describe('and user does NOT have global artifact management privilege', () => {
it('should error when attempting to create artifact with additional owner space id tags', async () => {
await supertestArtifactManager
@ -256,7 +340,7 @@ export default function ({ getService }: FtrProviderContext) {
.send(
exceptionItemToCreateExceptionItem({
...spaceOnePerPolicyArtifact.artifact,
description: 'updating item',
tags: [buildSpaceOwnerIdTag(spaceOneId)], // removed policy assignment
})
)
.expect(403);
@ -284,6 +368,22 @@ export default function ({ getService }: FtrProviderContext) {
`EndpointArtifactError: Updates to this shared item can only be done from the following space ID: ${spaceOneId} (or by someone having global artifact management privilege)`
);
});
it('should error when attempting to GET single artifact not associated with active space', async () => {
await supertestArtifactManager
.get(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log).ignoreCodes([404]))
.query({
// Artifact from Space 2
item_id: spaceTwoPerPolicyArtifact.artifact.item_id,
namespace_type: spaceTwoPerPolicyArtifact.artifact.namespace_type,
})
.send()
.expect(404);
});
});
describe('and user has privilege to manage global artifacts', () => {
@ -305,7 +405,15 @@ export default function ({ getService }: FtrProviderContext) {
)
.expect(200);
expect((body as ExceptionListItemSchema).tags).to.eql([
const itemCreated = body as ExceptionListItemSchema;
afterEachDataCleanup.push({
cleanup: () => {
return endpointArtifactTestResources.deleteExceptionItem(itemCreated);
},
});
expect(itemCreated.tags).to.eql([
buildSpaceOwnerIdTag('foo'),
buildSpaceOwnerIdTag(spaceOneId),
]);
@ -403,8 +511,148 @@ export default function ({ getService }: FtrProviderContext) {
// @ts-expect-error
spaceOnePerPolicyArtifact = undefined;
});
it('should allow GET of single artifact not associated with active space', async () => {
await supertestGlobalArtifactManager
.get(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log))
.query({
item_id: spaceTwoPerPolicyArtifact.artifact.item_id,
namespace_type: spaceTwoPerPolicyArtifact.artifact.namespace_type,
})
.send()
.expect(200);
});
});
});
}
// Endpoint exceptions are currently ONLY global, but an effort is underway to possibly fold them
// into a "regular" artifact, at which point we should just remove this entire `describe()` block
// and add endpoint exceptions to the execution of the tests above.
describe('and for Endpoint Exceptions', () => {
let endpointException: ArtifactTestData;
beforeEach(async () => {
endpointException = await endpointArtifactTestResources.createEndpointException(undefined, {
supertest: supertestGlobalArtifactManager,
spaceId: spaceOneId,
});
afterEachDataCleanup.push(endpointException);
});
afterEach(() => {
// @ts-expect-error
endpointException = undefined;
});
it('should error on create when user does not have global artifact privilege', async () => {
await supertestArtifactManager
.post(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
.send(
Object.assign(
exceptionItemToCreateExceptionItem({
...endpointException.artifact,
}),
{ item_id: undefined }
)
)
.expect(403);
});
it('should allow create when user has global artifact privilege', async () => {
const response = await supertestGlobalArtifactManager
.post(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
.send(
Object.assign(
exceptionItemToCreateExceptionItem({
...endpointException.artifact,
}),
{ item_id: undefined }
)
)
.expect(200);
afterEachDataCleanup.push({
cleanup: () =>
endpointArtifactTestResources.deleteExceptionItem(
response.body as ExceptionListItemSchema
),
});
});
it('should error on update when user does not have global artifact privilege', async () => {
await supertestArtifactManager
.put(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
.send(
exceptionItemToCreateExceptionItem({
...endpointException.artifact,
description: 'item was updated',
})
)
.expect(403);
});
it('should allow update when user has global artifact privilege', async () => {
await supertestGlobalArtifactManager
.put(addSpaceIdToPath('/', spaceOneId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log))
.send(
exceptionItemToCreateExceptionItem({
...endpointException.artifact,
description: 'item was updated',
})
)
.expect(200);
});
it('should error on delete when user does not have global artifact privilege', async () => {
await supertestArtifactManager
.delete(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log).ignoreCodes([403]))
.query({
item_id: endpointException.artifact.item_id,
namespace_type: endpointException.artifact.namespace_type,
})
.send()
.expect(403);
});
it('should allow delete when user has global artifact privilege', async () => {
await supertestGlobalArtifactManager
.delete(addSpaceIdToPath('/', spaceTwoId, EXCEPTION_LIST_ITEM_URL))
.set('elastic-api-version', '2023-10-31')
.set('x-elastic-internal-origin', 'kibana')
.set('kbn-xsrf', 'true')
.on('error', createSupertestErrorLogger(log))
.query({
item_id: endpointException.artifact.item_id,
namespace_type: endpointException.artifact.namespace_type,
})
.send()
.expect(200);
});
});
});
}

View file

@ -5,16 +5,20 @@
* 2.0.
*/
import type {
import {
CreateExceptionListItemSchema,
CreateExceptionListSchema,
ExceptionListItemSchema,
ExceptionListTypeEnum,
} from '@kbn/securitysolution-io-ts-list-types';
import {
ENDPOINT_ARTIFACT_LISTS,
ENDPOINT_ARTIFACT_LIST_IDS,
EXCEPTION_LIST_ITEM_URL,
EXCEPTION_LIST_URL,
ENDPOINT_LIST_NAME,
ENDPOINT_LIST_DESCRIPTION,
ENDPOINT_LIST_ID,
} from '@kbn/securitysolution-list-constants';
import { Response } from 'superagent';
import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator';
@ -26,6 +30,7 @@ import { BLOCKLISTS_LIST_DEFINITION } from '@kbn/security-solution-plugin/public
import { ManifestConstants } from '@kbn/security-solution-plugin/server/endpoint/lib/artifacts';
import TestAgent from 'supertest/lib/agent';
import { addSpaceIdToPath, DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { isArtifactGlobal } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts';
import { FtrService } from '../../functional/ftr_provider_context';
import { InternalUnifiedManifestSchemaResponseType } from '../apps/integrations/mocks';
@ -73,6 +78,8 @@ export class EndpointArtifactsTestResources extends FtrService {
createPayload: CreateExceptionListItemSchema,
{ supertest = this.supertest, spaceId = DEFAULT_SPACE_ID }: ArtifactCreateOptions = {}
): Promise<ArtifactTestData> {
this.log.verbose(`Creating exception item:\n${JSON.stringify(createPayload)}`);
const artifact = await supertest
.post(addSpaceIdToPath('/', spaceId, EXCEPTION_LIST_ITEM_URL))
.set('kbn-xsrf', 'true')
@ -80,28 +87,15 @@ export class EndpointArtifactsTestResources extends FtrService {
.then(this.getHttpResponseFailureHandler())
.then((response) => response.body as ExceptionListItemSchema);
const { item_id: itemId, namespace_type: namespaceType, list_id: listId } = artifact;
const { item_id: itemId, list_id: listId } = artifact;
const artifactAssignment = isArtifactGlobal(artifact) ? 'Global' : 'Per-Policy';
this.log.info(
`Created exception list item in space [${spaceId}], List ID [${listId}], Item ID ${itemId}`
`Created [${artifactAssignment}] exception list item in space [${spaceId}], List ID [${listId}], Item ID [${itemId}]`
);
const cleanup = async () => {
const deleteResponse = await supertest
.delete(
`${addSpaceIdToPath(
'/',
spaceId,
EXCEPTION_LIST_ITEM_URL
)}?item_id=${itemId}&namespace_type=${namespaceType}`
)
.set('kbn-xsrf', 'true')
.send()
.then(this.getHttpResponseFailureHandler([404]));
this.log.info(
`Deleted exception list item [${listId}]: ${itemId} (${deleteResponse.status})`
);
await this.deleteExceptionItem(artifact, { supertest, spaceId });
};
return {
@ -110,6 +104,49 @@ export class EndpointArtifactsTestResources extends FtrService {
};
}
async deleteExceptionItem(
{
list_id: listId,
item_id: itemId,
namespace_type: nameSpaceType,
}: Pick<ExceptionListItemSchema, 'list_id' | 'item_id' | 'namespace_type'>,
{ supertest = this.supertest, spaceId = DEFAULT_SPACE_ID }: ArtifactCreateOptions = {}
): Promise<void> {
const deleteResponse = await supertest
.delete(
`${addSpaceIdToPath(
'/',
spaceId,
EXCEPTION_LIST_ITEM_URL
)}?item_id=${itemId}&namespace_type=${nameSpaceType}`
)
.set('kbn-xsrf', 'true')
.send()
.then(this.getHttpResponseFailureHandler([404]));
this.log.info(`Deleted exception list item [${listId}]: ${itemId} (${deleteResponse.status})`);
}
async createEndpointException(
overrides: Partial<CreateExceptionListItemSchema> = {},
options?: ArtifactCreateOptions
): Promise<ArtifactTestData> {
await this.ensureListExists(
{
name: ENDPOINT_LIST_NAME,
description: ENDPOINT_LIST_DESCRIPTION,
list_id: ENDPOINT_LIST_ID,
type: ExceptionListTypeEnum.ENDPOINT,
namespace_type: 'agnostic',
},
options
);
const endpointException =
this.exceptionsGenerator.generateEndpointExceptionForCreate(overrides);
return this.createExceptionItem(endpointException, options);
}
async createTrustedApp(
overrides: Partial<CreateExceptionListItemSchema> = {},
options?: ArtifactCreateOptions
@ -168,6 +205,9 @@ export class EndpointArtifactsTestResources extends FtrService {
case ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id: {
return this.createHostIsolationException(overrides, options);
}
case ENDPOINT_LIST_ID: {
return this.createEndpointException(overrides, options);
}
default:
throw new Error(`Unexpected list id ${listId}`);
}

View file

@ -25,6 +25,7 @@ import { Immutable } from '@kbn/security-solution-plugin/common/endpoint/types';
// NOTE: import path below should be the deep path to the actual module - else we get CI errors
import { pkgKeyFromPackageInfo } from '@kbn/fleet-plugin/public/services/pkg_key_from_package_info';
import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/errors';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
import { FtrProviderContext } from '../configs/ftr_provider_context';
const FLEET_API_ROOT = '/api/fleet';
@ -157,9 +158,11 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
async createPolicy({
agentPolicyOverrides = {},
integrationPolicyOverrides = {},
options = {},
}: Partial<{
agentPolicyOverrides: Partial<CreateAgentPolicyRequest['body']>;
integrationPolicyOverrides: Partial<CreatePackagePolicyRequest['body']>;
options: Partial<{ spaceId: string }>;
}> = {}): Promise<PolicyTestResourceInfo> {
// create Agent Policy
let agentPolicy: CreateAgentPolicyResponse['item'];
@ -171,7 +174,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
...agentPolicyOverrides,
};
const { body: createResponse }: { body: CreateAgentPolicyResponse } = await supertest
.post(FLEET_API_AGENT_POLICIES)
.post(addSpaceIdToPath('/', options?.spaceId ?? '', FLEET_API_AGENT_POLICIES))
.set('kbn-xsrf', 'xxx')
.send(newAgentPolicyData)
.expect(200);
@ -220,7 +223,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
...integrationPolicyOverrides,
};
const { body: createResponse }: { body: CreatePackagePolicyResponse } = await supertest
.post(FLEET_API_PACKAGE_POLICIES)
.post(addSpaceIdToPath('/', options?.spaceId ?? '', FLEET_API_PACKAGE_POLICIES))
.set('kbn-xsrf', 'xxx')
.send(newPackagePolicyData)
.expect(200);
@ -245,7 +248,9 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
packagePolicyIds: [packagePolicy.id],
};
await supertest
.post(FLEET_API_PACKAGE_POLICIES_DELETE)
.post(
addSpaceIdToPath('/', options?.spaceId ?? '', FLEET_API_PACKAGE_POLICIES_DELETE)
)
.set('kbn-xsrf', 'xxx')
.send(deletePackagePolicyData)
.expect(200);
@ -263,7 +268,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC
agentPolicyId: agentPolicy.id,
};
await supertest
.post(FLEET_API_AGENT_POLICIES_DELETE)
.post(addSpaceIdToPath('/', options?.spaceId ?? '', FLEET_API_AGENT_POLICIES_DELETE))
.set('kbn-xsrf', 'xxx')
.send(deleteAgentPolicyData)
.expect(200);