mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Cloud Security] Azure - AMR Template form (#166910)
This commit is contained in:
parent
f3f1eec08e
commit
594cb14a28
36 changed files with 1370 additions and 124 deletions
|
@ -10,6 +10,7 @@ import {
|
|||
VulnSeverity,
|
||||
AwsCredentialsTypeFieldMap,
|
||||
GcpCredentialsTypeFieldMap,
|
||||
AzureCredentialsTypeFieldMap,
|
||||
} from './types';
|
||||
|
||||
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
|
||||
|
@ -156,3 +157,8 @@ export const GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP: GcpCredentialsTypeFieldMap = {
|
|||
'credentials-file': ['gcp.credentials.file'],
|
||||
'credentials-json': ['gcp.credentials.json'],
|
||||
};
|
||||
|
||||
export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP: AzureCredentialsTypeFieldMap = {
|
||||
manual: [],
|
||||
arm_template: [],
|
||||
};
|
||||
|
|
|
@ -30,6 +30,12 @@ export type GcpCredentialsTypeFieldMap = {
|
|||
[key in GcpCredentialsType]: string[];
|
||||
};
|
||||
|
||||
export type AzureCredentialsType = 'arm_template' | 'manual';
|
||||
|
||||
export type AzureCredentialsTypeFieldMap = {
|
||||
[key in AzureCredentialsType]: string[];
|
||||
};
|
||||
|
||||
export type Evaluation = 'passed' | 'failed' | 'NA';
|
||||
|
||||
export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all';
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE,
|
||||
AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP,
|
||||
GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP,
|
||||
AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP,
|
||||
} from '../constants';
|
||||
import type {
|
||||
BenchmarkId,
|
||||
|
@ -27,6 +28,7 @@ import type {
|
|||
BaseCspSetupStatus,
|
||||
AwsCredentialsType,
|
||||
GcpCredentialsType,
|
||||
AzureCredentialsType,
|
||||
RuleSection,
|
||||
} from '../types';
|
||||
|
||||
|
@ -119,6 +121,8 @@ export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePacka
|
|||
enabledInput?.streams?.[0].vars?.['aws.credentials.type']?.value;
|
||||
const gcpCredentialType: GcpCredentialsType | undefined =
|
||||
enabledInput?.streams?.[0].vars?.['gcp.credentials.type']?.value;
|
||||
const azureCredentialType: AzureCredentialsType | undefined =
|
||||
enabledInput?.streams?.[0].vars?.['azure.credentials.type']?.value;
|
||||
|
||||
if (awsCredentialType || gcpCredentialType) {
|
||||
let credsToKeep: string[] = [' '];
|
||||
|
@ -129,6 +133,9 @@ export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePacka
|
|||
} else if (gcpCredentialType) {
|
||||
credsToKeep = GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP[gcpCredentialType];
|
||||
credFields = Object.values(GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
|
||||
} else if (azureCredentialType) {
|
||||
credsToKeep = AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP[azureCredentialType];
|
||||
credFields = Object.values(AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
|
||||
}
|
||||
|
||||
if (credsToKeep) {
|
||||
|
|
|
@ -95,6 +95,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
|
|||
icon: googleCloudLogo,
|
||||
isBeta: true,
|
||||
},
|
||||
// needs to be a function that disables/enabled based on integration version
|
||||
{
|
||||
type: CLOUDBEAT_AZURE,
|
||||
name: i18n.translate('xpack.csp.cspmIntegration.azureOption.nameTitle', {
|
||||
|
@ -103,11 +104,9 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
|
|||
benchmark: i18n.translate('xpack.csp.cspmIntegration.azureOption.benchmarkTitle', {
|
||||
defaultMessage: 'CIS Azure',
|
||||
}),
|
||||
disabled: true,
|
||||
disabled: false,
|
||||
isBeta: true,
|
||||
icon: 'logoAzure',
|
||||
tooltip: i18n.translate('xpack.csp.cspmIntegration.azureOption.tooltipContent', {
|
||||
defaultMessage: 'Coming soon',
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import { EuiLink, EuiSpacer, EuiText, EuiTitle, EuiCallOut, EuiHorizontalRule } 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';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import semverValid from 'semver/functions/valid';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import { SetupFormat, useAzureCredentialsForm } from './hooks';
|
||||
import { NewPackagePolicyPostureInput } from '../utils';
|
||||
import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group';
|
||||
|
||||
interface AzureSetupInfoContentProps {
|
||||
integrationLink: string;
|
||||
}
|
||||
|
||||
export const AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE = 'arm_template';
|
||||
export const AZURE_MANUAL_CREDENTIAL_TYPE = 'manual';
|
||||
|
||||
const AzureSetupInfoContent = ({ integrationLink }: AzureSetupInfoContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<EuiHorizontalRule margin="xl" />
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.setupInfoContentTitle"
|
||||
defaultMessage="Setup Access"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.gettingStarted.setupInfoContent"
|
||||
defaultMessage="Utilize an Azure Resource Manager (ARM) template (a built-in Azure IaC tool) or a series of manual steps to set up and deploy CSPM for assessing your Azure environment's security posture. Refer to our {gettingStartedLink} for details."
|
||||
values={{
|
||||
gettingStartedLink: (
|
||||
<EuiLink href={integrationLink} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.gettingStarted.setupInfoContentLink"
|
||||
defaultMessage="Getting Started guide"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getSetupFormatOptions = (): CspRadioOption[] => [
|
||||
{
|
||||
id: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
|
||||
label: 'ARM Template',
|
||||
},
|
||||
{
|
||||
id: AZURE_MANUAL_CREDENTIAL_TYPE,
|
||||
label: i18n.translate('xpack.csp.azureIntegration.setupFormatOptions.manual', {
|
||||
defaultMessage: 'Manual',
|
||||
}),
|
||||
disabled: true,
|
||||
tooltip: i18n.translate(
|
||||
'xpack.csp.azureIntegration.setupFormatOptions.manual.disabledTooltip',
|
||||
{ defaultMessage: 'Coming Soon' }
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
newPolicy: NewPackagePolicy;
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>;
|
||||
updatePolicy(updatedPolicy: NewPackagePolicy): void;
|
||||
packageInfo: PackageInfo;
|
||||
onChange: any;
|
||||
setIsValid: (isValid: boolean) => void;
|
||||
}
|
||||
|
||||
const ARM_TEMPLATE_EXTERNAL_DOC_URL =
|
||||
'https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/';
|
||||
|
||||
const ArmTemplateSetup = ({
|
||||
hasArmTemplateUrl,
|
||||
input,
|
||||
}: {
|
||||
hasArmTemplateUrl: boolean;
|
||||
input: NewPackagePolicyInput;
|
||||
}) => {
|
||||
if (!hasArmTemplateUrl) {
|
||||
return (
|
||||
<EuiCallOut color="warning">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.armTemplateSetupStep.notSupported"
|
||||
defaultMessage="ARM Template is not supported on the current Integration version, please upgrade your integration to the latest version to use ARM Template"
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText color="subdued" size="s">
|
||||
<ol
|
||||
css={css`
|
||||
list-style: auto;
|
||||
`}
|
||||
>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.armTemplateSetupStep.login"
|
||||
defaultMessage="Log in to your Azure portal."
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.armTemplateSetupStep.save"
|
||||
defaultMessage="Click the Save and continue button on the bottom right of this page."
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.armTemplateSetupStep.launch"
|
||||
defaultMessage="On the subsequent pop-up modal, copy the relevant Bash command, then click on the Launch ARM Template button."
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.armTemplateSetupNote"
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AZURE_MINIMUM_PACKAGE_VERSION = '1.6.0';
|
||||
|
||||
export const AzureCredentialsForm = ({
|
||||
input,
|
||||
newPolicy,
|
||||
updatePolicy,
|
||||
packageInfo,
|
||||
onChange,
|
||||
setIsValid,
|
||||
}: Props) => {
|
||||
const { setupFormat, onSetupFormatChange, integrationLink, hasArmTemplateUrl } =
|
||||
useAzureCredentialsForm({
|
||||
newPolicy,
|
||||
input,
|
||||
packageInfo,
|
||||
onChange,
|
||||
setIsValid,
|
||||
updatePolicy,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupFormat) {
|
||||
onSetupFormatChange(AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE);
|
||||
}
|
||||
}, [setupFormat, onSetupFormatChange]);
|
||||
|
||||
const packageSemanticVersion = semverValid(packageInfo.version);
|
||||
const cleanPackageVersion = semverCoerce(packageSemanticVersion) || '';
|
||||
const isPackageVersionValidForAzure = !semverLt(
|
||||
cleanPackageVersion,
|
||||
AZURE_MINIMUM_PACKAGE_VERSION
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsValid(isPackageVersionValidForAzure);
|
||||
|
||||
onChange({
|
||||
isValid: isPackageVersionValidForAzure,
|
||||
updatedPolicy: newPolicy,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [input, packageInfo, setupFormat]);
|
||||
|
||||
if (!isPackageVersionValidForAzure) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiCallOut color="warning">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.azureIntegration.azureNotSupportedMessage"
|
||||
defaultMessage="CIS Azure is not supported on the current Integration version, please upgrade your integration to the latest version to use CIS Azure"
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AzureSetupInfoContent integrationLink={integrationLink} />
|
||||
<EuiSpacer size="l" />
|
||||
<RadioGroup
|
||||
size="m"
|
||||
options={getSetupFormatOptions()}
|
||||
idSelected={setupFormat}
|
||||
onChange={(idSelected: SetupFormat) =>
|
||||
idSelected !== setupFormat && onSetupFormatChange(idSelected)
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
{setupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE && (
|
||||
<ArmTemplateSetup hasArmTemplateUrl={hasArmTemplateUrl} input={input} />
|
||||
)}
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AzureCredentialsType } from '../../../../common/types';
|
||||
|
||||
export type AzureCredentialsFields = Record<string, { label: string; type?: 'password' | 'text' }>;
|
||||
|
||||
export interface AzureOptionValue {
|
||||
label: string;
|
||||
info: React.ReactNode;
|
||||
fields: AzureCredentialsFields;
|
||||
}
|
||||
|
||||
export type AzureOptions = Record<AzureCredentialsType, AzureOptionValue>;
|
||||
|
||||
export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AzureCredentialsFields) =>
|
||||
Object.entries(input.streams[0].vars || {})
|
||||
.filter(([id]) => id in fields)
|
||||
.map(([id, inputVar]) => {
|
||||
const field = fields[id];
|
||||
return {
|
||||
id,
|
||||
label: field.label,
|
||||
type: field.type || 'text',
|
||||
value: inputVar.value,
|
||||
} as const;
|
||||
});
|
||||
|
||||
export const DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE = 'manual';
|
||||
|
||||
export const getAzureCredentialsFormOptions = (): AzureOptions => ({
|
||||
arm_template: {
|
||||
label: 'ARM Template',
|
||||
info: [],
|
||||
fields: {},
|
||||
},
|
||||
manual: {
|
||||
label: i18n.translate('xpack.csp.azureIntegration.credentialType.manualLabel', {
|
||||
defaultMessage: 'Manual',
|
||||
}),
|
||||
info: [],
|
||||
fields: {},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { NewPackagePolicy, PackageInfo } from '@kbn/fleet-plugin/common';
|
||||
import { AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE } from './azure_credentials_form';
|
||||
import { cspIntegrationDocsNavigation } from '../../../common/navigation/constants';
|
||||
import {
|
||||
getArmTemplateUrlFromCspmPackage,
|
||||
getPosturePolicy,
|
||||
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;
|
||||
|
||||
const getAzureCredentialsType = (
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>
|
||||
): AzureCredentialsType | undefined => input.streams[0].vars?.['azure.credentials.type']?.value;
|
||||
|
||||
const getAzureArmTemplateUrl = (newPolicy: NewPackagePolicy) => {
|
||||
const template: string | undefined = newPolicy?.inputs?.find((i) => i.type === CLOUDBEAT_AZURE)
|
||||
?.config?.arm_template_url?.value;
|
||||
|
||||
return template || undefined;
|
||||
};
|
||||
|
||||
const updateAzureArmTemplateUrlInPolicy = (
|
||||
newPolicy: NewPackagePolicy,
|
||||
updatePolicy: (policy: NewPackagePolicy) => void,
|
||||
templateUrl: string | undefined
|
||||
) => {
|
||||
updatePolicy?.({
|
||||
...newPolicy,
|
||||
inputs: newPolicy.inputs.map((input) => {
|
||||
if (input.type === CLOUDBEAT_AZURE) {
|
||||
return {
|
||||
...input,
|
||||
config: { arm_template_url: { value: templateUrl } },
|
||||
};
|
||||
}
|
||||
return input;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const useUpdateAzureArmTemplate = ({
|
||||
packageInfo,
|
||||
newPolicy,
|
||||
updatePolicy,
|
||||
setupFormat,
|
||||
}: {
|
||||
packageInfo: PackageInfo;
|
||||
newPolicy: NewPackagePolicy;
|
||||
updatePolicy: (policy: NewPackagePolicy) => void;
|
||||
setupFormat: SetupFormat;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const azureArmTemplateUrl = getAzureArmTemplateUrl(newPolicy);
|
||||
|
||||
if (setupFormat === 'manual') {
|
||||
if (!!azureArmTemplateUrl) {
|
||||
updateAzureArmTemplateUrlInPolicy(newPolicy, updatePolicy, undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const templateUrl = getArmTemplateUrlFromCspmPackage(packageInfo);
|
||||
|
||||
if (templateUrl === '') return;
|
||||
|
||||
if (azureArmTemplateUrl === templateUrl) return;
|
||||
|
||||
updateAzureArmTemplateUrlInPolicy(newPolicy, updatePolicy, templateUrl);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [newPolicy?.vars?.arm_template_url, newPolicy, packageInfo, setupFormat]);
|
||||
};
|
||||
|
||||
export const useAzureCredentialsForm = ({
|
||||
newPolicy,
|
||||
input,
|
||||
packageInfo,
|
||||
onChange,
|
||||
setIsValid,
|
||||
updatePolicy,
|
||||
}: {
|
||||
newPolicy: NewPackagePolicy;
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>;
|
||||
packageInfo: PackageInfo;
|
||||
onChange: (opts: any) => void;
|
||||
setIsValid: (isValid: boolean) => void;
|
||||
updatePolicy: (updatedPolicy: NewPackagePolicy) => void;
|
||||
}) => {
|
||||
const azureCredentialsType: AzureCredentialsType =
|
||||
getAzureCredentialsType(input) || AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE;
|
||||
|
||||
const options = getAzureCredentialsFormOptions();
|
||||
|
||||
const hasArmTemplateUrl = !!getArmTemplateUrlFromCspmPackage(packageInfo);
|
||||
|
||||
const setupFormat = azureCredentialsType;
|
||||
|
||||
const group = options[azureCredentialsType];
|
||||
const fields = getInputVarsFields(input, group.fields);
|
||||
const fieldsSnapshot = useRef({});
|
||||
const lastManualCredentialsType = useRef<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const isInvalid = setupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE && !hasArmTemplateUrl;
|
||||
setIsValid(!isInvalid);
|
||||
|
||||
onChange({
|
||||
isValid: !isInvalid,
|
||||
updatedPolicy: newPolicy,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [setupFormat, input.type]);
|
||||
|
||||
const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath;
|
||||
|
||||
useUpdateAzureArmTemplate({
|
||||
packageInfo,
|
||||
newPolicy,
|
||||
updatePolicy,
|
||||
setupFormat,
|
||||
});
|
||||
|
||||
const onSetupFormatChange = (newSetupFormat: SetupFormat) => {
|
||||
if (newSetupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE) {
|
||||
fieldsSnapshot.current = Object.fromEntries(
|
||||
fields?.map((field) => [field.id, { value: field.value }])
|
||||
);
|
||||
|
||||
lastManualCredentialsType.current = getAzureCredentialsType(input);
|
||||
|
||||
updatePolicy(
|
||||
getPosturePolicy(newPolicy, input.type, {
|
||||
'azure.credentials.type': {
|
||||
value: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
|
||||
type: 'text',
|
||||
},
|
||||
...Object.fromEntries(fields?.map((field) => [field.id, { value: undefined }])),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
updatePolicy(
|
||||
getPosturePolicy(newPolicy, input.type, {
|
||||
'azure.credentials.type': {
|
||||
value: lastManualCredentialsType.current || DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE,
|
||||
type: 'text',
|
||||
},
|
||||
...fieldsSnapshot.current,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
azureCredentialsType,
|
||||
setupFormat,
|
||||
group,
|
||||
fields,
|
||||
integrationLink,
|
||||
hasArmTemplateUrl,
|
||||
onSetupFormatChange,
|
||||
};
|
||||
};
|
|
@ -17,7 +17,7 @@ export interface CspRadioGroupProps {
|
|||
size?: 's' | 'm';
|
||||
}
|
||||
|
||||
interface CspRadioOption {
|
||||
export interface CspRadioOption {
|
||||
disabled?: boolean;
|
||||
id: string;
|
||||
label: string;
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { PostureInput } from '../../../common/types';
|
|||
|
||||
export const getMockPolicyAWS = () => getPolicyMock(CLOUDBEAT_AWS, 'cspm', 'aws');
|
||||
export const getMockPolicyGCP = () => getPolicyMock(CLOUDBEAT_GCP, 'cspm', 'gcp');
|
||||
export const getMockPolicyAzure = () => getPolicyMock(CLOUDBEAT_AZURE, 'cspm', 'azure');
|
||||
export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA, 'kspm', 'self_managed');
|
||||
export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS, 'kspm', 'eks');
|
||||
export const getMockPolicyVulnMgmtAWS = () =>
|
||||
|
@ -102,6 +103,28 @@ export const getMockPackageInfoCspmGCP = (packageVersion = '1.5.2') => {
|
|||
} as PackageInfo;
|
||||
};
|
||||
|
||||
export const getMockPackageInfoCspmAzure = (packageVersion = '1.6.0') => {
|
||||
return {
|
||||
version: packageVersion,
|
||||
name: 'cspm',
|
||||
policy_templates: [
|
||||
{
|
||||
title: '',
|
||||
description: '',
|
||||
name: 'cspm',
|
||||
inputs: [
|
||||
{
|
||||
type: CLOUDBEAT_AZURE,
|
||||
title: 'Azure',
|
||||
description: '',
|
||||
vars: [{}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as PackageInfo;
|
||||
};
|
||||
|
||||
const getPolicyMock = (
|
||||
type: PostureInput,
|
||||
posture: string,
|
||||
|
@ -136,6 +159,11 @@ const getPolicyMock = (
|
|||
'gcp.credentials.type': { type: 'text' },
|
||||
};
|
||||
|
||||
const azureVarsMock = {
|
||||
'azure.account_type': { type: 'text' },
|
||||
'azure.credentials.type': { type: 'text' },
|
||||
};
|
||||
|
||||
const dataStream = { type: 'logs', dataset: 'cloud_security_posture.findings' };
|
||||
|
||||
return {
|
||||
|
@ -182,7 +210,9 @@ const getPolicyMock = (
|
|||
type: CLOUDBEAT_AZURE,
|
||||
policy_template: 'cspm',
|
||||
enabled: false,
|
||||
streams: [{ enabled: false, data_stream: dataStream }],
|
||||
streams: [
|
||||
{ enabled: type === CLOUDBEAT_AZURE, data_stream: dataStream, vars: azureVarsMock },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: CLOUDBEAT_VULN_MGMT_AWS,
|
||||
|
|
|
@ -14,9 +14,11 @@ import {
|
|||
import { TestProvider } from '../../test/test_provider';
|
||||
import {
|
||||
getMockPackageInfoCspmAWS,
|
||||
getMockPackageInfoCspmAzure,
|
||||
getMockPackageInfoCspmGCP,
|
||||
getMockPackageInfoVulnMgmtAWS,
|
||||
getMockPolicyAWS,
|
||||
getMockPolicyAzure,
|
||||
getMockPolicyEKS,
|
||||
getMockPolicyGCP,
|
||||
getMockPolicyK8s,
|
||||
|
@ -30,7 +32,12 @@ import type {
|
|||
} from '@kbn/fleet-plugin/common';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { getPosturePolicy } from './utils';
|
||||
import { CLOUDBEAT_AWS, CLOUDBEAT_EKS, CLOUDBEAT_GCP } from '../../../common/constants';
|
||||
import {
|
||||
CLOUDBEAT_AWS,
|
||||
CLOUDBEAT_AZURE,
|
||||
CLOUDBEAT_EKS,
|
||||
CLOUDBEAT_GCP,
|
||||
} from '../../../common/constants';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
|
@ -205,7 +212,7 @@ describe('<CspPolicyTemplateForm />', () => {
|
|||
expect(option3).toBeInTheDocument();
|
||||
expect(option1).toBeEnabled();
|
||||
expect(option2).toBeEnabled();
|
||||
expect(option3).toBeDisabled();
|
||||
expect(option3).toBeEnabled();
|
||||
expect(option1).toBeChecked();
|
||||
});
|
||||
|
||||
|
@ -1130,4 +1137,44 @@ describe('<CspPolicyTemplateForm />', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Azure Credentials input fields', () => {
|
||||
it(`renders ${CLOUDBEAT_AZURE} Not supported when version is not at least version 1.6.0`, () => {
|
||||
let policy = getMockPolicyAzure();
|
||||
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
|
||||
'azure.credentials.type': { value: 'arm_template' },
|
||||
'azure.account_type': { value: 'single-account-azure' },
|
||||
});
|
||||
|
||||
const { getByText } = render(
|
||||
<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure('1.5.0')} />
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
isValid: false,
|
||||
updatedPolicy: policy,
|
||||
});
|
||||
|
||||
expect(
|
||||
getByText(
|
||||
'CIS Azure is not supported on the current Integration version, please upgrade your integration to the latest version to use CIS Azure'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it(`selects default ${CLOUDBEAT_AZURE} fields`, () => {
|
||||
let policy = getMockPolicyAzure();
|
||||
policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
|
||||
'azure.credentials.type': { value: 'arm_template' },
|
||||
'azure.account_type': { value: 'single-account-azure' },
|
||||
});
|
||||
|
||||
render(<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmAzure()} />);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
isValid: true,
|
||||
updatedPolicy: policy,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
|||
import { PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE } from './azure_credentials_form/azure_credentials_form';
|
||||
import { CspRadioGroupProps, RadioGroup } from './csp_boxed_radio_group';
|
||||
import { assert } from '../../../common/utils/helpers';
|
||||
import type { PostureInput, CloudSecurityPolicyTemplate } from '../../../common/types';
|
||||
|
@ -83,7 +84,11 @@ export const AWS_SINGLE_ACCOUNT = 'single-account';
|
|||
export const AWS_ORGANIZATION_ACCOUNT = 'organization-account';
|
||||
export const GCP_SINGLE_ACCOUNT = 'single-account-gcp';
|
||||
export const GCP_ORGANIZATION_ACCOUNT = 'organization-account-gcp';
|
||||
export const AZURE_SINGLE_ACCOUNT = 'single-account-azure';
|
||||
export const AZURE_ORGANIZATION_ACCOUNT = 'organization-account-azure';
|
||||
|
||||
type AwsAccountType = typeof AWS_SINGLE_ACCOUNT | typeof AWS_ORGANIZATION_ACCOUNT;
|
||||
type AzureAccountType = typeof AZURE_SINGLE_ACCOUNT | typeof AZURE_ORGANIZATION_ACCOUNT;
|
||||
|
||||
const getAwsAccountTypeOptions = (isAwsOrgDisabled: boolean): CspRadioGroupProps['options'] => [
|
||||
{
|
||||
|
@ -128,6 +133,28 @@ const getGcpAccountTypeOptions = (): CspRadioGroupProps['options'] => [
|
|||
},
|
||||
];
|
||||
|
||||
const getAzureAccountTypeOptions = (): CspRadioGroupProps['options'] => [
|
||||
{
|
||||
id: AZURE_ORGANIZATION_ACCOUNT,
|
||||
label: i18n.translate('xpack.csp.fleetIntegration.azureAccountType.azureOrganizationLabel', {
|
||||
defaultMessage: 'Azure Organization',
|
||||
}),
|
||||
disabled: true,
|
||||
tooltip: i18n.translate(
|
||||
'xpack.csp.fleetIntegration.azureAccountType.azureOrganizationDisabledTooltip',
|
||||
{
|
||||
defaultMessage: 'Coming Soon',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
id: AZURE_SINGLE_ACCOUNT,
|
||||
label: i18n.translate('xpack.csp.fleetIntegration.azureAccountType.singleAccountLabel', {
|
||||
defaultMessage: 'Single Subscription',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const getAwsAccountType = (
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_aws' }>
|
||||
): AwsAccountType | undefined => input.streams[0].vars?.['aws.account_type']?.value;
|
||||
|
@ -175,7 +202,7 @@ const AwsAccountTypeSelect = ({
|
|||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.fleetIntegration.awsAccountTypeDescriptionLabel"
|
||||
defaultMessage="Select between single account or organization."
|
||||
defaultMessage="Select between single account or organization, and then fill in the name and description to help identify this integration."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
|
@ -277,6 +304,89 @@ const GcpAccountTypeSelect = ({
|
|||
);
|
||||
};
|
||||
|
||||
const getAzureAccountType = (
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>
|
||||
): AzureAccountType | undefined => input.streams[0].vars?.['azure.account_type']?.value;
|
||||
|
||||
const AzureAccountTypeSelect = ({
|
||||
input,
|
||||
newPolicy,
|
||||
updatePolicy,
|
||||
}: {
|
||||
input: Extract<NewPackagePolicyPostureInput, { type: 'cloudbeat/cis_azure' }>;
|
||||
newPolicy: NewPackagePolicy;
|
||||
updatePolicy: (updatedPolicy: NewPackagePolicy) => void;
|
||||
}) => {
|
||||
const azureAccountTypeOptions = getAzureAccountTypeOptions();
|
||||
|
||||
useEffect(() => {
|
||||
if (!getAzureAccountType(input)) {
|
||||
updatePolicy(
|
||||
getPosturePolicy(newPolicy, input.type, {
|
||||
'azure.account_type': {
|
||||
value: AZURE_SINGLE_ACCOUNT,
|
||||
type: 'text',
|
||||
},
|
||||
'azure.credentials.type': {
|
||||
value: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
|
||||
type: 'text',
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [input, updatePolicy]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.fleetIntegration.azureAccountTypeDescriptionLabel"
|
||||
defaultMessage="Select between onboarding an Azure Organization (tenant root group) or a single Azure subscription, and then fill in the name and description to help identify this integration."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<RadioGroup
|
||||
idSelected={getAzureAccountType(input) || ''}
|
||||
options={azureAccountTypeOptions}
|
||||
onChange={(accountType) => {
|
||||
updatePolicy(
|
||||
getPosturePolicy(newPolicy, input.type, {
|
||||
'azure.account_type': {
|
||||
value: accountType,
|
||||
type: 'text',
|
||||
},
|
||||
})
|
||||
);
|
||||
}}
|
||||
size="m"
|
||||
/>
|
||||
{getAzureAccountType(input) === AZURE_ORGANIZATION_ACCOUNT && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.fleetIntegration.azureAccountType.azureOrganizationDescription"
|
||||
defaultMessage="Connect Elastic to every Azure Subscription (current and future) in your environment by providing Elastic with read-only (configuration) access to your Azure Organization (tenant root group)."
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
{getAzureAccountType(input) === AZURE_SINGLE_ACCOUNT && (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.fleetIntegration.azureAccountType.singleAccountDescription"
|
||||
defaultMessage="Deploying to a single subscription is suitable for an initial POC. To ensure compete coverage, it is strongly recommended to deploy CSPM at the organization (tenant root group) level, which automatically connects all subscriptions (both current and future)."
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const IntegrationSettings = ({ onChange, fields }: IntegrationInfoFieldsProps) => (
|
||||
<div>
|
||||
{fields.map(({ value, id, label, error }) => (
|
||||
|
@ -303,7 +413,9 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
const input = getSelectedOption(newPolicy.inputs, integration);
|
||||
|
||||
const updatePolicy = useCallback(
|
||||
(updatedPolicy: NewPackagePolicy) => onChange({ isValid, updatedPolicy }),
|
||||
(updatedPolicy: NewPackagePolicy) => {
|
||||
onChange({ isValid, updatedPolicy });
|
||||
},
|
||||
[onChange, isValid]
|
||||
);
|
||||
/**
|
||||
|
@ -434,13 +546,6 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
/>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{/* Defines the name/description */}
|
||||
<IntegrationSettings
|
||||
fields={integrationFields}
|
||||
onChange={(field, value) => updatePolicy({ ...newPolicy, [field]: value })}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{/* AWS account type selection box */}
|
||||
{input.type === 'cloudbeat/cis_aws' && (
|
||||
<AwsAccountTypeSelect
|
||||
|
@ -460,6 +565,17 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
/>
|
||||
)}
|
||||
|
||||
{input.type === 'cloudbeat/cis_azure' && (
|
||||
<AzureAccountTypeSelect input={input} newPolicy={newPolicy} updatePolicy={updatePolicy} />
|
||||
)}
|
||||
|
||||
{/* Defines the name/description */}
|
||||
<EuiSpacer size="l" />
|
||||
<IntegrationSettings
|
||||
fields={integrationFields}
|
||||
onChange={(field, value) => updatePolicy({ ...newPolicy, [field]: value })}
|
||||
/>
|
||||
|
||||
{/* Defines the vars of the enabled input of the active policy template */}
|
||||
<PolicyTemplateVarsForm
|
||||
input={input}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
import type { PostureInput, CloudSecurityPolicyTemplate } from '../../../common/types';
|
||||
import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from './utils';
|
||||
import { RadioGroup } from './csp_boxed_radio_group';
|
||||
import { AzureCredentialsForm } from './azure_credentials_form/azure_credentials_form';
|
||||
import { AwsCredentialsForm } from './aws_credentials_form/aws_credentials_form';
|
||||
import { EksCredentialsForm } from './eks_credentials_form';
|
||||
import { GcpCredentialsForm } from './gcp_credential_form';
|
||||
|
@ -82,6 +83,8 @@ export const PolicyTemplateVarsForm = ({ input, ...props }: PolicyTemplateVarsFo
|
|||
return <EksCredentialsForm {...props} input={input} />;
|
||||
case 'cloudbeat/cis_gcp':
|
||||
return <GcpCredentialsForm {...props} input={input} />;
|
||||
case 'cloudbeat/cis_azure':
|
||||
return <AzureCredentialsForm {...props} input={input} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,6 @@ const getPostureInput = (
|
|||
enabled: isInputEnabled,
|
||||
// Merge new vars with existing vars
|
||||
...(isInputEnabled &&
|
||||
stream.vars &&
|
||||
inputVars && {
|
||||
vars: {
|
||||
...stream.vars,
|
||||
|
@ -183,6 +182,24 @@ export const getCspmCloudFormationDefaultValue = (packageInfo: PackageInfo): str
|
|||
return cloudFormationTemplate;
|
||||
};
|
||||
|
||||
export const getArmTemplateUrlFromCspmPackage = (packageInfo: PackageInfo): string => {
|
||||
if (!packageInfo.policy_templates) return '';
|
||||
|
||||
const policyTemplate = packageInfo.policy_templates.find((p) => p.name === CSPM_POLICY_TEMPLATE);
|
||||
if (!policyTemplate) return '';
|
||||
|
||||
const policyTemplateInputs = hasPolicyTemplateInputs(policyTemplate) && policyTemplate.inputs;
|
||||
if (!policyTemplateInputs) return '';
|
||||
|
||||
const armTemplateUrl = policyTemplateInputs.reduce((acc, input): string => {
|
||||
if (!input.vars) return acc;
|
||||
const template = input.vars.find((v) => v.name === 'arm_template_url')?.default;
|
||||
return template ? String(template) : acc;
|
||||
}, '');
|
||||
|
||||
return armTemplateUrl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Input vars that are hidden from the user
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { getAzureArmPropsFromPackagePolicy } from '../../../../../../../services/get_azure_arm_props_from_package_policy';
|
||||
|
||||
import { useCreateAzureArmTemplateUrl } from '../../../../../../../hooks/use_create_azure_arm_template_url';
|
||||
|
||||
import { AzureArmTemplateGuide } from '../../../../../../../components/azure_arm_template_guide';
|
||||
|
||||
import type { AgentPolicy, PackagePolicy } from '../../../../../types';
|
||||
import { sendGetEnrollmentAPIKeys } from '../../../../../hooks';
|
||||
|
||||
export const PostInstallAzureArmTemplateModal: React.FunctionComponent<{
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
agentPolicy: AgentPolicy;
|
||||
packagePolicy: PackagePolicy;
|
||||
}> = ({ onConfirm, onCancel, agentPolicy, packagePolicy }) => {
|
||||
const { data: apyKeysData } = useQuery(['cloudFormationApiKeys'], () =>
|
||||
sendGetEnrollmentAPIKeys({
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
kuery: `policy_id:${agentPolicy.id}`,
|
||||
})
|
||||
);
|
||||
|
||||
const azureArmTemplateProps = getAzureArmPropsFromPackagePolicy(packagePolicy);
|
||||
|
||||
const { azureArmTemplateUrl, error, isError, isLoading } = useCreateAzureArmTemplateUrl({
|
||||
enrollmentAPIKey: apyKeysData?.data?.items[0]?.api_key,
|
||||
azureArmTemplateProps,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiModal data-test-subj="postInstallAzureArmTemplateModal" onClose={onCancel}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle data-test-subj="confirmAzureArmTemplateModalTitleText">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicy.postInstallAzureArmTemplateModalModalTitle"
|
||||
defaultMessage="ARM Template deployment"
|
||||
/>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<AzureArmTemplateGuide azureAccountType={azureArmTemplateProps.azureAccountType} />
|
||||
{error && isError && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut title={error} color="danger" iconType="error" />
|
||||
</>
|
||||
)}
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="confirmAzureArmTemplateModalCancelButton"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicy.postInstallAzureArmTemplateModal.cancelButton"
|
||||
defaultMessage="Add ARM Template later"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
<EuiButton
|
||||
data-test-subj="confirmAzureArmTemplateModalConfirmButton"
|
||||
onClick={() => {
|
||||
window.open(azureArmTemplateUrl);
|
||||
onConfirm();
|
||||
}}
|
||||
fill
|
||||
color="primary"
|
||||
isLoading={isLoading}
|
||||
isDisabled={isError}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicy.postInstallAzureArmTemplateModalConfirmButtonLabel"
|
||||
defaultMessage="Launch ARM Template"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
|
||||
import { getAzureArmPropsFromPackagePolicy } from '../../../../../../../services/get_azure_arm_props_from_package_policy';
|
||||
|
||||
import type {
|
||||
AgentPolicy,
|
||||
NewPackagePolicy,
|
||||
|
@ -304,12 +306,21 @@ export function useOnSubmit({
|
|||
force,
|
||||
});
|
||||
|
||||
const hasAzureArmTemplate = data?.item
|
||||
? getAzureArmPropsFromPackagePolicy(data.item).templateUrl
|
||||
: false;
|
||||
|
||||
const hasCloudFormation = data?.item
|
||||
? getCloudFormationPropsFromPackagePolicy(data.item).templateUrl
|
||||
: false;
|
||||
|
||||
const hasGoogleCloudShell = data?.item ? getCloudShellUrlFromPackagePolicy(data.item) : false;
|
||||
|
||||
if (hasAzureArmTemplate) {
|
||||
setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_AZURE_ARM_TEMPLATE');
|
||||
} else {
|
||||
setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
|
||||
}
|
||||
if (hasCloudFormation) {
|
||||
setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_CLOUD_FORMATION');
|
||||
} else {
|
||||
|
@ -324,6 +335,10 @@ export function useOnSubmit({
|
|||
setSavedPackagePolicy(data!.item);
|
||||
|
||||
const hasAgentsAssigned = agentCount && agentPolicy;
|
||||
if (!hasAgentsAssigned && hasAzureArmTemplate) {
|
||||
setFormState('SUBMITTED_AZURE_ARM_TEMPLATE');
|
||||
return;
|
||||
}
|
||||
if (!hasAgentsAssigned && hasCloudFormation) {
|
||||
setFormState('SUBMITTED_CLOUD_FORMATION');
|
||||
return;
|
||||
|
|
|
@ -61,6 +61,7 @@ import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from '.
|
|||
import { useDevToolsRequest, useOnSubmit } from './hooks';
|
||||
import { PostInstallCloudFormationModal } from './components/post_install_cloud_formation_modal';
|
||||
import { PostInstallGoogleCloudShellModal } from './components/post_install_google_cloud_shell_modal';
|
||||
import { PostInstallAzureArmTemplateModal } from './components/post_install_azure_arm_template_modal';
|
||||
|
||||
const StepsWithLessPadding = styled(EuiSteps)`
|
||||
.euiStep__content {
|
||||
|
@ -415,6 +416,14 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
|
|||
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
|
||||
/>
|
||||
)}
|
||||
{formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' && agentPolicy && savedPackagePolicy && (
|
||||
<PostInstallAzureArmTemplateModal
|
||||
agentPolicy={agentPolicy}
|
||||
packagePolicy={savedPackagePolicy}
|
||||
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
|
||||
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
|
||||
/>
|
||||
)}
|
||||
{formState === 'SUBMITTED_CLOUD_FORMATION' && agentPolicy && savedPackagePolicy && (
|
||||
<PostInstallCloudFormationModal
|
||||
agentPolicy={agentPolicy}
|
||||
|
|
|
@ -22,6 +22,7 @@ export type PackagePolicyFormState =
|
|||
| 'LOADING'
|
||||
| 'SUBMITTED'
|
||||
| 'SUBMITTED_NO_AGENTS'
|
||||
| 'SUBMITTED_AZURE_ARM_TEMPLATE'
|
||||
| 'SUBMITTED_CLOUD_FORMATION'
|
||||
| 'SUBMITTED_GOOGLE_CLOUD_SHELL';
|
||||
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiSpacer, EuiCallOut, EuiSkeletonText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AzureArmTemplateGuide } from '../azure_arm_template_guide';
|
||||
|
||||
import { useCreateAzureArmTemplateUrl } from '../../hooks/use_create_azure_arm_template_url';
|
||||
|
||||
import type { CloudSecurityIntegration } from './types';
|
||||
|
||||
interface Props {
|
||||
enrollmentAPIKey?: string;
|
||||
cloudSecurityIntegration: CloudSecurityIntegration;
|
||||
}
|
||||
export const AzureArmTemplateInstructions: React.FunctionComponent<Props> = ({
|
||||
enrollmentAPIKey,
|
||||
cloudSecurityIntegration,
|
||||
}) => {
|
||||
const { isLoading, azureArmTemplateUrl, error, isError } = useCreateAzureArmTemplateUrl({
|
||||
enrollmentAPIKey,
|
||||
azureArmTemplateProps: cloudSecurityIntegration?.azureArmTemplateProps,
|
||||
});
|
||||
|
||||
if (error && isError) {
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut title={error} color="danger" iconType="error" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiSkeletonText
|
||||
lines={3}
|
||||
size="m"
|
||||
isLoading={isLoading || cloudSecurityIntegration?.isLoading}
|
||||
contentAriaLabel={i18n.translate(
|
||||
'xpack.fleet.agentEnrollment.azureArmTemplate.loadingAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Loading ARM Template instructions',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<AzureArmTemplateGuide
|
||||
azureAccountType={cloudSecurityIntegration?.azureArmTemplateProps?.azureAccountType}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
target="_blank"
|
||||
iconSide="left"
|
||||
iconType="launch"
|
||||
fullWidth
|
||||
href={azureArmTemplateUrl}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentEnrollment.azureArmTemplate.launchButton"
|
||||
defaultMessage="Launch ARM Template"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiSkeletonText>
|
||||
);
|
||||
};
|
|
@ -7,6 +7,10 @@
|
|||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS } from '../../services/get_template_url_from_package_info';
|
||||
|
||||
import { SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG } from '../../services/get_template_url_from_agent_policy';
|
||||
|
||||
import type { PackagePolicy, AgentPolicy } from '../../types';
|
||||
import { sendGetOneAgentPolicy, useGetPackageInfoByKeyQuery, useStartServices } from '../../hooks';
|
||||
import {
|
||||
|
@ -14,18 +18,16 @@ import {
|
|||
FLEET_CLOUD_SECURITY_POSTURE_PACKAGE,
|
||||
FLEET_CLOUD_DEFEND_PACKAGE,
|
||||
} from '../../../common';
|
||||
import { getCloudShellUrlFromAgentPolicy } from '../../services';
|
||||
import { getTemplateUrlFromPackageInfo, getCloudShellUrlFromAgentPolicy } from '../../services';
|
||||
|
||||
import {
|
||||
getCloudFormationTemplateUrlFromPackageInfo,
|
||||
getCloudFormationTemplateUrlFromAgentPolicy,
|
||||
} from '../../services';
|
||||
import { getTemplateUrlFromAgentPolicy } from '../../services';
|
||||
|
||||
import type {
|
||||
K8sMode,
|
||||
CloudSecurityIntegrationType,
|
||||
CloudSecurityIntegrationAwsAccountType,
|
||||
CloudSecurityIntegration,
|
||||
CloudSecurityIntegrationAzureAccountType,
|
||||
} from './types';
|
||||
|
||||
// Packages that requires custom elastic-agent manifest
|
||||
|
@ -99,6 +101,9 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
|
|||
{ enabled: Boolean(cloudSecurityPackagePolicy) }
|
||||
);
|
||||
|
||||
const AWS_ACCOUNT_TYPE = 'aws.account_type';
|
||||
const AZURE_ACCOUNT_TYPE = 'azure.account_type';
|
||||
|
||||
const cloudSecurityIntegration: CloudSecurityIntegration | undefined = useMemo(() => {
|
||||
if (!agentPolicy || !cloudSecurityPackagePolicy) {
|
||||
return undefined;
|
||||
|
@ -109,8 +114,15 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
|
|||
|
||||
if (!integrationType) return undefined;
|
||||
|
||||
const cloudFormationTemplateFromAgentPolicy =
|
||||
getCloudFormationTemplateUrlFromAgentPolicy(agentPolicy);
|
||||
const cloudFormationTemplateFromAgentPolicy = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
|
||||
agentPolicy
|
||||
);
|
||||
|
||||
const azureArmTemplateFromAgentPolicy = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.ARM_TEMPLATE,
|
||||
agentPolicy
|
||||
);
|
||||
|
||||
// Use the latest CloudFormation template for the current version
|
||||
// So it guarantee that the template version matches the integration version
|
||||
|
@ -118,16 +130,31 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
|
|||
// In case it can't find the template for the current version,
|
||||
// it will fallback to the one from the agent policy.
|
||||
const cloudFormationTemplateUrl = packageInfoData?.item
|
||||
? getCloudFormationTemplateUrlFromPackageInfo(packageInfoData.item, integrationType)
|
||||
? getTemplateUrlFromPackageInfo(
|
||||
packageInfoData.item,
|
||||
integrationType,
|
||||
SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION
|
||||
)
|
||||
: cloudFormationTemplateFromAgentPolicy;
|
||||
|
||||
const AWS_ACCOUNT_TYPE = 'aws.account_type';
|
||||
|
||||
const cloudFormationAwsAccountType: CloudSecurityIntegrationAwsAccountType | undefined =
|
||||
cloudSecurityPackagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[
|
||||
AWS_ACCOUNT_TYPE
|
||||
]?.value;
|
||||
|
||||
const azureArmTemplateUrl = packageInfoData?.item
|
||||
? getTemplateUrlFromPackageInfo(
|
||||
packageInfoData.item,
|
||||
integrationType,
|
||||
SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.ARM_TEMPLATE
|
||||
)
|
||||
: azureArmTemplateFromAgentPolicy;
|
||||
|
||||
const azureArmTemplateAccountType: CloudSecurityIntegrationAzureAccountType | undefined =
|
||||
cloudSecurityPackagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[
|
||||
AZURE_ACCOUNT_TYPE
|
||||
]?.value;
|
||||
|
||||
const cloudShellUrl = getCloudShellUrlFromAgentPolicy(agentPolicy);
|
||||
return {
|
||||
isLoading,
|
||||
|
@ -137,6 +164,11 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
|
|||
awsAccountType: cloudFormationAwsAccountType,
|
||||
templateUrl: cloudFormationTemplateUrl,
|
||||
},
|
||||
isAzureArmTemplate: Boolean(azureArmTemplateFromAgentPolicy),
|
||||
azureArmTemplateProps: {
|
||||
azureAccountType: azureArmTemplateAccountType,
|
||||
templateUrl: azureArmTemplateUrl,
|
||||
},
|
||||
cloudShellUrl,
|
||||
};
|
||||
}, [agentPolicy, packageInfoData?.item, isLoading, cloudSecurityPackagePolicy]);
|
||||
|
|
|
@ -82,6 +82,7 @@ export const Instructions = (props: InstructionProps) => {
|
|||
useEffect(() => {
|
||||
// If we detect a CloudFormation integration, we want to hide the selection type
|
||||
if (
|
||||
props.cloudSecurityIntegration?.isAzureArmTemplate ||
|
||||
props.cloudSecurityIntegration?.isCloudFormation ||
|
||||
props.cloudSecurityIntegration?.cloudShellUrl
|
||||
) {
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
InstallManagedAgentStep,
|
||||
InstallCloudFormationManagedAgentStep,
|
||||
InstallGoogleCloudShellManagedAgentStep,
|
||||
InstallAzureArmTemplateManagedAgentStep,
|
||||
IncomingDataConfirmationStep,
|
||||
} from '.';
|
||||
|
||||
|
@ -274,6 +275,15 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
|
|||
cloudShellCommand: installManagedCommands.googleCloudShell,
|
||||
})
|
||||
);
|
||||
} else if (cloudSecurityIntegration?.isAzureArmTemplate) {
|
||||
steps.push(
|
||||
InstallAzureArmTemplateManagedAgentStep({
|
||||
selectedApiKeyId,
|
||||
apiKeyData,
|
||||
enrollToken,
|
||||
cloudSecurityIntegration,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
steps.push(
|
||||
InstallManagedAgentStep({
|
||||
|
|
|
@ -10,6 +10,7 @@ export * from './agent_enrollment_key_selection_step';
|
|||
export * from './agent_policy_selection_step';
|
||||
export * from './configure_standalone_agent_step';
|
||||
export * from './incoming_data_confirmation_step';
|
||||
export * from './install_azure_arm_template_managed_agent_step';
|
||||
export * from './install_cloud_formation_managed_agent_step';
|
||||
export * from './install_google_cloud_shell_managed_agent_step';
|
||||
export * from './install_managed_agent_step';
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
|
||||
|
||||
import { AzureArmTemplateInstructions } from '../azure_arm_template_instructions';
|
||||
|
||||
import type { GetOneEnrollmentAPIKeyResponse } from '../../../../common/types/rest_spec/enrollment_api_key';
|
||||
|
||||
import type { CloudSecurityIntegration } from '../types';
|
||||
|
||||
export const InstallAzureArmTemplateManagedAgentStep = ({
|
||||
selectedApiKeyId,
|
||||
apiKeyData,
|
||||
enrollToken,
|
||||
isComplete,
|
||||
cloudSecurityIntegration,
|
||||
}: {
|
||||
selectedApiKeyId?: string;
|
||||
apiKeyData?: GetOneEnrollmentAPIKeyResponse | null;
|
||||
enrollToken?: string;
|
||||
isComplete?: boolean;
|
||||
cloudSecurityIntegration?: CloudSecurityIntegration | undefined;
|
||||
}): EuiContainedStepProps => {
|
||||
const nonCompleteStatus = selectedApiKeyId ? undefined : 'disabled';
|
||||
const status = isComplete ? 'complete' : nonCompleteStatus;
|
||||
|
||||
return {
|
||||
status,
|
||||
title: i18n.translate(
|
||||
'xpack.fleet.agentEnrollment.azureArmTemplate.stepEnrollAndRunAgentTitle',
|
||||
{ defaultMessage: 'Install Elastic Agent on your cloud' }
|
||||
),
|
||||
children:
|
||||
selectedApiKeyId && apiKeyData && cloudSecurityIntegration ? (
|
||||
<AzureArmTemplateInstructions
|
||||
cloudSecurityIntegration={cloudSecurityIntegration}
|
||||
enrollmentAPIKey={enrollToken}
|
||||
/>
|
||||
) : (
|
||||
<React.Fragment />
|
||||
),
|
||||
};
|
||||
};
|
|
@ -17,6 +17,9 @@ export type K8sMode =
|
|||
|
||||
export type CloudSecurityIntegrationType = 'kspm' | 'vuln_mgmt' | 'cspm';
|
||||
export type CloudSecurityIntegrationAwsAccountType = 'single-account' | 'organization-account';
|
||||
export type CloudSecurityIntegrationAzureAccountType =
|
||||
| 'single-account-azure'
|
||||
| 'organization-account-azure';
|
||||
|
||||
export type FlyoutMode = 'managed' | 'standalone';
|
||||
export type SelectionType = 'tabs' | 'radio' | undefined;
|
||||
|
@ -26,11 +29,18 @@ export interface CloudFormationProps {
|
|||
awsAccountType: CloudSecurityIntegrationAwsAccountType | undefined;
|
||||
}
|
||||
|
||||
export interface AzureArmTemplateProps {
|
||||
templateUrl: string | undefined;
|
||||
azureAccountType: CloudSecurityIntegrationAzureAccountType | undefined;
|
||||
}
|
||||
|
||||
export interface CloudSecurityIntegration {
|
||||
integrationType: CloudSecurityIntegrationType | undefined;
|
||||
isLoading: boolean;
|
||||
isCloudFormation: boolean;
|
||||
isAzureArmTemplate: boolean;
|
||||
cloudFormationProps?: CloudFormationProps;
|
||||
azureArmTemplateProps?: AzureArmTemplateProps;
|
||||
cloudShellUrl: string | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiLink, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import type { CloudSecurityIntegrationAzureAccountType } from './agent_enrollment_flyout/types';
|
||||
|
||||
const azureResourceManagerLink =
|
||||
'https://azure.microsoft.com/en-us/get-started/azure-portal/resource-manager';
|
||||
|
||||
export const AzureArmTemplateGuide = ({
|
||||
azureAccountType,
|
||||
}: {
|
||||
azureAccountType?: CloudSecurityIntegrationAzureAccountType;
|
||||
}) => {
|
||||
return (
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.azureArmTemplate.guide.description"
|
||||
defaultMessage="An Azure Resource Manager (ARM) Template will create all the necessary resources to evaluate the security posture of your Azure organization. Follow the steps below to launch the ARM template. Learn more about {learnMore}."
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink
|
||||
href={azureResourceManagerLink}
|
||||
target="_blank"
|
||||
rel="noopener nofollow noreferrer"
|
||||
data-test-subj="azure-resource-manager-link"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.azureArmTemplate.guide.learnMoreLinkText"
|
||||
defaultMessage="Azure Resource Manager"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<EuiText size="s" color="subdued">
|
||||
<ol>
|
||||
{azureAccountType === 'organization-account-azure' ? (
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.azureArmTemplate.guide.steps.organizationLogin"
|
||||
defaultMessage="Log into your Azure Portal"
|
||||
/>
|
||||
</li>
|
||||
) : (
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.azureArmTemplate.guide.steps.login"
|
||||
defaultMessage="Log into your Azure Portal"
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.azureArmTemplate.guide.steps.launch"
|
||||
defaultMessage="Click the Launch ARM Template button below."
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</EuiText>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { AzureArmTemplateProps } from '../components/agent_enrollment_flyout/types';
|
||||
|
||||
import { useGetSettings } from './use_request';
|
||||
|
||||
export const useCreateAzureArmTemplateUrl = ({
|
||||
enrollmentAPIKey,
|
||||
azureArmTemplateProps,
|
||||
}: {
|
||||
enrollmentAPIKey: string | undefined;
|
||||
azureArmTemplateProps: AzureArmTemplateProps | undefined;
|
||||
}) => {
|
||||
const { data, isLoading } = useGetSettings();
|
||||
|
||||
let isError = false;
|
||||
let error: string | undefined;
|
||||
|
||||
// Default fleet server host
|
||||
const fleetServerHost = data?.item.fleet_server_hosts?.[0];
|
||||
|
||||
if (!fleetServerHost && !isLoading) {
|
||||
isError = true;
|
||||
error = i18n.translate('xpack.fleet.agentEnrollment.cloudFormation.noFleetServerHost', {
|
||||
defaultMessage: 'No Fleet Server host found',
|
||||
});
|
||||
}
|
||||
|
||||
if (!enrollmentAPIKey && !isLoading) {
|
||||
isError = true;
|
||||
error = i18n.translate('xpack.fleet.agentEnrollment.cloudFormation.noApiKey', {
|
||||
defaultMessage: 'No enrollment token found',
|
||||
});
|
||||
}
|
||||
|
||||
const azureArmTemplateUrl = azureArmTemplateProps?.templateUrl;
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
azureArmTemplateUrl,
|
||||
isError,
|
||||
error,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CloudSecurityIntegrationAzureAccountType } from '../components/agent_enrollment_flyout/types';
|
||||
import type { PackagePolicy } from '../types';
|
||||
import type { AzureArmTemplateProps } from '../components/agent_enrollment_flyout/types';
|
||||
|
||||
const AZURE_ACCOUNT_TYPE = 'azure.account_type';
|
||||
|
||||
/**
|
||||
* Get the Azure Arm Template url from a package policy
|
||||
* It looks for a config with an arm_template_url object present in the enabled inputs of the package policy
|
||||
*/
|
||||
export const getAzureArmPropsFromPackagePolicy = (
|
||||
packagePolicy?: PackagePolicy
|
||||
): AzureArmTemplateProps => {
|
||||
const templateUrl: CloudSecurityIntegrationAzureAccountType | undefined =
|
||||
packagePolicy?.inputs?.find((input) => input.enabled)?.config?.arm_template_url?.value;
|
||||
|
||||
const azureAccountType: CloudSecurityIntegrationAzureAccountType | undefined =
|
||||
packagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[AZURE_ACCOUNT_TYPE]
|
||||
?.value;
|
||||
|
||||
return {
|
||||
templateUrl,
|
||||
azureAccountType,
|
||||
};
|
||||
};
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getCloudFormationTemplateUrlFromPackageInfo } from './get_cloud_formation_template_url_from_package_info';
|
||||
|
||||
describe('getCloudFormationTemplateUrlFromPackageInfo', () => {
|
||||
test('returns undefined when packageInfo is undefined', () => {
|
||||
const result = getCloudFormationTemplateUrlFromPackageInfo(undefined, 'test');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when packageInfo has no policy_templates', () => {
|
||||
const packageInfo = { inputs: [] };
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'test');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when integrationType is not found in policy_templates', () => {
|
||||
const packageInfo = { policy_templates: [{ name: 'template1' }, { name: 'template2' }] };
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'nonExistentTemplate');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when no input in the policy template has a cloudFormationTemplate', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
inputs: [
|
||||
{ name: 'input1', vars: [] },
|
||||
{ name: 'input2', vars: [{ name: 'var1', default: 'value1' }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'template1');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns the cloudFormationTemplate from the policy template', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
inputs: [
|
||||
{ name: 'input1', vars: [] },
|
||||
{
|
||||
name: 'input2',
|
||||
vars: [{ name: 'cloud_formation_template', default: 'cloud_formation_template_url' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'template1');
|
||||
expect(result).toBe('cloud_formation_template_url');
|
||||
});
|
||||
});
|
|
@ -4,18 +4,26 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { getCloudFormationTemplateUrlFromAgentPolicy } from './get_cloud_formation_template_url_from_agent_policy';
|
||||
import {
|
||||
getTemplateUrlFromAgentPolicy,
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG,
|
||||
} from './get_template_url_from_agent_policy';
|
||||
|
||||
describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
|
||||
describe('getTemplateUrlFromAgentPolicy', () => {
|
||||
it('should return undefined when selectedPolicy is undefined', () => {
|
||||
const result = getCloudFormationTemplateUrlFromAgentPolicy();
|
||||
const result = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when selectedPolicy has no package_policies', () => {
|
||||
const selectedPolicy = {};
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
|
||||
const result = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
|
||||
// @ts-expect-error
|
||||
selectedPolicy
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
|
@ -37,8 +45,11 @@ describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
|
||||
const result = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
|
||||
// @ts-expect-error
|
||||
selectedPolicy
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
|
@ -61,8 +72,38 @@ describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
// @ts-expect-error
|
||||
const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
|
||||
const result = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
|
||||
// @ts-expect-error
|
||||
selectedPolicy
|
||||
);
|
||||
expect(result).toBe('url3');
|
||||
});
|
||||
|
||||
it('should return the first config.arm_template_url when available', () => {
|
||||
const selectedPolicy = {
|
||||
package_policies: [
|
||||
{
|
||||
inputs: [
|
||||
{ enabled: false, config: { arm_template_url: { value: 'url1' } } },
|
||||
{ enabled: false, config: { arm_template_url: { value: 'url2' } } },
|
||||
{ enabled: false, config: { other_property: 'value' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ enabled: false, config: {} },
|
||||
{ enabled: true, config: { arm_template_url: { value: 'url3' } } },
|
||||
{ enabled: true, config: { arm_template_url: { value: 'url4' } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = getTemplateUrlFromAgentPolicy(
|
||||
SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.ARM_TEMPLATE,
|
||||
// @ts-expect-error
|
||||
selectedPolicy
|
||||
);
|
||||
expect(result).toBe('url3');
|
||||
});
|
||||
});
|
|
@ -7,12 +7,15 @@
|
|||
|
||||
import type { AgentPolicy } from '../types';
|
||||
|
||||
/**
|
||||
* Get the cloud formation template url from a agent policy
|
||||
* It looks for a config with a cloud_formation_template_url object present in
|
||||
* the enabled package_policies inputs of the agent policy
|
||||
*/
|
||||
export const getCloudFormationTemplateUrlFromAgentPolicy = (selectedPolicy?: AgentPolicy) => {
|
||||
export const SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG = {
|
||||
CLOUD_FORMATION: 'cloud_formation_template_url',
|
||||
ARM_TEMPLATE: 'arm_template_url',
|
||||
};
|
||||
|
||||
export const getTemplateUrlFromAgentPolicy = (
|
||||
templateUrlFieldName: string,
|
||||
selectedPolicy?: AgentPolicy
|
||||
) => {
|
||||
const cloudFormationTemplateUrl = selectedPolicy?.package_policies?.reduce(
|
||||
(acc, packagePolicy) => {
|
||||
const findCloudFormationTemplateUrlConfig = packagePolicy.inputs?.reduce(
|
||||
|
@ -20,8 +23,8 @@ export const getCloudFormationTemplateUrlFromAgentPolicy = (selectedPolicy?: Age
|
|||
if (accInput !== '') {
|
||||
return accInput;
|
||||
}
|
||||
if (input?.enabled && input?.config?.cloud_formation_template_url) {
|
||||
return input.config.cloud_formation_template_url.value;
|
||||
if (input?.enabled && input?.config?.[templateUrlFieldName]) {
|
||||
return input.config[templateUrlFieldName].value;
|
||||
}
|
||||
return accInput;
|
||||
},
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PackageInfo } from '../types';
|
||||
|
||||
import {
|
||||
getTemplateUrlFromPackageInfo,
|
||||
SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS,
|
||||
} from './get_template_url_from_package_info';
|
||||
|
||||
describe('getTemplateUrlFromPackageInfo', () => {
|
||||
test('returns undefined when packageInfo is undefined', () => {
|
||||
const result = getTemplateUrlFromPackageInfo(undefined, 'test', 'cloud_formation_template_url');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when packageInfo has no policy_templates', () => {
|
||||
const packageInfo = { inputs: [] } as unknown as PackageInfo;
|
||||
const result = getTemplateUrlFromPackageInfo(
|
||||
packageInfo,
|
||||
'test',
|
||||
'cloud_formation_template_url'
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when integrationType is not found in policy_templates', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [{ name: 'template1' }, { name: 'template2' }],
|
||||
} as PackageInfo;
|
||||
const result = getTemplateUrlFromPackageInfo(
|
||||
packageInfo,
|
||||
'nonExistentTemplate',
|
||||
'cloud_formation_template_url'
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns undefined when no input in the policy template has a cloudFormationTemplate', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
inputs: [
|
||||
{ name: 'input1', vars: [] },
|
||||
{ name: 'input2', vars: [{ name: 'var1', default: 'value1' }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as PackageInfo;
|
||||
|
||||
const result = getTemplateUrlFromPackageInfo(
|
||||
packageInfo,
|
||||
'template1',
|
||||
'cloud_formation_template_url'
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('returns the cloudFormationTemplate from the policy template', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
inputs: [
|
||||
{ name: 'input1', vars: [] },
|
||||
{
|
||||
name: 'input2',
|
||||
vars: [
|
||||
{
|
||||
name: SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION,
|
||||
default: 'cloud_formation_template_url',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as PackageInfo;
|
||||
|
||||
const result = getTemplateUrlFromPackageInfo(
|
||||
packageInfo,
|
||||
'template1',
|
||||
SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION
|
||||
);
|
||||
expect(result).toBe('cloud_formation_template_url');
|
||||
});
|
||||
|
||||
test('returns the armTemplateUrl from the policy template', () => {
|
||||
const packageInfo = {
|
||||
policy_templates: [
|
||||
{
|
||||
name: 'template1',
|
||||
inputs: [
|
||||
{ name: 'input1', vars: [] },
|
||||
{
|
||||
name: 'input2',
|
||||
vars: [{ name: 'arm_template_url', default: 'arm_template_url_value' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as PackageInfo;
|
||||
|
||||
const result = getTemplateUrlFromPackageInfo(packageInfo, 'template1', 'arm_template_url');
|
||||
expect(result).toBe('arm_template_url_value');
|
||||
});
|
||||
});
|
|
@ -7,14 +7,15 @@
|
|||
|
||||
import type { PackageInfo } from '../types';
|
||||
|
||||
/**
|
||||
* Get the cloud formation template url from the PackageInfo
|
||||
* It looks for a input var with a object containing cloud_formation_template_url present in
|
||||
* the package_policies inputs of the given integration type
|
||||
*/
|
||||
export const getCloudFormationTemplateUrlFromPackageInfo = (
|
||||
export const SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS = {
|
||||
CLOUD_FORMATION: 'cloud_formation_template',
|
||||
ARM_TEMPLATE: 'arm_template_url',
|
||||
};
|
||||
|
||||
export const getTemplateUrlFromPackageInfo = (
|
||||
packageInfo: PackageInfo | undefined,
|
||||
integrationType: string
|
||||
integrationType: string,
|
||||
templateUrlFieldName: string
|
||||
): string | undefined => {
|
||||
if (!packageInfo?.policy_templates) return undefined;
|
||||
|
||||
|
@ -24,7 +25,7 @@ export const getCloudFormationTemplateUrlFromPackageInfo = (
|
|||
if ('inputs' in policyTemplate) {
|
||||
const cloudFormationTemplate = policyTemplate.inputs?.reduce((acc, input): string => {
|
||||
if (!input.vars) return acc;
|
||||
const template = input.vars.find((v) => v.name === 'cloud_formation_template')?.default;
|
||||
const template = input.vars.find((v) => v.name === templateUrlFieldName)?.default;
|
||||
return template ? String(template) : acc;
|
||||
}, '');
|
||||
return cloudFormationTemplate !== '' ? cloudFormationTemplate : undefined;
|
|
@ -49,7 +49,7 @@ export { pkgKeyFromPackageInfo } from './pkg_key_from_package_info';
|
|||
export { createExtensionRegistrationCallback } from './ui_extensions';
|
||||
export { incrementPolicyName } from './increment_policy_name';
|
||||
export { getCloudFormationPropsFromPackagePolicy } from './get_cloud_formation_props_from_package_policy';
|
||||
export { getCloudFormationTemplateUrlFromAgentPolicy } from './get_cloud_formation_template_url_from_agent_policy';
|
||||
export { getCloudFormationTemplateUrlFromPackageInfo } from './get_cloud_formation_template_url_from_package_info';
|
||||
export { getTemplateUrlFromAgentPolicy } from './get_template_url_from_agent_policy';
|
||||
export { getTemplateUrlFromPackageInfo } from './get_template_url_from_package_info';
|
||||
export { getCloudShellUrlFromPackagePolicy } from './get_cloud_shell_url_from_package_policy';
|
||||
export { getCloudShellUrlFromAgentPolicy } from './get_cloud_shell_url_from_agent_policy';
|
||||
|
|
|
@ -11359,7 +11359,6 @@
|
|||
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
|
||||
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "Bientôt disponible",
|
||||
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
|
||||
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
|
||||
"xpack.csp.cspmIntegration.integration.nameTitle": "Gestion du niveau de sécurité du cloud",
|
||||
|
|
|
@ -11374,7 +11374,6 @@
|
|||
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
|
||||
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "まもなくリリース",
|
||||
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
|
||||
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
|
||||
"xpack.csp.cspmIntegration.integration.nameTitle": "クラウドセキュリティ態勢管理",
|
||||
|
|
|
@ -11374,7 +11374,6 @@
|
|||
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
|
||||
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
|
||||
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "即将推出",
|
||||
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
|
||||
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
|
||||
"xpack.csp.cspmIntegration.integration.nameTitle": "云安全态势管理",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue