[Cloud Security] Azure integration manual fields (#171069)

This commit is contained in:
Jordan 2023-11-29 11:18:29 -05:00 committed by GitHub
parent d3e9ab7cea
commit 3437e6d878
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 574 additions and 28 deletions

View file

@ -10,7 +10,6 @@ import {
VulnSeverity,
AwsCredentialsTypeFieldMap,
GcpCredentialsTypeFieldMap,
AzureCredentialsTypeFieldMap,
} from './types';
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
@ -161,7 +160,25 @@ export const GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP: GcpCredentialsTypeFieldMap = {
'credentials-json': ['gcp.credentials.json'],
};
export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP: AzureCredentialsTypeFieldMap = {
manual: [],
export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP = {
arm_template: [],
service_principal_with_client_secret: [
'azure.credentials.tenant_id',
'azure.credentials.client_id',
'azure.credentials.client_secret',
],
service_principal_with_client_certificate: [
'azure.credentials.tenant_id',
'azure.credentials.client_id',
'azure.credentials.client_certificate_path',
'azure.credentials.client_certificate_password',
],
service_principal_with_client_username_and_password: [
'azure.credentials.tenant_id',
'azure.credentials.client_id',
'azure.credentials.client_username',
'azure.credentials.client_password',
],
managed_identity: [],
manual: [],
};

View file

@ -30,7 +30,13 @@ export type GcpCredentialsTypeFieldMap = {
[key in GcpCredentialsType]: string[];
};
export type AzureCredentialsType = 'arm_template' | 'manual';
export type AzureCredentialsType =
| 'arm_template'
| 'service_principal_with_client_secret'
| 'service_principal_with_client_certificate'
| 'service_principal_with_client_username_and_password'
| 'managed_identity'
| 'manual';
export type AzureCredentialsTypeFieldMap = {
[key in AzureCredentialsType]: string[];

View file

@ -124,7 +124,7 @@ export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePacka
const azureCredentialType: AzureCredentialsType | undefined =
enabledInput?.streams?.[0].vars?.['azure.credentials.type']?.value;
if (awsCredentialType || gcpCredentialType) {
if (awsCredentialType || gcpCredentialType || azureCredentialType) {
let credsToKeep: string[] = [' '];
let credFields: string[] = [' '];
if (awsCredentialType) {

View file

@ -5,7 +5,18 @@
* 2.0.
*/
import React, { useEffect } from 'react';
import { EuiLink, EuiSpacer, EuiText, EuiTitle, EuiCallOut, EuiHorizontalRule } from '@elastic/eui';
import {
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
EuiCallOut,
EuiHorizontalRule,
EuiFormRow,
EuiSelect,
EuiFieldPassword,
EuiFieldText,
} from '@elastic/eui';
import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
import { NewPackagePolicyInput, PackageInfo } from '@kbn/fleet-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
@ -14,8 +25,13 @@ import { i18n } from '@kbn/i18n';
import semverValid from 'semver/functions/valid';
import semverCoerce from 'semver/functions/coerce';
import semverLt from 'semver/functions/lt';
import {
AzureOptions,
getAzureCredentialsFormManualOptions,
} from './get_azure_credentials_form_options';
import { AzureCredentialsType } from '../../../../common/types';
import { SetupFormat, useAzureCredentialsForm } from './hooks';
import { NewPackagePolicyPostureInput } from '../utils';
import { getPosturePolicy, NewPackagePolicyPostureInput } from '../utils';
import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group';
interface AzureSetupInfoContentProps {
@ -161,7 +177,31 @@ const ArmTemplateSetup = ({
);
};
const ManualSetup = ({ integrationLink }: { integrationLink: string }) => {
const AzureCredentialTypeSelector = ({
type,
onChange,
}: {
onChange(type: AzureCredentialsType): void;
type: AzureCredentialsType;
}) => (
<EuiFormRow
fullWidth
label={i18n.translate('xpack.csp.azureIntegration.azureCredentialTypeSelectorLabel', {
defaultMessage: 'Preferred manual method',
})}
>
<EuiSelect
fullWidth
options={getAzureCredentialsFormManualOptions()}
value={type}
onChange={(optionElem) => {
onChange(optionElem.target.value as AzureCredentialsType);
}}
/>
</EuiFormRow>
);
const TemporaryManualSetup = ({ integrationLink }: { integrationLink: string }) => {
return (
<>
<EuiText color="subdued" size="s">
@ -206,6 +246,53 @@ const ManualSetup = ({ integrationLink }: { integrationLink: string }) => {
};
const AZURE_MINIMUM_PACKAGE_VERSION = '1.6.0';
const AZURE_MANUAL_FIELDS_PACKAGE_VERSION = '1.7.0';
export const getDefaultAzureManualCredentialType = (packageInfo: PackageInfo) => {
const packageSemanticVersion = semverValid(packageInfo.version);
const cleanPackageVersion = semverCoerce(packageSemanticVersion) || '';
const isPackageVersionValidForManualFields = !semverLt(
cleanPackageVersion,
AZURE_MANUAL_FIELDS_PACKAGE_VERSION
);
return isPackageVersionValidForManualFields ? 'managed_identity' : 'manual';
};
const AzureInputVarFields = ({
fields,
onChange,
}: {
fields: Array<AzureOptions[keyof AzureOptions]['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>
);
export const AzureCredentialsForm = ({
input,
@ -216,15 +303,22 @@ export const AzureCredentialsForm = ({
setIsValid,
disabled,
}: Props) => {
const { setupFormat, onSetupFormatChange, integrationLink, hasArmTemplateUrl } =
useAzureCredentialsForm({
newPolicy,
input,
packageInfo,
onChange,
setIsValid,
updatePolicy,
});
const {
group,
fields,
azureCredentialsType,
setupFormat,
onSetupFormatChange,
integrationLink,
hasArmTemplateUrl,
} = useAzureCredentialsForm({
newPolicy,
input,
packageInfo,
onChange,
setIsValid,
updatePolicy,
});
useEffect(() => {
if (!setupFormat) {
@ -238,6 +332,10 @@ export const AzureCredentialsForm = ({
cleanPackageVersion,
AZURE_MINIMUM_PACKAGE_VERSION
);
const isPackageVersionValidForManualFields = !semverLt(
cleanPackageVersion,
AZURE_MANUAL_FIELDS_PACKAGE_VERSION
);
useEffect(() => {
setIsValid(isPackageVersionValidForAzure);
@ -280,8 +378,52 @@ export const AzureCredentialsForm = ({
{setupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE && (
<ArmTemplateSetup hasArmTemplateUrl={hasArmTemplateUrl} input={input} />
)}
{setupFormat === AZURE_MANUAL_CREDENTIAL_TYPE && (
<ManualSetup integrationLink={integrationLink} />
{setupFormat === AZURE_MANUAL_CREDENTIAL_TYPE && !isPackageVersionValidForManualFields && (
<TemporaryManualSetup integrationLink={integrationLink} />
)}
{setupFormat === AZURE_MANUAL_CREDENTIAL_TYPE && isPackageVersionValidForManualFields && (
<>
<AzureCredentialTypeSelector
type={azureCredentialsType}
onChange={(optionId) => {
updatePolicy(
getPosturePolicy(newPolicy, input.type, {
'azure.credentials.type': { value: optionId },
})
);
}}
/>
<EuiSpacer size="m" />
<AzureInputVarFields
fields={fields}
onChange={(key, value) => {
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }));
}}
/>
<EuiSpacer size="m" />
{group.info}
<EuiSpacer size="m" />
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.csp.azureIntegration.manualCredentialType.documentaion"
defaultMessage="Read the {documentation} for more details"
values={{
documentation: (
<EuiLink
href={ARM_TEMPLATE_EXTERNAL_DOC_URL}
target="_blank"
rel="noopener nofollow noreferrer"
data-test-subj="externalLink"
>
{i18n.translate('xpack.csp.azureIntegration.documentationLinkText', {
defaultMessage: 'documentation',
})}
</EuiLink>
),
}}
/>
</EuiText>
</>
)}
<EuiSpacer />
</>

View file

@ -8,18 +8,32 @@
import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiText } from '@elastic/eui';
import { AzureCredentialsType } from '../../../../common/types';
export type AzureCredentialsFields = Record<string, { label: string; type?: 'password' | 'text' }>;
export interface AzureOptionValue {
label: string;
info: React.ReactNode;
info?: React.ReactNode;
fields: AzureCredentialsFields;
}
export type AzureOptions = Record<AzureCredentialsType, AzureOptionValue>;
export const getAzureCredentialsFormManualOptions = (): Array<{
value: AzureCredentialsType;
text: string;
}> => {
return Object.entries(getAzureCredentialsFormOptions())
.map(([key, value]) => ({
value: key as AzureCredentialsType,
text: value.label,
}))
.filter(({ value }) => value !== 'arm_template');
};
export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AzureCredentialsFields) =>
Object.entries(input.streams[0].vars || {})
.filter(([id]) => id in fields)
@ -33,19 +47,102 @@ export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AzureCr
} as const;
});
export const DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE = 'manual';
const I18N_TENANT_ID = i18n.translate('xpack.csp.azureIntegration.tenantIdLabel', {
defaultMessage: 'Tenant ID',
});
const I18N_CLIENT_ID = i18n.translate('xpack.csp.azureIntegration.clientIdLabel', {
defaultMessage: 'Client ID',
});
export const getAzureCredentialsFormOptions = (): AzureOptions => ({
managed_identity: {
label: i18n.translate('xpack.csp.azureIntegration.credentialType.managedIdentityLabel', {
defaultMessage: 'Managed Identity',
}),
info: (
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.csp.azureIntegration.credentialType.managedIdentityInfo"
defaultMessage="Ensure the agent is deployed on a resource that supports managed identities (e.g., Azure Virtual Machines). No explicit credentials need to be provided; Azure handles the authentication."
/>
</EuiText>
),
fields: {},
},
arm_template: {
label: 'ARM Template',
info: [],
fields: {},
},
service_principal_with_client_secret: {
label: i18n.translate('xpack.csp.azureIntegration.servicePrincipalWithClientSecretLabel', {
defaultMessage: 'Service principal with Client Secret',
}),
fields: {
'azure.credentials.tenant_id': { label: I18N_TENANT_ID },
'azure.credentials.client_id': { label: I18N_CLIENT_ID },
'azure.credentials.client_secret': {
type: 'password',
label: i18n.translate('xpack.csp.azureIntegration.clientSecretLabel', {
defaultMessage: 'Client Secret',
}),
},
},
},
service_principal_with_client_certificate: {
label: i18n.translate('xpack.csp.azureIntegration.servicePrincipalWithClientCertificateLabel', {
defaultMessage: 'Service principal with Client Certificate',
}),
fields: {
'azure.credentials.tenant_id': { label: I18N_TENANT_ID },
'azure.credentials.client_id': { label: I18N_CLIENT_ID },
'azure.credentials.client_certificate_path': {
label: i18n.translate('xpack.csp.azureIntegration.clientCertificatePathLabel', {
defaultMessage: 'Client Certificate Path',
}),
},
'azure.credentials.client_certificate_password': {
type: 'password',
label: i18n.translate('xpack.csp.azureIntegration.clientCertificatePasswordLabel', {
defaultMessage: 'Client Certificate Password',
}),
},
},
},
service_principal_with_client_username_and_password: {
label: i18n.translate(
'xpack.csp.azureIntegration.servicePrincipalWithClientUsernameAndPasswordLabel',
{ defaultMessage: 'Service principal with Client Username and Password' }
),
fields: {
'azure.credentials.tenant_id': { label: I18N_TENANT_ID },
'azure.credentials.client_id': { label: I18N_CLIENT_ID },
'azure.credentials.client_username': {
label: i18n.translate('xpack.csp.azureIntegration.clientUsernameLabel', {
defaultMessage: 'Client Username',
}),
},
'azure.credentials.client_password': {
type: 'password',
label: i18n.translate('xpack.csp.azureIntegration.clientPasswordLabel', {
defaultMessage: 'Client Password',
}),
},
},
},
manual: {
label: i18n.translate('xpack.csp.azureIntegration.credentialType.manualLabel', {
defaultMessage: 'Manual',
}),
info: [],
info: (
<EuiText color="subdued" size="s">
<FormattedMessage
id="xpack.csp.azureIntegration.credentialType.manualInfo"
defaultMessage="Ensure the agent is deployed on a resource that supports managed identities (e.g., Azure Virtual Machines). No explicit credentials need to be provided; Azure handles the authentication."
/>
</EuiText>
),
fields: {},
},
});

View file

@ -7,7 +7,10 @@
import { useEffect, useRef } from 'react';
import { NewPackagePolicy, PackageInfo } from '@kbn/fleet-plugin/common';
import { AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE } from './azure_credentials_form';
import {
AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
getDefaultAzureManualCredentialType,
} from './azure_credentials_form';
import { cspIntegrationDocsNavigation } from '../../../common/navigation/constants';
import {
getArmTemplateUrlFromCspmPackage,
@ -15,14 +18,28 @@ import {
NewPackagePolicyPostureInput,
} from '../utils';
import {
DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE,
getAzureCredentialsFormOptions,
getInputVarsFields,
} from './get_azure_credentials_form_options';
import { CLOUDBEAT_AZURE } from '../../../../common/constants';
import { AzureCredentialsType } from '../../../../common/types';
export type SetupFormat = AzureCredentialsType;
export type SetupFormat = 'arm_template' | 'manual';
const getSetupFormatFromInput = (
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>,
hasArmTemplateUrl: boolean
): SetupFormat => {
const credentialsType = getAzureCredentialsType(input);
if (!credentialsType && hasArmTemplateUrl) {
return 'arm_template';
}
if (credentialsType !== 'arm_template') {
return 'manual';
}
return 'arm_template';
};
const getAzureCredentialsType = (
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>
@ -107,7 +124,7 @@ export const useAzureCredentialsForm = ({
const hasArmTemplateUrl = !!getArmTemplateUrlFromCspmPackage(packageInfo);
const setupFormat = azureCredentialsType;
const setupFormat = getSetupFormatFromInput(input, hasArmTemplateUrl);
const group = options[azureCredentialsType];
const fields = getInputVarsFields(input, group.fields);
@ -134,6 +151,8 @@ export const useAzureCredentialsForm = ({
setupFormat,
});
const defaultAzureManualCredentialType = getDefaultAzureManualCredentialType(packageInfo);
const onSetupFormatChange = (newSetupFormat: SetupFormat) => {
if (newSetupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE) {
fieldsSnapshot.current = Object.fromEntries(
@ -155,7 +174,7 @@ export const useAzureCredentialsForm = ({
updatePolicy(
getPosturePolicy(newPolicy, input.type, {
'azure.credentials.type': {
value: lastManualCredentialsType.current || DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE,
value: lastManualCredentialsType.current || defaultAzureManualCredentialType,
type: 'text',
},
...fieldsSnapshot.current,

View file

@ -162,8 +162,15 @@ const getPolicyMock = (
};
const azureVarsMock = {
'azure.credentials.type': { value: 'arm_template', type: 'text' },
'azure.account_type': { type: 'text' },
'azure.credentials.type': { type: 'text' },
'azure.credentials.tenant_id': { type: 'text' },
'azure.credentials.client_id': { type: 'text' },
'azure.credentials.client_secret': { type: 'text' },
'azure.credentials.client_certificate_path': { type: 'text' },
'azure.credentials.client_certificate_password': { type: 'text' },
'azure.credentials.client_username': { type: 'text' },
'azure.credentials.client_password': { type: 'text' },
};
const dataStream = { type: 'logs', dataset: 'cloud_security_posture.findings' };

View file

@ -1330,6 +1330,27 @@ describe('<CspPolicyTemplateForm />', () => {
).toBeInTheDocument();
});
it(`doesnt render ${CLOUDBEAT_AZURE} Manual fields when version is not at least version 1.7.0`, () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'manual' },
'azure.account_type': { value: 'single-account' },
});
const { queryByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.6.0')} />
);
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
expect(
queryByRole('option', { name: 'Service principal with Client Secret', selected: true })
).not.toBeInTheDocument();
});
it(`selects default ${CLOUDBEAT_AZURE} fields`, () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
@ -1344,5 +1365,242 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
});
it(`renders ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, () => {
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')} />
);
expect(
getByRole('option', { name: 'Service principal with Client Secret', selected: true })
).toBeInTheDocument();
expect(getByLabelText('Tenant ID')).toBeInTheDocument();
expect(getByLabelText('Client ID')).toBeInTheDocument();
expect(getByLabelText('Client Secret')).toBeInTheDocument();
});
it(`updates ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`, () => {
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')} />
);
userEvent.type(getByLabelText('Tenant ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.tenant_id': { value: 'a' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client ID'), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_id': { value: 'b' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client Secret'), 'c');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_secret': { value: 'c' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
});
});
it(`renders Service principal with Client Certificate fields`, () => {
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')} />
);
expect(
getByRole('option', { name: 'Service principal with Client Certificate', selected: true })
).toBeInTheDocument();
expect(getByLabelText('Tenant ID')).toBeInTheDocument();
expect(getByLabelText('Client ID')).toBeInTheDocument();
expect(getByLabelText('Client Certificate Path')).toBeInTheDocument();
expect(getByLabelText('Client Certificate Password')).toBeInTheDocument();
});
it(`updates Service principal with Client Certificate fields`, () => {
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')} />
);
userEvent.type(getByLabelText('Tenant ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.tenant_id': { value: 'a' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client ID'), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_id': { value: 'b' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client Certificate Path'), 'c');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_certificate_path': { value: 'c' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client Certificate Password'), 'd');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_certificate_password': { value: 'd' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
});
it(`renders Service principal with Client Username and Password fields`, () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_username_and_password' },
});
const { getByLabelText, getByRole } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
expect(
getByRole('option', {
name: 'Service principal with Client Username and Password',
selected: true,
})
).toBeInTheDocument();
expect(getByLabelText('Tenant ID')).toBeInTheDocument();
expect(getByLabelText('Client ID')).toBeInTheDocument();
expect(getByLabelText('Client Username')).toBeInTheDocument();
expect(getByLabelText('Client Password')).toBeInTheDocument();
});
it(`updates Service principal with Client Username and Password fields`, () => {
let policy = getMockPolicyAzure();
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.type': { value: 'service_principal_with_client_username_and_password' },
});
const { rerender, getByLabelText } = render(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Tenant ID'), 'a');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.tenant_id': { value: 'a' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client ID'), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_id': { value: 'b' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client Username'), 'c');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_username': { value: 'c' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
rerender(
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.7.0')} />
);
userEvent.type(getByLabelText('Client Password'), 'd');
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
'azure.credentials.client_password': { value: 'd' },
});
expect(onChange).toHaveBeenCalledWith({
isValid: true,
updatedPolicy: policy,
});
});
});