[Security Solution][Endpoint] Add validation for Trusted Apps APIs get, find, delete, export (#123973)

* Add API specific validation methods to validator class

fixes elastic/security-team/issues/2823

* Add TA validations for `get` single and multi list, get one item, get list summary and delete APIs

fixes elastic/security-team/issues/2823

* validate export

fixes elastic/security-team/issues/2823

* remove redundant validation for delete

review changes

* don't validate the other artifacts if validation fails

review changes

* rename test files

* merge cleanup

* add tests for API validations

fixes elastic/security-team/issues/

* better naming

review changes

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ashokaditya 2022-01-31 21:49:28 +01:00 committed by GitHub
parent 2760e916b2
commit 857b205520
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 174 additions and 55 deletions

View file

@ -12,9 +12,10 @@ import {
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { EventFilterValidator, TrustedAppValidator } from '../validators';
type ValidatorCallback = ExceptionsListPreCreateItemServerExtension['callback'];
export const getExceptionsPreCreateItemHandler = (
endpointAppContext: EndpointAppContextService
): ExceptionsListPreCreateItemServerExtension['callback'] => {
): ValidatorCallback => {
return async function ({ data, context: { request } }): Promise<CreateExceptionListItemOptions> {
// Validate trusted apps
if (TrustedAppValidator.isTrustedApp(data)) {

View file

@ -8,11 +8,13 @@
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreDeleteItemServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators/trusted_app_validator';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreDeleteItemServerExtension['callback'];
export const getExceptionsPreDeleteItemHandler = (
endpointAppContext: EndpointAppContextService
): ExceptionsListPreDeleteItemServerExtension['callback'] => {
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;
@ -29,12 +31,18 @@ export const getExceptionsPreDeleteItemHandler = (
return data;
}
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId: exceptionItem.list_id })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreDeleteItem();
return data;
}
// Host Isolation Exception
if (HostIsolationExceptionsValidator.isHostIsolationException(exceptionItem.list_id)) {
await new HostIsolationExceptionsValidator(
endpointAppContext,
endpointAppContextService,
request
).validatePreDeleteItem();
return data;
}
return data;

View file

@ -7,11 +7,13 @@
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreExportServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators/trusted_app_validator';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreExportServerExtension['callback'];
export const getExceptionsPreExportHandler = (
endpointAppContextService: EndpointAppContextService
): ExceptionsListPreExportServerExtension['callback'] => {
): ValidatorCallback => {
return async function ({ data, context: { request, exceptionListClient } }) {
const { listId: maybeListId, id } = data;
let listId: string | null | undefined = maybeListId;
@ -24,12 +26,18 @@ export const getExceptionsPreExportHandler = (
return data;
}
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreExport();
return data;
}
// Host Isolation Exceptions validations
if (HostIsolationExceptionsValidator.isHostIsolationException(listId)) {
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreExport();
return data;
}
return data;

View file

@ -8,11 +8,13 @@
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreGetOneItemServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators/trusted_app_validator';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreGetOneItemServerExtension['callback'];
export const getExceptionsPreGetOneHandler = (
endpointAppContextService: EndpointAppContextService
): ExceptionsListPreGetOneItemServerExtension['callback'] => {
): ValidatorCallback => {
return async function ({ data, context: { request, exceptionListClient } }) {
if (data.namespaceType !== 'agnostic') {
return data;
@ -29,13 +31,17 @@ export const getExceptionsPreGetOneHandler = (
return data;
}
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId: exceptionItem.list_id })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreGetOneItem();
return data;
}
// validate Host Isolation Exception
if (HostIsolationExceptionsValidator.isHostIsolationException(exceptionItem.list_id)) {
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreGetOneItem();
return data;
}

View file

@ -9,23 +9,23 @@ import { ExceptionsListPreImportServerExtension } from '../../../../../lists/ser
import { EndpointArtifactExceptionValidationError } from '../validators/errors';
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants';
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'
);
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);
}
return data;
};
return false;
});
if (hasEndpointArtifactListOrListItems) {
throw new EndpointArtifactExceptionValidationError(
'Import is not supported for Endpoint artifact exceptions'
);
}
return data;
};
};

View file

