Skip to content
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

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 @@ -5,6 +5,7 @@
* 2.0.
*/

export * from './installed_integrations';
export * from './rule_monitoring';
export * from './rule_params';
export * from './schemas';
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 {
Copy link
Member

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 in createInstalledIntegrationSet, but just wanted to mention this additional data that may be useful.

/**
* 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
Expand Up @@ -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';
Expand All @@ -31,6 +32,7 @@ import type {
SecuritySolutionApiRequestHandlerContext,
SecuritySolutionRequestHandlerContext,
} from '../../../../types';

import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz';
import { EndpointAuthz } from '../../../../../common/endpoint/types/authz';

Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be done in a follow-up PR with test coverage.

};
};

Expand Down
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
Copy link
Member

Choose a reason for hiding this comment

The 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 packagePolicies to determine all installed integrations, then querying for each applicable package to augment with additional data.

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
Copy link
Member

Choose a reason for hiding this comment

The 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,
});
}
}
);
};
Loading