mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Defend Workflows] Artifact Rollout Note field (#164838)
This PR is a second part of Artifact Rollout Epic (https://github.com/elastic/security-team/issues/3593) and it introduces **Note** field as described in https://github.com/elastic/security-team/issues/7238 ticket. Changes: 1. Added a new SO, `policy-settings-protection-updates-note` which holds non indexable `note` field of type **text** and reference to package policy 2. Added `getPackagePolicyDeleteCallback` that cleans up SO on package policy deletion 3. Exposed an API to interact with the SO (POST, GET) with POST method accepting both creation and update if SO exists. 4. Integrated UI with API with `react-query` hooks. Flow359d59bd
-1bde-417a-9449-467d08e81809 Read only access  --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c9958485fa
commit
e315c9c175
23 changed files with 780 additions and 16 deletions
|
@ -616,6 +616,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"apm-indices": {
|
||||
"dynamic": false,
|
||||
"properties": {}
|
||||
},
|
||||
"tag": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
@ -2987,6 +2991,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"policy-settings-protection-updates-note": {
|
||||
"properties": {
|
||||
"note": {
|
||||
"type": "text",
|
||||
"index": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"infrastructure-ui-source": {
|
||||
"dynamic": false,
|
||||
"properties": {}
|
||||
|
@ -3031,10 +3043,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"apm-indices": {
|
||||
"dynamic": false,
|
||||
"properties": {}
|
||||
},
|
||||
"apm-telemetry": {
|
||||
"dynamic": false,
|
||||
"properties": {}
|
||||
|
|
|
@ -125,6 +125,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"osquery-pack": "6ab4358ca4304a12dcfc1777c8135b75cffb4397",
|
||||
"osquery-pack-asset": "b14101d3172c4b60eb5404696881ce5275c84152",
|
||||
"osquery-saved-query": "44f1161e165defe3f9b6ad643c68c542a765fcdb",
|
||||
"policy-settings-protection-updates-note": "33924bb246f9e5bcb876109cc83e3c7a28308352",
|
||||
"query": "21cbbaa09abb679078145ce90087b1e88b7eae95",
|
||||
"risk-engine-configuration": "b105d4a3c6adce40708d729d12e5ef3c8fbd9508",
|
||||
"rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f",
|
||||
|
|
|
@ -96,6 +96,7 @@ const previouslyRegisteredTypes = [
|
|||
'osquery-saved-query',
|
||||
'osquery-usage-metric',
|
||||
'osquery-manager-usage-metric',
|
||||
'policy-settings-protection-updates-note',
|
||||
'query',
|
||||
'rules-settings',
|
||||
'sample-data-telemetry',
|
||||
|
|
|
@ -245,6 +245,7 @@ describe('split .kibana index into multiple system indices', () => {
|
|||
"osquery-pack",
|
||||
"osquery-pack-asset",
|
||||
"osquery-saved-query",
|
||||
"policy-settings-protection-updates-note",
|
||||
"query",
|
||||
"risk-engine-configuration",
|
||||
"rules-settings",
|
||||
|
|
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import type { SubFeatureConfig } from '@kbn/features-plugin/common';
|
||||
import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants';
|
||||
import { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from '../app_features_privileges';
|
||||
|
||||
import { SecuritySubFeatureId } from '../app_features_keys';
|
||||
import { APP_ID } from '../constants';
|
||||
import type { SecurityFeatureParams } from './types';
|
||||
|
@ -320,7 +321,7 @@ const policyManagementSubFeature: SubFeatureConfig = {
|
|||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
all: ['policy-settings-protection-updates-note'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writePolicyManagement', 'readPolicyManagement'],
|
||||
|
@ -332,7 +333,7 @@ const policyManagementSubFeature: SubFeatureConfig = {
|
|||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
read: ['policy-settings-protection-updates-note'],
|
||||
},
|
||||
ui: ['readPolicyManagement'],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const GetProtectionUpdatesNoteSchema = {
|
||||
params: schema.object({
|
||||
package_policy_id: schema.string(),
|
||||
}),
|
||||
};
|
||||
|
||||
export const CreateUpdateProtectionUpdatesNoteSchema = {
|
||||
body: schema.object({
|
||||
note: schema.string(),
|
||||
}),
|
||||
params: schema.object({
|
||||
package_policy_id: schema.string(),
|
||||
}),
|
||||
};
|
|
@ -63,6 +63,7 @@ export const METADATA_TRANSFORMS_STATUS_ROUTE = `${BASE_ENDPOINT_ROUTE}/metadata
|
|||
export const BASE_POLICY_RESPONSE_ROUTE = `${BASE_ENDPOINT_ROUTE}/policy_response`;
|
||||
export const BASE_POLICY_ROUTE = `${BASE_ENDPOINT_ROUTE}/policy`;
|
||||
export const AGENT_POLICY_SUMMARY_ROUTE = `${BASE_POLICY_ROUTE}/summaries`;
|
||||
export const PROTECTION_UPDATES_NOTE_ROUTE = `${BASE_ENDPOINT_ROUTE}/protection_updates_note/{package_policy_id}`;
|
||||
|
||||
/** Suggestions routes */
|
||||
export const SUGGESTIONS_ROUTE = `${BASE_ENDPOINT_ROUTE}/suggestions/{suggestion_type}`;
|
||||
|
|
|
@ -9,7 +9,10 @@ import moment from 'moment/moment';
|
|||
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
|
||||
import type { PolicyData } from '../../../../../common/endpoint/types';
|
||||
import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet';
|
||||
import { setCustomProtectionUpdatesManifestVersion } from '../../tasks/endpoint_policy';
|
||||
import {
|
||||
setCustomProtectionUpdatesManifestVersion,
|
||||
setCustomProtectionUpdatesNote,
|
||||
} from '../../tasks/endpoint_policy';
|
||||
import { login, ROLE } from '../../tasks/login';
|
||||
import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common';
|
||||
|
||||
|
@ -17,6 +20,8 @@ describe('Policy Details', () => {
|
|||
describe('Protection updates', () => {
|
||||
const loadProtectionUpdatesUrl = (policyId: string) =>
|
||||
loadPage(`/app/security/administration/policy/${policyId}/protectionUpdates`);
|
||||
const testNote = 'test note';
|
||||
const updatedTestNote = 'updated test note';
|
||||
|
||||
describe('Renders and saves protection updates', () => {
|
||||
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
|
||||
|
@ -59,13 +64,18 @@ describe('Policy Details', () => {
|
|||
cy.getByTestSubj('protection-updates-version-to-deploy-picker').within(() => {
|
||||
cy.get('input').should('have.value', formattedToday);
|
||||
});
|
||||
cy.getByTestSubj('protection-updates-manifest-name-note-title');
|
||||
cy.getByTestSubj('protection-updates-manifest-note');
|
||||
cy.getByTestSubj('policyDetailsSaveButton');
|
||||
});
|
||||
|
||||
it('should successfully update the manifest version to custom date', () => {
|
||||
loadProtectionUpdatesUrl(policy.id);
|
||||
cy.getByTestSubj('protection-updates-manifest-switch').click();
|
||||
cy.getByTestSubj('protection-updates-manifest-note').type(testNote);
|
||||
|
||||
cy.intercept('PUT', `/api/fleet/package_policies/${policy.id}`).as('policy');
|
||||
cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note');
|
||||
cy.getByTestSubj('policyDetailsSaveButton').click();
|
||||
cy.wait('@policy').then(({ request, response }) => {
|
||||
expect(request.body.inputs[0].config.policy.value.global_manifest_version).to.equal(
|
||||
|
@ -73,8 +83,15 @@ describe('Policy Details', () => {
|
|||
);
|
||||
expect(response?.statusCode).to.equal(200);
|
||||
});
|
||||
|
||||
cy.wait('@note').then(({ request, response }) => {
|
||||
expect(request.body.note).to.equal(testNote);
|
||||
expect(response?.statusCode).to.equal(200);
|
||||
});
|
||||
|
||||
cy.getByTestSubj('protectionUpdatesSuccessfulMessage');
|
||||
cy.getByTestSubj('protection-updates-deployed-version').contains(formattedToday);
|
||||
cy.getByTestSubj('protection-updates-manifest-note').contains(testNote);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -122,6 +139,50 @@ describe('Policy Details', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Renders and saves protection updates with custom note', () => {
|
||||
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
|
||||
let policy: PolicyData;
|
||||
|
||||
const twoMonthsAgo = moment().subtract(2, 'months').format('YYYY-MM-DD');
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
disableExpandableFlyoutAdvancedSettings();
|
||||
});
|
||||
|
||||
before(() => {
|
||||
getEndpointIntegrationVersion().then((version) => {
|
||||
createAgentPolicyTask(version).then((data) => {
|
||||
indexedPolicy = data;
|
||||
policy = indexedPolicy.integrationPolicies[0];
|
||||
setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo);
|
||||
setCustomProtectionUpdatesNote(policy.id, testNote);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (indexedPolicy) {
|
||||
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
|
||||
}
|
||||
});
|
||||
|
||||
it('should update note on save', () => {
|
||||
loadProtectionUpdatesUrl(policy.id);
|
||||
cy.getByTestSubj('protection-updates-manifest-note').contains(testNote);
|
||||
cy.getByTestSubj('protection-updates-manifest-note').clear().type(updatedTestNote);
|
||||
|
||||
cy.intercept('POST', `/api/endpoint/protection_updates_note/*`).as('note_updated');
|
||||
cy.getByTestSubj('policyDetailsSaveButton').click();
|
||||
cy.wait('@note_updated').then(({ request, response }) => {
|
||||
expect(request.body.note).to.equal(updatedTestNote);
|
||||
expect(response?.statusCode).to.equal(200);
|
||||
});
|
||||
cy.getByTestSubj('protectionUpdatesSuccessfulMessage');
|
||||
cy.getByTestSubj('protection-updates-manifest-note').contains(updatedTestNote);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Renders read only protection updates for user without write permissions', () => {
|
||||
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
|
||||
let policy: PolicyData;
|
||||
|
@ -138,6 +199,7 @@ describe('Policy Details', () => {
|
|||
indexedPolicy = data;
|
||||
policy = indexedPolicy.integrationPolicies[0];
|
||||
setCustomProtectionUpdatesManifestVersion(policy.id, twoMonthsAgo.format('YYYY-MM-DD'));
|
||||
setCustomProtectionUpdatesNote(policy.id, testNote);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -162,6 +224,10 @@ describe('Policy Details', () => {
|
|||
cy.getByTestSubj('protection-updates-manifest-name-version-to-deploy-title');
|
||||
cy.getByTestSubj('protection-updates-version-to-deploy-view-mode');
|
||||
cy.getByTestSubj('protection-updates-version-to-deploy-picker').should('not.exist');
|
||||
|
||||
cy.getByTestSubj('protection-updates-manifest-name-note-title');
|
||||
cy.getByTestSubj('protection-updates-manifest-note').should('not.exist');
|
||||
cy.getByTestSubj('protection-updates-manifest-note-view-mode').contains(testNote);
|
||||
cy.getByTestSubj('policyDetailsSaveButton').should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -94,3 +94,15 @@ export const setCustomProtectionUpdatesManifestVersion = (
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const setCustomProtectionUpdatesNote = (
|
||||
endpointPolicyId: string,
|
||||
note: string
|
||||
): Cypress.Chainable<Cypress.Response<{ note: string }>> => {
|
||||
return request<{ note: string }>({
|
||||
method: 'POST',
|
||||
url: `/api/endpoint/protection_updates_note/${endpointPolicyId}`,
|
||||
body: { note },
|
||||
headers: { 'Elastic-Api-Version': '2023-10-31' },
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { resolvePathVariables } from '../../../../../../common/utils/resolve_path_variables';
|
||||
import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../../../../common/endpoint/constants';
|
||||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
|
||||
export const getProtectionUpdatesNoteQueryKey = (packagePolicyId: string) =>
|
||||
`protection-updates-note-${packagePolicyId}`;
|
||||
|
||||
interface UseProtectionUpdatesNote {
|
||||
packagePolicyId: string;
|
||||
}
|
||||
|
||||
interface NoteResponse {
|
||||
note: string;
|
||||
}
|
||||
|
||||
export const useGetProtectionUpdatesNote = ({ packagePolicyId }: UseProtectionUpdatesNote) => {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
return useQuery<{ data: NoteResponse }, unknown, NoteResponse>(
|
||||
[getProtectionUpdatesNoteQueryKey(packagePolicyId)],
|
||||
() =>
|
||||
http.get(
|
||||
resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { package_policy_id: packagePolicyId }),
|
||||
{
|
||||
version: '2023-10-31',
|
||||
}
|
||||
),
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: !!packagePolicyId,
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getProtectionUpdatesNoteQueryKey } from './use_get_protection_updates_note';
|
||||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
import { resolvePathVariables } from '../../../../../../common/utils/resolve_path_variables';
|
||||
import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../../../../common/endpoint/constants';
|
||||
|
||||
interface ProtectionUpdatesNoteParams {
|
||||
packagePolicyId: string;
|
||||
}
|
||||
|
||||
interface NoteResponse {
|
||||
note: string;
|
||||
}
|
||||
|
||||
export const useCreateProtectionUpdatesNote = ({
|
||||
packagePolicyId,
|
||||
}: ProtectionUpdatesNoteParams) => {
|
||||
const { http } = useKibana().services;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
{ data: NoteResponse },
|
||||
{ body: { error: string; message: string } },
|
||||
NoteResponse
|
||||
>(
|
||||
(payload) =>
|
||||
http.post(
|
||||
resolvePathVariables(PROTECTION_UPDATES_NOTE_ROUTE, { policy_id: packagePolicyId }),
|
||||
{
|
||||
version: '2023-10-31',
|
||||
body: JSON.stringify(payload),
|
||||
}
|
||||
),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([getProtectionUpdatesNoteQueryKey(packagePolicyId)]);
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
|
@ -13,14 +13,16 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiIconTip,
|
||||
EuiPanel,
|
||||
EuiShowFor,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useContext, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -28,6 +30,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import type { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useCreateProtectionUpdatesNote } from './hooks/use_post_protection_updates_note';
|
||||
import { useGetProtectionUpdatesNote } from './hooks/use_get_protection_updates_note';
|
||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { useToasts } from '../../../../../common/lib/kibana';
|
||||
import { useUpdateEndpointPolicy } from '../../../../hooks/policy/use_update_endpoint_policy';
|
||||
|
@ -67,6 +71,20 @@ export const ProtectionUpdatesLayout = React.memo<ProtectionUpdatesLayoutProps>(
|
|||
const today = moment();
|
||||
const [selectedDate, setSelectedDate] = useState<Moment>(today);
|
||||
|
||||
const { data: fetchedNote, isLoading: getNoteInProgress } = useGetProtectionUpdatesNote({
|
||||
packagePolicyId: _policy.id,
|
||||
});
|
||||
const { isLoading: createNoteInProgress, mutate: createNote } = useCreateProtectionUpdatesNote({
|
||||
packagePolicyId: _policy.id,
|
||||
});
|
||||
const [note, setNote] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (fetchedNote && !getNoteInProgress) {
|
||||
setNote(fetchedNote.note);
|
||||
}
|
||||
}, [fetchedNote, getNoteInProgress]);
|
||||
|
||||
const automaticUpdatesEnabled = manifestVersion === 'latest';
|
||||
const internalDateFormat = 'YYYY-MM-DD';
|
||||
const displayDateFormat = 'MMMM DD, YYYY';
|
||||
|
@ -119,8 +137,27 @@ export const ProtectionUpdatesLayout = React.memo<ProtectionUpdatesLayoutProps>(
|
|||
text: err.message,
|
||||
});
|
||||
});
|
||||
if ((!fetchedNote && note !== '') || (fetchedNote && note !== fetchedNote.note)) {
|
||||
createNote(
|
||||
{ note },
|
||||
{
|
||||
onError: (error) => {
|
||||
toasts.addDanger({
|
||||
'data-test-subj': 'protectionUpdatesNoteUpdateFailureMessage',
|
||||
title: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.protectionUpdates.noteUpdateErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Note update failed!',
|
||||
}
|
||||
),
|
||||
text: error.body.message,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, policy, sendPolicyUpdate, toasts]
|
||||
[policy, sendPolicyUpdate, fetchedNote, note, toasts, dispatch, createNote]
|
||||
);
|
||||
|
||||
const toggleAutomaticUpdates = useCallback(
|
||||
|
@ -260,16 +297,57 @@ export const ProtectionUpdatesLayout = React.memo<ProtectionUpdatesLayoutProps>(
|
|||
)}
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="m" data-test-subj="protection-updates-deployed-version">
|
||||
{deployedVersion === 'latest' ? 'latest' : formattedDate}
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
{renderVersionToDeployPicker()}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="row" gutterSize="none" alignItems="center">
|
||||
<EuiTitle size="xxs" data-test-subj={'protection-updates-manifest-name-note-title'}>
|
||||
<h5>
|
||||
{i18n.translate('xpack.securitySolution.endpoint.protectionUpdates.note.label', {
|
||||
defaultMessage: 'Note',
|
||||
})}
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
content={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.protectionUpdates.note.tooltip"
|
||||
defaultMessage="You can add an optional note to explain the reason for selecting a particular policy version."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
{canWritePolicyManagement ? (
|
||||
<EuiTextArea
|
||||
value={note}
|
||||
disabled={getNoteInProgress || createNoteInProgress}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
fullWidth={true}
|
||||
rows={3}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.protectionUpdates.note.placeholder',
|
||||
{
|
||||
defaultMessage: 'Add relevant information about update here',
|
||||
}
|
||||
)}
|
||||
data-test-subj={'protection-updates-manifest-note'}
|
||||
/>
|
||||
) : (
|
||||
<EuiText data-test-subj={'protection-updates-manifest-note-view-mode'}>{note}</EuiText>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
fill={true}
|
||||
disabled={!canWritePolicyManagement}
|
||||
|
@ -315,7 +393,7 @@ export const ProtectionUpdatesLayout = React.memo<ProtectionUpdatesLayoutProps>(
|
|||
<EuiShowFor sizes={['l', 'xl', 'm']}>
|
||||
{canWritePolicyManagement ? (
|
||||
<EuiSwitch
|
||||
disabled={isUpdating}
|
||||
disabled={isUpdating || createNoteInProgress || getNoteInProgress}
|
||||
label={'Update manifest automatically'}
|
||||
labelProps={{ 'data-test-subj': 'protection-updates-manifest-switch-label' }}
|
||||
checked={automaticUpdatesEnabled}
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { KibanaRequest, Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type {
|
||||
KibanaRequest,
|
||||
Logger,
|
||||
ElasticsearchClient,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/server';
|
||||
import type { ExceptionListClient, ListsServerExtensionRegistrar } from '@kbn/lists-plugin/server';
|
||||
import type { CasesClient, CasesStart } from '@kbn/cases-plugin/server';
|
||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
|
@ -71,6 +76,7 @@ export interface EndpointAppContextServiceStartContract {
|
|||
actionCreateService: ActionCreateService | undefined;
|
||||
esClient: ElasticsearchClient;
|
||||
appFeaturesService: AppFeaturesService;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,6 +114,7 @@ export class EndpointAppContextService {
|
|||
endpointMetadataService,
|
||||
esClient,
|
||||
appFeaturesService,
|
||||
savedObjectsClient,
|
||||
} = dependencies;
|
||||
|
||||
registerIngestCallback(
|
||||
|
@ -144,7 +151,7 @@ export class EndpointAppContextService {
|
|||
|
||||
registerIngestCallback(
|
||||
'packagePolicyPostDelete',
|
||||
getPackagePolicyDeleteCallback(exceptionListsClient)
|
||||
getPackagePolicyDeleteCallback(exceptionListsClient, savedObjectsClient)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { SavedObjectsType } from '@kbn/core-saved-objects-server';
|
||||
import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
|
||||
|
||||
export const protectionUpdatesNoteSavedObjectType = 'policy-settings-protection-updates-note';
|
||||
|
||||
export const protectionUpdatesNoteSavedObjectMappings: SavedObjectsType['mappings'] = {
|
||||
properties: {
|
||||
note: {
|
||||
type: 'text',
|
||||
index: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const protectionUpdatesNoteType: SavedObjectsType = {
|
||||
name: protectionUpdatesNoteSavedObjectType,
|
||||
indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: protectionUpdatesNoteSavedObjectMappings,
|
||||
};
|
|
@ -221,6 +221,7 @@ export const createMockEndpointAppContextServiceStartContract =
|
|||
createFleetActionsClient: jest.fn((_) => fleetActionsClientMock),
|
||||
esClient: elasticsearchClientMock.createElasticsearchClient(),
|
||||
appFeaturesService,
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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 { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import type { KibanaResponseFactory, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
createMockEndpointAppContextServiceSetupContract,
|
||||
createMockEndpointAppContextServiceStartContract,
|
||||
createRouteHandlerContext,
|
||||
} from '../../mocks';
|
||||
import type { ScopedClusterClientMock } from '@kbn/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
savedObjectsClientMock,
|
||||
} from '@kbn/core/server/mocks';
|
||||
import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } from './handlers';
|
||||
import { requestContextMock } from '../../../lib/detection_engine/routes/__mocks__';
|
||||
|
||||
const mockedSOSuccessfulFindResponse = {
|
||||
total: 1,
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
references: [
|
||||
{
|
||||
id: 'id_package_policy',
|
||||
name: 'package_policy',
|
||||
type: 'ingest-package-policies',
|
||||
},
|
||||
],
|
||||
attributes: { note: 'note' },
|
||||
score: 1,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
};
|
||||
|
||||
const mockedSOSuccessfulFindResponseEmpty = {
|
||||
total: 0,
|
||||
saved_objects: [],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
};
|
||||
|
||||
const createMockedSOSuccessfulCreateResponse = (note: string) => ({
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
references: [],
|
||||
attributes: { note },
|
||||
});
|
||||
|
||||
const mockedSOSuccessfulUpdateResponse = [
|
||||
'policy-settings-protection-updates-note',
|
||||
'id',
|
||||
{ note: 'note2' },
|
||||
{
|
||||
references: [
|
||||
{
|
||||
id: 'id_package_policy',
|
||||
name: 'package_policy',
|
||||
type: 'ingest-package-policies',
|
||||
},
|
||||
],
|
||||
refresh: 'wait_for',
|
||||
},
|
||||
];
|
||||
|
||||
describe('test protection updates note handler', () => {
|
||||
let endpointAppContextService: EndpointAppContextService;
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockScopedClient: ScopedClusterClientMock;
|
||||
|
||||
describe('test protection updates note handler', () => {
|
||||
beforeEach(() => {
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract());
|
||||
endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
|
||||
});
|
||||
|
||||
afterEach(() => endpointAppContextService.stop());
|
||||
|
||||
it('should create a new note if one does not exist', async () => {
|
||||
const protectionUpdatesNoteHandler = postProtectionUpdatesNoteHandler();
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { policyId: 'id' },
|
||||
body: { note: 'note' },
|
||||
});
|
||||
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponseEmpty);
|
||||
|
||||
mockSavedObjectClient.create.mockResolvedValueOnce(
|
||||
createMockedSOSuccessfulCreateResponse('note')
|
||||
);
|
||||
|
||||
await protectionUpdatesNoteHandler(
|
||||
requestContextMock.convertContext(
|
||||
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient)
|
||||
),
|
||||
mockRequest,
|
||||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
expect(mockSavedObjectClient.create).toBeCalledWith(
|
||||
'policy-settings-protection-updates-note',
|
||||
{ note: 'note' },
|
||||
{
|
||||
references: [{ id: undefined, name: 'package_policy', type: 'ingest-package-policies' }],
|
||||
refresh: 'wait_for',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should update an existing note on post if one exists', async () => {
|
||||
const protectionUpdatesNoteHandler = postProtectionUpdatesNoteHandler();
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { policyId: 'id' },
|
||||
body: { note: 'note2' },
|
||||
});
|
||||
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponse);
|
||||
|
||||
mockSavedObjectClient.update.mockResolvedValueOnce(
|
||||
createMockedSOSuccessfulCreateResponse('note2')
|
||||
);
|
||||
|
||||
await protectionUpdatesNoteHandler(
|
||||
requestContextMock.convertContext(
|
||||
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient)
|
||||
),
|
||||
mockRequest,
|
||||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
expect(mockSavedObjectClient.update).toBeCalledWith(...mockedSOSuccessfulUpdateResponse);
|
||||
});
|
||||
|
||||
it('should return the note if one exists', async () => {
|
||||
const protectionUpdatesNoteHandler = getProtectionUpdatesNoteHandler();
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { policyId: 'id' },
|
||||
});
|
||||
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponse);
|
||||
|
||||
await protectionUpdatesNoteHandler(
|
||||
requestContextMock.convertContext(
|
||||
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient)
|
||||
),
|
||||
mockRequest,
|
||||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const result = mockResponse.ok.mock.calls[0][0]?.body as { note: string };
|
||||
expect(result.note).toEqual('note');
|
||||
});
|
||||
|
||||
it('should return notFound if no note exists', async () => {
|
||||
const protectionUpdatesNoteHandler = getProtectionUpdatesNoteHandler();
|
||||
const mockRequest = httpServerMock.createKibanaRequest({
|
||||
params: { policyId: 'id' },
|
||||
});
|
||||
|
||||
mockSavedObjectClient.find.mockResolvedValueOnce(mockedSOSuccessfulFindResponseEmpty);
|
||||
|
||||
await protectionUpdatesNoteHandler(
|
||||
requestContextMock.convertContext(
|
||||
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient)
|
||||
),
|
||||
mockRequest,
|
||||
mockResponse
|
||||
);
|
||||
|
||||
expect(mockResponse.notFound).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 {
|
||||
RequestHandler,
|
||||
SavedObjectReference,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/server';
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||
import { protectionUpdatesNoteSavedObjectType } from '../../lib/protection_updates_note/saved_object_mappings';
|
||||
import type {
|
||||
CreateUpdateProtectionUpdatesNoteSchema,
|
||||
GetProtectionUpdatesNoteSchema,
|
||||
} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema';
|
||||
|
||||
const getProtectionNote = async (SOClient: SavedObjectsClientContract, packagePolicyId: string) => {
|
||||
return SOClient.find<{ note: string }>({
|
||||
type: protectionUpdatesNoteSavedObjectType,
|
||||
hasReference: { type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, id: packagePolicyId },
|
||||
});
|
||||
};
|
||||
|
||||
const updateProtectionNote = async (
|
||||
SOClient: SavedObjectsClientContract,
|
||||
noteId: string,
|
||||
note: string,
|
||||
references: SavedObjectReference[]
|
||||
) => {
|
||||
return SOClient.update(
|
||||
protectionUpdatesNoteSavedObjectType,
|
||||
noteId,
|
||||
{
|
||||
note,
|
||||
},
|
||||
{
|
||||
references,
|
||||
refresh: 'wait_for',
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const createProtectionNote = async (
|
||||
SOClient: SavedObjectsClientContract,
|
||||
note: string,
|
||||
references: SavedObjectReference[]
|
||||
) => {
|
||||
return SOClient.create(
|
||||
protectionUpdatesNoteSavedObjectType,
|
||||
{
|
||||
note,
|
||||
},
|
||||
{
|
||||
references,
|
||||
refresh: 'wait_for',
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const postProtectionUpdatesNoteHandler = function (): RequestHandler<
|
||||
TypeOf<typeof CreateUpdateProtectionUpdatesNoteSchema.params>,
|
||||
undefined,
|
||||
TypeOf<typeof CreateUpdateProtectionUpdatesNoteSchema.body>
|
||||
> {
|
||||
return async (context, request, response) => {
|
||||
const SOClient = (await context.core).savedObjects.client;
|
||||
const { package_policy_id: packagePolicyId } = request.params;
|
||||
const { note } = request.body;
|
||||
|
||||
const soClientResponse = await getProtectionNote(SOClient, packagePolicyId);
|
||||
|
||||
if (soClientResponse.saved_objects[0]) {
|
||||
const { references } = soClientResponse.saved_objects[0];
|
||||
|
||||
const updatedNoteSO = await updateProtectionNote(
|
||||
SOClient,
|
||||
soClientResponse.saved_objects[0].id,
|
||||
note,
|
||||
references
|
||||
);
|
||||
|
||||
const { attributes } = updatedNoteSO;
|
||||
|
||||
return response.ok({ body: attributes });
|
||||
}
|
||||
|
||||
const references: SavedObjectReference[] = [
|
||||
{
|
||||
id: packagePolicyId,
|
||||
name: 'package_policy',
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
},
|
||||
];
|
||||
|
||||
const noteSO = await createProtectionNote(SOClient, note, references);
|
||||
|
||||
const { attributes } = noteSO;
|
||||
|
||||
return response.ok({ body: attributes });
|
||||
};
|
||||
};
|
||||
|
||||
export const getProtectionUpdatesNoteHandler = function (): RequestHandler<
|
||||
TypeOf<typeof GetProtectionUpdatesNoteSchema.params>,
|
||||
undefined,
|
||||
undefined
|
||||
> {
|
||||
return async (context, request, response) => {
|
||||
const SOClient = (await context.core).savedObjects.client;
|
||||
const { package_policy_id: packagePolicyId } = request.params;
|
||||
|
||||
const soClientResponse = await getProtectionNote(SOClient, packagePolicyId);
|
||||
|
||||
if (!soClientResponse.saved_objects[0] || !soClientResponse.saved_objects[0].attributes) {
|
||||
return response.notFound({ body: { message: 'No note found for this policy' } });
|
||||
}
|
||||
|
||||
const { attributes } = soClientResponse.saved_objects[0];
|
||||
|
||||
return response.ok({ body: attributes });
|
||||
};
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { IRouter } from '@kbn/core/server';
|
||||
import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } from './handlers';
|
||||
import {
|
||||
GetProtectionUpdatesNoteSchema,
|
||||
CreateUpdateProtectionUpdatesNoteSchema,
|
||||
} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema';
|
||||
import { withEndpointAuthz } from '../with_endpoint_authz';
|
||||
import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../common/endpoint/constants';
|
||||
import type { EndpointAppContext } from '../../types';
|
||||
|
||||
export function registerProtectionUpdatesNoteRoutes(
|
||||
router: IRouter,
|
||||
endpointAppContext: EndpointAppContext
|
||||
) {
|
||||
const logger = endpointAppContext.logFactory.get('protectionUpdatesNote');
|
||||
|
||||
router.versioned
|
||||
.post({
|
||||
access: 'public',
|
||||
path: PROTECTION_UPDATES_NOTE_ROUTE,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '2023-10-31',
|
||||
validate: {
|
||||
request: CreateUpdateProtectionUpdatesNoteSchema,
|
||||
},
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canWritePolicyManagement'] },
|
||||
logger,
|
||||
postProtectionUpdatesNoteHandler()
|
||||
)
|
||||
);
|
||||
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'public',
|
||||
path: PROTECTION_UPDATES_NOTE_ROUTE,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '2023-10-31',
|
||||
validate: {
|
||||
request: GetProtectionUpdatesNoteSchema,
|
||||
},
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canReadPolicyManagement'] },
|
||||
logger,
|
||||
getProtectionUpdatesNoteHandler()
|
||||
)
|
||||
);
|
||||
}
|
|
@ -615,7 +615,7 @@ describe('ingest_integration tests ', () => {
|
|||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const invokeDeleteCallback = async (): Promise<void> => {
|
||||
const callback = getPackagePolicyDeleteCallback(exceptionListClient);
|
||||
const callback = getPackagePolicyDeleteCallback(exceptionListClient, soClient);
|
||||
await callback(deletePackagePolicyMock(), soClient, esClient);
|
||||
};
|
||||
|
||||
|
@ -640,6 +640,27 @@ describe('ingest_integration tests ', () => {
|
|||
});
|
||||
|
||||
it('removes policy from artifact', async () => {
|
||||
soClient.find.mockResolvedValueOnce({
|
||||
total: 1,
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
references: [
|
||||
{
|
||||
id: 'id_package_policy',
|
||||
name: 'package_policy',
|
||||
type: 'ingest-package-policies',
|
||||
},
|
||||
],
|
||||
attributes: { note: 'note' },
|
||||
score: 1,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
});
|
||||
|
||||
await invokeDeleteCallback();
|
||||
|
||||
expect(exceptionListClient.findExceptionListsItem).toHaveBeenCalledWith({
|
||||
|
@ -660,6 +681,8 @@ describe('ingest_integration tests ', () => {
|
|||
osTypes: fakeArtifact.os_types,
|
||||
tags: [],
|
||||
});
|
||||
|
||||
expect(soClient.delete).toBeCalledWith('policy-settings-protection-updates-note', 'id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
|
||||
import type { PluginStartContract as AlertsStartContract } from '@kbn/alerting-plugin/server';
|
||||
import type {
|
||||
|
@ -44,6 +44,7 @@ import type { AnyPolicyCreateConfig } from './types';
|
|||
import { ENDPOINT_INTEGRATION_CONFIG_KEY } from './constants';
|
||||
import { createEventFilters } from './handlers/create_event_filters';
|
||||
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service';
|
||||
import { removeProtectionUpdatesNote } from './handlers/remove_protection_updates_note';
|
||||
|
||||
const isEndpointPackagePolicy = <T extends { package?: { name: string } }>(
|
||||
packagePolicy: T
|
||||
|
@ -280,7 +281,8 @@ export const getPackagePolicyPostCreateCallback = (
|
|||
};
|
||||
|
||||
export const getPackagePolicyDeleteCallback = (
|
||||
exceptionsClient: ExceptionListClient | undefined
|
||||
exceptionsClient: ExceptionListClient | undefined,
|
||||
savedObjectsClient: SavedObjectsClientContract | undefined
|
||||
): PostPackagePolicyPostDeleteCallback => {
|
||||
return async (deletePackagePolicy): Promise<void> => {
|
||||
if (!exceptionsClient) {
|
||||
|
@ -290,8 +292,12 @@ export const getPackagePolicyDeleteCallback = (
|
|||
for (const policy of deletePackagePolicy) {
|
||||
if (isEndpointPackagePolicy(policy)) {
|
||||
policiesToRemove.push(removePolicyFromArtifacts(exceptionsClient, policy));
|
||||
if (savedObjectsClient) {
|
||||
policiesToRemove.push(removeProtectionUpdatesNote(savedObjectsClient, policy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(policiesToRemove);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { PostPackagePolicyPostDeleteCallback } from '@kbn/fleet-plugin/server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||
import pMap from 'p-map';
|
||||
import { protectionUpdatesNoteSavedObjectType } from '../../endpoint/lib/protection_updates_note/saved_object_mappings';
|
||||
|
||||
export const removeProtectionUpdatesNote = async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
policy: Parameters<PostPackagePolicyPostDeleteCallback>[0][0]
|
||||
) => {
|
||||
if (policy.id) {
|
||||
const foundProtectionUpdatesNotes = await soClient.find({
|
||||
type: protectionUpdatesNoteSavedObjectType,
|
||||
hasReference: {
|
||||
type: PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
id: policy.id,
|
||||
},
|
||||
});
|
||||
await pMap(
|
||||
foundProtectionUpdatesNotes.saved_objects,
|
||||
(protectionUpdatesNote: { id: string }) => {
|
||||
soClient.delete(protectionUpdatesNoteSavedObjectType, protectionUpdatesNote.id);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
|
@ -98,6 +98,7 @@ import {
|
|||
|
||||
import { AppFeaturesService } from './lib/app_features_service/app_features_service';
|
||||
import { registerRiskScoringTask } from './lib/risk_engine/tasks/risk_scoring_task';
|
||||
import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note';
|
||||
|
||||
export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract';
|
||||
|
||||
|
@ -317,6 +318,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
);
|
||||
registerLimitedConcurrencyRoutes(core);
|
||||
registerPolicyRoutes(router, this.endpointContext);
|
||||
registerProtectionUpdatesNoteRoutes(router, this.endpointContext);
|
||||
registerActionRoutes(
|
||||
router,
|
||||
this.endpointContext,
|
||||
|
@ -533,6 +535,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
createFleetActionsClient,
|
||||
esClient: core.elasticsearch.client.asInternalUser,
|
||||
appFeaturesService,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
this.telemetryReceiver.start(
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { CoreSetup } from '@kbn/core/server';
|
||||
|
||||
import { protectionUpdatesNoteType } from './endpoint/lib/protection_updates_note/saved_object_mappings';
|
||||
import { noteType, pinnedEventType, timelineType } from './lib/timeline/saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule_actions_legacy';
|
||||
|
@ -24,6 +25,7 @@ const types = [
|
|||
manifestType,
|
||||
signalsMigrationType,
|
||||
riskEngineConfigurationType,
|
||||
protectionUpdatesNoteType,
|
||||
];
|
||||
|
||||
export const savedObjectTypes = types.map((type) => type.name);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue