Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cloud Security] Azure integration manual fields #171069

Merged
merged 14 commits into from
Nov 29, 2023
Merged
23 changes: 20 additions & 3 deletions x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
VulnSeverity,
AwsCredentialsTypeFieldMap,
GcpCredentialsTypeFieldMap,
AzureCredentialsTypeFieldMap,
} from './types';

export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
Expand Down Expand Up @@ -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: [],
};
8 changes: 7 additions & 1 deletion x-pack/plugins/cloud_security_posture/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
*/

import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks';
import { getBenchmarkFromPackagePolicy, getBenchmarkFilter, cleanupCredentials } from './helpers';
import {
getBenchmarkFromPackagePolicy,
getBenchmarkFilter,
cleanupCredentials,
mapCredentialsOptionsToFieldMap,
} from './helpers';

describe('test helper methods', () => {
it('get default integration type from inputs with multiple enabled types', () => {
Expand Down Expand Up @@ -296,3 +301,50 @@ describe('test helper methods', () => {
});
});
});

describe('mapCredentialsOptionsToFieldMap', () => {
it('should return an empty object when given an empty object as input', () => {
const options = {};
const result = mapCredentialsOptionsToFieldMap(options);
expect(result).toEqual({});
});

it('should return a field map with the same keys as the input object', () => {
const options = {
option1: { fields: {} },
option2: { fields: {} },
option3: { fields: {} },
};
const result = mapCredentialsOptionsToFieldMap(options);
expect(Object.keys(result)).toEqual(Object.keys(options));
});

it('should return a field map with the correct fields for each option', () => {
const options = {
option1: { fields: { field1: 'value1', field2: 'value2' } },
option2: { fields: { field3: 'value3', field4: 'value4' } },
option3: { fields: { field5: 'value5', field6: 'value6', field7: 'value7' } },
};
const result = mapCredentialsOptionsToFieldMap(options);
expect(result).toEqual({
option1: ['field1', 'field2'],
option2: ['field3', 'field4'],
option3: ['field5', 'field6', 'field7'],
});
});

it('should return an empty object when given null as input', () => {
const options = null as any;
const result = mapCredentialsOptionsToFieldMap(options);
expect(result).toEqual({});
});

it('should return an empty object when given an object with null or undefined fields as input', () => {
const options = {
option1: { fields: null },
option2: { fields: undefined },
} as any;
const result = mapCredentialsOptionsToFieldMap(options);
expect(result).toEqual({});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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">
Expand Down Expand Up @@ -206,6 +246,41 @@ const ManualSetup = ({ integrationLink }: { integrationLink: string }) => {
};

const AZURE_MINIMUM_PACKAGE_VERSION = '1.6.0';
const AZURE_MANUAL_FIELDS_PACKAGE_VERSION = '1.7.0';

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,
Expand All @@ -216,15 +291,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) {
Expand All @@ -238,6 +320,10 @@ export const AzureCredentialsForm = ({
cleanPackageVersion,
AZURE_MINIMUM_PACKAGE_VERSION
);
const isPackageVersionValidForManualFields = !semverLt(
cleanPackageVersion,
AZURE_MANUAL_FIELDS_PACKAGE_VERSION
);

useEffect(() => {
setIsValid(isPackageVersionValidForAzure);
Expand Down Expand Up @@ -280,8 +366,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 />
</>
Expand Down
Loading