Skip to content

Commit

Permalink
[Cloud Security] Agentless integration deletion flow (elastic#191557)
Browse files Browse the repository at this point in the history
## Summary

Summarize your PR. If it involves visual changes include a screenshot or
gif.
This PR is completes the deletion flow for  Agentless CSPM.

**Current Agentless Integraton deletion flow**: 

1. Successfully delete integration policy
2. Successfully unenrolls agent from agent policy 
3. Successfully revokes enrollment token
4. Successfully deletes agentless deployment
5. Successfully deletes agent policy 
6. Successful notification shows when deleted integration policy is
successful


## Agentless Agent API 
- Unenrolls agent and revokes token first to avoid 404 save object
client error.
- Update `is_managed` property to no longer check for
`agentPolicy.supports_agentless`. Agentless policies will now be a
regular policy.
- Adds logging for DELETE  agentless Agent API endpoint 
- Adds agentless API deleteendpoint using try & catch. No errors will be
thrown. Agent status will become offline after deployment deletion
- If agentless deployment api fails, then we will continue to delete the
agent policy

## UI Changes

**CSPM Integration** 
- Updates  Agent Policy Error toast notification title 
- Updates Agent Policy Error toast notification message 

<img width="1612" alt="image"
src="https://github.com/user-attachments/assets/0003ce04-c53c-4e11-8363-ddc25ba342a7">

**Edit Mode**
- Adds back the Agentless selector in Edit Integration

<img width="1316" alt="image"
src="https://github.com/user-attachments/assets/0d2f20ce-32fc-421c-a15a-48ca6226b67f">

**Integration Policies Page**
- Removes automatic navigation to agent policies page when deleting an
integration. In 8.17, we have a ticket to [hide the agentless agent
policies.](elastic/security-team#9857)
- Enables delete button when deleting package policy with agents for
agentless policies
- Disables Upgrade Action
- Removes Add Agent Action

<img width="1717" alt="image"
src="https://github.com/user-attachments/assets/1b7ac4c7-e8bc-41b8-836f-4d3c79a449dd">

<img width="670" alt="image"
src="https://github.com/user-attachments/assets/0ab6a4c4-d7c6-43ea-9537-67e7fbcca2b0">


**Agent Policies Page**
- Updates messaging when deleting the agentless policy from agent policy
page. Warning users that deleting agentless policy will also delete the
integration and unenroll agent.
- Enables delete button when deleting agentless policy with agents for
agentless policies
- Removes Add agent menu action
- Removes Upgrade  policy menu action
- Removes Uninstall agent action
- Removes Copy policy menu action

<img width="1595" alt="image"
src="https://github.com/user-attachments/assets/2f195da2-4594-4f54-8f8d-7995e829a5ac">
<img width="1365" alt="image"
src="https://github.com/user-attachments/assets/4915642d-41e8-4e83-80f9-f334cb879506">


**Agent Policy Settings**
For agent policy that are agentless, we disabled the following [fleet
actions:](https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-types)
- Disables Agent monitoring
- Disables Inactivity timeout
- Disables Fleet Server
- Disables Output for integrations
- Disables Output for agent monitoring
- Disables Agent binary download
- Disables Host name format
- Disables Inactive agent unenrollment timeout 
- Disables Advanced Settings  - Limit CPU usage
- Disables HTTP monitoring endpoint
- Disables Agent Logging

<img width="1569" alt="image"
src="https://github.com/user-attachments/assets/2639be9f-ea10-4d42-b379-a13c4c2b08a1">
<img width="1517" alt="image"
src="https://github.com/user-attachments/assets/ae6f3e10-8c2b-42fe-8f27-7e8621d373c0">

**Agents Page**

- Disables Assign to Policy action
- Disables Upgrade Policy action
- Removes Unassign agent action
- Removes agentless policies where user can add agent to agentless
policy

<img width="1710" alt="image"
src="https://github.com/user-attachments/assets/61bf2d06-d337-45dd-8255-499db1e1ed42">
<img width="1723" alt="image"
src="https://github.com/user-attachments/assets/cc76787f-d6a2-44fb-9289-7f1f643620ec">


### How to test in Serverless
 Use vault access and open the security Project in [build
]([Buildkite
Build](https://buildkite.com/elastic/kibana-pull-request/builds/234438))

### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
  • Loading branch information
Omolola-Akinleye authored Oct 1, 2024
1 parent 39ac875 commit 6742f77
Show file tree
Hide file tree
Showing 33 changed files with 712 additions and 277 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,39 @@ describe('AgentPolicyActionMenu', () => {
const deleteButton = result.getByTestId('agentPolicyActionMenuDeleteButton');
expect(deleteButton).toHaveAttribute('disabled');
});

it('is disabled when agent policy support agentless is true', () => {
const testRenderer = createFleetTestRendererMock();
const agentlessPolicy: AgentPolicy = {
...baseAgentPolicy,
supports_agentless: true,
package_policies: [
{
id: 'test-package-policy',
is_managed: false,
created_at: new Date().toISOString(),
created_by: 'test',
enabled: true,
inputs: [],
name: 'test-package-policy',
namespace: 'default',
policy_id: 'test',
policy_ids: ['test'],
revision: 1,
updated_at: new Date().toISOString(),
updated_by: 'test',
},
],
};

const result = testRenderer.render(<AgentPolicyActionMenu agentPolicy={agentlessPolicy} />);

const agentActionsButton = result.getByTestId('agentActionsBtn');
agentActionsButton.click();

const deleteButton = result.getByTestId('agentPolicyActionMenuDeleteButton');
expect(deleteButton).not.toHaveAttribute('disabled');
});
});

describe('add agent', () => {
Expand Down Expand Up @@ -176,6 +209,39 @@ describe('AgentPolicyActionMenu', () => {
const addButton = result.getByTestId('agentPolicyActionMenuAddAgentButton');
expect(addButton).toHaveAttribute('disabled');
});

it('should remove add agent button when agent policy support agentless is true', () => {
const testRenderer = createFleetTestRendererMock();
const agentlessPolicy: AgentPolicy = {
...baseAgentPolicy,
supports_agentless: true,
package_policies: [
{
id: 'test-package-policy',
is_managed: false,
created_at: new Date().toISOString(),
created_by: 'test',
enabled: true,
inputs: [],
name: 'test-package-policy',
namespace: 'default',
policy_id: 'test',
policy_ids: ['test'],
revision: 1,
updated_at: new Date().toISOString(),
updated_by: 'test',
},
],
};

const result = testRenderer.render(<AgentPolicyActionMenu agentPolicy={agentlessPolicy} />);

const agentActionsButton = result.getByTestId('agentActionsBtn');
agentActionsButton.click();

const addAgentActionButton = result.queryByTestId('agentPolicyActionMenuAddAgentButton');
expect(addAgentActionButton).toBeNull();
});
});

describe('add fleet server', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,82 +100,106 @@ export const AgentPolicyActionMenu = memo<{
</EuiContextMenuItem>
);

const menuItems = agentPolicy?.is_managed
? [viewPolicyItem]
: [
const deletePolicyItem = (
<AgentPolicyDeleteProvider
hasFleetServer={policyHasFleetServer(agentPolicy as AgentPolicy)}
key="deletePolicy"
agentPolicy={agentPolicy}
packagePolicies={agentPolicy.package_policies}
>
{(deleteAgentPolicyPrompt) => (
<EuiContextMenuItem
icon="plusInCircle"
disabled={
(isFleetServerPolicy && !authz.fleet.addFleetServers) ||
(!isFleetServerPolicy && !authz.fleet.addAgents)
data-test-subj="agentPolicyActionMenuDeleteButton"
disabled={!authz.fleet.allAgentPolicies || hasManagedPackagePolicy}
toolTipContent={
hasManagedPackagePolicy ? (
<FormattedMessage
id="xpack.fleet.policyForm.deletePolicyActionText.disabled"
defaultMessage="Agent policy with managed package policies cannot be deleted."
data-test-subj="agentPolicyActionMenuDeleteButtonDisabledTooltip"
/>
) : undefined
}
data-test-subj="agentPolicyActionMenuAddAgentButton"
onClick={() => {
setIsContextMenuOpen(false);
setIsEnrollmentFlyoutOpen(true);
}}
key="enrollAgents"
>
{isFleetServerPolicy ? (
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.addFleetServerActionText"
defaultMessage="Add Fleet Server"
/>
) : (
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.enrollAgentActionText"
defaultMessage="Add agent"
/>
)}
</EuiContextMenuItem>,
viewPolicyItem,
<EuiContextMenuItem
disabled={!authz.integrations.writeIntegrationPolicies}
icon="copy"
icon="trash"
onClick={() => {
setIsContextMenuOpen(false);
copyAgentPolicyPrompt(agentPolicy, onCopySuccess);
deleteAgentPolicyPrompt(agentPolicy.id);
}}
key="copyPolicy"
>
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.copyPolicyActionText"
defaultMessage="Duplicate policy"
id="xpack.fleet.agentPolicyActionMenu.deletePolicyActionText"
defaultMessage="Delete policy"
/>
</EuiContextMenuItem>,
<AgentPolicyDeleteProvider
hasFleetServer={policyHasFleetServer(agentPolicy as AgentPolicy)}
key="deletePolicy"
packagePolicies={agentPolicy.package_policies}
>
{(deleteAgentPolicyPrompt) => (
<EuiContextMenuItem
data-test-subj="agentPolicyActionMenuDeleteButton"
disabled={!authz.fleet.allAgentPolicies || hasManagedPackagePolicy}
toolTipContent={
hasManagedPackagePolicy ? (
<FormattedMessage
id="xpack.fleet.policyForm.deletePolicyActionText.disabled"
defaultMessage="Agent policy with managed package policies cannot be deleted."
data-test-subj="agentPolicyActionMenuDeleteButtonDisabledTooltip"
/>
) : undefined
}
icon="trash"
onClick={() => {
deleteAgentPolicyPrompt(agentPolicy.id);
}}
>
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.deletePolicyActionText"
defaultMessage="Delete policy"
/>
</EuiContextMenuItem>
)}
</AgentPolicyDeleteProvider>,
];
</EuiContextMenuItem>
)}
</AgentPolicyDeleteProvider>
);

const copyPolicyItem = (
<EuiContextMenuItem
data-test-subj="agentPolicyActionMenuCopyButton"
disabled={!authz.integrations.writeIntegrationPolicies}
icon="copy"
onClick={() => {
setIsContextMenuOpen(false);
copyAgentPolicyPrompt(agentPolicy, onCopySuccess);
}}
key="copyPolicy"
>
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.copyPolicyActionText"
defaultMessage="Duplicate policy"
/>
</EuiContextMenuItem>
);

const managedMenuItems = [viewPolicyItem];
const agentBasedMenuItems = [
<EuiContextMenuItem
icon="plusInCircle"
disabled={
(isFleetServerPolicy && !authz.fleet.addFleetServers) ||
(!isFleetServerPolicy && !authz.fleet.addAgents)
}
data-test-subj="agentPolicyActionMenuAddAgentButton"
onClick={() => {
setIsContextMenuOpen(false);
setIsEnrollmentFlyoutOpen(true);
}}
key="enrollAgents"
>
{isFleetServerPolicy ? (
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.addFleetServerActionText"
defaultMessage="Add Fleet Server"
/>
) : (
<FormattedMessage
id="xpack.fleet.agentPolicyActionMenu.enrollAgentActionText"
defaultMessage="Add agent"
/>
)}
</EuiContextMenuItem>,
viewPolicyItem,
copyPolicyItem,
deletePolicyItem,
];
const agentlessMenuItems = [viewPolicyItem, deletePolicyItem];

let menuItems;

if (agentPolicy?.is_managed) {
menuItems = managedMenuItems;
} else if (agentPolicy?.supports_agentless) {
menuItems = agentlessMenuItems;
} else {
menuItems = agentBasedMenuItems;
}

if (authz.fleet.allAgents && !agentPolicy?.is_managed) {
if (
authz.fleet.allAgents &&
!agentPolicy?.is_managed &&
!agentPolicy?.supports_agentless
) {
menuItems.push(
<EuiContextMenuItem
icon="refresh"
Expand All @@ -193,7 +217,12 @@ export const AgentPolicyActionMenu = memo<{
);
}

if (authz.fleet.allAgents && agentTamperProtectionEnabled && !agentPolicy?.is_managed) {
if (
authz.fleet.allAgents &&
agentTamperProtectionEnabled &&
!agentPolicy?.is_managed &&
!agentPolicy?.supports_agentless
) {
menuItems.push(
<EuiContextMenuItem
icon="minusInCircle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
const licenseService = useLicense();
const [isUninstallCommandFlyoutOpen, setIsUninstallCommandFlyoutOpen] = useState(false);
const policyHasElasticDefend = useMemo(() => hasElasticDefend(agentPolicy), [agentPolicy]);
const isManagedorAgentlessPolicy =
agentPolicy.is_managed === true || agentPolicy?.supports_agentless === true;

const AgentTamperProtectionSectionContent = useMemo(
() => (
Expand Down Expand Up @@ -196,7 +198,12 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
);

const AgentTamperProtectionSection = useMemo(() => {
if (agentTamperProtectionEnabled && licenseService.isPlatinum() && !agentPolicy.is_managed) {
if (
agentTamperProtectionEnabled &&
licenseService.isPlatinum() &&
!agentPolicy.is_managed &&
!agentPolicy.supports_agentless
) {
if (AgentTamperProtectionWrapper) {
return (
<Suspense fallback={null}>
Expand All @@ -214,6 +221,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
agentPolicy.is_managed,
AgentTamperProtectionWrapper,
AgentTamperProtectionSectionContent,
agentPolicy.supports_agentless,
]);

return (
Expand Down Expand Up @@ -405,7 +413,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
>
<EuiSpacer size="l" />
<EuiCheckboxGroup
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
options={[
{
id: `${dataTypes.Logs}_${monitoringCheckboxIdSuffix}`,
Expand Down Expand Up @@ -541,7 +549,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
>
<EuiFieldNumber
fullWidth
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
value={agentPolicy.inactivity_timeout || ''}
min={0}
onChange={(e) => {
Expand Down Expand Up @@ -582,7 +590,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
isInvalid={Boolean(touchedFields.fleet_server_host_id && validation.fleet_server_host_id)}
>
<EuiSuperSelect
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
valueOfSelected={agentPolicy.fleet_server_host_id || DEFAULT_SELECT_VALUE}
fullWidth
isLoading={isLoadingFleetServerHostsOption}
Expand Down Expand Up @@ -623,7 +631,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
isDisabled={disabled}
>
<EuiSuperSelect
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
valueOfSelected={agentPolicy.data_output_id || DEFAULT_SELECT_VALUE}
fullWidth
isLoading={isLoadingOptions}
Expand Down Expand Up @@ -664,7 +672,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
isDisabled={disabled}
>
<EuiSuperSelect
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
valueOfSelected={agentPolicy.monitoring_output_id || DEFAULT_SELECT_VALUE}
fullWidth
isLoading={isLoadingOptions}
Expand Down Expand Up @@ -706,7 +714,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
isDisabled={disabled}
>
<EuiSuperSelect
disabled={disabled}
disabled={disabled || agentPolicy?.supports_agentless === true}
valueOfSelected={agentPolicy.download_source_id || DEFAULT_SELECT_VALUE}
fullWidth
isLoading={isLoadingDownloadSources}
Expand Down Expand Up @@ -739,7 +747,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
>
<EuiFormRow fullWidth isDisabled={disabled}>
<EuiRadioGroup
disabled={disabled}
disabled={disabled || agentPolicy?.supports_agentless === true}
options={[
{
id: 'hostname',
Expand Down Expand Up @@ -834,7 +842,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
>
<EuiFieldNumber
fullWidth
disabled={disabled || agentPolicy.is_managed === true}
disabled={disabled || isManagedorAgentlessPolicy}
value={agentPolicy.unenroll_timeout || ''}
min={0}
onChange={(e) => {
Expand Down
Loading

0 comments on commit 6742f77

Please sign in to comment.