-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SECURITY_SOLUTION][ENDPOINT] Trusted Apps List API #75476
Changes from all commits
6defd9c
6f2ce18
a835bca
83739bc
0f4baba
d0b6ea4
0b97949
f7939de
6c94624
fafa9d6
c66077b
697a57b
ae8d876
c717647
6a7fbd6
b97524b
1394a76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,5 @@ export const policyIndexPattern = 'metrics-endpoint.policy-*'; | |
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_LIST_API = '/api/endpoint/trusted_apps'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why /endpoint? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. other endpoint apis are mounted at this location |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* 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 { GetTrustedAppsRequestSchema } from './trusted_apps'; | ||
|
||
describe('When invoking Trusted Apps Schema', () => { | ||
describe('for GET List', () => { | ||
const getListQueryParams = (page: unknown = 1, perPage: unknown = 20) => ({ | ||
page, | ||
per_page: perPage, | ||
}); | ||
const query = GetTrustedAppsRequestSchema.query; | ||
|
||
describe('query param validation', () => { | ||
it('should return query params if valid', () => { | ||
expect(query.validate(getListQueryParams())).toEqual({ | ||
page: 1, | ||
per_page: 20, | ||
}); | ||
}); | ||
|
||
it('should use default values', () => { | ||
expect(query.validate(getListQueryParams(undefined, undefined))).toEqual({ | ||
page: 1, | ||
per_page: 20, | ||
}); | ||
expect(query.validate(getListQueryParams(undefined, 100))).toEqual({ | ||
page: 1, | ||
per_page: 100, | ||
}); | ||
expect(query.validate(getListQueryParams(10, undefined))).toEqual({ | ||
page: 10, | ||
per_page: 20, | ||
}); | ||
}); | ||
|
||
it('should throw if `page` param is not a number', () => { | ||
expect(() => { | ||
query.validate(getListQueryParams('one')); | ||
}).toThrowError(); | ||
}); | ||
|
||
it('should throw if `page` param is less than 1', () => { | ||
expect(() => { | ||
query.validate(getListQueryParams(0)); | ||
}).toThrowError(); | ||
expect(() => { | ||
query.validate(getListQueryParams(-1)); | ||
}).toThrowError(); | ||
}); | ||
|
||
it('should throw if `per_page` param is not a number', () => { | ||
expect(() => { | ||
query.validate(getListQueryParams(1, 'twenty')); | ||
}).toThrowError(); | ||
}); | ||
|
||
it('should throw if `per_page` param is less than 1', () => { | ||
expect(() => { | ||
query.validate(getListQueryParams(1, 0)); | ||
}).toThrowError(); | ||
expect(() => { | ||
query.validate(getListQueryParams(1, -1)); | ||
}).toThrowError(); | ||
}); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* 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 { schema } from '@kbn/config-schema'; | ||
|
||
export const GetTrustedAppsRequestSchema = { | ||
query: schema.object({ | ||
page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I always assumed 0 based is easier to implement, but maybe it's true that we need to show one based. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm of the opposite opinion - I find it 1 based to be clearer from a consumer (UI?) standpoint. I want page 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
per_page: schema.maybe(schema.number({ defaultValue: 20, min: 1 })), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just out of curiosity and not for bikesheding - why not page_size? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mirror of lists API - they use these values. I also seem to remember that anything outside of code should use |
||
}), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* 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 { TypeOf } from '@kbn/config-schema'; | ||
import { GetTrustedAppsRequestSchema } from '../schema/trusted_apps'; | ||
|
||
/** API request params for retrieving a list of Trusted Apps */ | ||
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>; | ||
export interface GetTrustedListAppsResponse { | ||
per_page: number; | ||
page: number; | ||
total: number; | ||
data: TrustedApp[]; | ||
} | ||
|
||
interface MacosLinuxConditionEntry { | ||
field: 'hash' | 'path'; | ||
type: 'match'; | ||
operator: 'included'; | ||
value: string; | ||
} | ||
|
||
type WindowsConditionEntry = | ||
| MacosLinuxConditionEntry | ||
| (Omit<MacosLinuxConditionEntry, 'field'> & { | ||
field: 'signer'; | ||
}); | ||
|
||
/** Type for a new Trusted App Entry */ | ||
export type NewTrustedApp = { | ||
name: string; | ||
description?: string; | ||
} & ( | ||
| { | ||
os: 'linux' | 'macos'; | ||
entries: MacosLinuxConditionEntry[]; | ||
} | ||
| { | ||
os: 'windows'; | ||
entries: WindowsConditionEntry[]; | ||
} | ||
); | ||
|
||
/** A trusted app entry */ | ||
export type TrustedApp = NewTrustedApp & { | ||
id: string; | ||
created_at: string; | ||
created_by: string; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/usr/bin/env node | ||
/* | ||
* 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. | ||
*/ | ||
|
||
require('../../../../../src/setup_node_env'); | ||
require('./trusted_apps').cli(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI - this utility is to assist with dev. of the Trusted Apps list until we have a |
||
* 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 { v4 as generateUUID } from 'uuid'; | ||
// @ts-ignore | ||
import minimist from 'minimist'; | ||
import { KbnClient, ToolingLog } from '@kbn/dev-utils'; | ||
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../lists/common/constants'; | ||
import { TRUSTED_APPS_LIST_API } from '../../../common/endpoint/constants'; | ||
import { ExceptionListItemSchema } from '../../../../lists/common/schemas/response'; | ||
|
||
interface RunOptions { | ||
count?: number; | ||
} | ||
|
||
const logger = new ToolingLog({ level: 'info', writeTo: process.stdout }); | ||
const separator = '----------------------------------------'; | ||
|
||
export const cli = async () => { | ||
const options: RunOptions = minimist(process.argv.slice(2), { | ||
default: { | ||
count: 10, | ||
}, | ||
}); | ||
logger.write(`${separator} | ||
Loading ${options.count} Trusted App Entries`); | ||
await run(options); | ||
logger.write(`Done! | ||
${separator}`); | ||
}; | ||
|
||
export const run: (options?: RunOptions) => Promise<ExceptionListItemSchema[]> = async ({ | ||
count = 10, | ||
}: RunOptions = {}) => { | ||
const kbnClient = new KbnClient(logger, { url: 'http://elastic:changeme@localhost:5601' }); | ||
|
||
// touch the Trusted Apps List so it can be created | ||
await kbnClient.request({ | ||
method: 'GET', | ||
path: TRUSTED_APPS_LIST_API, | ||
}); | ||
|
||
return Promise.all( | ||
Array.from({ length: count }, () => { | ||
return kbnClient | ||
.request({ | ||
method: 'POST', | ||
path: '/api/exception_lists/items', | ||
body: generateTrustedAppEntry(), | ||
}) | ||
.then<ExceptionListItemSchema>((item) => (item as unknown) as ExceptionListItemSchema); | ||
}) | ||
); | ||
}; | ||
|
||
interface GenerateTrustedAppEntryOptions { | ||
os?: 'windows' | 'macos' | 'linux'; | ||
name?: string; | ||
} | ||
|
||
const generateTrustedAppEntry: (options?: GenerateTrustedAppEntryOptions) => object = ({ | ||
os = 'windows', | ||
name = `Sample Endpoint Trusted App Entry ${Date.now()}`, | ||
} = {}) => { | ||
return { | ||
list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, | ||
item_id: `generator_endpoint_trusted_apps_${generateUUID()}`, | ||
_tags: ['endpoint', `os:${os}`], | ||
tags: ['user added string for a tag', 'malware'], | ||
type: 'simple', | ||
description: 'This is a sample agnostic endpoint trusted app entry', | ||
name, | ||
namespace_type: 'agnostic', | ||
entries: [ | ||
{ | ||
field: 'actingProcess.file.signer', | ||
operator: 'included', | ||
type: 'match', | ||
value: 'Elastic, N.V.', | ||
}, | ||
{ | ||
field: 'actingProcess.file.path', | ||
operator: 'included', | ||
type: 'match', | ||
value: '/one/two/three', | ||
}, | ||
], | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@madirey FYI
Created this so that the client mock (below) can include it. I debated whether I should have created a sub-class of the Client under trusted apps and felt it belonged here more than in our feature code