@ -7,22 +7,30 @@
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreMultiListFindServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators/trusted_app_validator';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreMultiListFindServerExtension['callback'];
export const getExceptionsPreMultiListFindHandler = (
endpointAppContext: EndpointAppContextService
): ExceptionsListPreMultiListFindServerExtension['callback'] => {
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
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;
}
// Validate Host Isolation Exceptions
if (data.listId.some(HostIsolationExceptionsValidator.isHostIsolationException)) {
await new HostIsolationExceptionsValidator(
endpointAppContext,
endpointAppContextService,
request
).validatePreMultiListFind();
return data;
}
return data;

View file

@ -7,22 +7,29 @@
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreSingleListFindServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators/trusted_app_validator';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreSingleListFindServerExtension['callback'];
export const getExceptionsPreSingleListFindHandler = (
endpointAppContext: EndpointAppContextService
): ExceptionsListPreSingleListFindServerExtension['callback'] => {
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
return async function ({ data, context: { request } }) {
if (data.namespaceType !== 'agnostic') {
return data;
}
// Validate Host Isolation Exceptions
if (TrustedAppValidator.isTrustedApp({ listId: data.listId })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreSingleListFind();
return data;
}
if (HostIsolationExceptionsValidator.isHostIsolationException(data.listId)) {
await new HostIsolationExceptionsValidator(
endpointAppContext,
endpointAppContextService,
request
).validatePreSingleListFind();
return data;
}
return data;

View file

@ -7,11 +7,13 @@
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionsListPreSummaryServerExtension } from '../../../../../lists/server';
import { TrustedAppValidator } from '../validators';
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
type ValidatorCallback = ExceptionsListPreSummaryServerExtension['callback'];
export const getExceptionsPreSummaryHandler = (
endpointAppContext: EndpointAppContextService
): ExceptionsListPreSummaryServerExtension['callback'] => {
endpointAppContextService: EndpointAppContextService
): ValidatorCallback => {
return async function ({ data, context: { request, exceptionListClient } }) {
const { listId: maybeListId, id } = data;
let listId: string | null | undefined = maybeListId;
@ -24,9 +26,18 @@ export const getExceptionsPreSummaryHandler = (
return data;
}
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
await new TrustedAppValidator(endpointAppContextService, request).validatePreGetListSummary();
return data;
}
// Host Isolation Exceptions
if (HostIsolationExceptionsValidator.isHostIsolationException(listId)) {
await new HostIsolationExceptionsValidator(endpointAppContext, request).validatePreSummary();
await new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreSummary();
return data;
}
return data;

View file

@ -12,9 +12,10 @@ import {
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { EventFilterValidator, TrustedAppValidator } from '../validators';
type ValidatorCallback = ExceptionsListPreUpdateItemServerExtension['callback'];
export const getExceptionsPreUpdateItemHandler = (
endpointAppContextService: EndpointAppContextService
): ExceptionsListPreUpdateItemServerExtension['callback'] => {
): ValidatorCallback => {
return async function ({
data,
context: { request, exceptionListClient },
@ -37,7 +38,7 @@ export const getExceptionsPreUpdateItemHandler = (
const listId = currentSavedItem.list_id;
// Validate trusted apps
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
return new TrustedAppValidator(endpointAppContextService, request).validatePreUpdateItem(
data,
@ -45,7 +46,7 @@ export const getExceptionsPreUpdateItemHandler = (
);
}
// Validate event filter
// Validate Event Filters
if (EventFilterValidator.isEventFilter({ listId })) {
return new EventFilterValidator(endpointAppContextService, request).validatePreUpdateItem(
data,

View file

@ -187,6 +187,30 @@ export class TrustedAppValidator extends BaseValidator {
return item;
}
async validatePreDeleteItem(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreGetOneItem(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreMultiListFind(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreExport(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreSingleListFind(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreGetListSummary(): Promise<void> {
await this.validateCanManageEndpointArtifacts();
}
async validatePreUpdateItem(
_updatedItem: UpdateExceptionListItemOptions,
currentItem: ExceptionListItemSchema

View file

@ -25,7 +25,7 @@ export default function ({ getService }: FtrProviderContext) {
const endpointPolicyTestResources = getService('endpointPolicyTestResources');
const endpointArtifactTestResources = getService('endpointArtifactTestResources');
describe('Endpoint artifacts (via lists plugin) event filter', () => {
describe('Endpoint artifacts (via lists plugin): Event Filters', () => {
let fleetEndpointPolicy: PolicyTestResourceInfo;
before(async () => {

View file

@ -6,19 +6,19 @@
*/
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import { FtrProviderContext } from '../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../security_solution_endpoint/services/endpoint_policy';
import { ArtifactTestData } from '../../security_solution_endpoint/services/endpoint_artifacts';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../plugins/security_solution/common/endpoint/service/artifacts';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy';
import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../../plugins/security_solution/common/endpoint/service/artifacts';
import {
createUserAndRole,
deleteUserAndRole,
ROLES,
} from '../../common/services/security_solution';
} from '../../../common/services/security_solution';
import {
getImportExceptionsListSchemaMock,
toNdJsonString,
} from '../../../plugins/lists/common/schemas/request/import_exceptions_schema.mock';
} from '../../../../plugins/lists/common/schemas/request/import_exceptions_schema.mock';
export default function ({ getService }: FtrProviderContext) {
const USER = ROLES.detections_admin;

View file

@ -5,19 +5,19 @@
* 2.0.
*/
import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants';
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../security_solution_endpoint/services/endpoint_policy';
import { ArtifactTestData } from '../../security_solution_endpoint/services/endpoint_artifacts';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../plugins/security_solution/common/endpoint/service/artifacts';
import { ExceptionsListItemGenerator } from '../../../plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy';
import { ArtifactTestData } from '../../../security_solution_endpoint/services//endpoint_artifacts';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../../plugins/security_solution/common/endpoint/service/artifacts';
import { ExceptionsListItemGenerator } from '../../../../plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator';
import {
createUserAndRole,
deleteUserAndRole,
ROLES,
} from '../../common/services/security_solution';
} from '../../../common/services/security_solution';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
@ -25,7 +25,7 @@ export default function ({ getService }: FtrProviderContext) {
const endpointPolicyTestResources = getService('endpointPolicyTestResources');
const endpointArtifactTestResources = getService('endpointArtifactTestResources');
describe('Endpoint artifacts (via lists plugin)', () => {
describe('Endpoint artifacts (via lists plugin): Trusted Applications', () => {
let fleetEndpointPolicy: PolicyTestResourceInfo;
before(async () => {
@ -66,6 +66,7 @@ export default function ({ getService }: FtrProviderContext) {
type TrustedAppApiCallsInterface<BodyReturnType = unknown> = Array<{
method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>;
info?: string;
path: string;
// The body just needs to have the properties we care about in the tests. This should cover most
// mocks used for testing that support different interfaces
@ -213,7 +214,51 @@ export default function ({ getService }: FtrProviderContext) {
});
describe('and user DOES NOT have authorization to manage endpoint security', () => {
for (const trustedAppApiCall of trustedAppApiCalls) {
const allTrustedAppApiCalls: TrustedAppApiCallsInterface = [
...trustedAppApiCalls,
{
method: 'get',
info: 'single item',
get path() {
return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`;
},
getBody: () => undefined,
},
{
method: 'get',
info: 'list summary',
get path() {
return `${EXCEPTION_LIST_URL}/summary?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}`;
},
getBody: () => undefined,
},
{
method: 'delete',
info: 'single item',
get path() {
return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`;
},
getBody: () => undefined,
},
{
method: 'post',
info: 'list export',
get path() {
return `${EXCEPTION_LIST_URL}/_export?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&id=1`;
},
getBody: () => undefined,
},
{
method: 'get',
info: 'single items',
get path() {
return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`;
},
getBody: () => undefined,
},
];
for (const trustedAppApiCall of allTrustedAppApiCalls) {
it(`should error on [${trustedAppApiCall.method}]`, async () => {
await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path)
.auth(ROLES.detections_admin, 'changeme')

View file

@ -32,8 +32,8 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider
loadTestFile(require.resolve('./policy'));
loadTestFile(require.resolve('./package'));
loadTestFile(require.resolve('./endpoint_authz'));
loadTestFile(require.resolve('./endpoint_artifacts'));
loadTestFile(require.resolve('./endpoint_artifacts/event_filter'));
loadTestFile(require.resolve('./endpoint_host_isolation_exceptions_artifacts'));
loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps'));
loadTestFile(require.resolve('./endpoint_artifacts/event_filters'));
loadTestFile(require.resolve('./endpoint_artifacts/host_isolation_exceptions'));
});
}