Skip to content

Commit

Permalink
[Security Solution] Put Artifacts by Policy feature behind a feature …
Browse files Browse the repository at this point in the history
…flag (elastic#95284)

* Added sync_master file for tracking/triggering PRs for merging master into feature branch

* removed unnecessary (temporary) markdown file

* Trusted apps by policy api (elastic#88025)

* Initial version of API for trusted apps per policy.

* Fixed compilation errors because of missing new property.

* Mapping from tags to policies and back. (No testing)

* Fixed compilation error after pulling in main.

* Fixed failing tests.

* Separated out the prefix in tag for policy reference into constant.

Co-authored-by: Kibana Machine <[email protected]>

* [SECURITY_SOLUTION][ENDPOINT] Ability to create a Trusted App as either Global or Policy Specific (elastic#88707)

* Create form supports selecting policies or making Trusted app global
* New component `EffectedPolicySelect` - for selecting policies
* Enhanced `waitForAction()` test utility to provide a `validate()` option

* [SECURITY SOLUTION][ENDPOINT] UI for editing Trusted Application items (elastic#89479)

* Add Edit button to TA card UI
* Support additional url params (`show`, `id`)
* Refactor TrustedAppForm to support Editing of an existing entry

* [SECURITY SOLUTION][ENDPOINT] API (`PUT`) for Trusted Apps Edit flow (elastic#90333)

* New API route for Update (`PUT`)
* Connect UI to Update (PUT) API
* Add `version` to TrustedApp type and return it on the API responses
* Refactor - moved some public/server shared modules to top-level `common/*`

* [SECURITY SOLUTION][ENDPOINT] Trusted Apps API to retrieve a single Trusted App item (elastic#90842)

* Get One Trusted App API - route, service, handler
* Adjust UI to call GET api to retrieve trusted app for edit
* Deleted ununsed trusted app types file
* Add UI handling of non-existing TA for edit or when id is missing in url

* [Security Solution][Endpoint] Multiple misc. updates/fixes for Edit Trusted Apps (elastic#91656)

* correct trusted app schema to ensure `version` is not exposed on TS type for POST
* Added updated_by, updated_on properties to TrustedApp
* Refactored TA List view to fix bug where card was not updated on a successful edit
* Test cases for card interaction from the TA List view
* Change title of policy selection to `Assignment`
* Selectable Policy CSS adjustments based on UX feedback

* Fix failing server tests

* [Security Solution][Endpoint] Trusted Apps list API KQL filtering support (elastic#92611)

* Fix bad merge from master
* Fix trusted apps generator
* Add `kuery` to the GET (list) Trusted Apps api

* Refactor schema with Put method after merging changes with master

* WIP: allow effectScope only when feature flag is enabled

* Fixes errors with non declared logger

* Uses experimental features module to allow or not effectScope on create/update trusted app schema

* Set default value for effectScope when feature flag is disabled

* Adds experimentals into redux store. Also creates hook to retrieve a feature flag value from state

* Hides effectPolicy when feature flag is not enabled

* Fixes unit test mocking hook and adds new test case

* Changes file extension for custom hook

* Adds new unit test for custom hook

* Hides horizontal bar with feature flag

* Compress text area depending on feature flag

* Fixes failing test because feature flag

* Fixes wrong import and unit test

* Thwrows error if invalid feature flag check

* Adds snapshoot checks with feature flag enabled/disabled

* Test snapshots

* Changes type name

* Add experimentalFeatures in app context

* Fixes type checks due AppContext changes

* Fixes test due changes on custom hook

Co-authored-by: Paul Tavares <[email protected]>
Co-authored-by: Bohdan Tsymbala <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Paul Tavares <[email protected]>
  • Loading branch information
5 people committed Mar 26, 2021
1 parent 71ebbdc commit fe247d6
Show file tree
Hide file tree
Showing 73 changed files with 9,574 additions and 694 deletions.
5 changes: 4 additions & 1 deletion x-pack/plugins/lists/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { ListPlugin } from './plugin';

// exporting these since its required at top level in siem plugin
export { ListClient } from './services/lists/list_client';
export { CreateExceptionListItemOptions } from './services/exception_lists/exception_list_client_types';
export {
CreateExceptionListItemOptions,
UpdateExceptionListItemOptions,
} from './services/exception_lists/exception_list_client_types';
export { ExceptionListClient } from './services/exception_lists/exception_list_client';
export type { ListPluginSetup, ListsApiRequestHandlerContext } from './types';

Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*';
export const LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG = 'endpoint:limited-concurrency';
export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100;

export const TRUSTED_APPS_GET_API = '/api/endpoint/trusted_apps/{id}';
export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_UPDATE_API = '/api/endpoint/trusted_apps/{id}';
export const TRUSTED_APPS_DELETE_API = '/api/endpoint/trusted_apps/{id}';
export const TRUSTED_APPS_SUMMARY_API = '/api/endpoint/trusted_apps/summary';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@
* 2.0.
*/

import { GetTrustedAppsRequestSchema, PostTrustedAppCreateRequestSchema } from './trusted_apps';
import { ConditionEntryField, OperatingSystem } from '../types';
import {
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
PutTrustedAppUpdateRequestSchema,
} from './trusted_apps';
import {
ConditionEntry,
ConditionEntryField,
NewTrustedApp,
OperatingSystem,
PutTrustedAppsRequestParams,
} from '../types';

describe('When invoking Trusted Apps Schema', () => {
describe('for GET List', () => {
Expand Down Expand Up @@ -72,17 +82,18 @@ describe('When invoking Trusted Apps Schema', () => {
});

describe('for POST Create', () => {
const createConditionEntry = <T>(data?: T) => ({
const createConditionEntry = <T>(data?: T): ConditionEntry => ({
field: ConditionEntryField.PATH,
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
...(data || {}),
});
const createNewTrustedApp = <T>(data?: T) => ({
const createNewTrustedApp = <T>(data?: T): NewTrustedApp => ({
name: 'Some Anti-Virus App',
description: 'this one is ok',
os: 'windows',
os: OperatingSystem.WINDOWS,
effectScope: { type: 'global' },
entries: [createConditionEntry()],
...(data || {}),
});
Expand Down Expand Up @@ -329,4 +340,55 @@ describe('When invoking Trusted Apps Schema', () => {
});
});
});

describe('for PUT Update', () => {
const createConditionEntry = <T>(data?: T): ConditionEntry => ({
field: ConditionEntryField.PATH,
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
...(data || {}),
});
const createNewTrustedApp = <T>(data?: T): NewTrustedApp => ({
name: 'Some Anti-Virus App',
description: 'this one is ok',
os: OperatingSystem.WINDOWS,
effectScope: { type: 'global' },
entries: [createConditionEntry()],
...(data || {}),
});

const updateParams = <T>(data?: T): PutTrustedAppsRequestParams => ({
id: 'validId',
...(data || {}),
});

const body = PutTrustedAppUpdateRequestSchema.body;
const params = PutTrustedAppUpdateRequestSchema.params;

it('should not error on a valid message', () => {
const bodyMsg = createNewTrustedApp();
const paramsMsg = updateParams();
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
expect(params.validate(paramsMsg)).toStrictEqual(paramsMsg);
});

it('should validate `id` params is required', () => {
expect(() => params.validate(updateParams({ id: undefined }))).toThrow();
});

it('should validate `id` params to be string', () => {
expect(() => params.validate(updateParams({ id: 1 }))).toThrow();
});

it('should validate `version`', () => {
const bodyMsg = createNewTrustedApp({ version: 'v1' });
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
});

it('should validate `version` must be string', () => {
const bodyMsg = createNewTrustedApp({ version: 1 });
expect(() => body.validate(bodyMsg)).toThrow();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@
*/

import { schema } from '@kbn/config-schema';
import { ConditionEntryField, OperatingSystem } from '../types';
import { getDuplicateFields, isValidHash } from '../validation/trusted_apps';
import { ConditionEntry, ConditionEntryField, OperatingSystem } from '../types';
import { getDuplicateFields, isValidHash } from '../service/trusted_apps/validations';

export const DeleteTrustedAppsRequestSchema = {
params: schema.object({
id: schema.string(),
}),
};

export const GetOneTrustedAppRequestSchema = {
params: schema.object({
id: schema.string(),
}),
};

export const GetTrustedAppsRequestSchema = {
query: schema.object({
page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })),
per_page: schema.maybe(schema.number({ defaultValue: 20, min: 1 })),
kuery: schema.maybe(schema.string()),
}),
};

Expand All @@ -40,18 +47,18 @@ const CommonEntrySchema = {
schema.siblingRef('field'),
ConditionEntryField.HASH,
schema.string({
validate: (hash) =>
validate: (hash: string) =>
isValidHash(hash) ? undefined : `invalidField.${ConditionEntryField.HASH}`,
}),
schema.conditional(
schema.siblingRef('field'),
ConditionEntryField.PATH,
schema.string({
validate: (field) =>
validate: (field: string) =>
field.length > 0 ? undefined : `invalidField.${ConditionEntryField.PATH}`,
}),
schema.string({
validate: (field) =>
validate: (field: string) =>
field.length > 0 ? undefined : `invalidField.${ConditionEntryField.SIGNER}`,
})
)
Expand Down Expand Up @@ -99,7 +106,7 @@ const EntrySchemaDependingOnOS = schema.conditional(
*/
const EntriesSchema = schema.arrayOf(EntrySchemaDependingOnOS, {
minSize: 1,
validate(entries) {
validate(entries: ConditionEntry[]) {
return (
getDuplicateFields(entries)
.map((field) => `duplicatedEntry.${field}`)
Expand All @@ -108,15 +115,35 @@ const EntriesSchema = schema.arrayOf(EntrySchemaDependingOnOS, {
},
});

export const PostTrustedAppCreateRequestSchema = {
body: schema.object({
const getTrustedAppForOsScheme = (forUpdateFlow: boolean = false) =>
schema.object({
name: schema.string({ minLength: 1, maxLength: 256 }),
description: schema.maybe(schema.string({ minLength: 0, maxLength: 256, defaultValue: '' })),
os: schema.oneOf([
schema.literal(OperatingSystem.WINDOWS),
schema.literal(OperatingSystem.LINUX),
schema.literal(OperatingSystem.MAC),
]),
effectScope: schema.oneOf([
schema.object({
type: schema.literal('global'),
}),
schema.object({
type: schema.literal('policy'),
policies: schema.arrayOf(schema.string({ minLength: 1 })),
}),
]),
entries: EntriesSchema,
...(forUpdateFlow ? { version: schema.maybe(schema.string()) } : {}),
});

export const PostTrustedAppCreateRequestSchema = {
body: getTrustedAppForOsScheme(),
};

export const PutTrustedAppUpdateRequestSchema = {
params: schema.object({
id: schema.string(),
}),
body: getTrustedAppForOsScheme(true),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { MaybeImmutable, NewTrustedApp, UpdateTrustedApp } from '../../types';

const NEW_TRUSTED_APP_KEYS: Array<keyof UpdateTrustedApp> = [
'name',
'effectScope',
'entries',
'description',
'os',
'version',
];

export const toUpdateTrustedApp = <T extends NewTrustedApp>(
trustedApp: MaybeImmutable<T>
): UpdateTrustedApp => {
const trustedAppForUpdate: UpdateTrustedApp = {} as UpdateTrustedApp;

for (const key of NEW_TRUSTED_APP_KEYS) {
// This should be safe. Its needed due to the inter-dependency on property values (`os` <=> `entries`)
// @ts-expect-error
trustedAppForUpdate[key] = trustedApp[key];
}
return trustedAppForUpdate;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { ConditionEntry, ConditionEntryField } from '../types';
import { ConditionEntry, ConditionEntryField } from '../../types';

const HASH_LENGTHS: readonly number[] = [
32, // MD5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };

/**
* Utility type that will return back a union of the given [T]ype and an Immutable version of it
*/
export type MaybeImmutable<T> = T | Immutable<T>;

/**
* Stats for related events for a particular node in a resolver graph.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ import { TypeOf } from '@kbn/config-schema';
import { ApplicationStart } from 'kibana/public';
import {
DeleteTrustedAppsRequestSchema,
GetOneTrustedAppRequestSchema,
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
PutTrustedAppUpdateRequestSchema,
} from '../schema/trusted_apps';
import { OperatingSystem } from './os';

/** API request params for deleting Trusted App entry */
export type DeleteTrustedAppsRequestParams = TypeOf<typeof DeleteTrustedAppsRequestSchema.params>;

export type GetOneTrustedAppRequestParams = TypeOf<typeof GetOneTrustedAppRequestSchema.params>;

export interface GetOneTrustedAppResponse {
data: TrustedApp;
}

/** API request params for retrieving a list of Trusted Apps */
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;

Expand All @@ -39,6 +47,15 @@ export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

/** API request params for updating a Trusted App */
export type PutTrustedAppsRequestParams = TypeOf<typeof PutTrustedAppUpdateRequestSchema.params>;

/** API Request body for Updating a new Trusted App entry */
export type PutTrustedAppUpdateRequest = TypeOf<typeof PutTrustedAppUpdateRequestSchema.body> &
(MacosLinuxConditionEntries | WindowsConditionEntries);

export type PutTrustedAppUpdateResponse = PostTrustedAppCreateResponse;

export interface GetTrustedAppsSummaryResponse {
total: number;
windows: number;
Expand Down Expand Up @@ -76,17 +93,38 @@ export interface WindowsConditionEntries {
entries: WindowsConditionEntry[];
}

export interface GlobalEffectScope {
type: 'global';
}

export interface PolicyEffectScope {
type: 'policy';
/** An array of Endpoint Integration Policy UUIDs */
policies: string[];
}

export type EffectScope = GlobalEffectScope | PolicyEffectScope;

/** Type for a new Trusted App Entry */
export type NewTrustedApp = {
name: string;
description?: string;
effectScope: EffectScope;
} & (MacosLinuxConditionEntries | WindowsConditionEntries);

/** An Update to a Trusted App Entry */
export type UpdateTrustedApp = NewTrustedApp & {
version?: string;
};

/** A trusted app entry */
export type TrustedApp = NewTrustedApp & {
version: string;
id: string;
created_at: string;
created_by: string;
updated_at: string;
updated_by: string;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues;
*/
const allowedExperimentalValues = Object.freeze({
fleetServerEnabled: false,
trustedAppsByPolicyEnabled: false,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Loading

0 comments on commit fe247d6

Please sign in to comment.