Skip to content

Commit 176e44c

Browse files
committed
[Defend Workflows][Osquery] Multiple fixes for pack management (elastic#241655)
This PR combines three separate osquery pack management fixes: 1. **Fix global packs not preserving policy references**: Resolves issues where global packs would lose references to existing policies when creating new osquery integrations or deleting policies. Fixed by including `pack.references` in SavedObject mapping and correcting the filter logic in the delete callback. https://github.com/user-attachments/assets/452b52b3-198a-428f-a63e-e33857bfa930 2. **Fix pack edit cancel button**: Fixes the cancel button on the pack edit page that was redirecting to `/packs/undefined` instead of the correct pack details page. 3. **Update pack confirmation modal**: Improves the confirmation modal to clearly display both the number of agent policies and the number of agents that will be affected by pack updates. <img width="581" height="300" alt="Screenshot 2025-10-31 at 16 47 11" src="https://github.com/user-attachments/assets/fa14067f-de8f-4377-8898-411a0454fe91" /> Closes elastic/security-team#14422 Closes elastic/security-team#14423 Closes elastic/security-team#14424 (cherry picked from commit eebbf7b)
1 parent 43618e9 commit 176e44c

File tree

15 files changed

+683
-17
lines changed

15 files changed

+683
-17
lines changed

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30972,7 +30972,6 @@
3097230972
"xpack.osquery.agent.attachedQuery": "angehängte Abfrage",
3097330973
"xpack.osquery.agentDetails.fetchError": "Fehler beim Abrufen von Agentendetails",
3097430974
"xpack.osquery.agentPolicy.confirmModalCalloutDescription": "Fleet hat festgestellt, dass die ausgewählte {agentPolicyCount, plural, one {agent policy} other {Agent Policies}} bereits von einigen Ihrer Agenten verwendet wird. Als Ergebnis dieser Aktion wird Fleet Updates an alle Agenten bereitstellen, die diese {agentPolicyCount, plural, one {agent policy} other {Agent Policies}} verwenden.",
30975-
"xpack.osquery.agentPolicy.confirmModalCalloutTitle": "Diese Aktion wird {agentCount, plural, one {# agent} other {# Agenten}} aktualisieren",
3097630975
"xpack.osquery.agentPolicy.confirmModalCancelButtonLabel": "Abbrechen",
3097730976
"xpack.osquery.agentPolicy.confirmModalConfirmButtonLabel": "Änderungen speichern und bereitstellen",
3097830977
"xpack.osquery.agentPolicy.confirmModalDescription": "Möchten Sie wirklich fortfahren?",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31153,7 +31153,6 @@
3115331153
"xpack.osquery.agent.attachedQuery": "requête attachée",
3115431154
"xpack.osquery.agentDetails.fetchError": "Erreur lors de la récupération des détails des agents",
3115531155
"xpack.osquery.agentPolicy.confirmModalCalloutDescription": "Fleet a détecté que {agentPolicyCount, plural, one {la politique d'agent sélectionnée est} other {les politiques d'agent sélectionnées sont}} déjà en cours d'utilisation par certains de vos agents. Par conséquent, Fleet déploiera des mises à jour pour tous les agents utilisant {agentPolicyCount, plural, one {cette politique d'agent} other {ces politiques d'agent}}.",
31156-
"xpack.osquery.agentPolicy.confirmModalCalloutTitle": "Cette action mettra à jour {agentCount, plural, one {# agent} other {# agents}}",
3115731156
"xpack.osquery.agentPolicy.confirmModalCancelButtonLabel": "Annuler",
3115831157
"xpack.osquery.agentPolicy.confirmModalConfirmButtonLabel": "Enregistrer et déployer les modifications",
3115931158
"xpack.osquery.agentPolicy.confirmModalDescription": "Voulez-vous vraiment continuer ?",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31192,7 +31192,6 @@
3119231192
"xpack.osquery.agent.attachedQuery": "関連付けられたクエリー",
3119331193
"xpack.osquery.agentDetails.fetchError": "エージェント詳細の取得中にエラーが発生しました",
3119431194
"xpack.osquery.agentPolicy.confirmModalCalloutDescription": "選択した{agentPolicyCount, plural, one {agent policy} other {エージェントポリシー}}が一部のエージェントですでに使用されていることをFleetが検出しました。このアクションの結果、Fleetはこの{agentPolicyCount, plural, one {agent policy} other {エージェントポリシー}}を使用するすべてのエージェントにアップデートをデプロイします。",
31195-
"xpack.osquery.agentPolicy.confirmModalCalloutTitle": "このアクションは {agentCount, plural, one {# agent} other {# 個のエージェント}} を更新します。",
3119631195
"xpack.osquery.agentPolicy.confirmModalCancelButtonLabel": "キャンセル",
3119731196
"xpack.osquery.agentPolicy.confirmModalConfirmButtonLabel": "変更を保存してデプロイ",
3119831197
"xpack.osquery.agentPolicy.confirmModalDescription": "続行していいですか?",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31174,7 +31174,6 @@
3117431174
"xpack.osquery.agent_status.fetchError": "提取代理状态时出错",
3117531175
"xpack.osquery.agent.attachedQuery": "已附加查询",
3117631176
"xpack.osquery.agentDetails.fetchError": "提取代理详细信息时出错",
31177-
"xpack.osquery.agentPolicy.confirmModalCalloutTitle": "此操作将更新 {agentCount, plural, one {# 个代理} other {# 个代理}}",
3117831177
"xpack.osquery.agentPolicy.confirmModalCancelButtonLabel": "取消",
3117931178
"xpack.osquery.agentPolicy.confirmModalConfirmButtonLabel": "保存并部署更改",
3118031179
"xpack.osquery.agentPolicy.confirmModalDescription": "是否确定要继续?",

x-pack/platform/plugins/shared/osquery/public/packs/form/confirmation_modal.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,17 @@ const ConfirmDeployAgentPolicyModalComponent: React.FC<ConfirmDeployAgentPolicyM
5656
iconType="info"
5757
title={i18n.translate('xpack.osquery.agentPolicy.confirmModalCalloutTitle', {
5858
defaultMessage:
59-
'This action will update {agentCount, plural, one {# agent} other {# agents}}',
59+
'This action will update {agentPolicyCount, plural, one {# agent policy} other {# agent policies}} affecting {agentCount, plural, one {# agent} other {# agents}}',
6060
values: {
6161
agentCount,
62+
agentPolicyCount,
6263
},
6364
})}
6465
>
6566
<div className="eui-textBreakWord">
6667
<FormattedMessage
6768
id="xpack.osquery.agentPolicy.confirmModalCalloutDescription"
68-
defaultMessage="Fleet has detected that the selected {agentPolicyCount, plural, one {agent policy} other {agent policies}}, is already in use by
69-
some of your agents. As a result of this action, Fleet will deploy updates to all agents
70-
that use this {agentPolicyCount, plural, one {agent policy} other {agent policies}}."
69+
defaultMessage="Fleet will deploy updates to all agents that use the selected {agentPolicyCount, plural, one {agent policy} other {agent policies}}."
7170
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
7271
values={{
7372
agentPolicyCount,
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
10+
import { render } from '@testing-library/react';
11+
import { QueryClientProvider } from '@kbn/react-query';
12+
import type { EuiThemeComputed } from '@elastic/eui';
13+
import { EuiProvider } from '@elastic/eui';
14+
import { ThemeProvider } from '@emotion/react';
15+
16+
import { PackForm } from '.';
17+
import { queryClient } from '../../query_client';
18+
19+
const mockUseRouterNavigate = jest.fn();
20+
21+
jest.mock('../../common/lib/kibana', () => ({
22+
...jest.requireActual('../../common/lib/kibana'),
23+
useRouterNavigate: (path: string) => {
24+
mockUseRouterNavigate(path);
25+
26+
return {
27+
onClick: jest.fn(),
28+
href: path,
29+
};
30+
},
31+
}));
32+
33+
jest.mock('../../agent_policies', () => ({
34+
useAgentPolicies: () => ({
35+
data: {
36+
agentPoliciesById: {},
37+
},
38+
}),
39+
}));
40+
41+
jest.mock('../use_create_pack', () => ({
42+
useCreatePack: () => ({
43+
mutateAsync: jest.fn(),
44+
}),
45+
}));
46+
47+
jest.mock('../use_update_pack', () => ({
48+
useUpdatePack: () => ({
49+
mutateAsync: jest.fn(),
50+
}),
51+
}));
52+
53+
const renderWithContext = (Element: React.ReactElement) =>
54+
render(
55+
<EuiProvider>
56+
<ThemeProvider
57+
theme={{
58+
euiTheme: {
59+
colors: { primary: '#006BB4' },
60+
border: { width: { thin: '1px' } },
61+
size: { base: '16px' },
62+
} as unknown as EuiThemeComputed<{}>,
63+
}}
64+
>
65+
<IntlProvider locale={'en'}>
66+
<QueryClientProvider client={queryClient}>{Element}</QueryClientProvider>
67+
</IntlProvider>
68+
</ThemeProvider>
69+
</EuiProvider>
70+
);
71+
72+
describe('PackForm', () => {
73+
beforeEach(() => {
74+
jest.clearAllMocks();
75+
});
76+
77+
it('should use packId for cancel button navigation in edit mode when provided', async () => {
78+
const testPackId = 'test-pack-id-123';
79+
const defaultValue = {
80+
id: 'different-id',
81+
saved_object_id: 'saved-object-id',
82+
name: 'Test Pack',
83+
description: 'Test Description',
84+
enabled: true,
85+
queries: {},
86+
created_at: '2024-01-01',
87+
created_by: 'test-user',
88+
updated_at: '2024-01-01',
89+
updated_by: 'test-user',
90+
policy_ids: [],
91+
references: [],
92+
};
93+
94+
renderWithContext(<PackForm editMode={true} defaultValue={defaultValue} packId={testPackId} />);
95+
96+
expect(mockUseRouterNavigate).toHaveBeenCalledWith(`packs/${testPackId}`);
97+
});
98+
99+
it('should fallback to defaultValue.id for cancel button navigation when packId not provided', async () => {
100+
const defaultValue = {
101+
id: 'fallback-id',
102+
saved_object_id: 'saved-object-id',
103+
name: 'Test Pack',
104+
description: 'Test Description',
105+
enabled: true,
106+
queries: {},
107+
created_at: '2024-01-01',
108+
created_by: 'test-user',
109+
updated_at: '2024-01-01',
110+
updated_by: 'test-user',
111+
policy_ids: [],
112+
references: [],
113+
};
114+
115+
renderWithContext(<PackForm editMode={true} defaultValue={defaultValue} />);
116+
117+
expect(mockUseRouterNavigate).toHaveBeenCalledWith(`packs/${defaultValue.id}`);
118+
});
119+
120+
it('should use empty string for cancel button navigation in create mode', async () => {
121+
renderWithContext(<PackForm editMode={false} />);
122+
123+
expect(mockUseRouterNavigate).toHaveBeenCalledWith('packs/');
124+
});
125+
126+
it('should prioritize packId over defaultValue.id when both are provided', async () => {
127+
const testPackId = 'priority-pack-id';
128+
const defaultValue = {
129+
id: 'should-not-be-used',
130+
saved_object_id: 'saved-object-id',
131+
name: 'Test Pack',
132+
description: 'Test Description',
133+
enabled: true,
134+
queries: {},
135+
created_at: '2024-01-01',
136+
created_by: 'test-user',
137+
updated_at: '2024-01-01',
138+
updated_by: 'test-user',
139+
policy_ids: [],
140+
references: [],
141+
};
142+
143+
renderWithContext(<PackForm editMode={true} defaultValue={defaultValue} packId={testPackId} />);
144+
145+
expect(mockUseRouterNavigate).toHaveBeenCalledWith(`packs/${testPackId}`);
146+
expect(mockUseRouterNavigate).not.toHaveBeenCalledWith(`packs/${defaultValue.id}`);
147+
});
148+
});

x-pack/platform/plugins/shared/osquery/public/packs/form/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ interface PackFormProps {
5151
defaultValue?: PackItem;
5252
editMode?: boolean;
5353
isReadOnly?: boolean;
54+
packId?: string;
5455
}
5556

5657
const PackFormComponent: React.FC<PackFormProps> = ({
5758
defaultValue,
5859
editMode = false,
5960
isReadOnly = false,
61+
packId,
6062
}) => {
6163
const [shardsToggleState, setShardsToggleState] =
6264
useState<EuiAccordionProps['forceState']>('closed');
@@ -70,7 +72,9 @@ const PackFormComponent: React.FC<PackFormProps> = ({
7072

7173
const { data: { agentPoliciesById } = {} } = useAgentPolicies();
7274

73-
const cancelButtonProps = useRouterNavigate(`packs/${editMode ? defaultValue?.id : ''}`);
75+
const cancelButtonProps = useRouterNavigate(
76+
`packs/${editMode ? packId ?? defaultValue?.id : ''}`
77+
);
7478

7579
const { mutateAsync: createAsync } = useCreatePack({
7680
withRedirect: true,

x-pack/platform/plugins/shared/osquery/public/routes/packs/edit/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ const EditPackPageComponent = () => {
133133
{!data ? (
134134
<EuiSkeletonText lines={10} />
135135
) : (
136-
<PackForm editMode={true} defaultValue={data} isReadOnly={isReadOnly} />
136+
<PackForm editMode={true} defaultValue={data} isReadOnly={isReadOnly} packId={packId} />
137137
)}
138138
{isDeleteModalVisible ? (
139139
<EuiConfirmModal

0 commit comments

Comments
 (0)