Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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_SUPPORTED_OS_TYPES: readonly string[] = ['macos', 'windows', 'linux'];
export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_DELETE_API = '/api/endpoint/trusted_apps/{id}';
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
os: 'windows',
entries: [
{
field: 'path',
field: 'process.path',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
Expand Down Expand Up @@ -111,14 +111,6 @@ describe('When invoking Trusted Apps Schema', () => {
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
});

it('should validate `description` to be non-empty if defined', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
description: '',
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `os` to to only accept known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
Expand Down Expand Up @@ -202,7 +194,7 @@ describe('When invoking Trusted Apps Schema', () => {
};
expect(() => body.validate(bodyMsg2)).toThrow();

['hash', 'path'].forEach((field) => {
['process.hash.*', 'process.path'].forEach((field) => {
const bodyMsg3 = {
...getCreateTrustedAppItem(),
entries: [
Expand All @@ -217,9 +209,55 @@ describe('When invoking Trusted Apps Schema', () => {
});
});

it.todo('should validate `entry.type` is limited to known values');
it('should validate `entry.type` is limited to known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
type: 'invalid',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();

// Allow `match`
const bodyMsg2 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
type: 'match',
},
],
};
expect(() => body.validate(bodyMsg2)).not.toThrow();
});

it('should validate `entry.operator` is limited to known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
operator: 'invalid',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();

it.todo('should validate `entry.operator` is limited to known values');
// Allow `match`
const bodyMsg2 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
operator: 'included',
},
],
};
expect(() => body.validate(bodyMsg2)).not.toThrow();
});

