mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
5067645daa
commit
73d0a46a39
17 changed files with 654 additions and 233 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 } }));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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 />
|
||||
</>
|
||||
|
|
|
@ -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} />;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) || '';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue