[Cloud Security] limit setup options for Agentless CSPM (#172562)

## Summary

Follow up after 
- https://github.com/elastic/kibana/pull/171671

Closes
- https://github.com/elastic/security-team/issues/7969

Includes:
- limiting setup options for agentless to only Direct Access Keys and
Temporary Keys
- covering Agentless for edit flow

### How to test

Make sure to have the FF in your `serverless.security.dev.yml` (it's
similar to the `kibana.dev.yml` but specifically for Serverless Security
Projects) enabled. Also specify some serverless project id, to enable
the logic of `isServerlessEnabled`
```
xpack.fleet.enableExperimental: ['agentless']
xpack.cloud.serverless.project_id: 'some_fake_project_id'
``` 

The follow the steps from this comment
https://github.com/elastic/security-team/issues/7972#issuecomment-1808096052
to have the Agentless artifacts (agent, policy, output, and fleet server
host) locally

After that, you should be able to test the flow. 

### Screencast

[screencast-mail.google.com-2023.12.08-16_37_35.webm](b94b685f-ed37-4e45-9907-bbd95cb8975a)


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maxim Kholod 2023-12-14 17:27:57 +01:00 committed by GitHub
parent 5067645daa
commit 73d0a46a39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 654 additions and 233 deletions

View file

@ -4,18 +4,16 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import {
EuiFieldText,
EuiFieldPassword,
EuiCallOut,
EuiFormRow,
EuiHorizontalRule,
EuiLink,
EuiSelect,
EuiSpacer,
EuiText,
EuiTitle,
EuiSelect,
EuiCallOut,
EuiHorizontalRule,
} from '@elastic/eui';
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { NewPackagePolicyInput, PackageInfo } from '@kbn/fleet-plugin/common';
@ -23,25 +21,24 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import {
AwsCredentialsTypeOptions,
getAwsCredentialsFormManualOptions,
AwsOptions,
DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE,
} from './get_aws_credentials_form_options';
import { RadioGroup } from '../csp_boxed_radio_group';
import {
getCspmCloudFormationDefaultValue,
getPosturePolicy,
NewPackagePolicyPostureInput,
} from '../utils';
import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group';
import { getPosturePolicy, NewPackagePolicyPostureInput } from '../utils';
import { SetupFormat, useAwsCredentialsForm } from './hooks';
import { AWS_ORGANIZATION_ACCOUNT } from '../policy_template_form';
import { AwsCredentialsType } from '../../../../common/types_old';
import { AwsInputVarFields } from './aws_input_var_fields';
import {
AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ,
AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ,
} from '../../test_subjects';
interface AWSSetupInfoContentProps {
integrationLink: string;
info: ReactNode;
}
const AWSSetupInfoContent = ({ integrationLink }: AWSSetupInfoContentProps) => {
export const AWSSetupInfoContent = ({ info }: AWSSetupInfoContentProps) => {
return (
<>
<EuiHorizontalRule margin="xl" />
@ -55,48 +52,28 @@ const AWSSetupInfoContent = ({ integrationLink }: AWSSetupInfoContentProps) => {
</EuiTitle>
<EuiSpacer size="l" />
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContent"
defaultMessage="Utilize AWS CloudFormation (a built-in AWS tool) or a series of manual steps to set up and deploy CSPM for assessing your AWS environment's security posture. Refer to our {gettingStartedLink} guide for details."
values={{
gettingStartedLink: (
<EuiLink href={integrationLink} target="_blank">
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContentLink"
defaultMessage="Getting Started"
/>
</EuiLink>
),
}}
/>
{info}
</EuiText>
</>
);
};
const getSetupFormatOptions = (): Array<{ id: SetupFormat; label: string }> => [
const getSetupFormatOptions = (): CspRadioOption[] => [
{
id: 'cloud_formation',
label: 'CloudFormation',
testId: AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.CLOUDFORMATION,
},
{
id: 'manual',
label: i18n.translate('xpack.csp.awsIntegration.setupFormatOptions.manual', {
defaultMessage: 'Manual',
}),
testId: AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.MANUAL,
},
];
export const getDefaultAwsVarsGroup = (packageInfo: PackageInfo): AwsCredentialsType => {
const hasCloudFormationTemplate = !!getCspmCloudFormationDefaultValue(packageInfo);
if (hasCloudFormationTemplate) {
return 'cloud_formation';
}
return DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE;
};
interface Props {
export interface AwsFormProps {
newPolicy: NewPackagePolicy;
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_aws' }>;
updatePolicy(updatedPolicy: NewPackagePolicy): void;
@ -217,7 +194,7 @@ export const AwsCredentialsForm = ({
onChange,
setIsValid,
disabled,
}: Props) => {
}: AwsFormProps) => {
const {
awsCredentialsType,
setupFormat,
@ -237,7 +214,24 @@ export const AwsCredentialsForm = ({
return (
<>
<AWSSetupInfoContent integrationLink={integrationLink} />
<AWSSetupInfoContent
info={
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContent"
defaultMessage="Utilize AWS CloudFormation (a built-in AWS tool) or a series of manual steps to set up and deploy CSPM for assessing your AWS environment's security posture. Refer to our {gettingStartedLink} guide for details."
values={{
gettingStartedLink: (
<EuiLink href={integrationLink} target="_blank">
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContentLink"
defaultMessage="Getting Started"
/>
</EuiLink>
),
}}
/>
}
/>
<EuiSpacer size="l" />
<RadioGroup
disabled={disabled}
@ -255,6 +249,10 @@ export const AwsCredentialsForm = ({
{setupFormat === 'manual' && (
<>
<AwsCredentialTypeSelector
label={i18n.translate('xpack.csp.awsIntegration.awsCredentialTypeSelectorLabel', {
defaultMessage: 'Preferred manual method',
})}
options={getAwsCredentialsFormManualOptions()}
type={awsCredentialsType}
onChange={(optionId) => {
updatePolicy(
@ -281,60 +279,26 @@ export const AwsCredentialsForm = ({
</>
);
};
const AwsCredentialTypeSelector = ({
export const AwsCredentialTypeSelector = ({
type,
onChange,
label,
options,
}: {
onChange(type: AwsCredentialsType): void;
type: AwsCredentialsType;
label: string;
options: AwsCredentialsTypeOptions;
}) => (
<EuiFormRow
fullWidth
label={i18n.translate('xpack.csp.awsIntegration.awsCredentialTypeSelectorLabel', {
defaultMessage: 'Preferred manual method',
})}
>
<EuiFormRow fullWidth label={label}>
<EuiSelect
fullWidth
options={getAwsCredentialsFormManualOptions()}
options={options}
value={type}
onChange={(optionElem) => {
onChange(optionElem.target.value as AwsCredentialsType);
}}
data-test-subj={AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ}
/>
</EuiFormRow>
);
const AwsInputVarFields = ({
fields,
onChange,
}: {
fields: Array<AwsOptions[keyof AwsOptions]['fields'][number] & { value: string; id: string }>;
onChange: (key: string, value: string) => void;
}) => (
<div>
{fields.map((field) => (
<EuiFormRow key={field.id} label={field.label} fullWidth hasChildLabel={true} id={field.id}>
<>
{field.type === 'password' && (
<EuiFieldPassword
id={field.id}
type="dual"
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
{field.type === 'text' && (
<EuiFieldText
id={field.id}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
</>
</EuiFormRow>
))}
</div>
);

View file

@ -0,0 +1,83 @@
/*
* 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 React from 'react';
import { EuiLink, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { cspIntegrationDocsNavigation } from '../../../common/navigation/constants';
import {
DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE,
getAwsCredentialsFormAgentlessOptions,
getAwsCredentialsFormOptions,
getInputVarsFields,
} from './get_aws_credentials_form_options';
import { getAwsCredentialsType, getPosturePolicy } from '../utils';
import { AwsInputVarFields } from './aws_input_var_fields';
import {
AwsFormProps,
ReadDocumentation,
AWSSetupInfoContent,
AwsCredentialTypeSelector,
} from './aws_credentials_form';
export const AwsCredentialsFormAgentless = ({ input, newPolicy, updatePolicy }: AwsFormProps) => {
const awsCredentialsType = getAwsCredentialsType(input) || DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE;
const options = getAwsCredentialsFormOptions();
const group = options[awsCredentialsType];
const fields = getInputVarsFields(input, group.fields);
const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath;
return (
<>
<AWSSetupInfoContent
info={
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContentAgentless"
defaultMessage="Utilize AWS Access Keys to set up and deploy CSPM for assessing your AWS environment's security posture. Refer to our {gettingStartedLink} guide for details."
values={{
gettingStartedLink: (
<EuiLink href={integrationLink} target="_blank">
<FormattedMessage
id="xpack.csp.awsIntegration.gettingStarted.setupInfoContentLink"
defaultMessage="Getting Started"
/>
</EuiLink>
),
}}
/>
}
/>
<EuiSpacer size="l" />
<AwsCredentialTypeSelector
label={i18n.translate('xpack.csp.awsIntegration.awsCredentialTypeSelectorLabelAgentless', {
defaultMessage: 'Preferred method',
})}
type={awsCredentialsType}
options={getAwsCredentialsFormAgentlessOptions()}
onChange={(optionId) => {
updatePolicy(
getPosturePolicy(newPolicy, input.type, {
'aws.credentials.type': { value: optionId },
})
);
}}
/>
<EuiSpacer size="m" />
{group.info}
<EuiSpacer size="m" />
<ReadDocumentation url={integrationLink} />
<EuiSpacer size="l" />
<AwsInputVarFields
fields={fields}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));
}}
/>
</>
);
};

View file

@ -0,0 +1,44 @@
/*
* 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 React from 'react';
import { EuiFieldPassword, EuiFieldText, EuiFormRow } from '@elastic/eui';
import { AwsOptions } from './get_aws_credentials_form_options';
export const AwsInputVarFields = ({
fields,
onChange,
}: {
fields: Array<AwsOptions[keyof AwsOptions]['fields'][number] & { value: string; id: string }>;
onChange: (key: string, value: string) => void;
}) => (
<div>
{fields.map((field) => (
<EuiFormRow key={field.id} label={field.label} fullWidth hasChildLabel={true} id={field.id}>
<>
{field.type === 'password' && (
<EuiFieldPassword
id={field.id}
type="dual"
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
{field.type === 'text' && (
<EuiFieldText
id={field.id}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
</>
</EuiFormRow>
))}
</div>
);

View file

@ -92,20 +92,33 @@ export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AwsCred
});
export type AwsOptions = Record<AwsCredentialsType, AwsOptionValue>;
export const getAwsCredentialsFormManualOptions = (): Array<{
export type AwsCredentialsTypeOptions = Array<{
value: AwsCredentialsType;
text: string;
}> => {
}>;
const getAwsCredentialsTypeSelectorOptions = (
filterFn: ({ value }: { value: AwsCredentialsType }) => boolean
): AwsCredentialsTypeOptions => {
return Object.entries(getAwsCredentialsFormOptions())
.map(([key, value]) => ({
value: key as AwsCredentialsType,
text: value.label,
}))
.filter(({ value }) => value !== 'cloud_formation');
.filter(filterFn);
};
export const getAwsCredentialsFormManualOptions = (): AwsCredentialsTypeOptions =>
getAwsCredentialsTypeSelectorOptions(({ value }) => value !== 'cloud_formation');
export const getAwsCredentialsFormAgentlessOptions = (): AwsCredentialsTypeOptions =>
getAwsCredentialsTypeSelectorOptions(
({ value }) => value === 'direct_access_keys' || value === 'temporary_keys'
);
export const DEFAULT_AWS_CREDENTIALS_TYPE = 'cloud_formation';
export const DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE = 'assume_role';
export const DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE = 'direct_access_keys';
export const getAwsCredentialsFormOptions = (): AwsOptions => ({
assume_role: {

View file

@ -12,6 +12,7 @@ import {
getCspmCloudFormationDefaultValue,
getPosturePolicy,
NewPackagePolicyPostureInput,
getAwsCredentialsType,
} from '../utils';
import {
DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE,
@ -43,10 +44,6 @@ const getSetupFormatFromInput = (
return 'cloud_formation';
};
const getAwsCredentialsType = (
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_aws' }>
): AwsCredentialsType | undefined => input.streams[0].vars?.['aws.credentials.type'].value;
export const useAwsCredentialsForm = ({
newPolicy,
input,

View file

@ -5,22 +5,14 @@
* 2.0.
*/
import React from 'react';
import {
EuiFieldText,
EuiFieldPassword,
EuiFormRow,
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
EuiHorizontalRule,
} from '@elastic/eui';
import { EuiLink, EuiSpacer, EuiText, EuiTitle, EuiHorizontalRule } from '@elastic/eui';
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { RadioGroup } from './csp_boxed_radio_group';
import { getPosturePolicy, NewPackagePolicyPostureInput } from './utils';
import { AwsInputVarFields } from './aws_credentials_form/aws_input_var_fields';
const AWSSetupInfoContent = () => (
<>
@ -279,37 +271,3 @@ const AwsCredentialTypeSelector = ({
onChange={(id) => onChange(id as AwsCredentialsType)}
/>
);
const AwsInputVarFields = ({
fields,
onChange,
}: {
fields: Array<AwsOptions[keyof AwsOptions]['fields'][number] & { value: string; id: string }>;
onChange: (key: string, value: string) => void;
}) => (
<div>
{fields.map((field) => (
<EuiFormRow key={field.id} label={field.label} fullWidth hasChildLabel={true} id={field.id}>
<>
{field.type === 'password' && (
<EuiFieldPassword
id={field.id}
type="dual"
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
{field.type === 'text' && (
<EuiFieldText
id={field.id}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
)}
</>
</EuiFormRow>
))}
</div>
);

View file

@ -31,7 +31,7 @@ import {
SETUP_ACCESS_CLOUD_SHELL,
SETUP_ACCESS_MANUAL,
} from '../../../common/constants';
import { RadioGroup } from './csp_boxed_radio_group';
import { CspRadioOption, RadioGroup } from './csp_boxed_radio_group';
import {
getCspmCloudShellDefaultValue,
getPosturePolicy,
@ -231,12 +231,7 @@ export const gcpField: GcpInputFields = {
},
};
const getSetupFormatOptions = (): Array<{
id: SetupFormatGCP;
label: string;
disabled: boolean;
testId: string;
}> => [
const getSetupFormatOptions = (): CspRadioOption[] => [
{
id: SETUP_ACCESS_CLOUD_SHELL,
label: i18n.translate('xpack.csp.gcpIntegration.setupFormatOptions.googleCloudShell', {

View file

@ -6,7 +6,7 @@
*/
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import type { PackageInfo } from '@kbn/fleet-plugin/common';
import { createNewPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks';
import { createNewPackagePolicyMock, createAgentPolicyMock } from '@kbn/fleet-plugin/common/mocks';
import {
CLOUDBEAT_GCP,
CLOUDBEAT_AZURE,
@ -24,6 +24,9 @@ export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA, 'kspm', '
export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS, 'kspm', 'eks');
export const getMockPolicyVulnMgmtAWS = () =>
getPolicyMock(CLOUDBEAT_VULN_MGMT_AWS, 'vuln_mgmt', 'aws');
export const getMockAgentlessAgentPolicy = () => {
return createAgentPolicyMock({ id: 'agentless' });
};
export const getMockPackageInfoVulnMgmtAWS = () => {
return {

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import React from 'react';
import { render } from '@testing-library/react';
import { render, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
CspPolicyTemplateForm,
AWS_ORGANIZATION_ACCOUNT,
@ -15,6 +16,7 @@ import {
} from './policy_template_form';
import { TestProvider } from '../../test/test_provider';
import {
getMockAgentlessAgentPolicy,
getMockPackageInfoCspmAWS,
getMockPackageInfoCspmAzure,
getMockPackageInfoCspmGCP,
@ -32,7 +34,6 @@ import type {
PackageInfo,
PackagePolicy,
} from '@kbn/fleet-plugin/common';
import userEvent from '@testing-library/user-event';
import { getPosturePolicy } from './utils';
import {
CLOUDBEAT_AWS,
@ -45,6 +46,13 @@ import { createReactQueryResponse } from '../../test/fixtures/react_query';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import { usePackagePolicyList } from '../../common/api/use_package_policy_list';
import { CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS } from './gcp_credential_form';
import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
import {
AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ,
AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ,
} from '../test_subjects';
// mock useParams
jest.mock('react-router-dom', () => ({
@ -94,12 +102,14 @@ describe('<CspPolicyTemplateForm />', () => {
edit = false,
agentPolicy,
packageInfo = {} as PackageInfo,
agentlessPolicy,
}: {
edit?: boolean;
newPolicy: NewPackagePolicy;
agentPolicy?: AgentPolicy;
packageInfo?: PackageInfo;
onChange?: jest.Mock<void, [NewPackagePolicy]>;
agentlessPolicy?: AgentPolicy;
}) => (
<TestProvider>
{edit && (
@ -110,6 +120,7 @@ describe('<CspPolicyTemplateForm />', () => {
packageInfo={packageInfo}
isEditPage={true}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
{!edit && (
@ -119,6 +130,7 @@ describe('<CspPolicyTemplateForm />', () => {
packageInfo={packageInfo}
isEditPage={false}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
</TestProvider>
@ -141,7 +153,7 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it('renders and updates name field', () => {
it('renders and updates name field', async () => {
const policy = getMockPolicyK8s();
const { getByLabelText } = render(<WrappedComponent newPolicy={policy} />);
const name = getByLabelText('Name');
@ -149,15 +161,15 @@ describe('<CspPolicyTemplateForm />', () => {
userEvent.type(name, '1');
// Listen to the 2nd triggered by the test.
// The 1st is done on mount to ensure initial state is valid.
expect(onChange).toHaveBeenNthCalledWith(2, {
isValid: true,
updatedPolicy: { ...policy, name: `${policy.name}1` },
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: { ...policy, name: `${policy.name}1` },
});
});
});
it('renders and updates description field', () => {
it('renders and updates description field', async () => {
const policy = getMockPolicyK8s();
const { getByLabelText } = render(<WrappedComponent newPolicy={policy} />);
const description = getByLabelText('Description');
@ -165,11 +177,11 @@ describe('<CspPolicyTemplateForm />', () => {
userEvent.type(description, '1');
// Listen to the 2nd triggered by the test.
// The 1st is done on mount to ensure initial state is valid.
expect(onChange).toHaveBeenNthCalledWith(2, {
isValid: true,
updatedPolicy: { ...policy, description: `${policy.description}1` },
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: { ...policy, description: `${policy.description}1` },
});
});
});
@ -186,7 +198,7 @@ describe('<CspPolicyTemplateForm />', () => {
expect(option1).toBeChecked();
});
it('updates selected KSPM input', () => {
it('updates selected KSPM input', async () => {
const k8sPolicy = getMockPolicyK8s();
const eksPolicy = getMockPolicyEKS();
@ -194,11 +206,11 @@ describe('<CspPolicyTemplateForm />', () => {
const option = getByLabelText('EKS');
userEvent.click(option);
// Listen to the 2nd triggered by the test.
// The 1st is done on mount to ensure initial state is valid.
expect(onChange).toHaveBeenNthCalledWith(2, {
isValid: true,
updatedPolicy: eksPolicy,
await waitFor(() => {
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: eksPolicy,
});
});
});
@ -1436,6 +1448,119 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
describe('Agentless', () => {
it('should render setup technology selector for AWS and allow to select agent-based', async () => {
const agentlessPolicy = getMockAgentlessAgentPolicy();
const newPackagePolicy = getMockPolicyAWS();
const { getByTestId, getByRole } = render(
<WrappedComponent newPolicy={newPackagePolicy} agentlessPolicy={agentlessPolicy} />
);
const setupTechnologySelectorAccordion = getByTestId(
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ
);
const setupTechnologySelector = getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ);
const awsCredentialsTypeSelector = getByTestId(AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ);
const options: HTMLOptionElement[] = within(awsCredentialsTypeSelector).getAllByRole(
'option'
);
const optionValues = options.map((option) => option.value);
// default state
expect(setupTechnologySelectorAccordion).toBeInTheDocument();
expect(setupTechnologySelector).toBeInTheDocument();
expect(setupTechnologySelector).toHaveTextContent(/agentless/i);
expect(options).toHaveLength(2);
expect(optionValues).toEqual(
expect.arrayContaining(['direct_access_keys', 'temporary_keys'])
);
// select agent-based and check for cloudformation option
userEvent.click(setupTechnologySelector);
const agentBasedOption = getByRole('option', { name: /agent-based/i });
await waitForEuiPopoverOpen();
userEvent.click(agentBasedOption);
await waitFor(() => {
expect(
getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.CLOUDFORMATION)
).toBeInTheDocument();
expect(getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.MANUAL)).toBeInTheDocument();
});
});
it('should not render setup technology selector for KSPM', () => {
const agentlessPolicy = getMockAgentlessAgentPolicy();
const newPackagePolicy = getMockPolicyEKS();
const { queryByTestId } = render(
<WrappedComponent newPolicy={newPackagePolicy} agentlessPolicy={agentlessPolicy} />
);
const setupTechnologySelectorAccordion = queryByTestId(
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ
);
expect(setupTechnologySelectorAccordion).not.toBeInTheDocument();
});
it('should not render setup technology selector for CNVM', () => {
const agentlessPolicy = getMockAgentlessAgentPolicy();
const newPackagePolicy = getMockPolicyVulnMgmtAWS();
const { queryByTestId } = render(
<WrappedComponent newPolicy={newPackagePolicy} agentlessPolicy={agentlessPolicy} />
);
const setupTechnologySelectorAccordion = queryByTestId(
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ
);
expect(setupTechnologySelectorAccordion).not.toBeInTheDocument();
});
it('should not render setup technology selector for CSPM GCP', () => {
const agentlessPolicy = getMockAgentlessAgentPolicy();
const newPackagePolicy = getMockPolicyGCP();
const { queryByTestId } = render(
<WrappedComponent
newPolicy={newPackagePolicy}
packageInfo={getMockPackageInfoCspmGCP()}
agentlessPolicy={agentlessPolicy}
/>
);
const setupTechnologySelectorAccordion = queryByTestId(
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ
);
expect(setupTechnologySelectorAccordion).not.toBeInTheDocument();
});
it('should not render setup technology selector for CSPM Azure', () => {
const agentlessPolicy = getMockAgentlessAgentPolicy();
let newPackagePolicy = getMockPolicyAzure();
newPackagePolicy = getPosturePolicy(newPackagePolicy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_certificate' },
});
const { queryByTestId } = render(
<WrappedComponent
newPolicy={newPackagePolicy}
packageInfo={getMockPackageInfoCspmAzure('1.7.0')}
agentlessPolicy={agentlessPolicy}
/>
);
const setupTechnologySelectorAccordion = queryByTestId(
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ
);
expect(setupTechnologySelectorAccordion).not.toBeInTheDocument();
});
});
it(`renders Service principal with Client Certificate fields`, () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {

View file

@ -21,6 +21,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { SetupTechnology } from '@kbn/fleet-plugin/public';
import { FormattedMessage } from '@kbn/i18n-react';
import type {
NewPackagePolicyInput,
@ -544,7 +545,11 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
agentPolicy,
agentlessPolicy,
handleSetupTechnologyChange,
isEditPage,
});
const shouldRenderAgentlessSelector =
(!isEditPage && isAgentlessAvailable) ||
(isEditPage && setupTechnology === SetupTechnology.AGENTLESS);
const updatePolicy = useCallback(
(updatedPolicy: NewPackagePolicy) => {
@ -558,11 +563,11 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
*/
const setEnabledPolicyInput = useCallback(
(inputType: PostureInput) => {
const inputVars = getPostureInputHiddenVars(inputType, packageInfo);
const inputVars = getPostureInputHiddenVars(inputType, packageInfo, setupTechnology);
const policy = getPosturePolicy(newPolicy, inputType, inputVars);
updatePolicy(policy);
},
[newPolicy, updatePolicy, packageInfo]
[setupTechnology, packageInfo, newPolicy, updatePolicy]
);
// search for non null fields of the validation?.vars object
@ -601,6 +606,15 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading, input.policy_template, isEditPage]);
useEffect(() => {
if (isEditPage) {
return;
}
setEnabledPolicyInput(input.type);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setupTechnology]);
useEnsureDefaultNamespace({ newPolicy, input, updatePolicy });
useCloudFormationTemplate({
@ -739,8 +753,9 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
onChange={(field, value) => updatePolicy({ ...newPolicy, [field]: value })}
/>
{isAgentlessAvailable && (
{shouldRenderAgentlessSelector && (
<SetupTechnologySelector
disabled={isEditPage}
setupTechnology={setupTechnology}
onSetupTechnologyChange={setSetupTechnology}
/>
@ -755,6 +770,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
onChange={onChange}
setIsValid={setIsValid}
disabled={isEditPage}
setupTechnology={setupTechnology}
/>
<EuiSpacer />
</>

View file

@ -8,6 +8,7 @@ import React from 'react';
import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { NewPackagePolicy, PackageInfo } from '@kbn/fleet-plugin/common';
import { SetupTechnology } from '@kbn/fleet-plugin/public';
import { PackagePolicyReplaceDefineStepExtensionComponentProps } from '@kbn/fleet-plugin/public/types';
import {
CSPM_POLICY_TEMPLATE,
@ -20,6 +21,7 @@ import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from
import { RadioGroup } from './csp_boxed_radio_group';
import { AzureCredentialsForm } from './azure_credentials_form/azure_credentials_form';
import { AwsCredentialsForm } from './aws_credentials_form/aws_credentials_form';
import { AwsCredentialsFormAgentless } from './aws_credentials_form/aws_credentials_form_agentless';
import { EksCredentialsForm } from './eks_credentials_form';
import { GcpCredentialsForm } from './gcp_credential_form';
@ -74,11 +76,20 @@ interface PolicyTemplateVarsFormProps {
onChange: PackagePolicyReplaceDefineStepExtensionComponentProps['onChange'];
setIsValid: (isValid: boolean) => void;
disabled: boolean;
setupTechnology: SetupTechnology;
}
export const PolicyTemplateVarsForm = ({ input, ...props }: PolicyTemplateVarsFormProps) => {
export const PolicyTemplateVarsForm = ({
input,
setupTechnology,
...props
}: PolicyTemplateVarsFormProps) => {
switch (input.type) {
case 'cloudbeat/cis_aws':
if (setupTechnology === SetupTechnology.AGENTLESS) {
return <AwsCredentialsFormAgentless {...props} input={input} />;
}
return <AwsCredentialsForm {...props} input={input} />;
case 'cloudbeat/cis_eks':
return <EksCredentialsForm {...props} input={input} />;

View file

@ -18,11 +18,17 @@ import {
EuiText,
useGeneratedHtmlId,
} from '@elastic/eui';
import {
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ,
} from '../../test_subjects';
export const SetupTechnologySelector = ({
disabled,
setupTechnology,
onSetupTechnologyChange,
}: {
disabled: boolean;
setupTechnology: SetupTechnology;
onSetupTechnologyChange: (value: SetupTechnology) => void;
}) => {
@ -87,15 +93,18 @@ export const SetupTechnologySelector = ({
<>
<EuiSpacer size="l" />
<EuiAccordion
isDisabled={disabled}
initialIsOpen={disabled}
id={useGeneratedHtmlId({ prefix: 'setup-type' })}
buttonContent={
<EuiLink>
<EuiLink disabled={disabled}>
<FormattedMessage
id="xpack.csp.fleetIntegration.setupTechnology.advancedOptionsLabel"
defaultMessage="Advanced options"
/>
</EuiLink>
}
data-test-subj={SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ}
>
<EuiSpacer size="l" />
<EuiFormRow
@ -108,6 +117,7 @@ export const SetupTechnologySelector = ({
}
>
<EuiSuperSelect
disabled={disabled}
options={options}
valueOfSelected={setupTechnology}
placeholder={
@ -120,6 +130,7 @@ export const SetupTechnologySelector = ({
itemLayoutAlign="top"
hasDividers
fullWidth
data-test-subj={SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ}
/>
</EuiFormRow>
</EuiAccordion>

View file

@ -14,46 +14,122 @@ import { CLOUDBEAT_AWS } from '../../../../common/constants';
import { useSetupTechnology } from './use_setup_technology';
describe('useSetupTechnology', () => {
it('initializes with AGENT_BASED technology', () => {
const { result } = renderHook(() =>
useSetupTechnology({
input: { type: 'cloudbeat/no-agentless-support' } as NewPackagePolicyInput,
})
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
describe('create page flow', () => {
const isEditPage = false;
it('sets to AGENTLESS when agentless is available', () => {
const agentlessPolicy = { id: 'agentlessPolicyId' } as AgentPolicy;
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const { result } = renderHook(() => useSetupTechnology({ input, agentlessPolicy }));
expect(result.current.isAgentlessAvailable).toBeTruthy();
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS);
});
it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId', () => {
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const agentPolicy = { id: 'agentPolicyId' } as AgentPolicy;
const agentlessPolicy = { id: 'agentlessPolicyId' } as AgentPolicy;
const { result } = renderHook(() =>
useSetupTechnology({ input, agentPolicy, agentlessPolicy })
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
it('calls handleSetupTechnologyChange when setupTechnology changes', () => {
const handleSetupTechnologyChangeMock = jest.fn();
const { result } = renderHook(() =>
useSetupTechnology({
input: { type: 'someType' } as NewPackagePolicyInput,
handleSetupTechnologyChange: handleSetupTechnologyChangeMock,
})
);
act(() => {
result.current.setSetupTechnology(SetupTechnology.AGENTLESS);
it('initializes with AGENT_BASED technology', () => {
const { result } = renderHook(() =>
useSetupTechnology({
input: { type: 'cloudbeat/no-agentless-support' } as NewPackagePolicyInput,
isEditPage,
})
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
expect(handleSetupTechnologyChangeMock).toHaveBeenCalledWith(SetupTechnology.AGENTLESS);
it('sets to AGENTLESS when agentless is available', () => {
const agentlessPolicy = { id: 'agentlessPolicyId' } as AgentPolicy;
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const { result } = renderHook(() =>
useSetupTechnology({ input, agentlessPolicy, isEditPage })
);
expect(result.current.isAgentlessAvailable).toBeTruthy();
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS);
});
it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId', () => {
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const agentPolicy = { id: 'agentPolicyId' } as AgentPolicy;
const agentlessPolicy = { id: 'agentlessPolicyId' } as AgentPolicy;
const { result } = renderHook(() =>
useSetupTechnology({ input, agentPolicy, agentlessPolicy, isEditPage })
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
it('calls handleSetupTechnologyChange when setupTechnology changes', () => {
const handleSetupTechnologyChangeMock = jest.fn();
const { result } = renderHook(() =>
useSetupTechnology({
input: { type: 'someType' } as NewPackagePolicyInput,
handleSetupTechnologyChange: handleSetupTechnologyChangeMock,
isEditPage,
})
);
act(() => {
result.current.setSetupTechnology(SetupTechnology.AGENTLESS);
});
expect(handleSetupTechnologyChangeMock).toHaveBeenCalledWith(SetupTechnology.AGENTLESS);
});
});
describe('edit page flow', () => {
const isEditPage = true;
it('initializes with AGENT_BASED technology', () => {
const { result } = renderHook(() =>
useSetupTechnology({
input: { type: 'cloudbeat/no-agentless-support' } as NewPackagePolicyInput,
isEditPage,
})
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
it('initializes with AGENTLESS technology if the agent policy id is "agentless"', () => {
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const agentPolicy = { id: 'agentless' } as AgentPolicy;
const { result } = renderHook(() =>
useSetupTechnology({
input,
agentPolicy,
isEditPage,
})
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS);
});
it('should not call handleSetupTechnologyChange when setupTechnology changes', () => {
const handleSetupTechnologyChangeMock = jest.fn();
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const { result } = renderHook(() =>
useSetupTechnology({
input,
handleSetupTechnologyChange: handleSetupTechnologyChangeMock,
isEditPage,
})
);
act(() => {
result.current.setSetupTechnology(SetupTechnology.AGENTLESS);
});
expect(handleSetupTechnologyChangeMock).not.toHaveBeenCalled();
});
it('should not update setupTechnology when agentlessPolicyId becomes available', () => {
const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput;
const agentlessPolicy = { id: 'agentlessPolicyId' } as AgentPolicy;
const { result, rerender } = renderHook(() =>
useSetupTechnology({
input,
isEditPage,
})
);
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
act(() => {
rerender({
input,
agentlessPolicy,
isEditPage,
});
});
expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED);
});
});
});

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { AgentPolicy, NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { SetupTechnology } from '@kbn/fleet-plugin/public';
@ -15,24 +15,31 @@ export const useSetupTechnology = ({
agentPolicy,
agentlessPolicy,
handleSetupTechnologyChange,
isEditPage,
}: {
input: NewPackagePolicyInput;
agentPolicy?: AgentPolicy;
agentlessPolicy?: AgentPolicy;
handleSetupTechnologyChange?: (value: SetupTechnology) => void;
isEditPage: boolean;
}) => {
const [setupTechnology, setSetupTechnology] = useState<SetupTechnology>(
SetupTechnology.AGENT_BASED
);
const isCspmAws = input.type === CLOUDBEAT_AWS;
const isAgentlessAvailable = useMemo(
() => Boolean(isCspmAws && agentlessPolicy),
[isCspmAws, agentlessPolicy]
);
const agentPolicyId = useMemo(() => agentPolicy?.id, [agentPolicy]);
const agentlessPolicyId = useMemo(() => agentlessPolicy?.id, [agentlessPolicy]);
const isAgentlessAvailable = Boolean(isCspmAws && agentlessPolicy);
const agentPolicyId = agentPolicy?.id;
const agentlessPolicyId = agentlessPolicy?.id;
const [setupTechnology, setSetupTechnology] = useState<SetupTechnology>(() => {
if (isEditPage && agentPolicyId === SetupTechnology.AGENTLESS) {
return SetupTechnology.AGENTLESS;
}
return SetupTechnology.AGENT_BASED;
});
useEffect(() => {
if (isEditPage) {
return;
}
if (agentPolicyId && agentPolicyId !== agentlessPolicyId) {
/*
handle case when agent policy is coming from outside,
@ -41,20 +48,24 @@ export const useSetupTechnology = ({
setSetupTechnology(SetupTechnology.AGENT_BASED);
} else if (isAgentlessAvailable) {
/*
preselecting agenteless when available
preselecting agentless when available
and resetting to agent-based when switching to another integration type, which doesn't support agentless
*/
setSetupTechnology(SetupTechnology.AGENTLESS);
} else {
setSetupTechnology(SetupTechnology.AGENT_BASED);
}
}, [agentPolicyId, agentlessPolicyId, isAgentlessAvailable]);
}, [agentPolicyId, agentlessPolicyId, isAgentlessAvailable, isEditPage]);
useEffect(() => {
if (isEditPage) {
return;
}
if (handleSetupTechnologyChange) {
handleSetupTechnologyChange(setupTechnology);
}
}, [handleSetupTechnologyChange, setupTechnology]);
}, [handleSetupTechnologyChange, isEditPage, setupTechnology]);
return {
isAgentlessAvailable,

View file

@ -4,6 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PackageInfo } from '@kbn/fleet-plugin/common';
import { SetupTechnology } from '@kbn/fleet-plugin/public';
import {
getMaxPackageName,
@ -11,9 +13,9 @@ import {
getPosturePolicy,
getCspmCloudShellDefaultValue,
isBelowMinVersion,
getDefaultAwsCredentialsType,
} from './utils';
import { getMockPolicyAWS, getMockPolicyK8s, getMockPolicyEKS } from './mocks';
import type { PackageInfo } from '@kbn/fleet-plugin/common';
describe('getPosturePolicy', () => {
for (const [name, getPolicy, expectedVars] of [
@ -22,7 +24,11 @@ describe('getPosturePolicy', () => {
['cloudbeat/cis_k8s', getMockPolicyK8s, null],
] as const) {
it(`updates package policy with hidden vars for ${name}`, () => {
const inputVars = getPostureInputHiddenVars(name, {} as any);
const inputVars = getPostureInputHiddenVars(
name,
{} as PackageInfo,
SetupTechnology.AGENT_BASED
);
const policy = getPosturePolicy(getPolicy(), name, inputVars);
const enabledInputs = policy.inputs.filter(
@ -280,3 +286,67 @@ describe('isBelowMinVersion', () => {
}
});
});
describe('getDefaultAwsCredentialsType', () => {
let packageInfo: PackageInfo;
beforeEach(() => {
packageInfo = {
policy_templates: [
{
name: 'cspm',
inputs: [
{
vars: [
{
name: 'cloud_formation_template',
default: 'http://example.com/cloud_formation_template',
},
],
},
],
},
],
} as PackageInfo;
});
it('should return "direct_access_key" for agentless', () => {
const setupTechnology = SetupTechnology.AGENTLESS;
const result = getDefaultAwsCredentialsType(packageInfo, setupTechnology);
expect(result).toBe('direct_access_keys');
});
it('should return "assume_role" for agent-based, when cloudformation is not available', () => {
const setupTechnology = SetupTechnology.AGENT_BASED;
packageInfo = {
policy_templates: [
{
name: 'cspm',
inputs: [
{
vars: [
{
name: 'cloud_shell',
default: 'http://example.com/cloud_shell',
},
],
},
],
},
],
} as PackageInfo;
const result = getDefaultAwsCredentialsType({} as PackageInfo, setupTechnology);
expect(result).toBe('assume_role');
});
it('should return "cloud_formation" for agent-based, when cloudformation is available', () => {
const setupTechnology = SetupTechnology.AGENT_BASED;
const result = getDefaultAwsCredentialsType(packageInfo, setupTechnology);
expect(result).toBe('cloud_formation');
});
});

View file

@ -12,27 +12,36 @@ import type {
RegistryPolicyTemplate,
RegistryVarsEntry,
} from '@kbn/fleet-plugin/common';
import { SetupTechnology } from '@kbn/fleet-plugin/public';
import merge from 'lodash/merge';
import semverValid from 'semver/functions/valid';
import semverCoerce from 'semver/functions/coerce';
import semverLt from 'semver/functions/lt';
import {
CLOUDBEAT_AWS,
CLOUDBEAT_EKS,
CLOUDBEAT_VANILLA,
CLOUDBEAT_GCP,
CLOUDBEAT_AZURE,
CLOUDBEAT_EKS,
CLOUDBEAT_GCP,
CLOUDBEAT_VANILLA,
CLOUDBEAT_VULN_MGMT_AWS,
SUPPORTED_POLICY_TEMPLATES,
SUPPORTED_CLOUDBEAT_INPUTS,
CSPM_POLICY_TEMPLATE,
KSPM_POLICY_TEMPLATE,
SUPPORTED_CLOUDBEAT_INPUTS,
SUPPORTED_POLICY_TEMPLATES,
VULN_MGMT_POLICY_TEMPLATE,
} from '../../../common/constants';
import { getDefaultAwsVarsGroup } from './aws_credentials_form/aws_credentials_form';
import type { PostureInput, CloudSecurityPolicyTemplate } from '../../../common/types_old';
import type {
AwsCredentialsType,
PostureInput,
CloudSecurityPolicyTemplate,
} from '../../../common/types_old';
import { cloudPostureIntegrations } from '../../common/constants';
import { DEFAULT_EKS_VARS_GROUP } from './eks_credentials_form';
import {
DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE,
DEFAULT_AWS_CREDENTIALS_TYPE,
DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE,
} from './aws_credentials_form/get_aws_credentials_form_options';
// Posture policies only support the default namespace
export const POSTURE_NAMESPACE = 'default';
@ -203,14 +212,36 @@ export const getArmTemplateUrlFromCspmPackage = (packageInfo: PackageInfo): stri
return armTemplateUrl;
};
export const getDefaultAwsCredentialsType = (
packageInfo: PackageInfo,
setupTechnology?: SetupTechnology
): AwsCredentialsType => {
if (setupTechnology && setupTechnology === SetupTechnology.AGENTLESS) {
return DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE;
}
const hasCloudFormationTemplate = !!getCspmCloudFormationDefaultValue(packageInfo);
if (hasCloudFormationTemplate) {
return DEFAULT_AWS_CREDENTIALS_TYPE;
}
return DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE;
};
/**
* Input vars that are hidden from the user
*/
export const getPostureInputHiddenVars = (inputType: PostureInput, packageInfo: PackageInfo) => {
export const getPostureInputHiddenVars = (
inputType: PostureInput,
packageInfo: PackageInfo,
setupTechnology: SetupTechnology
) => {
switch (inputType) {
case 'cloudbeat/cis_aws':
return {
'aws.credentials.type': { value: getDefaultAwsVarsGroup(packageInfo), type: 'text' },
'aws.credentials.type': {
value: getDefaultAwsCredentialsType(packageInfo, setupTechnology),
type: 'text',
},
};
case 'cloudbeat/cis_eks':
return { 'aws.credentials.type': { value: DEFAULT_EKS_VARS_GROUP, type: 'text' } };
@ -267,6 +298,10 @@ export const getCspmCloudShellDefaultValue = (packageInfo: PackageInfo): string
return cloudShellUrl;
};
export const getAwsCredentialsType = (
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_aws' }>
): AwsCredentialsType | undefined => input.streams[0].vars?.['aws.credentials.type'].value;
export const isBelowMinVersion = (version: string, minVersion: string) => {
const semanticVersion = semverValid(version);
const versionNumberOnly = semverCoerce(semanticVersion) || '';

View file

@ -43,3 +43,12 @@ export const CREATE_RULE_ACTION_SUBJ = 'csp:create_rule';
export const CSP_GROUPING = 'cloudSecurityGrouping';
export const CSP_GROUPING_LOADING = 'cloudSecurityGroupingLoading';
export const CSP_FINDINGS_COMPLIANCE_SCORE = 'cloudSecurityFindingsComplianceScore';
export const AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ = 'aws-credentials-type-selector';
export const AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ = {
CLOUDFORMATION: 'aws-cloudformation-setup-option',
MANUAL: 'aws-manual-setup-option',
};
export const SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ = 'setup-technology-selector-accordion';
export const SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ = 'setup-technology-selector';