Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
VulnSeverity,
AwsCredentialsTypeFieldMap,
GcpCredentialsTypeFieldMap,
AzureCredentialsTypeFieldMap,
} from './types';

export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
Expand Down Expand Up @@ -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: [],
};
6 changes: 6 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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,
Score,
BaseCspSetupStatus,
AwsCredentialsType,
GcpCredentialsType,
AzureCredentialsType,
RuleSection,
} from '../types';

Expand Down Expand Up @@ -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[] = [' '];
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand All @@ -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',
}),
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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 />
</>
);
};
Original file line number Diff line number Diff line change
@@ -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: {},
},
});
Loading