Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
import { EuiBadge, EuiDescriptionList, EuiLink, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { CspFinding } from '../../../../common/schemas/csp_finding';
import { RulesDetectionRuleCounter } from '../../rules/rules_detection_rule_counter';
import { CisKubernetesIcons, CspFlyoutMarkdown } from './findings_flyout';

export const getRuleList = (rule: CspFinding['rule'], ruleFlyoutLink?: string) => [
export const getRuleList = (
rule: CspFinding['rule'],
ruleState = 'unmuted',
ruleFlyoutLink?: string
) => [
{
title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.nameTitle', {
defaultMessage: 'Name',
Expand All @@ -35,6 +41,20 @@ export const getRuleList = (rule: CspFinding['rule'], ruleFlyoutLink?: string) =
}),
description: <CspFlyoutMarkdown>{rule.description}</CspFlyoutMarkdown>,
},
{
title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle', {
defaultMessage: 'Alerts',
}),
description:
ruleState === 'unmuted' ? (
<RulesDetectionRuleCounter benchmarkRule={rule} />
) : (
<FormattedMessage
id="xpack.csp.findings.findingsFlyout.ruleTab.disabledRuleText"
defaultMessage="Disabled"
/>
),
},
{
title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle', {
defaultMessage: 'Tags',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@animehart @opauloh This file is almost identical to create_detection_rule_from_finding.ts. Let's avoid such repetition and think about how to abstract the logic. I can see that the only different thing is where we take the data from. In one case it's finding.rule.benchmark and in another, it's benchmarkRule. All the rest look the same. If that's the case, it should be very simple to abstract and just pass the benchmark object

* 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 { HttpSetup } from '@kbn/core/public';
import { CspBenchmarkRule } from '../../../../common/types/latest';
import {
FINDINGS_INDEX_PATTERN,
LATEST_FINDINGS_RETENTION_POLICY,
} from '../../../../common/constants';
import { createDetectionRule } from '../../../common/api/create_detection_rule';
import { generateBenchmarkRuleTags } from '../../../../common/utils/detection_rules';

const DEFAULT_RULE_RISK_SCORE = 0;
const DEFAULT_RULE_SEVERITY = 'low';
const DEFAULT_RULE_ENABLED = true;
const DEFAULT_RULE_AUTHOR = 'Elastic';
const DEFAULT_RULE_LICENSE = 'Elastic License v2';
const DEFAULT_MAX_ALERTS_PER_RULE = 100;
const ALERT_SUPPRESSION_FIELD = 'resource.id';
const ALERT_TIMESTAMP_FIELD = 'event.ingested';
const DEFAULT_INVESTIGATION_FIELDS = {
field_names: ['resource.name', 'resource.id', 'resource.type', 'resource.sub_type'],
};

enum AlertSuppressionMissingFieldsStrategy {
// per each document a separate alert will be created
DoNotSuppress = 'doNotSuppress',
// only one alert will be created per suppress by bucket
Suppress = 'suppress',
}

const convertReferencesLinksToArray = (input: string | undefined) => {
if (!input) {
return [];
}
// Match all URLs in the input string using a regular expression
const matches = input.match(/(https?:\/\/\S+)/g);

if (!matches) {
return [];
}

// Remove the numbers and new lines
return matches.map((link) => link.replace(/^\d+\. /, '').replace(/\n/g, ''));
};

const generateFindingsRuleQuery = (benchmarkRule: CspBenchmarkRule['metadata']) => {
const currentTimestamp = new Date().toISOString();

return `rule.benchmark.rule_number: "${benchmarkRule.benchmark.rule_number}"
AND rule.benchmark.id: "${benchmarkRule.benchmark.id}"
AND result.evaluation: "failed"
AND event.ingested >= "${currentTimestamp}"`;
};

/*
* Creates a detection rule from a Benchmark rule
*/
export const createDetectionRuleFromBenchmark = async (
http: HttpSetup,
benchmarkRule: CspBenchmarkRule['metadata']
) => {
return await createDetectionRule({
http,
rule: {
type: 'query',
language: 'kuery',
license: DEFAULT_RULE_LICENSE,
author: [DEFAULT_RULE_AUTHOR],
filters: [],
false_positives: [],
risk_score: DEFAULT_RULE_RISK_SCORE,
risk_score_mapping: [],
severity: DEFAULT_RULE_SEVERITY,
severity_mapping: [],
threat: [],
interval: '1h',
from: `now-${LATEST_FINDINGS_RETENTION_POLICY}`,
to: 'now',
max_signals: DEFAULT_MAX_ALERTS_PER_RULE,
timestamp_override: ALERT_TIMESTAMP_FIELD,
timestamp_override_fallback_disabled: false,
actions: [],
enabled: DEFAULT_RULE_ENABLED,
alert_suppression: {
group_by: [ALERT_SUPPRESSION_FIELD],
missing_fields_strategy: AlertSuppressionMissingFieldsStrategy.Suppress,
},
index: [FINDINGS_INDEX_PATTERN],
query: generateFindingsRuleQuery(benchmarkRule),
references: convertReferencesLinksToArray(benchmarkRule.references),
name: benchmarkRule.name,
description: benchmarkRule.rationale,
tags: generateBenchmarkRuleTags(benchmarkRule),
investigation_fields: DEFAULT_INVESTIGATION_FIELDS,
note: benchmarkRule.remediation,
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 type { HttpSetup } from '@kbn/core/public';
import React from 'react';
import { CspBenchmarkRule } from '../../../common/types/latest';
import { getFindingsDetectionRuleSearchTags } from '../../../common/utils/detection_rules';
import { DetectionRuleCounter } from '../../components/detection_rule_counter';
import { createDetectionRuleFromBenchmark } from '../configurations/utils/create_detection_rule_from_benchmark';

export const RulesDetectionRuleCounter = ({
benchmarkRule,
}: {
benchmarkRule: CspBenchmarkRule['metadata'];
}) => {
const createBenchmarkRuleFn = async (http: HttpSetup) =>
await createDetectionRuleFromBenchmark(http, benchmarkRule);

return (
<DetectionRuleCounter
tags={getFindingsDetectionRuleSearchTags(benchmarkRule)}
createRuleFn={createBenchmarkRuleFn}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,25 @@ export const RuleFlyout = ({ onClose, rule, refetchRulesStates }: RuleFlyoutProp
);
};

const getRuleStateSwitch = (
rule: CspBenchmarkRulesWithStates,
switchRuleStates: () => Promise<void>
) => [
const RuleOverviewTab = ({
rule,
ruleData,
switchRuleStates,
}: {
rule: CspBenchmarkRuleMetadata;
ruleData: CspBenchmarkRulesWithStates;
switchRuleStates: () => Promise<void>;
}) => (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiDescriptionList
listItems={[...ruleState(ruleData, switchRuleStates), ...getRuleList(rule, ruleData.state)]}
/>
</EuiFlexItem>
</EuiFlexGroup>
);

const ruleState = (rule: CspBenchmarkRulesWithStates, switchRuleStates: () => Promise<void>) => [
{
title: (
<EuiFlexGroup gutterSize="xs" alignItems="center">
Expand Down Expand Up @@ -172,21 +187,3 @@ const getRuleStateSwitch = (
),
},
];

const RuleOverviewTab = ({
rule,
ruleData,
switchRuleStates,
}: {
rule: CspBenchmarkRuleMetadata;
ruleData: CspBenchmarkRulesWithStates;
switchRuleStates: () => Promise<void>;
}) => (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiDescriptionList
listItems={[...getRuleStateSwitch(ruleData, switchRuleStates), ...getRuleList(rule)]}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider
const disabledRulesButton = await testSubjects.find('rules-counters-disabled-rules-button');
await disabledRulesButton.click();
},

doesElementExist: async (selector: string) => {
return await testSubjects.exists(selector);
},
};

const navigateToRulePage = async (benchmarkCisId: string, benchmarkCisVersion: string) => {
Expand Down
Loading