it('should validate `entry.value` required', () => {
const { value, ...entry } = getTrustedAppItemEntryItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export const GetTrustedAppsRequestSchema = {
export const PostTrustedAppCreateRequestSchema = {
body: schema.object({
name: schema.string({ minLength: 1 }),
description: schema.maybe(schema.string({ minLength: 1 })),
description: schema.maybe(schema.string({ minLength: 0, defaultValue: '' })),
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
entries: schema.arrayOf(
schema.object({
field: schema.oneOf([schema.literal('hash'), schema.literal('path')]),
field: schema.oneOf([schema.literal('process.hash.*'), schema.literal('process.path')]),
type: schema.literal('match'),
operator: schema.literal('included'),
value: schema.string({ minLength: 1 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

interface MacosLinuxConditionEntry {
field: 'hash' | 'path';
export interface MacosLinuxConditionEntry {
field: 'process.hash.*' | 'process.path';
type: 'match';
operator: 'included';
value: string;
}

type WindowsConditionEntry =
export type WindowsConditionEntry =
| MacosLinuxConditionEntry
| (Omit<MacosLinuxConditionEntry, 'field'> & {
field: 'signer';
field: 'process.code_signature';
});

/** Type for a new Trusted App Entry */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
import { TrustedAppsUrlParams } from '../pages/trusted_apps/types';

// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
Expand Down Expand Up @@ -89,18 +90,16 @@ export const getPolicyDetailPath = (policyId: string, search?: string) => {
})}${appendSearch(search)}`;
};

interface ListPaginationParams {
page_index: number;
page_size: number;
}

const isDefaultOrMissing = (value: number | undefined, defaultValue: number) => {
const isDefaultOrMissing = (
value: number | string | undefined,
defaultValue: number | undefined
) => {
return value === undefined || value === defaultValue;
};

const normalizeListPaginationParams = (
params?: Partial<ListPaginationParams>
): Partial<ListPaginationParams> => {
params?: Partial<TrustedAppsUrlParams>
): Partial<TrustedAppsUrlParams> => {
if (params) {
return {
...(!isDefaultOrMissing(params.page_index, MANAGEMENT_DEFAULT_PAGE)
Expand All @@ -109,13 +108,19 @@ const normalizeListPaginationParams = (
...(!isDefaultOrMissing(params.page_size, MANAGEMENT_DEFAULT_PAGE_SIZE)
? { page_size: params.page_size }
: {}),
...(!isDefaultOrMissing(params.show, undefined) ? { show: params.show } : {}),
};
} else {
return {};
}
};

const extractFirstParamValue = (query: querystring.ParsedUrlQuery, key: string): string => {
/**
* Given an object with url params, and a given key, return back only the first param value (case multiples were defined)
* @param query
* @param key
*/
export const extractFirstParamValue = (query: querystring.ParsedUrlQuery, key: string): string => {
const value = query[key];

return Array.isArray(value) ? value[value.length - 1] : value;
Expand All @@ -135,12 +140,12 @@ const extractPageSize = (query: querystring.ParsedUrlQuery): number => {

export const extractListPaginationParams = (
query: querystring.ParsedUrlQuery
): ListPaginationParams => ({
): TrustedAppsUrlParams => ({
page_index: extractPageIndex(query),
page_size: extractPageSize(query),
});

export const getTrustedAppsListPath = (params?: Partial<ListPaginationParams>): string => {
export const getTrustedAppsListPath = (params?: Partial<TrustedAppsUrlParams>): string => {
const path = generatePath(MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, {
tabName: AdministrationSubTab.trustedApps,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import React, { FC, memo } from 'react';
import { EuiPanel, EuiSpacer, CommonProps } from '@elastic/eui';
import styled from 'styled-components';
import { SecurityPageName } from '../../../common/constants';
import { WrapperPage } from '../../common/components/wrapper_page';
import { HeaderPage } from '../../common/components/header_page';
Expand All @@ -14,6 +15,13 @@ import { AdministrationSubTab } from '../types';
import { ENDPOINTS_TAB, TRUSTED_APPS_TAB, BETA_BADGE_LABEL } from '../common/translations';
import { getEndpointListPath, getTrustedAppsListPath } from '../common/routing';

/** Ensure that all flyouts z-index in Administation area show the flyout header */
const EuiPanelStyled = styled(EuiPanel)`
.euiFlyout {
z-index: ${({ theme }) => theme.eui.euiZNavigation + 1};
}
`;

interface AdministrationListPageProps {
beta: boolean;
title: React.ReactNode;
Expand Down Expand Up @@ -54,7 +62,7 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp

<EuiSpacer />

<EuiPanel>{children}</EuiPanel>
<EuiPanelStyled>{children}</EuiPanelStyled>

<SpyRoute pageName={SecurityPageName.administration} />
</WrapperPage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
*/

import { HttpStart } from 'kibana/public';
import { TRUSTED_APPS_LIST_API } from '../../../../../common/endpoint/constants';
import {
TRUSTED_APPS_CREATE_API,
TRUSTED_APPS_LIST_API,
} from '../../../../../common/endpoint/constants';
import {
GetTrustedListAppsResponse,
GetTrustedAppsListRequest,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
} from '../../../../../common/endpoint/types/trusted_apps';

export interface TrustedAppsService {
getTrustedAppsList(request: GetTrustedAppsListRequest): Promise<GetTrustedListAppsResponse>;
createTrustedApp(request: PostTrustedAppCreateRequest): Promise<PostTrustedAppCreateResponse>;
}

export class TrustedAppsHttpService implements TrustedAppsService {
Expand All @@ -23,4 +29,10 @@ export class TrustedAppsHttpService implements TrustedAppsService {
query: request,
});
}

async createTrustedApp(request: PostTrustedAppCreateRequest) {
return this.http.post<PostTrustedAppCreateResponse>(TRUSTED_APPS_CREATE_API, {
body: JSON.stringify(request),
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps';
import { AsyncResourceState } from '.';
import { TrustedAppsUrlParams } from '../types';
import { ServerApiError } from '../../../../common/types';

export interface PaginationInfo {
index: number;
Expand All @@ -18,10 +20,34 @@ export interface TrustedAppsListData {
paginationInfo: PaginationInfo;
}

/** Store State when an API request has been sent to create a new trusted app entry */
export interface TrustedAppCreatePending {
type: 'pending';
data: NewTrustedApp;
}

/** Store State when creation of a new Trusted APP entry was successful */
export interface TrustedAppCreateSuccess {
type: 'success';
data: TrustedApp;
}

/** Store State when creation of a new Trusted App Entry failed */
export interface TrustedAppCreateFailure {
type: 'failure';
data: ServerApiError;
}

export interface TrustedAppsListPageState {
listView: {
currentListResourceState: AsyncResourceState<TrustedAppsListData>;
currentPaginationInfo: PaginationInfo;
show: TrustedAppsUrlParams['show'] | undefined;
};
createView:
| undefined
| TrustedAppCreatePending
| TrustedAppCreateSuccess
| TrustedAppCreateFailure;
active: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
TrustedAppCreatePending,
TrustedAppsListPageState,
TrustedAppCreateFailure,
TrustedAppCreateSuccess,
} from './trusted_apps_list_page_state';
import {
Immutable,
NewTrustedApp,
WindowsConditionEntry,
} from '../../../../../common/endpoint/types';
import { TRUSTED_APPS_SUPPORTED_OS_TYPES } from '../../../../../common/endpoint/constants';

type CreateViewPossibleStates =
| TrustedAppsListPageState['createView']
| Immutable<TrustedAppsListPageState['createView']>;

export const isTrustedAppCreatePendingState = (
data: CreateViewPossibleStates
): data is TrustedAppCreatePending => {
return data?.type === 'pending';
};

export const isTrustedAppCreateSuccessState = (
data: CreateViewPossibleStates
): data is TrustedAppCreateSuccess => {
return data?.type === 'success';
};

export const isTrustedAppCreateFailureState = (
data: CreateViewPossibleStates
): data is TrustedAppCreateFailure => {
return data?.type === 'failure';
};

export const isWindowsTrustedApp = <T extends NewTrustedApp = NewTrustedApp>(
trustedApp: T
): trustedApp is T & { os: 'windows' } => {
return trustedApp.os === 'windows';
};

export const isWindowsTrustedAppCondition = (condition: {
field: string;
}): condition is WindowsConditionEntry => {
return condition.field === 'process.code_signature' || true;
};

export const isTrustedAppSupportedOs = (os: string): os is NewTrustedApp['os'] =>
TRUSTED_APPS_SUPPORTED_OS_TYPES.includes(os);
Loading