[Cloud Security][Bug] Fix for [Object object] issue on secret fields (#179237)

## Summary

This PR addresses the issue where secret fields value are rendered as
[Object object] when user tries to edit it

<img width="801" alt="Screenshot 2024-03-26 at 12 21 25 AM"
src="2b1995fb-7a41-4223-9564-43e80a75720f">

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Maxim Kholod <maxim.kholod@elastic.co>
This commit is contained in:
Rickyanto Ang 2024-04-10 08:20:05 -07:00 committed by GitHub
parent fe507aa0b9
commit 3744a30b32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 496 additions and 157 deletions

View file

@ -284,6 +284,7 @@ export const AwsCredentialsForm = ({
<EuiSpacer size="l" />
<AwsInputVarFields
fields={fields}
packageInfo={packageInfo}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));
}}

View file

@ -25,7 +25,12 @@ import {
AwsCredentialTypeSelector,
} from './aws_credentials_form';
export const AwsCredentialsFormAgentless = ({ input, newPolicy, updatePolicy }: AwsFormProps) => {
export const AwsCredentialsFormAgentless = ({
input,
newPolicy,
packageInfo,
updatePolicy,
}: AwsFormProps) => {
const awsCredentialsType = getAwsCredentialsType(input) || DEFAULT_AGENTLESS_AWS_CREDENTIALS_TYPE;
const options = getAwsCredentialsFormOptions();
const group = options[awsCredentialsType];
@ -74,6 +79,7 @@ export const AwsCredentialsFormAgentless = ({ input, newPolicy, updatePolicy }:
<EuiSpacer size="l" />
<AwsInputVarFields
fields={fields}
packageInfo={packageInfo}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));
}}

View file

@ -5,40 +5,80 @@
* 2.0.
*/
import React from 'react';
import { EuiFieldPassword, EuiFieldText, EuiFormRow } from '@elastic/eui';
import React, { Suspense } from 'react';
import { EuiFieldText, EuiFormRow, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui';
import { PackageInfo } from '@kbn/fleet-plugin/common';
import { css } from '@emotion/react';
import { LazyPackagePolicyInputVarField } from '@kbn/fleet-plugin/public';
import { AwsOptions } from './get_aws_credentials_form_options';
import { findVariableDef } from '../utils';
export const AwsInputVarFields = ({
fields,
onChange,
packageInfo,
}: {
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}>
packageInfo: PackageInfo;
}) => {
return (
<div>
{fields.map((field) => (
<>
{field.type === 'password' && (
<EuiFieldPassword
id={field.id}
type="dual"
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
{field.type === 'password' && field.isSecret === true && (
<>
<EuiSpacer size="m" />
<div
css={css`
width: 100%;
.euiFormControlLayout,
.euiFormControlLayout__childrenWrapper,
.euiFormRow,
input {
max-width: 100%;
width: 100%;
}
`}
>
<Suspense fallback={<EuiLoadingSpinner size="l" />}>
<LazyPackagePolicyInputVarField
varDef={{
...findVariableDef(packageInfo, field.id)!,
required: true,
type: 'password',
}}
value={field.value || ''}
onChange={(value) => {
onChange(field.id, value);
}}
errors={[]}
forceShowErrors={false}
isEditPage={true}
/>
</Suspense>
</div>
<EuiSpacer size="m" />
</>
)}
{field.type === 'text' && (
<EuiFieldText
id={field.id}
<EuiFormRow
key={field.id}
label={field.label}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
hasChildLabel={true}
id={field.id}
>
<EuiFieldText
id={field.id}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
/>
</EuiFormRow>
)}
</>
</EuiFormRow>
))}
</div>
);
))}
</div>
);
};

View file

