Skip to content

Commit

Permalink
[Cloud Security] Allow force install package policy to agentless agen…
Browse files Browse the repository at this point in the history
…t policy (elastic#173553)

## Summary

Right now every Security project in serverless is created with [an
Agentless
policy](https://github.com/elastic/project-controller/blob/main/internal/project/security/security_kibana_config.go#L80)
(gated be the feature flag based on or org id) and in this policy
`is_managed` set to `false`. We in Cloud Security want to make the
policy managed.

This change is to allow us to do that and still be able to install
integrations on the policy. In a nutshell, the logic is to force install
integration if the agent policy id is `agentless`. If we are not missing
something, it should be safe, as when managed, the agentless agent
policy won't be available in the list of Existing Hosts and the only way
to pick it for installation in the UI is to implement the same logic we
implemented in CSP integration in
- elastic#171671
- elastic#172562

Part of:
- elastic/security-team#8117

## Screencast

https://github.com/elastic/security-team/assets/478762/c41f2f33-0c43-467f-a54a-8710b26a0abc

### Checklist

Delete any items that are not applicable to this PR.

- [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
maxcold authored Dec 27, 2023
1 parent 4f9dc10 commit 460ef86
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
sendGetPackagePolicies,
} from '../../../../../hooks';
import {
ExperimentalFeaturesService,
getCloudShellUrlFromPackagePolicy,
isVerificationError,
packageToPackagePolicy,
Expand All @@ -47,6 +48,8 @@ import { useOnSaveNavigate } from '../../hooks';
import { prepareInputPackagePolicyDataset } from '../../services/prepare_input_pkg_policy_dataset';
import { getCloudFormationPropsFromPackagePolicy } from '../../../../../services';

import { AGENTLESS_POLICY_ID } from './setup_technology';

async function createAgentPolicy({
packagePolicy,
newAgentPolicy,
Expand Down Expand Up @@ -106,7 +109,7 @@ export function useOnSubmit({
queryParamsPolicyId: string | undefined;
integrationToEnable?: string;
}) {
const { notifications } = useStartServices();
const { notifications, cloud } = useStartServices();
const confirmForceInstall = useConfirmForceInstall();
// only used to store the resulting package policy once saved
const [savedPackagePolicy, setSavedPackagePolicy] = useState<PackagePolicy>();
Expand All @@ -129,6 +132,9 @@ export function useOnSubmit({
const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false);
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;

const isServerless = cloud?.isServerlessEnabled ?? false;
const { agentless: isAgentlessEnabled } = ExperimentalFeaturesService.get();

// Update agent policy method
const updateAgentPolicy = useCallback(
(updatedAgentPolicy: AgentPolicy | undefined) => {
Expand Down Expand Up @@ -298,12 +304,17 @@ export function useOnSubmit({
}
}

const agentPolicyIdToSave = createdPolicy?.id ?? packagePolicy.policy_id;
const shouldForceInstallOnAgentless =
agentPolicyIdToSave === AGENTLESS_POLICY_ID && isServerless && isAgentlessEnabled;
const forceInstall = force || shouldForceInstallOnAgentless;

setFormState('LOADING');
// passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
const { error, data } = await savePackagePolicy({
...packagePolicy,
policy_id: createdPolicy?.id ?? packagePolicy.policy_id,
force,
policy_id: agentPolicyIdToSave,
force: forceInstall,
});

const hasAzureArmTemplate = data?.item
Expand Down Expand Up @@ -373,9 +384,11 @@ export function useOnSubmit({
} else {
if (isVerificationError(error)) {
setFormState('VALID'); // don't show the add agent modal
const forceInstall = await confirmForceInstall(packagePolicy.package!);
const forceInstallUnverifiedIntegration = await confirmForceInstall(
packagePolicy.package!
);

if (forceInstall) {
if (forceInstallUnverifiedIntegration) {
// skip creating the agent policy because it will have already been successfully created
onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true });
}
Expand All @@ -393,14 +406,16 @@ export function useOnSubmit({
agentCount,
selectedPolicyTab,
packagePolicy,
isServerless,
isAgentlessEnabled,
withSysMonitoring,
newAgentPolicy,
updatePackagePolicy,
packageInfo,
notifications.toasts,
agentPolicy,
onSaveNavigate,
confirmForceInstall,
newAgentPolicy,
updatePackagePolicy,
withSysMonitoring,
packageInfo,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { SetupTechnology } from '../../../../../types';
import { sendGetOneAgentPolicy, useStartServices } from '../../../../../hooks';
import { SelectedPolicyTab } from '../../components';

const AGENTLESS_POLICY_ID = 'agentless';
export const AGENTLESS_POLICY_ID = 'agentless';

export function useSetupTechnology({
updateNewAgentPolicy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import { createFleetTestRendererMock } from '../../../../../../mock';
import { FLEET_ROUTING_PATHS, pagePathGetters, PLUGIN_ID } from '../../../../constants';
import type { CreatePackagePolicyRouteState } from '../../../../types';

import { ExperimentalFeaturesService } from '../../../../../../services';

import {
sendCreatePackagePolicy,
sendCreateAgentPolicy,
sendGetAgentStatus,
sendGetOneAgentPolicy,
useIntraAppState,
useStartServices,
useGetAgentPolicies,
useGetPackageInfoByKeyQuery,
} from '../../../../hooks';

Expand Down Expand Up @@ -78,6 +82,9 @@ jest.mock('../../../../hooks', () => {
},
setBreadcrumbs: jest.fn(),
},
cloud: {
isServerlessEnabled: false,
},
}),
};
});
Expand All @@ -95,6 +102,7 @@ jest.mock('react-router-dom', () => ({
}));

import { CreatePackagePolicySinglePage } from '.';
import { AGENTLESS_POLICY_ID } from './hooks/setup_technology';

// mock console.debug to prevent noisy logs from console.debugs in ./index.tsx
let consoleDebugMock: any;
Expand Down Expand Up @@ -290,6 +298,7 @@ describe('when on the package policy create page', () => {
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({
...newPackagePolicy,
policy_id: 'agent-policy-1',
force: false,
});
expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled();

Expand Down Expand Up @@ -441,6 +450,7 @@ describe('when on the package policy create page', () => {
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({
...newPackagePolicy,
policy_id: 'agent-policy-2',
force: false,
});

await waitFor(() => {
Expand Down Expand Up @@ -503,6 +513,7 @@ describe('when on the package policy create page', () => {
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({
...newPackagePolicy,
policy_id: 'agent-policy-1',
force: false,
});

await waitFor(() => {
Expand Down Expand Up @@ -573,10 +584,82 @@ describe('when on the package policy create page', () => {
],
},
],
force: false,
});
});
});
});

describe('with agentless policy available', () => {
beforeEach(async () => {
(sendGetOneAgentPolicy as jest.MockedFunction<any>).mockResolvedValue({
data: { item: { id: AGENTLESS_POLICY_ID, name: 'Agentless CSPM', namespace: 'default' } },
});
(useGetAgentPolicies as jest.MockedFunction<any>).mockReturnValue({
data: {
items: [{ id: AGENTLESS_POLICY_ID, name: 'Agentless CSPM', namespace: 'default' }],
},
error: undefined,
isLoading: false,
resendRequest: jest.fn(),
});

await act(async () => {
render();
});
});

test('should not force create package policy when not in serverless', async () => {
await act(async () => {
fireEvent.click(renderResult.getByText('Existing hosts')!);
});

await act(async () => {
fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!);
});

expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled();
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({
...newPackagePolicy,
force: false,
policy_id: AGENTLESS_POLICY_ID,
});

await waitFor(() => {
expect(renderResult.getByText('Nginx integration added')).toBeInTheDocument();
});
});

test('should force create package policy', async () => {
(useStartServices as jest.MockedFunction<any>).mockReturnValue({
...useStartServices(),
cloud: {
...useStartServices().cloud,
isServerlessEnabled: true,
},
});
jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ agentless: true });

await act(async () => {
fireEvent.click(renderResult.getByText('Existing hosts')!);
});

await act(async () => {
fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!);
});

expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled();
expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({
...newPackagePolicy,
force: true,
policy_id: AGENTLESS_POLICY_ID,
});

await waitFor(() => {
expect(renderResult.getByText('Nginx integration added')).toBeInTheDocument();
});
});
});
});
});

Expand Down

0 comments on commit 460ef86

Please sign in to comment.