From fc506b85eac30b39d6b439369b806e8e1621ef70 Mon Sep 17 00:00:00 2001 From: seanrathier Date: Tue, 28 Jan 2025 01:18:50 -0500 Subject: [PATCH] [Cloud Security] Fleet validation using the RequiredVars and CSPM showing validation errors (#207130) (cherry picked from commit bec72f00ac2ccb3ee72661cdebbcc180b07f78f8) # Conflicts: # x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts --- .../services/validate_package_policy.test.ts | 335 +++++++++++++++++- .../services/validate_package_policy.ts | 79 ++++- .../shared/fleet/common/types/models/epm.ts | 15 + .../single_page_layout/hooks/form.tsx | 3 +- .../aws_credentials_form.tsx | 3 + .../aws_credentials_form_agentless.tsx | 15 +- .../aws_input_var_fields.tsx | 127 ++++--- .../aws_credentials_form/hooks.ts | 22 +- .../azure_credentials_form.tsx | 134 ++++--- .../azure_credentials_form_agentless.tsx | 2 + .../gcp_credential_form.tsx | 115 +++++- .../gcp_credentials_form_agentless.tsx | 2 + .../fleet_extensions/policy_template_form.tsx | 3 + .../policy_template_selectors.tsx | 1 + .../components/fleet_extensions/utils.ts | 13 + .../agentless/create_agent.ts | 16 +- .../agentless/security_posture.ts | 8 +- .../constants.ts | 8 + .../cspm/cis_integration_azure.ts | 15 +- .../pages/cis_integrations/cspm/constants.ts | 6 + .../agentless_api/create_agent.ts | 3 + 21 files changed, 789 insertions(+), 136 deletions(-) create mode 100644 x-pack/test/cloud_security_posture_functional/constants.ts create mode 100644 x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/constants.ts diff --git a/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.test.ts b/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.test.ts index 90e20dbae362a..db490c43140ee 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.test.ts @@ -8,7 +8,12 @@ import { safeLoad } from 'js-yaml'; import { installationStatuses } from '../constants'; -import type { PackageInfo, NewPackagePolicy, RegistryPolicyTemplate } from '../types'; +import type { + PackageInfo, + NewPackagePolicy, + RegistryPolicyTemplate, + NewPackagePolicyInputStream, +} from '../types'; import { validatePackagePolicy, @@ -775,6 +780,334 @@ describe('Fleet - validatePackagePolicy()', () => { }); }); +describe('Fleet - validateConditionalRequiredVars()', () => { + const createMockRequiredVarPackageInfo = (streams: unknown) => { + return { + name: 'mock-package', + title: 'Mock package', + version: '0.0.0', + description: 'description', + type: 'mock', + categories: [], + requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } }, + format_version: '', + download: '', + path: '', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + 'index-pattern': [], + }, + }, + status: installationStatuses.NotInstalled, + data_streams: [ + { + dataset: 'foo', + streams, + }, + { + dataset: 'bar', + streams: [ + { + input: 'bar', + title: 'Bar', + vars: [ + { name: 'bar-name', type: 'text', required: true }, + { name: 'bar-age', type: 'text' }, + ], + }, + { + input: 'with-no-stream-vars', + title: 'Bar stream no vars', + enabled: true, + }, + ], + }, + ], + policy_templates: [ + { + name: 'pkgPolicy1', + title: 'Package policy 1', + description: 'test package policy', + inputs: [ + { + type: 'foo', + title: 'Foo', + vars: [ + { default: 'foo-input-var-value', name: 'foo-input-var-name', type: 'text' }, + { + default: 'foo-input2-var-value', + name: 'foo-input2-var-name', + required: true, + type: 'text', + }, + { name: 'foo-input3-var-name', type: 'text', required: true, multi: true }, + ], + }, + { + type: 'bar', + title: 'Bar', + vars: [ + { + default: ['value1', 'value2'], + name: 'bar-input-var-name', + type: 'text', + multi: true, + }, + { name: 'bar-input2-var-name', required: true, type: 'text' }, + ], + }, + { + type: 'with-no-config-or-streams', + title: 'With no config or streams', + }, + { + type: 'with-disabled-streams', + title: 'With disabled streams', + }, + { + type: 'with-no-stream-vars', + enabled: true, + vars: [{ required: true, name: 'var-name', type: 'text' }], + }, + ], + }, + ], + } as unknown as PackageInfo; + }; + + const createPackagePolicyForRequiredVars = (streams: NewPackagePolicyInputStream[]) => { + return { + name: 'pkgPolicy1-1', + namespace: 'default', + policy_id: 'test-policy', + policy_ids: ['test-policy'], + enabled: true, + inputs: [ + { + type: 'foo-input', + policy_template: 'pkgPolicy1', + enabled: true, + streams, + }, + ], + vars: {}, + }; + }; + + it('should return package policy validation error if invalid required_vars exist', () => { + const mockPackageInfoRequireVars = createMockRequiredVarPackageInfo([ + { + title: 'Foo', + input: 'foo-input', + vars: [ + { name: 'foo-name', type: 'text' }, + { name: 'foo-age', type: 'text' }, + ], + required_vars: { + 'foo-required-var-name': [{ name: 'foo-name' }, { name: 'foo-age', value: '1' }], + }, + }, + ]); + const invalidPackagePolicyWithRequiredVars = createPackagePolicyForRequiredVars([ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { 'foo-name': { type: 'text' }, 'foo-age': { type: 'text' } }, + }, + ]); + + const validationResults = validatePackagePolicy( + invalidPackagePolicyWithRequiredVars, + mockPackageInfoRequireVars, + load + ); + + expect(validationResults).toEqual( + expect.objectContaining({ + inputs: { + 'foo-input': { + streams: { + foo: { + required_vars: { + 'foo-required-var-name': [ + { name: 'foo-name', invalid: true }, + { name: 'foo-age', invalid: true }, + ], + }, + vars: { + 'foo-name': null, + 'foo-age': null, + }, + }, + }, + }, + }, + }) + ); + + expect(validationHasErrors(validationResults)).toBe(true); + }); + + it('should return package policy validation error if partial invalid required_vars exist', () => { + const mockPackageInfoRequireVars = createMockRequiredVarPackageInfo([ + { + title: 'Foo', + input: 'foo-input', + vars: [ + { name: 'foo-name', type: 'text' }, + { name: 'foo-age', type: 'text' }, + ], + required_vars: { + 'foo-required-var-name': [{ name: 'foo-name' }, { name: 'foo-age', value: '1' }], + }, + }, + ]); + const invalidPackagePolicyWithRequiredVars = createPackagePolicyForRequiredVars([ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { 'foo-name': { type: 'text' }, 'foo-age': { type: 'text', value: '1' } }, + }, + ]); + + const validationResults = validatePackagePolicy( + invalidPackagePolicyWithRequiredVars, + mockPackageInfoRequireVars, + load + ); + + expect(validationResults).toEqual( + expect.objectContaining({ + inputs: { + 'foo-input': { + streams: { + foo: { + required_vars: { + 'foo-required-var-name': [{ name: 'foo-name', invalid: true }], + }, + vars: { + 'foo-name': null, + 'foo-age': null, + }, + }, + }, + }, + }, + }) + ); + + expect(validationHasErrors(validationResults)).toBe(true); + }); + + it('should not return package policy validation errors if required_vars have existence and a value', () => { + const mockPackageInfoRequireVars = createMockRequiredVarPackageInfo([ + { + title: 'Foo', + input: 'foo-input', + vars: [ + { name: 'foo-name', type: 'text' }, + { name: 'foo-age', type: 'text' }, + ], + required_vars: { + 'foo-required-var-name': [{ name: 'foo-name' }, { name: 'foo-age', value: '1' }], + }, + }, + ]); + const invalidPackagePolicyWithRequiredVars = createPackagePolicyForRequiredVars([ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { + 'foo-name': { type: 'text', value: 'Some name' }, + 'foo-age': { type: 'text', value: '1' }, + }, + }, + ]); + + const validationResults = validatePackagePolicy( + invalidPackagePolicyWithRequiredVars, + mockPackageInfoRequireVars, + load + ); + + expect(validationResults).toEqual( + expect.objectContaining({ + inputs: { + 'foo-input': { + streams: { + foo: { + vars: { + 'foo-name': null, + 'foo-age': null, + }, + }, + }, + }, + }, + }) + ); + + expect(validationHasErrors(validationResults)).toBe(false); + }); + + it('should not return package policy validation errors if required_vars all have values', () => { + const mockPackageInfoRequireVars = createMockRequiredVarPackageInfo([ + { + title: 'Foo', + input: 'foo-input', + vars: [ + { name: 'foo-name', type: 'text' }, + { name: 'foo-age', type: 'text' }, + ], + required_vars: { + 'foo-required-var-name': [ + { name: 'foo-name', value: 'Some name' }, + { name: 'foo-age', value: '1' }, + ], + }, + }, + ]); + const invalidPackagePolicyWithRequiredVars = createPackagePolicyForRequiredVars([ + { + data_stream: { dataset: 'foo', type: 'logs' }, + enabled: true, + vars: { + 'foo-name': { type: 'text', value: 'Some name' }, + 'foo-age': { type: 'text', value: '1' }, + }, + }, + ]); + + const validationResults = validatePackagePolicy( + invalidPackagePolicyWithRequiredVars, + mockPackageInfoRequireVars, + load + ); + + expect(validationResults).toEqual( + expect.objectContaining({ + inputs: { + 'foo-input': { + streams: { + foo: { + vars: { + 'foo-name': null, + 'foo-age': null, + }, + }, + }, + }, + }, + }) + ); + + expect(validationHasErrors(validationResults)).toBe(false); + }); +}); + describe('Fleet - validationHasErrors()', () => { it('returns true for stream validation results with errors', () => { expect( diff --git a/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.ts b/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.ts index 69a26e6747771..d56900633a874 100644 --- a/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.ts +++ b/x-pack/platform/plugins/shared/fleet/common/services/validate_package_policy.ts @@ -17,6 +17,8 @@ import type { PackageInfo, RegistryStream, RegistryVarsEntry, + RegistryRequiredVars, + NewPackagePolicyInputStream, } from '../types'; import { DATASET_VAR_NAME } from '../constants'; @@ -33,8 +35,14 @@ import { isValidDataset } from './is_valid_namespace'; type Errors = string[] | null; type ValidationEntry = Record; +interface ValidationRequiredVarsEntry { + name: string; + invalid: boolean; +} +type ValidationRequiredVars = Record; export interface PackagePolicyConfigValidationResults { + required_vars?: ValidationRequiredVars | null; vars?: ValidationEntry; } @@ -49,6 +57,58 @@ export type PackagePolicyValidationResults = { inputs: Record | null; } & PackagePolicyConfigValidationResults; +const validatePackageRequiredVars = ( + stream: NewPackagePolicyInputStream, + requiredVars?: RegistryRequiredVars +) => { + const evaluatedRequiredVars: ValidationRequiredVars = {}; + + if (!requiredVars || !stream.vars || !stream.enabled) { + return null; + } + + let hasMetRequiredCriteria = false; + + for (const [requiredVarDefinitionName, requiredVarDefinitionConstraints] of Object.entries( + requiredVars + )) { + evaluatedRequiredVars[requiredVarDefinitionName] = + requiredVarDefinitionConstraints?.map((constraint) => { + return { + name: constraint.name, + invalid: true, + }; + }) || []; + + if (evaluatedRequiredVars[requiredVarDefinitionName]) { + requiredVarDefinitionConstraints.forEach((requiredCondition) => { + if (stream.vars && stream.vars[requiredCondition.name]) { + const varItem = stream.vars[requiredCondition.name]; + + if (varItem) { + if ( + (!requiredCondition.value && varItem.value) || + (requiredCondition.value && + varItem.value && + requiredCondition.value === varItem.value) + ) { + evaluatedRequiredVars[requiredVarDefinitionName] = evaluatedRequiredVars[ + requiredVarDefinitionName + ].filter((item) => item.name !== requiredCondition.name); + } + } + } + }); + } + + if (evaluatedRequiredVars[requiredVarDefinitionName]?.length === 0) { + hasMetRequiredCriteria = true; + } + } + + return hasMetRequiredCriteria ? null : evaluatedRequiredVars; +}; + /* * Returns validation information for a given package policy and package info * Note: this method assumes that `packagePolicy` is correctly structured for the given package @@ -89,6 +149,7 @@ export const validatePackagePolicy = ( // Validate package-level vars const packageVarsByName = keyBy(packageInfo.vars || [], 'name'); const packageVars = Object.entries(packagePolicy.vars || {}); + if (packageVars.length) { validationResults.vars = packageVars.reduce((results, [name, varEntry]) => { results[name] = validatePackagePolicyConfig( @@ -138,6 +199,13 @@ export const validatePackagePolicy = ( return varDefs; }, {}); + const streamRequiredVarsDefsByDataAndInput = Object.entries(streamsByDatasetAndInput).reduce< + Record + >((reqVarDefs, [path, stream]) => { + reqVarDefs[path] = stream.required_vars; + return reqVarDefs; + }, {}); + // Validate each package policy input with either its own var fields and stream vars packagePolicy.inputs.forEach((input) => { if (!input.vars && !input.streams) { @@ -174,7 +242,6 @@ export const validatePackagePolicy = ( const streamVarDefs = streamVarDefsByDatasetAndInput[`${stream.data_stream.dataset}-${input.type}`]; - if (streamVarDefs && Object.keys(streamVarDefs).length) { streamValidationResults.vars = Object.keys(streamVarDefs).reduce((results, name) => { const configEntry = stream?.vars?.[name]; @@ -194,6 +261,16 @@ export const validatePackagePolicy = ( }, {} as ValidationEntry); } + if (stream.vars && stream.enabled) { + const requiredVars = validatePackageRequiredVars( + stream, + streamRequiredVarsDefsByDataAndInput[`${stream.data_stream.dataset}-${input.type}`] + ); + if (requiredVars) { + streamValidationResults.required_vars = requiredVars; + } + } + inputValidationResults.streams![stream.data_stream.dataset] = streamValidationResults; }); } else { diff --git a/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts b/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts index 3661d8b8ed7d1..8bfb264165b3b 100644 --- a/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts +++ b/x-pack/platform/plugins/shared/fleet/common/types/models/epm.ts @@ -229,6 +229,7 @@ export enum RegistryPolicyTemplateKeys { readme = 'readme', multiple = 'multiple', type = 'type', + required_vars = 'required_vars', vars = 'vars', input = 'input', template_path = 'template_path', @@ -261,6 +262,7 @@ export interface RegistryPolicyInputOnlyTemplate extends BaseTemplate { [RegistryPolicyTemplateKeys.type]: string; [RegistryPolicyTemplateKeys.input]: string; [RegistryPolicyTemplateKeys.template_path]: string; + [RegistryPolicyTemplateKeys.required_vars]?: RegistryRequiredVars; [RegistryPolicyTemplateKeys.vars]?: RegistryVarsEntry[]; } @@ -275,6 +277,7 @@ export enum RegistryInputKeys { template_path = 'template_path', condition = 'condition', input_group = 'input_group', + required_vars = 'required_vars', vars = 'vars', } @@ -287,6 +290,7 @@ export interface RegistryInput { [RegistryInputKeys.template_path]?: string; [RegistryInputKeys.condition]?: string; [RegistryInputKeys.input_group]?: RegistryInputGroup; + [RegistryInputKeys.required_vars]?: RegistryRequiredVars; [RegistryInputKeys.vars]?: RegistryVarsEntry[]; } @@ -295,6 +299,7 @@ export enum RegistryStreamKeys { title = 'title', description = 'description', enabled = 'enabled', + required_vars = 'required_vars', vars = 'vars', template_path = 'template_path', } @@ -304,6 +309,7 @@ export interface RegistryStream { [RegistryStreamKeys.title]: string; [RegistryStreamKeys.description]?: string; [RegistryStreamKeys.enabled]?: boolean; + [RegistryStreamKeys.required_vars]?: RegistryRequiredVars; [RegistryStreamKeys.vars]?: RegistryVarsEntry[]; [RegistryStreamKeys.template_path]: string; } @@ -452,6 +458,15 @@ export interface RegistryDataStreamLifecycle { data_retention: string; } +export interface RegistryRequireVarConstraint { + name: string; + value?: string; +} + +export interface RegistryRequiredVars { + [key: string]: RegistryRequireVarConstraint[]; +} + export type RegistryVarType = | 'integer' | 'bool' diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 152b03f0eccb3..22806ee39cf2f 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -188,10 +188,11 @@ export function useOnSubmit({ // Validation state const [validationResults, setValidationResults] = useState(); const [hasAgentPolicyError, setHasAgentPolicyError] = useState(false); - const hasErrors = validationResults ? validationHasErrors(validationResults) : false; const { isAgentlessIntegration, isAgentlessAgentPolicy } = useAgentless(); + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + // Update agent policy method const updateAgentPolicies = useCallback( (updatedAgentPolicies: AgentPolicy[]) => { diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx index 42c775fad3006..4ef1783639536 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form.tsx @@ -97,6 +97,7 @@ export interface AwsFormProps { onChange: any; setIsValid: (isValid: boolean) => void; disabled: boolean; + hasInvalidRequiredVars: boolean; } const CloudFormationSetup = ({ @@ -210,6 +211,7 @@ export const AwsCredentialsForm = ({ onChange, setIsValid, disabled, + hasInvalidRequiredVars, }: AwsFormProps) => { const { awsCredentialsType, @@ -289,6 +291,7 @@ export const AwsCredentialsForm = ({ onChange={(key, value) => { updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } })); }} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> )} diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx index 55262020db928..5e452d99fb257 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx @@ -37,7 +37,6 @@ import { AwsCredentialTypeSelector, ReadDocumentation, } from './aws_credentials_form'; - const CLOUD_FORMATION_EXTERNAL_DOC_URL = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-howdoesitwork.html'; @@ -179,6 +178,7 @@ export const AwsCredentialsFormAgentless = ({ newPolicy, packageInfo, updatePolicy, + hasInvalidRequiredVars, }: AwsFormProps) => { const awsCredentialsType = getAwsCredentialsType(input) || DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE; const options = getAwsCredentialsFormOptions(); @@ -187,6 +187,18 @@ export const AwsCredentialsFormAgentless = ({ const documentationLink = cspIntegrationDocsNavigation.cspm.awsGetStartedPath; const accountType = input?.streams?.[0].vars?.['aws.account_type']?.value ?? SINGLE_ACCOUNT; + // This should ony set the credentials after the initial render + if (!getAwsCredentialsType(input)) { + updatePolicy({ + ...getPosturePolicy(newPolicy, input.type, { + 'aws.credentials.type': { + value: awsCredentialsType, + type: 'text', + }, + }), + }); + } + const isValidSemantic = semverValid(packageInfo.version); const showCloudCredentialsButton = isValidSemantic ? semverCompare(packageInfo.version, CLOUD_CREDENTIALS_PACKAGE_VERSION) >= 0 @@ -282,6 +294,7 @@ export const AwsCredentialsFormAgentless = ({ onChange={(key, value) => { updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } })); }} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_input_var_fields.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_input_var_fields.tsx index f4c7fd1b8d501..2a5fadae583e0 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_input_var_fields.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_input_var_fields.tsx @@ -10,13 +10,15 @@ import { EuiFieldText, EuiFormRow, EuiSpacer, EuiLoadingSpinner } from '@elastic import { PackageInfo } from '@kbn/fleet-plugin/common'; import { css } from '@emotion/react'; import { LazyPackagePolicyInputVarField } from '@kbn/fleet-plugin/public'; +import { i18n } from '@kbn/i18n'; import { AwsOptions } from './get_aws_credentials_form_options'; -import { findVariableDef } from '../utils'; +import { findVariableDef, fieldIsInvalid } from '../utils'; export const AwsInputVarFields = ({ fields, onChange, packageInfo, + hasInvalidRequiredVars = false, }: { fields: Array< AwsOptions[keyof AwsOptions]['fields'][number] & { @@ -27,66 +29,79 @@ export const AwsInputVarFields = ({ >; onChange: (key: string, value: string) => void; packageInfo: PackageInfo; + hasInvalidRequiredVars?: boolean; }) => { return (
- {fields.map((field, index) => ( -
- {field.type === 'password' && field.isSecret === true && ( - <> - -
{ + const invalid = fieldIsInvalid(field.value, hasInvalidRequiredVars); + const invalidError = i18n.translate('xpack.csp.cspmIntegration.integration.fieldRequired', { + defaultMessage: '{field} is required', + values: { + field: field.label, + }, + }); + return ( +
+ {field.type === 'password' && field.isSecret === true && ( + <> + +
- }> - { - onChange(field.id, value); - }} - errors={[]} - forceShowErrors={false} - isEditPage={true} - data-test-subj={field.dataTestSubj} - /> - -
- - - )} - {field.type === 'text' && ( - - + }> + { + onChange(field.id, value); + }} + errors={invalid ? [invalidError] : []} + forceShowErrors={invalid} + isEditPage={true} + data-test-subj={field.dataTestSubj} + /> + +
+ + + )} + {field.type === 'text' && ( + onChange(field.id, event.target.value)} - data-test-subj={field.dataTestSubj} - /> - - )} -
- ))} + hasChildLabel={true} + id={field.id} + > + onChange(field.id, event.target.value)} + data-test-subj={field.dataTestSubj} + /> + + )} +
+ ); + })}
); }; diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts index 0e562c17b552a..b0d44ea2a5a67 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/hooks.ts @@ -60,19 +60,35 @@ export const useAwsCredentialsForm = ({ }) => { // We only have a value for 'aws.credentials.type' once the form has mounted. // On initial render we don't have that value, so we fall back to the default option. - const awsCredentialsType: AwsCredentialsType = - getAwsCredentialsType(input) || DEFAULT_MANUAL_AWS_CREDENTIALS_TYPE; const options = getAwsCredentialsFormOptions(); const hasCloudFormationTemplate = !!getCspmCloudFormationDefaultValue(packageInfo); const setupFormat = getSetupFormatFromInput(input, hasCloudFormationTemplate); + const lastManualCredentialsType = useRef(undefined); + + // Assumes if the credentials type is not set, the default is CloudFormation + const awsCredentialsType: AwsCredentialsType = + getAwsCredentialsType(input) || AWS_SETUP_FORMAT.CLOUD_FORMATION; const group = options[awsCredentialsType]; const fields = getInputVarsFields(input, group.fields); const fieldsSnapshot = useRef({}); - const lastManualCredentialsType = useRef(undefined); + + useEffect(() => { + // This should ony set the credentials after the initial render + if (!getAwsCredentialsType(input) && !lastManualCredentialsType.current) { + onChange({ + updatedPolicy: getPosturePolicy(newPolicy, input.type, { + 'aws.credentials.type': { + value: awsCredentialsType, + type: 'text', + }, + }), + }); + } + }, [awsCredentialsType, input, newPolicy, onChange]); useEffect(() => { const isInvalid = diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx index 25f7be8c8f4ee..558a23b77fdc0 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx @@ -32,7 +32,12 @@ import { } from './get_azure_credentials_form_options'; import { AzureCredentialsType } from '../../../../common/types_old'; import { useAzureCredentialsForm } from './hooks'; -import { findVariableDef, getPosturePolicy, NewPackagePolicyPostureInput } from '../utils'; +import { + fieldIsInvalid, + findVariableDef, + getPosturePolicy, + NewPackagePolicyPostureInput, +} from '../utils'; import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group'; import { CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS } from '../../test_subjects'; import { AZURE_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ } from '../../test_subjects'; @@ -114,6 +119,7 @@ export interface AzureCredentialsFormProps { onChange: any; setIsValid: (isValid: boolean) => void; disabled: boolean; + hasInvalidRequiredVars: boolean; } export const ARM_TEMPLATE_EXTERNAL_DOC_URL = @@ -272,71 +278,85 @@ export const AzureInputVarFields = ({ fields, packageInfo, onChange, + hasInvalidRequiredVars, }: { fields: Array; packageInfo: PackageInfo; onChange: (key: string, value: string) => void; + hasInvalidRequiredVars: boolean; }) => { return (
- {fields.map((field, index) => ( -
- {field.type === 'password' && field.isSecret === true && ( - <> - -
{ + const invalid = fieldIsInvalid(field.value, hasInvalidRequiredVars); + const invalidError = i18n.translate('xpack.csp.cspmIntegration.integration.fieldRequired', { + defaultMessage: '{field} is required', + values: { + field: field.label, + }, + }); + return ( +
+ {field.type === 'password' && field.isSecret === true && ( + <> + +
- }> - + }> + { + onChange(field.id, value); + }} + errors={invalid ? [invalidError] : []} + forceShowErrors={invalid} + isEditPage={true} + /> + +
+ + )} + {field.type === 'text' && ( + <> + + { - onChange(field.id, value); - }} - errors={[]} - forceShowErrors={false} - isEditPage={true} + onChange={(event) => onChange(field.id, event.target.value)} + data-test-subj={field.testSubj} + isInvalid={invalid} /> - -
- - )} - {field.type === 'text' && ( - <> - - onChange(field.id, event.target.value)} - data-test-subj={field.testSubj} - /> - - - - )} -
- ))} + + + + )} +
+ ); + })}
); }; @@ -349,6 +369,7 @@ export const AzureCredentialsForm = ({ onChange, setIsValid, disabled, + hasInvalidRequiredVars, }: AzureCredentialsFormProps) => { const { group, @@ -447,6 +468,7 @@ export const AzureCredentialsForm = ({ onChange={(key, value) => { updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } })); }} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> {group.info} diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx index d8a88e5754864..4d625e4088de3 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form_agentless.tsx @@ -30,6 +30,7 @@ export const AzureCredentialsFormAgentless = ({ newPolicy, updatePolicy, packageInfo, + hasInvalidRequiredVars, }: AzureCredentialsFormProps) => { const documentationLink = cspIntegrationDocsNavigation.cspm.azureGetStartedPath; const options = getAzureCredentialsFormOptions(); @@ -46,6 +47,7 @@ export const AzureCredentialsFormAgentless = ({ onChange={(key, value) => { updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } })); }} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx index 7d6d42c70e767..e0e0690cf7edf 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credential_form.tsx @@ -31,6 +31,7 @@ import { GcpCredentialsType } from '../../../../common/types_old'; import { CLOUDBEAT_GCP } from '../../../../common/constants'; import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group'; import { + fieldIsInvalid, findVariableDef, getCspmCloudShellDefaultValue, getPosturePolicy, @@ -114,18 +115,28 @@ const GoogleCloudShellSetup = ({ onChange, input, disabled, + hasInvalidRequiredVars, }: { fields: Array; onChange: (key: string, value: string) => void; input: NewPackagePolicyInput; disabled: boolean; + hasInvalidRequiredVars: boolean; }) => { const accountType = input.streams?.[0]?.vars?.['gcp.account_type']?.value; const getFieldById = (id: keyof GcpInputFields['fields']) => { return fields.find((element) => element.id === id); }; const projectIdFields = getFieldById('gcp.project_id'); + const projectIdValueInvalid = fieldIsInvalid(projectIdFields?.value, hasInvalidRequiredVars); + const projectIdError = `${projectIdFields?.label} is required`; + const organizationIdFields = getFieldById('gcp.organization_id'); + const organizationIdValueInvalid = fieldIsInvalid( + organizationIdFields?.value, + hasInvalidRequiredVars + ); + const organizationIdError = `${organizationIdFields?.label} is required`; return ( <> {organizationIdFields && accountType === GCP_ORGANIZATION_ACCOUNT && ( - + onChange(organizationIdFields.id, event.target.value)} + isInvalid={organizationIdValueInvalid} /> )} {projectIdFields && ( - + onChange(projectIdFields.id, event.target.value)} + isInvalid={projectIdValueInvalid} /> )} @@ -298,6 +321,7 @@ export interface GcpFormProps { onChange: any; disabled: boolean; isEditPage?: boolean; + hasInvalidRequiredVars: boolean; } export const getInputVarsFields = (input: NewPackagePolicyInput, fields: GcpFields) => @@ -403,6 +427,7 @@ export const GcpCredentialsForm = ({ onChange, disabled, isEditPage, + hasInvalidRequiredVars, }: GcpFormProps) => { /* Create a subset of properties from GcpField to use for hiding value of credentials json and credentials file when user switch from Manual to Cloud Shell, we wanna keep Project and Organization ID */ const subsetOfGcpField = (({ ['gcp.credentials.file']: a, ['gcp.credentials.json']: b }) => ({ @@ -516,6 +541,7 @@ export const GcpCredentialsForm = ({ updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } })) } input={input} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> ) : ( )} @@ -544,6 +571,7 @@ export const GcpInputVarFields = ({ disabled, packageInfo, isEditPage, + hasInvalidRequiredVars, }: { fields: Array; onChange: (key: string, value: string) => void; @@ -551,17 +579,67 @@ export const GcpInputVarFields = ({ disabled: boolean; packageInfo: PackageInfo; isEditPage?: boolean; + hasInvalidRequiredVars: boolean; }) => { const getFieldById = (id: keyof GcpInputFields['fields']) => { return fields.find((element) => element.id === id); }; const organizationIdFields = getFieldById('gcp.organization_id'); + const organizationIdValueInvalid = fieldIsInvalid( + organizationIdFields?.value, + hasInvalidRequiredVars + ); + const organizationIdError = i18n.translate( + 'xpack.csp.cspmIntegration.integration.fieldRequired', + { + defaultMessage: '{field} is required', + values: { + field: organizationIdFields?.label, + }, + } + ); const projectIdFields = getFieldById('gcp.project_id'); + const projectIdValueInvalid = fieldIsInvalid(projectIdFields?.value, hasInvalidRequiredVars); + const projectIdError = i18n.translate('xpack.csp.cspmIntegration.integration.fieldRequired', { + defaultMessage: '{field} is required', + values: { + field: projectIdFields?.label, + }, + }); + const credentialsTypeFields = getFieldById('gcp.credentials.type'); + const credentialFilesFields = getFieldById('gcp.credentials.file'); + const credentialFilesFieldsInvalid = fieldIsInvalid( + credentialFilesFields?.value, + hasInvalidRequiredVars + ); + const credentialFilesError = i18n.translate( + 'xpack.csp.cspmIntegration.integration.fieldRequired', + { + defaultMessage: '{field} is required', + values: { + field: credentialFilesFields?.label, + }, + } + ); + const credentialJSONFields = getFieldById('gcp.credentials.json'); + const credentialJSONFieldsInvalid = fieldIsInvalid( + credentialJSONFields?.value, + hasInvalidRequiredVars + ); + const credentialJSONError = i18n.translate( + 'xpack.csp.cspmIntegration.integration.fieldRequired', + { + defaultMessage: '{field} is required', + values: { + field: credentialJSONFields?.label, + }, + } + ); const credentialFieldValue = credentialOptionsList[0].value; const credentialJSONValue = credentialOptionsList[1].value; @@ -575,7 +653,12 @@ export const GcpInputVarFields = ({
{organizationIdFields && isOrganization && ( - + onChange(organizationIdFields.id, event.target.value)} + isInvalid={organizationIdValueInvalid} /> )} {projectIdFields && ( - + onChange(projectIdFields.id, event.target.value)} + isInvalid={projectIdValueInvalid} /> )} @@ -612,13 +702,19 @@ export const GcpInputVarFields = ({ )} {credentialsTypeValue === credentialFieldValue && credentialFilesFields && ( - + onChange(credentialFilesFields.id, event.target.value)} + isInvalid={credentialFilesFieldsInvalid} /> )} @@ -636,7 +732,12 @@ export const GcpInputVarFields = ({ `} > - + }> { onChange(credentialJSONFields.id, value); }} - errors={[]} + errors={credentialJSONFieldsInvalid ? [credentialJSONError] : []} forceShowErrors={false} isEditPage={isEditPage} /> diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx index 4cec65cc695bb..087e5961a3a91 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/gcp_credentials_form/gcp_credentials_form_agentless.tsx @@ -164,6 +164,7 @@ export const GcpCredentialsFormAgentless = ({ updatePolicy, disabled, packageInfo, + hasInvalidRequiredVars, }: GcpFormProps) => { const accountType = input.streams?.[0]?.vars?.['gcp.account_type']?.value; const isOrganization = accountType === ORGANIZATION_ACCOUNT; @@ -250,6 +251,7 @@ export const GcpCredentialsFormAgentless = ({ } isOrganization={isOrganization} packageInfo={packageInfo} + hasInvalidRequiredVars={hasInvalidRequiredVars} /> diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index 40ce67f297057..7a5589c31af07 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -52,6 +52,7 @@ import { type NewPackagePolicyPostureInput, POSTURE_NAMESPACE, POLICY_TEMPLATE_FORM_DTS, + hasErrors, } from './utils'; import { PolicyTemplateInfo, @@ -763,6 +764,7 @@ export const CspPolicyTemplateForm = memo (validationResults?.vars || {})[key] !== null ); + const hasInvalidRequiredVars = !!hasErrors(validationResults); const [isLoading, setIsLoading] = useState(validationResultsNonNullFields.length > 0); const [canFetchIntegration, setCanFetchIntegration] = useState(true); @@ -1010,6 +1012,7 @@ export const CspPolicyTemplateForm = memo diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx index c479b3250fb7c..844946228d8e9 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx @@ -80,6 +80,7 @@ interface PolicyTemplateVarsFormProps { disabled: boolean; setupTechnology: SetupTechnology; isEditPage?: boolean; + hasInvalidRequiredVars: boolean; } export const PolicyTemplateVarsForm = ({ diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts index d815cc01fcdbd..0a0bdddf731a4 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts @@ -18,6 +18,8 @@ import merge from 'lodash/merge'; import semverValid from 'semver/functions/valid'; import semverCoerce from 'semver/functions/coerce'; import semverLt from 'semver/functions/lt'; +import { PackagePolicyValidationResults } from '@kbn/fleet-plugin/common/services'; +import { getFlattenedObject } from '@kbn/std'; import { CLOUDBEAT_AWS, CLOUDBEAT_AZURE, @@ -395,6 +397,17 @@ export const findVariableDef = (packageInfo: PackageInfo, key: string) => { .find((vars) => vars?.name === key); }; +export const fieldIsInvalid = (value: string | undefined, hasInvalidRequiredVars: boolean) => + hasInvalidRequiredVars && !value; + export const POLICY_TEMPLATE_FORM_DTS = { LOADER: 'policy-template-form-loader', }; + +export const hasErrors = (validationResults: PackagePolicyValidationResults | undefined) => { + if (!validationResults) return 0; + + const flattenedValidation = getFlattenedObject(validationResults); + const errors = Object.values(flattenedValidation).filter((value) => Boolean(value)) || []; + return errors.length; +}; diff --git a/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts index 8f48bbb7d1bf4..f7bc13017acd8 100644 --- a/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts +++ b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { CLOUD_CREDENTIALS_PACKAGE_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; import * as http from 'http'; import expect from '@kbn/expect'; +import { AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION } from '../constants'; import type { FtrProviderContext } from '../ftr_provider_context'; import { setupMockServer } from './mock_agentless_api'; // eslint-disable-next-line import/no-default-export @@ -44,7 +44,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should create agentless-agent`, async () => { const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); @@ -57,6 +57,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.header.waitUntilLoadingHasFinished(); + await cisIntegration.fillInTextField('awsDirectAccessKeyId', 'access_key_id'); + await cisIntegration.fillInTextField('passwordInput-secret-access-key', 'secret_access_key'); + await cisIntegration.clickSaveButton(); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -74,7 +77,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should show setup technology selector in edit mode`, async () => { const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); @@ -87,6 +90,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.header.waitUntilLoadingHasFinished(); + await cisIntegration.fillInTextField('awsDirectAccessKeyId', 'access_key_id'); + await cisIntegration.fillInTextField('passwordInput-secret-access-key', 'secret_access_key'); + await cisIntegration.clickSaveButton(); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -104,7 +110,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should hide setup technology selector in edit mode`, async () => { const integrationPolicyName = `cloud_security_posture1-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); @@ -131,7 +137,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); diff --git a/x-pack/test/cloud_security_posture_functional/agentless/security_posture.ts b/x-pack/test/cloud_security_posture_functional/agentless/security_posture.ts index c7ee5ff8400e6..03660340085cf 100644 --- a/x-pack/test/cloud_security_posture_functional/agentless/security_posture.ts +++ b/x-pack/test/cloud_security_posture_functional/agentless/security_posture.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { CLOUD_CREDENTIALS_PACKAGE_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; import expect from '@kbn/expect'; import type { FtrProviderContext } from '../ftr_provider_context'; +import { AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION } from '../constants'; // eslint-disable-next-line import/no-default-export export default function ({ getPageObjects, getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); @@ -39,7 +39,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should show kspm without agentless option`, async () => { await cisIntegration.navigateToAddIntegrationWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(KSPM_RADIO_OPTION); @@ -55,7 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should show cnvm without agentless option`, async () => { // const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CNVM_RADIO_OPTION); @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`should show cspm with agentless option`, async () => { // const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; await cisIntegration.navigateToAddIntegrationWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION + AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CSPM_RADIO_OPTION); diff --git a/x-pack/test/cloud_security_posture_functional/constants.ts b/x-pack/test/cloud_security_posture_functional/constants.ts new file mode 100644 index 0000000000000..e1ac04d4ee8cf --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/constants.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const AGENTLESS_SECURITY_POSTURE_PACKAGE_VERSION = '1.13.0-preview02'; diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts index 4415ec5a34160..d40276a33162b 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts @@ -16,13 +16,14 @@ const clientId = 'clientIdTest'; const tenantId = 'tenantIdTest'; const clientCertificatePath = 'clientCertificatePathTest'; const clientSecret = 'clientSecretTest'; +const clientCertificatePassword = 'clientCertificatePasswordTest'; export const CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS = { TENANT_ID: 'cisAzureTenantId', CLIENT_ID: 'cisAzureClientId', CLIENT_SECRET: 'passwordInput-client-secret', CLIENT_CERTIFICATE_PATH: 'cisAzureClientCertificatePath', - CLIENT_CERTIFICATE_PASSWORD: 'cisAzureClientCertificatePassword', + CLIENT_CERTIFICATE_PASSWORD: 'passwordInput-client-certificate-password', CLIENT_USERNAME: 'cisAzureClientUsername', CLIENT_PASSWORD: 'cisAzureClientPassword', }; @@ -136,6 +137,12 @@ export default function (providerContext: FtrProviderContext) { CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_CERTIFICATE_PATH, clientCertificatePath ); + + await cisIntegration.fillInTextField( + CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_CERTIFICATE_PASSWORD, + clientCertificatePassword + ); + await cisIntegration.clickSaveButton(); await pageObjects.header.waitUntilLoadingHasFinished(); expect((await cisIntegration.getPostInstallModal()) !== undefined).to.be(true); @@ -248,6 +255,12 @@ export default function (providerContext: FtrProviderContext) { CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_CERTIFICATE_PATH, clientCertificatePath ); + + await cisIntegration.fillInTextField( + CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_CERTIFICATE_PASSWORD, + clientCertificatePassword + ); + await cisIntegration.clickSaveButton(); await pageObjects.header.waitUntilLoadingHasFinished(); expect((await cisIntegration.getPostInstallModal()) !== undefined).to.be(true); diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/constants.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/constants.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/constants.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts index 017ef1b2fa82a..0c718c3991075 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts @@ -94,6 +94,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); + await cisIntegration.selectSetupTechnology('agent-based'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await cisIntegration.inputIntegrationName(integrationPolicyName); await cisIntegration.clickSaveButton();