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 - AMR Template form #166910

Merged
merged 27 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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