@ -71,7 +71,10 @@ const AWS_FIELD_LABEL = {
}),
};
export type AwsCredentialsFields = Record<string, { label: string; type?: 'password' | 'text' }>;
export type AwsCredentialsFields = Record<
string,
{ label: string; type?: 'password' | 'text'; isSecret?: boolean }
>;
export interface AwsOptionValue {
label: string;
@ -89,6 +92,7 @@ export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AwsCred
label: field.label,
type: field.type || 'text',
value: inputVar.value,
isSecret: field.isSecret,
} as const;
});
@ -147,7 +151,11 @@ export const getAwsCredentialsFormOptions = (): AwsOptions => ({
info: DirectAccessKeysDescription,
fields: {
access_key_id: { label: AWS_FIELD_LABEL.access_key_id },
secret_access_key: { label: AWS_FIELD_LABEL.secret_access_key, type: 'password' },
secret_access_key: {
label: AWS_FIELD_LABEL.secret_access_key,
type: 'password',
isSecret: true,
},
},
},
[AWS_CREDENTIALS_TYPE.TEMPORARY_KEYS]: {
@ -157,7 +165,11 @@ export const getAwsCredentialsFormOptions = (): AwsOptions => ({
}),
fields: {
access_key_id: { label: AWS_FIELD_LABEL.access_key_id },
secret_access_key: { label: AWS_FIELD_LABEL.secret_access_key, type: 'password' },
secret_access_key: {
label: AWS_FIELD_LABEL.secret_access_key,
type: 'password',
isSecret: true,
},
session_token: {
label: i18n.translate('xpack.csp.awsIntegration.sessionTokenLabel', {
defaultMessage: 'Session Token',

View file

@ -4,10 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect } from 'react';
import React, { Suspense, useEffect } from 'react';
import {
EuiCallOut,
EuiFieldPassword,
EuiFieldText,
EuiFormRow,
EuiHorizontalRule,
@ -16,6 +15,7 @@ import {
EuiSpacer,
EuiText,
EuiTitle,
EuiLoadingSpinner,
} from '@elastic/eui';
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { NewPackagePolicyInput, PackageInfo } from '@kbn/fleet-plugin/common';
@ -25,13 +25,14 @@ import { i18n } from '@kbn/i18n';
import semverValid from 'semver/functions/valid';
import semverCoerce from 'semver/functions/coerce';
import semverLt from 'semver/functions/lt';
import { LazyPackagePolicyInputVarField } from '@kbn/fleet-plugin/public';
import {
AzureOptions,
getAzureCredentialsFormManualOptions,
} from './get_azure_credentials_form_options';
import { AzureCredentialsType } from '../../../../common/types_old';
import { useAzureCredentialsForm } from './hooks';
import { getPosturePolicy, NewPackagePolicyPostureInput } from '../utils';
import { findVariableDef, getPosturePolicy, NewPackagePolicyPostureInput } from '../utils';
import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group';
import { CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS } from '../../test_subjects';
@ -267,39 +268,73 @@ const AZURE_MANUAL_FIELDS_PACKAGE_VERSION = '1.7.0';
export const AzureInputVarFields = ({
fields,
packageInfo,
onChange,
}: {
fields: Array<AzureOptions[keyof AzureOptions]['fields'][number] & { value: string; id: string }>;
packageInfo: PackageInfo;
onChange: (key: string, value: string) => void;
}) => (
<div>
{fields.map((field) => (
<EuiFormRow key={field.id} label={field.label} fullWidth hasChildLabel={true} id={field.id}>
}) => {
return (
<div>
{fields.map((field) => (
<>
{field.type === 'password' && (
<EuiFieldPassword
id={field.id}
type="dual"
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
data-test-subj={field.testSubj}
/>
{field.type === 'password' && field.isSecret === true && (
<>
<EuiSpacer size="m" />
<div
css={css`
width: 100%;
.euiFormControlLayout,
.euiFormControlLayout__childrenWrapper,
.euiFormRow,
input {
max-width: 100%;
width: 100%;
}
`}
>
<Suspense fallback={<EuiLoadingSpinner size="l" />}>
<LazyPackagePolicyInputVarField
varDef={{
...findVariableDef(packageInfo, field.id)!,
required: true,
type: 'password',
}}
value={field.value || ''}
onChange={(value) => {
onChange(field.id, value);
}}
errors={[]}
forceShowErrors={false}
isEditPage={true}
/>
</Suspense>
</div>
</>
)}
{field.type === 'text' && (
<EuiFieldText
id={field.id}
<EuiFormRow
key={field.id}
label={field.label}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
data-test-subj={field.testSubj}
/>
hasChildLabel={true}
id={field.id}
>
<EuiFieldText
id={field.id}
fullWidth
value={field.value || ''}
onChange={(event) => onChange(field.id, event.target.value)}
data-test-subj={field.testSubj}
/>
</EuiFormRow>
)}
</>
</EuiFormRow>
))}
</div>
);
))}
</div>
);
};
export const AzureCredentialsForm = ({
input,
@ -403,6 +438,7 @@ export const AzureCredentialsForm = ({
<EuiSpacer size="m" />
<AzureInputVarFields
fields={fields}
packageInfo={packageInfo}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));
}}
@ -412,7 +448,7 @@ export const AzureCredentialsForm = ({
<EuiSpacer size="m" />
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.csp.azureIntegration.manualCredentialType.documentaion"
id="xpack.csp.azureIntegration.manualCredentialType.documentation"
defaultMessage="Read the {documentation} for more details"
values={{
documentation: (

View file

@ -29,6 +29,7 @@ export const AzureCredentialsFormAgentless = ({
input,
newPolicy,
updatePolicy,
packageInfo,
}: AzureCredentialsFormProps) => {
const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath;
const options = getAzureCredentialsFormOptions();
@ -40,6 +41,7 @@ export const AzureCredentialsFormAgentless = ({
<AzureSetupInfoContent integrationLink={integrationLink} />
<EuiSpacer size="l" />
<AzureInputVarFields
packageInfo={packageInfo}
fields={fields}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));

View file

@ -16,7 +16,7 @@ import { AZURE_CREDENTIALS_TYPE } from './azure_credentials_form';
export type AzureCredentialsFields = Record<
string,
{ label: string; type?: 'password' | 'text'; testSubj?: string }
{ label: string; type?: 'password' | 'text'; testSubj?: string; isSecret?: boolean }
>;
export interface AzureOptionValue {
@ -55,6 +55,7 @@ export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AzureCr
type: field.type || 'text',
testSubj: field.testSubj,
value: inputVar.value,
isSecret: field?.isSecret,
} as const;
});
@ -107,6 +108,7 @@ export const getAzureCredentialsFormOptions = (): AzureOptions => ({
},
'azure.credentials.client_secret': {
type: 'password',
isSecret: true,
label: i18n.translate('xpack.csp.azureIntegration.clientSecretLabel', {
defaultMessage: 'Client Secret',
}),
@ -135,6 +137,7 @@ export const getAzureCredentialsFormOptions = (): AzureOptions => ({
},
'azure.credentials.client_certificate_password': {
type: 'password',
isSecret: true,
label: i18n.translate('xpack.csp.azureIntegration.clientCertificatePasswordLabel', {
defaultMessage: 'Client Certificate Password',
}),
@ -164,6 +167,7 @@ export const getAzureCredentialsFormOptions = (): AzureOptions => ({
},
'azure.credentials.client_password': {
type: 'password',
isSecret: true,
label: i18n.translate('xpack.csp.azureIntegration.clientPasswordLabel', {
defaultMessage: 'Client Password',
}),

View file

@ -7,7 +7,7 @@
import React from 'react';
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 { NewPackagePolicyInput, PackageInfo } from '@kbn/fleet-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { RadioGroup } from './csp_boxed_radio_group';
@ -119,7 +119,7 @@ type AwsOptions = Record<
{
label: string;
info: React.ReactNode;
fields: Record<string, { label: string; type?: 'password' | 'text' }>;
fields: Record<string, { label: string; type?: 'password' | 'text'; isSecret?: boolean }>;
testId: string;
}
>;
@ -146,7 +146,11 @@ const options: AwsOptions = {
info: DirectAccessKeysDescription,
fields: {
access_key_id: { label: AWS_FIELD_LABEL.access_key_id },
secret_access_key: { label: AWS_FIELD_LABEL.secret_access_key, type: 'password' },
secret_access_key: {
label: AWS_FIELD_LABEL.secret_access_key,
type: 'password',
isSecret: true,
},
},
testId: 'directAccessKeyTestId',
},
@ -157,7 +161,11 @@ const options: AwsOptions = {
}),
fields: {
access_key_id: { label: AWS_FIELD_LABEL.access_key_id },
secret_access_key: { label: AWS_FIELD_LABEL.secret_access_key, type: 'password' },
secret_access_key: {
label: AWS_FIELD_LABEL.secret_access_key,
type: 'password',
isSecret: true,
},
session_token: {
label: i18n.translate('xpack.csp.eksIntegration.sessionTokenLabel', {
defaultMessage: 'Session Token',
@ -197,6 +205,7 @@ const AWS_CREDENTIALS_OPTIONS = Object.keys(options).map((value) => ({
interface Props {
newPolicy: NewPackagePolicy;
packageInfo: PackageInfo;
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_aws' | 'cloudbeat/cis_eks' }>;
updatePolicy(updatedPolicy: NewPackagePolicy): void;
}
@ -214,13 +223,14 @@ const getInputVarsFields = (
label: field.label,
type: field.type || 'text',
value: inputVar.value,
isSecret: field?.isSecret,
} as const;
});
const getAwsCredentialsType = (input: Props['input']): AwsCredentialsType | undefined =>
input.streams[0].vars?.['aws.credentials.type'].value;
export const EksCredentialsForm = ({ input, newPolicy, updatePolicy }: Props) => {
export const EksCredentialsForm = ({ input, newPolicy, packageInfo, updatePolicy }: Props) => {
// 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 default to the first option.
const awsCredentialsType = getAwsCredentialsType(input) || AWS_CREDENTIALS_OPTIONS[0].id;
@ -248,6 +258,7 @@ export const EksCredentialsForm = ({ input, newPolicy, updatePolicy }: Props) =>
<EuiSpacer />
<AwsInputVarFields
fields={fields}
packageInfo={packageInfo}
onChange={(key, value) =>
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }))
}

View file

@ -7,6 +7,7 @@
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import type { PackageInfo, PackagePolicyConfigRecord } from '@kbn/fleet-plugin/common';
import { createNewPackagePolicyMock, createAgentPolicyMock } from '@kbn/fleet-plugin/common/mocks';
import { RegistryRelease, RegistryVarType } from '@kbn/fleet-plugin/common/types';
import {
CLOUDBEAT_GCP,
CLOUDBEAT_AZURE,
@ -31,6 +32,7 @@ export const getMockPolicyVulnMgmtAWS = () =>
export const getMockAgentlessAgentPolicy = () => {
return createAgentPolicyMock({ id: 'agentless' });
};
export const getMockPackageInfo = () => getPackageInfoMock();
export const getMockPackageInfoVulnMgmtAWS = () => {
return {
@ -142,7 +144,7 @@ const getPolicyMock = (
const awsVarsMock = {
access_key_id: { type: 'text' },
secret_access_key: { type: 'text' },
secret_access_key: { type: 'password', isSecret: true },
session_token: { type: 'text' },
shared_credential_file: { type: 'text' },
credential_profile_name: { type: 'text' },
@ -152,7 +154,7 @@ const getPolicyMock = (
const eksVarsMock = {
access_key_id: { type: 'text' },
secret_access_key: { type: 'text' },
secret_access_key: { type: 'password', isSecret: true },
session_token: { type: 'text' },
shared_credential_file: { type: 'text' },
credential_profile_name: { type: 'text' },
@ -262,3 +264,95 @@ const getPolicyMock = (
],
};
};
export const getPackageInfoMock = () => {
return {
data_streams: [
{
dataset: 'cloud_security_posture.findings',
type: 'logs',
package: 'cloud_security_posture',
path: 'findings',
release: 'ga' as RegistryRelease,
title: 'Cloud Security Posture Findings',
streams: [
{
input: 'cloudbeat/cis_aws',
template_path: 'aws.yml.hbs',
title: 'CIS AWS Benchmark',
vars: [
{
name: 'secret_access_key',
title: 'Secret Access Key',
secret: true,
type: 'text' as RegistryVarType,
},
],
},
{
input: 'cloudbeat/cis_eks',
template_path: 'eks.yml.hbs',
title: 'Amazon EKS Benchmark',
vars: [
{
name: 'secret_access_key',
title: 'Secret Access Key',
secret: true,
type: 'text' as RegistryVarType,
},
],
},
{
input: 'cloudbeat/cis_azure',
template_path: 'azure.yml.hbs',
title: 'CIS Azure Benchmark',
vars: [
{
multi: false,
name: 'azure.credentials.client_secret',
required: false,
secret: true,
show_user: true,
title: 'Client Secret',
type: 'text' as RegistryVarType,
},
{
multi: false,
name: 'azure.credentials.client_password',
required: false,
secret: true,
show_user: true,
title: 'Client Password',
type: 'text' as RegistryVarType,
},
{
multi: false,
name: 'azure.credentials.client_certificate_password',
required: false,
secret: true,
show_user: true,
title: 'Client Certificate Password',
type: 'text' as RegistryVarType,
},
],
},
],
},
],
format_version: '3.0.0',
version: '1.9.0-preview109',
name: 'cloud_security_posture',
description: 'Identify & remediate configuration risks in your Cloud infrastructure',
owner: {
github: 'elastic/cloud-security-posture',
type: 'elastic' as 'elastic' | 'partner' | 'community' | undefined,
},
title: 'Security Posture Management',
latestVersion: '1.9.0',
assets: {
kibana: {},
},
};
};

View file

@ -17,6 +17,7 @@ import {
import { TestProvider } from '../../test/test_provider';
import {
getMockAgentlessAgentPolicy,
getMockPackageInfo,
getMockPackageInfoCspmAWS,
getMockPackageInfoCspmAzure,
getMockPackageInfoCspmGCP,
@ -27,6 +28,7 @@ import {
getMockPolicyGCP,
getMockPolicyK8s,
getMockPolicyVulnMgmtAWS,
getPackageInfoMock,
} from './mocks';
import type {
AgentPolicy,
@ -58,6 +60,8 @@ import {
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ,
} from '../test_subjects';
import { ExperimentalFeaturesService } from '@kbn/fleet-plugin/public/services';
import { ThemeProvider } from '@emotion/react';
// mock useParams
jest.mock('react-router-dom', () => ({
@ -68,8 +72,10 @@ jest.mock('react-router-dom', () => ({
}));
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_package_policy_list');
jest.mock('@kbn/fleet-plugin/public/services/experimental_features');
const onChange = jest.fn();
const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService);
const createReactQueryResponseWithRefetch = (
data: Parameters<typeof createReactQueryResponse>[0]
@ -85,6 +91,9 @@ describe('<CspPolicyTemplateForm />', () => {
(useParams as jest.Mock).mockReturnValue({
integration: undefined,
});
mockedExperimentalFeaturesService.get.mockReturnValue({
secretsStorage: true,
} as any);
(usePackagePolicyList as jest.Mock).mockImplementation((packageName) =>
createReactQueryResponseWithRefetch({
status: 'success',
@ -116,29 +125,31 @@ describe('<CspPolicyTemplateForm />', () => {
onChange?: jest.Mock<void, [NewPackagePolicy]>;
agentlessPolicy?: AgentPolicy;
}) => (
<TestProvider>
{edit && (
<CspPolicyTemplateForm
policy={newPolicy as PackagePolicy}
newPolicy={newPolicy}
onChange={onChange}
packageInfo={packageInfo}
isEditPage={true}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
{!edit && (
<CspPolicyTemplateForm
newPolicy={newPolicy}
onChange={onChange}
packageInfo={packageInfo}
isEditPage={false}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
</TestProvider>
<ThemeProvider theme={() => ({ eui: { euiSizeXS: '4px' } })}>
<TestProvider>
{edit && (
<CspPolicyTemplateForm
policy={newPolicy as PackagePolicy}
newPolicy={newPolicy}
onChange={onChange}
packageInfo={packageInfo}
isEditPage={true}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
{!edit && (
<CspPolicyTemplateForm
newPolicy={newPolicy}
onChange={onChange}
packageInfo={packageInfo}
isEditPage={false}
agentPolicy={agentPolicy}
agentlessPolicy={agentlessPolicy}
/>
)}
</TestProvider>
</ThemeProvider>
);
it('updates package policy namespace to default when it changes', () => {
@ -625,29 +636,32 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders ${CLOUDBEAT_EKS} Direct Access Keys fields`, () => {
let policy: NewPackagePolicy = getMockPolicyEKS();
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, {
'aws.credentials.type': { value: 'direct_access_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText } = render(<WrappedComponent newPolicy={policy} />);
const option = getByLabelText('Direct access keys');
expect(option).toBeChecked();
expect(getByLabelText('Access Key ID')).toBeInTheDocument();
expect(getByLabelText('Secret Access Key')).toBeInTheDocument();
});
it(`updates ${CLOUDBEAT_EKS} Direct Access Keys fields`, () => {
it(`renders ${CLOUDBEAT_EKS} Direct Access Keys fields`, async () => {
let policy = getMockPolicyEKS();
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, {
'aws.credentials.type': { value: 'direct_access_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, rerender } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
const option = getByLabelText('Direct access keys');
expect(option).toBeChecked();
expect(getByLabelText('Access Key ID')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Secret Access Key')).toBeInTheDocument());
});
it(`updates ${CLOUDBEAT_EKS} Direct Access Keys fields`, async () => {
let policy = getMockPolicyEKS();
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, {
'aws.credentials.type': { value: 'direct_access_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, rerender, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Access Key ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { access_key_id: { value: 'a' } });
@ -658,10 +672,12 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Secret Access Key'), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { secret_access_key: { value: 'b' } });
await waitFor(() => userEvent.type(getByTestId('passwordInput-secret-access-key'), 'c'));
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { secret_access_key: { value: 'c' } });
expect(onChange).toHaveBeenCalledWith({
isValid: true,
@ -669,30 +685,34 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders ${CLOUDBEAT_EKS} Temporary Keys fields`, () => {
it(`renders ${CLOUDBEAT_EKS} Temporary Keys fields`, async () => {
let policy: NewPackagePolicy = getMockPolicyEKS();
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, {
'aws.credentials.type': { value: 'temporary_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
const option = getByLabelText('Temporary keys');
expect(option).toBeChecked();
expect(getByLabelText('Access Key ID')).toBeInTheDocument();
expect(getByLabelText('Secret Access Key')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Secret Access Key')).toBeInTheDocument());
expect(getByLabelText('Session Token')).toBeInTheDocument();
});
it(`updates ${CLOUDBEAT_EKS} Temporary Keys fields`, () => {
it(`updates ${CLOUDBEAT_EKS} Temporary Keys fields`, async () => {
let policy = getMockPolicyEKS();
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, {
'aws.credentials.type': { value: 'temporary_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, rerender } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText, rerender, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Access Key ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { access_key_id: { value: 'a' } });
@ -702,17 +722,21 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Secret Access Key'), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { secret_access_key: { value: 'b' } });
await waitFor(() => userEvent.type(getByTestId('passwordInput-secret-access-key'), 'c'));
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { secret_access_key: { value: 'c' } });
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Session Token'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_EKS, { session_token: { value: 'a' } });
@ -907,30 +931,34 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders ${CLOUDBEAT_AWS} Direct Access Keys fields`, () => {
it(`renders ${CLOUDBEAT_AWS} Direct Access Keys fields`, async () => {
let policy: NewPackagePolicy = getMockPolicyAWS();
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, {
'aws.credentials.type': { value: 'direct_access_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, getByRole } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText, getByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
expect(
getByRole('option', { name: 'Direct access keys', selected: true })
).toBeInTheDocument();
expect(getByLabelText('Access Key ID')).toBeInTheDocument();
expect(getByLabelText('Secret Access Key')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Secret Access Key')).toBeInTheDocument());
});
it(`updates ${CLOUDBEAT_AWS} Direct Access Keys fields`, () => {
it(`updates ${CLOUDBEAT_AWS} Direct Access Keys fields`, async () => {
let policy = getMockPolicyAWS();
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, {
'aws.credentials.type': { value: 'direct_access_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, rerender } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText, rerender, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Access Key ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, { access_key_id: { value: 'a' } });
@ -941,9 +969,11 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Secret Access Key'), 'b');
await waitFor(() => userEvent.type(getByTestId('passwordInput-secret-access-key'), 'b'));
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, { secret_access_key: { value: 'b' } });
expect(onChange).toHaveBeenCalledWith({
@ -952,28 +982,32 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders ${CLOUDBEAT_AWS} Temporary Keys fields`, () => {
it(`renders ${CLOUDBEAT_AWS} Temporary Keys fields`, async () => {
let policy: NewPackagePolicy = getMockPolicyAWS();
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, {
'aws.credentials.type': { value: 'temporary_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, getByRole } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText, getByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
expect(getByRole('option', { name: 'Temporary keys', selected: true })).toBeInTheDocument();
expect(getByLabelText('Access Key ID')).toBeInTheDocument();
expect(getByLabelText('Secret Access Key')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Secret Access Key')).toBeInTheDocument());
expect(getByLabelText('Session Token')).toBeInTheDocument();
});
it(`updates ${CLOUDBEAT_AWS} Temporary Keys fields`, () => {
it(`updates ${CLOUDBEAT_AWS} Temporary Keys fields`, async () => {
let policy = getMockPolicyAWS();
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, {
'aws.credentials.type': { value: 'temporary_keys' },
'aws.setup.format': { value: 'manual' },
});
const { getByLabelText, rerender } = render(<WrappedComponent newPolicy={policy} />);
const { getByLabelText, rerender, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Access Key ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, { access_key_id: { value: 'a' } });
@ -983,9 +1017,16 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
userEvent.type(getByLabelText('Secret Access Key'), 'b');
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
await waitFor(() => userEvent.type(getByTestId('passwordInput-secret-access-key'), 'b'));
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, { secret_access_key: { value: 'b' } });
expect(onChange).toHaveBeenCalledWith({
@ -993,7 +1034,9 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
rerender(<WrappedComponent newPolicy={policy} />);
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfo() as PackageInfo} />
);
userEvent.type(getByLabelText('Session Token'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AWS, { session_token: { value: 'a' } });
@ -1384,14 +1427,14 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, () => {
it(`renders ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, async () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_secret' },
});
const { getByLabelText, getByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
expect(
@ -1399,17 +1442,17 @@ describe('<CspPolicyTemplateForm />', () => {
).toBeInTheDocument();
expect(getByLabelText('Tenant ID')).toBeInTheDocument();
expect(getByLabelText('Client ID')).toBeInTheDocument();
expect(getByLabelText('Client Secret')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Client Secret')).toBeInTheDocument());
});
it(`updates ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, () => {
it(`updates ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, async () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_secret' },
});
const { rerender, getByLabelText } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
const { rerender, getByLabelText, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Tenant ID'), 'a');
@ -1424,7 +1467,7 @@ describe('<CspPolicyTemplateForm />', () => {
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Client ID'), 'b');
@ -1438,10 +1481,10 @@ describe('<CspPolicyTemplateForm />', () => {
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Client Secret'), 'c');
await waitFor(() => userEvent.type(getByTestId('passwordInput-client-secret'), 'c'));
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_secret': { value: 'c' },
});
@ -1600,7 +1643,7 @@ describe('<CspPolicyTemplateForm />', () => {
<WrappedComponent
newPolicy={newPackagePolicy}
agentlessPolicy={agentlessPolicy}
packageInfo={{ version: '1.8.0' } as PackageInfo}
packageInfo={getPackageInfoMock() as PackageInfo}
/>
);
@ -1649,7 +1692,7 @@ describe('<CspPolicyTemplateForm />', () => {
<WrappedComponent
newPolicy={newPackagePolicy}
agentlessPolicy={agentlessPolicy}
packageInfo={{ version: '1.8.0' } as PackageInfo}
packageInfo={getPackageInfoMock() as PackageInfo}
/>
);
@ -1709,14 +1752,14 @@ describe('<CspPolicyTemplateForm />', () => {
});
});
it(`renders Service principal with Client Certificate fields`, () => {
it(`renders Service principal with Client Certificate fields`, async () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_certificate' },
});
const { getByLabelText, getByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
expect(
@ -1725,17 +1768,17 @@ describe('<CspPolicyTemplateForm />', () => {
expect(getByLabelText('Tenant ID')).toBeInTheDocument();
expect(getByLabelText('Client ID')).toBeInTheDocument();
expect(getByLabelText('Client Certificate Path')).toBeInTheDocument();
expect(getByLabelText('Client Certificate Password')).toBeInTheDocument();
await waitFor(() => expect(getByLabelText('Client Certificate Password')).toBeInTheDocument());
});
it(`updates Service principal with Client Certificate fields`, () => {
it(`updates Service principal with Client Certificate fields`, async () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_certificate' },
});
const { rerender, getByLabelText } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
const { rerender, getByLabelText, getByTestId } = render(
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Tenant ID'), 'a');
@ -1750,7 +1793,7 @@ describe('<CspPolicyTemplateForm />', () => {
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Client ID'), 'b');
@ -1764,7 +1807,7 @@ describe('<CspPolicyTemplateForm />', () => {
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Client Certificate Path'), 'c');
@ -1778,10 +1821,12 @@ describe('<CspPolicyTemplateForm />', () => {
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
<WrappedComponent newPolicy={policy} packageInfo={getPackageInfoMock() as PackageInfo} />
);
userEvent.type(getByLabelText('Client Certificate Password'), 'd');
await waitFor(() =>
userEvent.type(getByTestId('passwordInput-client-certificate-password'), 'd')
);
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_certificate_password': { value: 'd' },
});

View file

@ -16,8 +16,9 @@ import {
getDefaultAwsCredentialsType,
getDefaultAzureCredentialsType,
getDefaultGcpHiddenVars,
findVariableDef,
} from './utils';
import { getMockPolicyAWS, getMockPolicyK8s, getMockPolicyEKS } from './mocks';
import { getMockPolicyAWS, getMockPolicyK8s, getMockPolicyEKS, getPackageInfoMock } from './mocks';
describe('getPosturePolicy', () => {
for (const [name, getPolicy, expectedVars] of [
@ -486,3 +487,65 @@ describe('getDefaultGcpHiddenVars', () => {
});
});
});
describe('findVariableDef', () => {
it('Should return var item when key exist', () => {
const packageInfo = getPackageInfoMock() as PackageInfo;
const key = 'secret_access_key';
const result = findVariableDef(packageInfo, key);
expect(result).toMatchObject({
name: 'secret_access_key',
secret: true,
title: 'Secret Access Key',
});
});
it('Should return undefined when key is invalid', () => {
const packageInfo = getPackageInfoMock() as PackageInfo;
const key = 'invalid_access_key';
const result = findVariableDef(packageInfo, key);
expect(result).toBeUndefined();
});
it('Should return undefined when datastream is undefined', () => {
const packageInfo = {
data_streams: [{}],
} as PackageInfo;
const key = 'secret_access_key';
const result = findVariableDef(packageInfo, key);
expect(result).toBeUndefined();
});
it('Should return undefined when stream is undefined', () => {
const packageInfo = {
data_streams: [
{
title: 'Cloud Security Posture Findings',
streams: [{}],
},
],
} as PackageInfo;
const key = 'secret_access_key';
const result = findVariableDef(packageInfo, key);
expect(result).toBeUndefined();
});
it('Should return undefined when stream.var is invalid', () => {
const packageInfo = {
data_streams: [
{
title: 'Cloud Security Posture Findings',
streams: [{ vars: {} }],
},
],
} as PackageInfo;
const key = 'secret_access_key';
const result = findVariableDef(packageInfo, key);
expect(result).toBeUndefined();
});
});

View file

@ -378,3 +378,20 @@ export const isBelowMinVersion = (version: string, minVersion: string) => {
const versionNumberOnly = semverCoerce(semanticVersion) || '';
return semverLt(versionNumberOnly, minVersion);
};
/**
* Searches for a variable definition in a given packageInfo object based on a specified key.
* It navigates through nested arrays within the packageInfo object to locate the variable definition associated with the provided key.
* If found, it returns the variable definition object; otherwise, it returns undefined.
*/
export const findVariableDef = (packageInfo: PackageInfo, key: string) => {
return packageInfo?.data_streams
?.filter((datastreams) => datastreams !== undefined)
.map((ds) => ds.streams)
.filter((streams) => streams !== undefined)
.flat()
.filter((streams) => streams?.vars !== undefined)
.map((cis) => cis?.vars)
.flat()
.find((vars) => vars?.name === key);
};

View file

@ -66,7 +66,7 @@ export const SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ = 'setup-technology-selector';
export const CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS = {
TENANT_ID: 'cisAzureTenantId',
CLIENT_ID: 'cisAzureClientId',
CLIENT_SECRET: 'cisAzureClientSecret',
CLIENT_SECRET: 'passwordInput-client-secret',
CLIENT_CERTIFICATE_PATH: 'cisAzureClientCertificatePath',
CLIENT_CERTIFICATE_PASSWORD: 'cisAzureClientCertificatePassword',
CLIENT_USERNAME: 'cisAzureClientUsername',

View file

@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, memo, useMemo, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import { i18n } from '@kbn/i18n';
@ -51,11 +50,11 @@ const FormRow = styled(EuiFormRow)`
}
.euiFormRow__fieldWrapper > .euiPanel {
padding: ${(props) => props.theme.eui.euiSizeXS};
padding: ${(props) => props.theme.eui?.euiSizeXS};
}
`;
interface InputFieldProps {
export interface InputFieldProps {
varDef: RegistryVarsEntry;
value: any;
onChange: (newValue: any) => void;

View file

@ -7,6 +7,8 @@
import type { PluginInitializerContext } from '@kbn/core/public';
import { lazy } from 'react';
import { FleetPlugin } from './plugin';
export type { FleetSetup, FleetStart, FleetStartServices } from './plugin';
@ -62,9 +64,16 @@ export { PackagePolicyEditorDatastreamPipelines } from './applications/fleet/sec
export type { PackagePolicyEditorDatastreamPipelinesProps } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_pipelines';
export { PackagePolicyEditorDatastreamMappings } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_mappings';
export type { PackagePolicyEditorDatastreamMappingsProps } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/datastream_mappings';
export type { DynamicPagePathValues } from './constants';
// This Type export is added to prevent error TS4023
export type { InputFieldProps } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field';
export const LazyPackagePolicyInputVarField = lazy(() =>
import(
'./applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field'
).then((module) => ({ default: module.PackagePolicyInputVarField }))
);
export type { PackageListGridProps } from './applications/integrations/sections/epm/components/package_list_grid';
export type { AvailablePackagesHookType } from './applications/integrations/sections/epm/screens/home/hooks/use_available_packages';
export type { IntegrationCardItem } from './applications/integrations/sections/epm/screens/home';