-
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][Detections] Implement endpoint for fetching installed Fleet integrations #132667
[Security Solution][Detections] Implement endpoint for fetching installed Fleet integrations #132667
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,241 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
// ------------------------------------------------------------------------------------------------- | ||
// Installed package | ||
|
||
/** | ||
* Basic information about an installed Fleet package. | ||
*/ | ||
export interface InstalledPackageBasicInfo { | ||
/** | ||
* Name is a unique package id within a given cluster. | ||
* There can't be 2 or more different packages with the same name. | ||
* @example 'aws' | ||
*/ | ||
package_name: string; | ||
|
||
/** | ||
* Title is a user-friendly name of the package that we show in the UI. | ||
* @example 'AWS' | ||
*/ | ||
package_title: string; | ||
|
||
/** | ||
* Version of the package. Semver-compatible. | ||
* @example '1.2.3' | ||
*/ | ||
package_version: string; | ||
} | ||
|
||
/** | ||
* Information about an installed Fleet package including its integrations. | ||
* | ||
* @example | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integrations: [ | ||
* { | ||
* integration_name: 'billing', | ||
* integration_title: 'AWS Billing', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudtrail', | ||
* integration_title: 'AWS CloudTrail', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudwatch', | ||
* integration_title: 'AWS CloudWatch', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudfront', | ||
* integration_title: 'Amazon CloudFront', | ||
* is_enabled: true | ||
* } | ||
* ] | ||
* } | ||
*/ | ||
export interface InstalledPackage extends InstalledPackageBasicInfo { | ||
integrations: InstalledIntegrationBasicInfo[]; | ||
} | ||
|
||
// ------------------------------------------------------------------------------------------------- | ||
// Installed integration | ||
|
||
/** | ||
* Basic information about an installed Fleet integration. | ||
* An integration belongs to a package. A package can contain one or many integrations. | ||
*/ | ||
export interface InstalledIntegrationBasicInfo { | ||
/** | ||
* Name identifies an integration within its package. | ||
* @example 'cloudtrail' | ||
*/ | ||
integration_name: string; | ||
|
||
/** | ||
* Title is a user-friendly name of the integration that we show in the UI. | ||
* @example 'AWS CloudTrail' | ||
*/ | ||
integration_title: string; | ||
|
||
/** | ||
* Whether this integration is enabled or not in at least one package policy in Fleet. | ||
*/ | ||
is_enabled: boolean; | ||
} | ||
|
||
/** | ||
* Information about an installed Fleet integration including info about its package. | ||
* | ||
* @example | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integration_name: 'cloudtrail', | ||
* integration_title: 'AWS CloudTrail', | ||
* is_enabled: false | ||
* } | ||
* | ||
* @example | ||
* { | ||
* package_name: 'system', | ||
* package_title: 'System', | ||
* package_version: '1.13.0', | ||
* is_enabled: true | ||
* } | ||
*/ | ||
export interface InstalledIntegration extends InstalledPackageBasicInfo { | ||
/** | ||
* Name identifies an integration within its package. | ||
* Undefined when package name === integration name. This indicates that it's the only integration | ||
* within this package. | ||
* @example 'cloudtrail' | ||
* @example undefined | ||
*/ | ||
integration_name?: string; | ||
|
||
/** | ||
* Title is a user-friendly name of the integration that we show in the UI. | ||
* Undefined when package name === integration name. This indicates that it's the only integration | ||
* within this package. | ||
* @example 'AWS CloudTrail' | ||
* @example undefined | ||
*/ | ||
integration_title?: string; | ||
|
||
/** | ||
* Whether this integration is enabled or not in at least one package policy in Fleet. | ||
*/ | ||
is_enabled: boolean; | ||
} | ||
|
||
// ------------------------------------------------------------------------------------------------- | ||
// Arrays of installed packages and integrations | ||
|
||
/** | ||
* An array of installed packages with their integrations. | ||
* This is a hierarchical way of representing installed integrations. | ||
* | ||
* @example | ||
* [ | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integrations: [ | ||
* { | ||
* integration_name: 'billing', | ||
* integration_title: 'AWS Billing', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudtrail', | ||
* integration_title: 'AWS CloudTrail', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudwatch', | ||
* integration_title: 'AWS CloudWatch', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* integration_name: 'cloudfront', | ||
* integration_title: 'Amazon CloudFront', | ||
* is_enabled: true | ||
* } | ||
* ] | ||
* }, | ||
* { | ||
* package_name: 'system', | ||
* package_title: 'System', | ||
* package_version: '1.13.0', | ||
* integrations: [ | ||
* { | ||
* integration_name: 'system', | ||
* integration_title: 'System logs and metrics', | ||
* is_enabled: true | ||
* } | ||
* ] | ||
* } | ||
* ] | ||
*/ | ||
export type InstalledPackageArray = InstalledPackage[]; | ||
|
||
/** | ||
* An array of installed integrations with info about their packages. | ||
* This is a flattened way of representing installed integrations. | ||
* | ||
* @example | ||
* [ | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integration_name: 'billing', | ||
* integration_title: 'AWS Billing', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integration_name: 'cloudtrail', | ||
* integration_title: 'AWS CloudTrail', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integration_name: 'cloudwatch', | ||
* integration_title: 'AWS CloudWatch', | ||
* is_enabled: false | ||
* }, | ||
* { | ||
* package_name: 'aws', | ||
* package_title: 'AWS', | ||
* package_version: '1.16.1', | ||
* integration_name: 'cloudfront', | ||
* integration_title: 'Amazon CloudFront', | ||
* is_enabled: true | ||
* }, | ||
* { | ||
* package_name: 'system', | ||
* package_title: 'System', | ||
* package_version: '1.13.0', | ||
* is_enabled: true | ||
* } | ||
* ] | ||
*/ | ||
export type InstalledIntegrationArray = InstalledIntegration[]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* 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 { InstalledIntegrationArray } from '../common'; | ||
|
||
export interface GetInstalledIntegrationsResponse { | ||
installed_integrations: InstalledIntegrationArray; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
import type { MockedKeys } from '@kbn/utility-types/jest'; | ||
import type { AwaitedProperties } from '@kbn/utility-types'; | ||
import type { KibanaRequest } from '@kbn/core/server'; | ||
import { coreMock } from '@kbn/core/server/mocks'; | ||
|
||
import { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; | ||
|
@@ -31,6 +32,7 @@ import type { | |
SecuritySolutionApiRequestHandlerContext, | ||
SecuritySolutionRequestHandlerContext, | ||
} from '../../../../types'; | ||
|
||
import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz'; | ||
import { EndpointAuthz } from '../../../../../common/endpoint/types/authz'; | ||
|
||
|
@@ -125,6 +127,14 @@ const createSecuritySolutionRequestContextMock = ( | |
getRuleDataService: jest.fn(() => clients.ruleDataService), | ||
getRuleExecutionLog: jest.fn(() => clients.ruleExecutionLog), | ||
getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), | ||
getInternalFleetServices: jest.fn(() => { | ||
// TODO: Mock EndpointInternalFleetServicesInterface and return the mocked object. | ||
throw new Error('Not implemented'); | ||
}), | ||
getScopedFleetServices: jest.fn((req: KibanaRequest) => { | ||
// TODO: Mock EndpointScopedFleetServicesInterface and return the mocked object. | ||
throw new Error('Not implemented'); | ||
}), | ||
Comment on lines
+130
to
+137
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. Will be done in a follow-up PR with test coverage. |
||
}; | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* 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 { transformError } from '@kbn/securitysolution-es-utils'; | ||
|
||
import type { SecuritySolutionPluginRouter } from '../../../../../types'; | ||
import { DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL } from '../../../../../../common/constants'; | ||
import { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/schemas/response/get_installed_integrations_response_schema'; | ||
import { buildSiemResponse } from '../../utils'; | ||
import { createInstalledIntegrationSet } from './installed_integration_set'; | ||
|
||
/** | ||
* Returns an array of installed Fleet integrations and their packages. | ||
*/ | ||
Comment on lines
+16
to
+18
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. May be worth adding a quick summary on the current method for collecting this information -- that we're first querying all |
||
export const getInstalledIntegrationsRoute = (router: SecuritySolutionPluginRouter) => { | ||
router.get( | ||
{ | ||
path: DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL, | ||
validate: {}, | ||
options: { | ||
tags: ['access:securitySolution'], | ||
}, | ||
}, | ||
async (context, request, response) => { | ||
const siemResponse = buildSiemResponse(response); | ||
|
||
try { | ||
const ctx = await context.resolve(['core', 'securitySolution']); | ||
const fleet = ctx.securitySolution.getInternalFleetServices(); | ||
const soClient = ctx.core.savedObjects.client; | ||
const set = createInstalledIntegrationSet(); | ||
|
||
const packagePolicies = await fleet.packagePolicy.list(soClient, {}); | ||
|
||
packagePolicies.items.forEach((policy) => { | ||
set.addPackagePolicy(policy); | ||
}); | ||
|
||
const registryPackages = await Promise.all( | ||
set.getPackages().map((packageInfo) => { | ||
return fleet.packages.getRegistryPackage( | ||
packageInfo.package_name, | ||
packageInfo.package_version | ||
); | ||
}) | ||
Comment on lines
+44
to
+49
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. We should reach out to the fleet folks to see why there's an HTTP API for getting all packages but not a corresponding client API. Feels weird that we need to make a request for each individual package. Will be interesting to see how this scales on deployments with >100+ installed packages 😅 |
||
); | ||
|
||
registryPackages.forEach((registryPackage) => { | ||
set.addRegistryPackage(registryPackage.packageInfo); | ||
}); | ||
|
||
const installedIntegrations = set.getIntegrations(); | ||
|
||
const body: GetInstalledIntegrationsResponse = { | ||
installed_integrations: installedIntegrations, | ||
}; | ||
|
||
return response.ok({ body }); | ||
} catch (err) { | ||
const error = transformError(err); | ||
return siemResponse.error({ | ||
body: error.message, | ||
statusCode: error.statusCode, | ||
}); | ||
} | ||
} | ||
); | ||
}; |
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.
We may want to add
iconPath
in the future so we can easily reference the package/integrations icon in the UI. There's no requirement for this now, and would be easy to add later with how you've structured things over increateInstalledIntegrationSet
, but just wanted to mention this additional data that may be useful.