Skip to content

Commit

Permalink
[Security Solution][Detections] Integrates installed integrations int…
Browse files Browse the repository at this point in the history
…o interface (#132847)

## Summary

Wires up the new Installed Integrations API added in #132667 to the new Related Integrations UI added in #131475.

#### Additional changes include (though not all necessary for this specific PR):
- [X] Updates integrations badge icon to `package` on Rules Table
- [ ] Add Kibana Advanced Setting for disabling integrations badge on Rules Table
- [ ] Add loaders where necessary since there can now be API delay
- [ ] Separate description step components to specific files

Please see #131475 for screenshots and additional details.


#### Steps to test
In this initial implementation these new fields are only visible with Prebuilt Rules, and so there is limited API support and currently no UI for editing them. If a Prebuilt Rule is duplicated, these fields are emptied (set to `''` or `[]`). When a Rule is exported these fields are included (as empty values), and it is possible to edit the `ndjson` and re-import and then see these fields for the Custom Rule (but still not editable in the UI). This is expected behavior, and is actually a nice and easy way to test. 

Here is a sample export you can paste into a `test.ndjson` file and import to test this feature. You can modify the `package`/`version` fields to test corner cases like if a package is installed but it's the wrong version.

```
{"id":"6cc39c80-da3a-11ec-9fce-65c1a0bee904","updated_at":"2022-05-23T01:48:23.422Z","updated_by":"elastic","created_at":"2022-05-23T01:48:20.940Z","created_by":"elastic","name":"Testing #131475, don't mind me...","tags":["Elastic","Endpoint Security"],"interval":"5m","enabled":false,"description":"Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.","risk_score":47,"severity":"medium","license":"Elastic License v2","output_index":".siem-signals-default","meta":{"from":"5m"},"rule_name_override":"message","timestamp_override":"event.ingested","author":["Elastic"],"false_positives":[],"from":"now-600s","rule_id":"2c66bf23-6ae9-4eb2-859e-446bea181ae9","max_signals":10000,"risk_score_mapping":[{"field":"event.risk_score","operator":"equals","value":""}],"severity_mapping":[{"field":"event.severity","operator":"equals","severity":"low","value":"21"},{"field":"event.severity","operator":"equals","severity":"medium","value":"47"},{"field":"event.severity","operator":"equals","severity":"high","value":"73"},{"field":"event.severity","operator":"equals","severity":"critical","value":"99"}],"threat":[],"to":"now","references":[],"version":7,"exceptions_list":[{"id":"endpoint_list","list_id":"endpoint_list","namespace_type":"agnostic","type":"endpoint"}],"immutable":false,"related_integrations":[{"package":"system","version":"1.6.4"},{"package":"aws","integration":"cloudtrail","version":"1.11.0"}],"required_fields":[{"ecs":true,"name":"event.code","type":"keyword"},{"ecs":true,"name":"message","type":"match_only_text"},{"ecs":false,"name":"winlog.event_data.AttributeLDAPDisplayName","type":"keyword"},{"ecs":false,"name":"winlog.event_data.AttributeValue","type":"keyword"},{"ecs":false,"name":"winlog.event_data.ShareName","type":"keyword"},{"ecs":false,"name":"winlog.event_data.RelativeTargetName","type":"keyword"},{"ecs":false,"name":"winlog.event_data.AccessList","type":"keyword"}],"setup":"## Config\\n\\nThe 'Audit Detailed File Share' audit policy must be configured (Success Failure).\\nSteps to implement the logging policy with with Advanced Audit Configuration:\\n\\n```\\nComputer Configuration > \\nPolicies > \\nWindows Settings > \\nSecurity Settings > \\nAdvanced Audit Policies Configuration > \\nAudit Policies > \\nObject Access > \\nAudit Detailed File Share (Success,Failure)\\n```\\n\\nThe 'Audit Directory Service Changes' audit policy must be configured (Success Failure).\\nSteps to implement the logging policy with with Advanced Audit Configuration:\\n\\n```\\nComputer Configuration > \\nPolicies > \\nWindows Settings > \\nSecurity Settings > \\nAdvanced Audit Policies Configuration > \\nAudit Policies > \\nDS Access > \\nAudit Directory Service Changes (Success,Failure)\\n```\\n","type":"query","language":"kuery","index":["logs-endpoint.alerts-*"],"query":"event.kind:alert and event.module:(endpoint and not endgame)\\n","filters":[],"throttle":"no_actions","actions":[]}
{"exported_count":1,"exported_rules_count":1,"missing_rules":[],"missing_rules_count":0,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}
```
  • Loading branch information
spong authored May 25, 2022
1 parent b5ab073 commit 3cacbe7
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,58 @@ import { capitalize } from 'lodash';
import React from 'react';
import semver from 'semver';
import {
InstalledIntegration,
InstalledIntegrationArray,
RelatedIntegration,
RelatedIntegrationArray,
} from '../../../../common/detection_engine/schemas/common';

/**
* Returns an `EuiLink` that will link to a given package/integration/version page within fleet
* @param integration
* @param basePath
* TODO: Add `title` to RelatedIntegration so we can accurately display the integration pretty name
*
* @param integration either RelatedIntegration or InstalledIntegration
* @param basePath kbn basepath for composing the fleet URL
*/
export const getIntegrationLink = (integration: RelatedIntegration, basePath: string) => {
const integrationURL = `${basePath}/app/integrations/detail/${integration.package}-${
integration.version
}/overview${integration.integration ? `?integration=${integration.integration}` : ''}`;
export const getIntegrationLink = (
integration: RelatedIntegration | InstalledIntegration,
basePath: string
) => {
let packageName: string;
let integrationName: string | undefined;
let integrationTitle: string;
let version: string | null;

// InstalledIntegration
if ('package_name' in integration) {
packageName = integration.package_name;
integrationName = integration.integration_name;
integrationTitle = integration.integration_title ?? integration.package_name;
version = integration.package_version;
} else {
// RelatedIntegration
packageName = integration.package;
integrationName = integration.integration;
integrationTitle = `${capitalize(integration.package)} ${capitalize(integration.integration)}`;
version = semver.valid(semver.coerce(integration.version));
}

const integrationURL =
version != null
? `${basePath}/app/integrations/detail/${packageName}-${version}/overview${
integrationName ? `?integration=${integrationName}` : ''
}`
: `${basePath}/app/integrations/detail/${packageName}`;
return (
<EuiLink href={integrationURL} target="_blank">
{`${capitalize(integration.package)} ${capitalize(integration.integration)}`}
{integrationTitle}
</EuiLink>
);
};

export interface InstalledIntegration extends RelatedIntegration {
export interface InstalledIntegrationAugmented extends InstalledIntegration {
targetVersion: string;
versionSatisfied?: boolean;
versionSatisfied: boolean;
}

/**
Expand All @@ -44,21 +73,22 @@ export interface InstalledIntegration extends RelatedIntegration {
*/
export const getInstalledRelatedIntegrations = (
integrations: RelatedIntegrationArray,
installedIntegrations: RelatedIntegrationArray
installedIntegrations: InstalledIntegrationArray
): {
availableIntegrations: RelatedIntegrationArray;
installedRelatedIntegrations: InstalledIntegration[];
installedRelatedIntegrations: InstalledIntegrationAugmented[];
} => {
const availableIntegrations: RelatedIntegrationArray = [];
const installedRelatedIntegrations: InstalledIntegration[] = [];
const installedRelatedIntegrations: InstalledIntegrationAugmented[] = [];

integrations.forEach((i: RelatedIntegration) => {
const match = installedIntegrations.find(
(installed) => installed.package === i.package && installed?.integration === i?.integration
(installed) =>
installed.package_name === i.package && installed?.integration_name === i?.integration
);
if (match != null) {
// Version check e.g. fleet match `1.2.3` satisfies rule dependency `~1.2.1`
const versionSatisfied = semver.satisfies(match.version, i.version);
const versionSatisfied = semver.satisfies(match.package_version, i.version);
installedRelatedIntegrations.push({ ...match, targetVersion: i.version, versionSatisfied });
} else {
availableIntegrations.push(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
EuiIconTip,
} from '@elastic/eui';
import styled from 'styled-components';
import { InstalledIntegrationArray } from '../../../../common/detection_engine/schemas/common';
import { useBasePath } from '../../lib/kibana';
import { getInstalledRelatedIntegrations, getIntegrationLink } from './helpers';
import { useInstalledIntegrations } from '../../../detections/containers/detection_engine/rules/use_installed_integrations';
Expand Down Expand Up @@ -50,12 +51,13 @@ const IntegrationsPopoverComponent = ({ integrations }: IntegrationsPopoverProps
const { data } = useInstalledIntegrations({ packages: [] });
const basePath = useBasePath();

const allInstalledIntegrations: RelatedIntegrationArray = data ?? [];
const allInstalledIntegrations: InstalledIntegrationArray = data?.installed_integrations ?? [];
const { availableIntegrations, installedRelatedIntegrations } = getInstalledRelatedIntegrations(
integrations,
allInstalledIntegrations
);

// TODO: Add loader to installed integrations value
const badgeTitle =
data != null
? `${installedRelatedIntegrations.length}/${integrations.length} ${i18n.INTEGRATIONS_BADGE}`
Expand All @@ -72,7 +74,7 @@ const IntegrationsPopoverComponent = ({ integrations }: IntegrationsPopoverProps
data-test-subj={'IntegrationsDisplayPopover'}
button={
<EuiBadge
iconType={'tag'}
iconType={'package'}
color="hollow"
data-test-subj={'IntegrationsDisplayPopoverButton'}
onClick={() => setPopoverOpen(!isPopoverOpen)}
Expand Down Expand Up @@ -105,7 +107,7 @@ const IntegrationsPopoverComponent = ({ integrations }: IntegrationsPopoverProps
<EuiIconTip
type="alert"
content={i18n.INTEGRATIONS_INSTALLED_VERSION_TOOLTIP(
integration.version,
integration.package_version,
integration.targetVersion
)}
position="right"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '../../../../common/components/integrations_popover/helpers';

import {
InstalledIntegrationArray,
RelatedIntegration,
RelatedIntegrationArray,
} from '../../../../../common/detection_engine/schemas/common';
Expand All @@ -35,7 +36,7 @@ const IntegrationDescriptionComponent: React.FC<{ integration: RelatedIntegratio
const badgeUninstalledColor = 'accent';
const { data } = useInstalledIntegrations({ packages: [] });

const allInstalledIntegrations: RelatedIntegrationArray = data ?? [];
const allInstalledIntegrations: InstalledIntegrationArray = data?.installed_integrations ?? [];
const { availableIntegrations, installedRelatedIntegrations } = getInstalledRelatedIntegrations(
[integration],
allInstalledIntegrations
Expand All @@ -59,7 +60,7 @@ const IntegrationDescriptionComponent: React.FC<{ integration: RelatedIntegratio
<EuiIconTip
type="alert"
content={INTEGRATIONS_INSTALLED_VERSION_TOOLTIP(
installedRelatedIntegrations[0]?.version,
installedRelatedIntegrations[0]?.package_version,
installedRelatedIntegrations[0]?.targetVersion
)}
position="right"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import {
AggregateRuleExecutionEvent,
BulkAction,
RelatedIntegrationArray,
RuleExecutionStatus,
} from '../../../../../common/detection_engine/schemas/common';
import {
Expand All @@ -34,6 +33,7 @@ import {
RulesSchema,
GetAggregateRuleExecutionEventsResponse,
} from '../../../../../common/detection_engine/schemas/response';
import { GetInstalledIntegrationsResponse } from '../../../../../common/detection_engine/schemas/response/get_installed_integrations_response_schema';

import {
UpdateRulesProps,
Expand Down Expand Up @@ -425,8 +425,8 @@ export const fetchInstalledIntegrations = async ({
}: {
packages?: string[];
signal?: AbortSignal;
}): Promise<RelatedIntegrationArray> =>
KibanaServices.get().http.fetch<RelatedIntegrationArray>(
}): Promise<GetInstalledIntegrationsResponse> =>
KibanaServices.get().http.fetch<GetInstalledIntegrationsResponse>(
DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL,
{
method: 'GET',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import { useQuery } from 'react-query';
import { RelatedIntegrationArray } from '../../../../../common/detection_engine/schemas/common';
import { GetInstalledIntegrationsResponse } from '../../../../../common/detection_engine/schemas/response/get_installed_integrations_response_schema';
import { fetchInstalledIntegrations } from './api';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import * as i18n from './translations';

Expand All @@ -17,37 +18,18 @@ export interface UseInstalledIntegrationsArgs {
export const useInstalledIntegrations = ({ packages }: UseInstalledIntegrationsArgs) => {
const { addError } = useAppToasts();

// TODO: Once API is merged update return type:
// See: https://github.com/elastic/kibana/pull/132667/files#diff-f9d9583d37123ed28fd08fc153eb06026e7ee0c3241364656fb707dcbc0a4872R58-R65
return useQuery<RelatedIntegrationArray | undefined>(
return useQuery<GetInstalledIntegrationsResponse>(
[
'installedIntegrations',
{
packages,
},
],
async ({ signal }) => {
return undefined;

// Mock data -- uncomment to test full UI
// const mockInstalledIntegrations = [
// {
// package: 'system',
// version: '1.7.4',
// },
// // {
// // package: 'aws',
// // integration: 'cloudtrail',
// // version: '1.11.0',
// // },
// ];
// return mockInstalledIntegrations;

// Or fetch from new API
// return fetchInstalledIntegrations({
// packages,
// signal,
// });
return fetchInstalledIntegrations({
packages,
signal,
});
},
{
keepPreviousData: true,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3cacbe7

Please sign in to comment.