[Cloud Security][Onboarding]GCP Onboarding - Manual (#161913)

## Summary

Added basic form for GCP onboarding



e99f0eac-02f4-4f37-a41c-b78825e7e230

<img width="924" alt="Screenshot 2023-07-20 at 3 51 25 PM"
src="7f5783fa-6e29-4cb8-837a-3095c6fb292a">
<img width="849" alt="Screenshot 2023-07-20 at 3 53 25 PM"
src="2ef71e15-3597-4dc8-a97d-40990462d7ce">
<img width="934" alt="Screenshot 2023-07-20 at 3 53 33 PM"
src="a8b14e54-079a-4c23-900a-f0d237568bd2">
This commit is contained in:
Rickyanto Ang 2023-07-24 09:05:56 -07:00 committed by GitHub
parent cbe2a09598
commit cb04b5553e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 318 additions and 10 deletions

View file

@ -89,11 +89,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
benchmark: i18n.translate('xpack.csp.cspmIntegration.gcpOption.benchmarkTitle', {
defaultMessage: 'CIS GCP',
}),
disabled: true,
icon: 'logoGCP',
tooltip: i18n.translate('xpack.csp.cspmIntegration.gcpOption.tooltipContent', {
defaultMessage: 'Coming soon',
}),
},
{
type: CLOUDBEAT_AZURE,
@ -214,3 +210,4 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
},
};
export const FINDINGS_DOCS_URL = 'https://ela.st/findings';
export const MIN_VERSION_GCP_CIS = '1.5.0';

View file

@ -0,0 +1,295 @@
/*
* 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, useState } from 'react';
import {
EuiFieldText,
EuiFormRow,
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
EuiSelect,
EuiForm,
EuiCallOut,
EuiTextArea,
} 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 { i18n } from '@kbn/i18n';
import { RadioGroup } from './csp_boxed_radio_group';
import { getPosturePolicy, NewPackagePolicyPostureInput } from './utils';
import { MIN_VERSION_GCP_CIS } from '../../common/constants';
type SetupFormatGCP = 'google_cloud_shell' | 'manual';
const GCPSetupInfoContent = () => (
<>
<EuiSpacer size="l" />
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.csp.gcpIntegration.setupInfoContentTitle"
defaultMessage="Setup Access"
/>
</h2>
</EuiTitle>
<EuiSpacer size="l" />
<EuiText color={'subdued'} size="s">
<FormattedMessage
id="xpack.csp.gcpIntegration.setupInfoContent"
defaultMessage="The integration will need elevated access to run some CIS benchmark rules. Select your preferred
method of providing the GCP credentials this integration will use. You can follow these
step-by-step instructions to generate the necessary credentials."
/>
</EuiText>
</>
);
/* NEED TO FIND THE REAL URL HERE LATER*/
const DocsLink = (
<EuiText color={'subdued'} size="s">
<FormattedMessage
id="xpack.csp.gcpIntegration.docsLink"
defaultMessage="Read the {docs} for more details"
values={{
docs: (
<EuiLink href="https://cloud.google.com/docs/authentication" external>
documentation
</EuiLink>
),
}}
/>
</EuiText>
);
const CredentialFileText = i18n.translate(
'xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText',
{ defaultMessage: 'Path to JSON file containing the credentials and key used to subscribe' }
);
const CredentialJSONText = i18n.translate(
'xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText',
{ defaultMessage: 'JSON blob containing the credentials and key used to subscribe' }
);
type GcpCredentialsType = 'credentials_file' | 'credentials_json';
type GcpFields = Record<string, { label: string; type?: 'password' | 'text' }>;
interface GcpInputFields {
fields: GcpFields;
}
const gcpField: GcpInputFields = {
fields: {
project_id: {
label: i18n.translate('xpack.csp.gcpIntegration.projectidFieldLabel', {
defaultMessage: 'Project ID',
}),
type: 'text',
},
credentials_file: {
label: i18n.translate('xpack.csp.gcpIntegration.credentialsFileFieldLabel', {
defaultMessage: 'Credentials File',
}),
type: 'text',
},
credentials_json: {
label: i18n.translate('xpack.csp.gcpIntegration.credentialsJSONFieldLabel', {
defaultMessage: 'Credentials JSON',
}),
type: 'text',
},
},
};
const credentialOptionsList = [
{
label: i18n.translate('xpack.csp.gcpIntegration.credentialsFileOption', {
defaultMessage: 'Credentials File',
}),
text: 'Credentials File',
},
{
label: i18n.translate('xpack.csp.gcpIntegration.credentialsjsonOption', {
defaultMessage: 'Credentials JSON',
}),
text: 'Credentials JSON',
},
];
const getSetupFormatOptions = (): Array<{
id: SetupFormatGCP;
label: string;
disabled: boolean;
}> => [
{
id: 'google_cloud_shell',
label: i18n.translate('xpack.csp.gcpIntegration.setupFormatOptions.googleCloudShell', {
defaultMessage: 'Google Cloud Shell',
}),
disabled: true,
},
{
id: 'manual',
label: i18n.translate('xpack.csp.gcpIntegration.setupFormatOptions.manual', {
defaultMessage: 'Manual',
}),
disabled: false,
},
];
interface Props {
newPolicy: NewPackagePolicy;
input: Extract<
NewPackagePolicyPostureInput,
{ type: 'cloudbeat/cis_aws' | 'cloudbeat/cis_eks' | 'cloudbeat/cis_gcp' }
>;
updatePolicy(updatedPolicy: NewPackagePolicy): void;
packageInfo: PackageInfo;
setIsValid: (isValid: boolean) => void;
onChange: any;
}
const getInputVarsFields = (
input: NewPackagePolicyInput,
fields: GcpInputFields[keyof GcpInputFields]
) =>
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 GcpCredentialsForm = ({
input,
newPolicy,
updatePolicy,
packageInfo,
setIsValid,
onChange,
}: Props) => {
const fields = getInputVarsFields(input, gcpField.fields);
useEffect(() => {
const isInvalid = packageInfo.version < MIN_VERSION_GCP_CIS;
setIsValid(!isInvalid);
onChange({
isValid: !isInvalid,
updatedPolicy: newPolicy,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [input, packageInfo]);
if (packageInfo.version < MIN_VERSION_GCP_CIS) {
return (
<>
<EuiSpacer size="l" />
<EuiCallOut color="warning">
<FormattedMessage
id="xpack.csp.gcpIntegration.gcpNotSupportedMessage"
defaultMessage="CIS GCP is not supported on the current Integration version, please upgrade your integration to the latest version to use CIS GCP"
/>
</EuiCallOut>
</>
);
}
return (
<>
<GCPSetupInfoContent />
<EuiSpacer size="l" />
<GcpSetupAccessSelector
onChange={(optionId) => updatePolicy(getPosturePolicy(newPolicy, input.type))}
/>
<EuiSpacer size="l" />
<GcpInputVarFields
fields={fields}
onChange={(key, value) =>
updatePolicy(getPosturePolicy(newPolicy, input.type, { [key]: { value } }))
}
/>
<EuiSpacer size="s" />
{DocsLink}
<EuiSpacer />
</>
);
};
const GcpSetupAccessSelector = ({ onChange }: { onChange(type: GcpCredentialsType): void }) => (
<RadioGroup
size="s"
options={getSetupFormatOptions()}
idSelected={'manual'}
onChange={(id: GcpCredentialsType) => onChange(id)}
/>
);
const GcpInputVarFields = ({
fields,
onChange,
}: {
fields: Array<GcpFields[keyof GcpFields] & { value: string; id: string }>;
onChange: (key: string, value: string) => void;
}) => {
const [credentialOption, setCredentialOption] = useState('Credentials File');
const targetFieldName = (id: string) => {
return fields.find((element) => element.id === id);
};
return (
<div>
<EuiForm component="form">
<EuiFormRow fullWidth label={gcpField.fields.project_id.label}>
<EuiFieldText
id={targetFieldName('project_id')!.id}
fullWidth
value={targetFieldName('project_id')!.value || ''}
onChange={(event) => onChange(targetFieldName('project_id')!.id, event.target.value)}
/>
</EuiFormRow>
<EuiFormRow fullWidth label={'Credentials'}>
<EuiSelect
fullWidth
options={credentialOptionsList}
value={credentialOption}
onChange={(optionElem) => {
setCredentialOption(optionElem.target.value);
}}
/>
</EuiFormRow>
{credentialOption === 'Credentials File' && (
<EuiFormRow fullWidth label={CredentialFileText}>
<EuiFieldText
id={targetFieldName('credentials_file')!.id}
fullWidth
value={targetFieldName('credentials_file')!.value || ''}
onChange={(event) =>
onChange(targetFieldName('credentials_file')!.id, event.target.value)
}
/>
</EuiFormRow>
)}
{credentialOption === 'Credentials JSON' && (
<EuiFormRow fullWidth label={CredentialJSONText}>
<EuiTextArea
id={targetFieldName('credentials_json')!.id}
fullWidth
value={targetFieldName('credentials_json')!.value || ''}
onChange={(event) =>
onChange(targetFieldName('credentials_json')!.id, event.target.value)
}
/>
</EuiFormRow>
)}
</EuiForm>
</div>
);
};

View file

@ -197,7 +197,7 @@ describe('<CspPolicyTemplateForm />', () => {
expect(option2).toBeInTheDocument();
expect(option3).toBeInTheDocument();
expect(option1).toBeEnabled();
expect(option2).toBeDisabled();
expect(option2).toBeEnabled();
expect(option3).toBeDisabled();
expect(option1).toBeChecked();
});

View file

@ -20,6 +20,7 @@ import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from
import { RadioGroup } from './csp_boxed_radio_group';
import { AwsCredentialsForm } from './aws_credentials_form/aws_credentials_form';
import { EksCredentialsForm } from './eks_credentials_form';
import { GcpCredentialsForm } from './gcp_credential_form';
interface PolicyTemplateSelectorProps {
selectedTemplate: CloudSecurityPolicyTemplate;
@ -79,6 +80,8 @@ export const PolicyTemplateVarsForm = ({ input, ...props }: PolicyTemplateVarsFo
return <AwsCredentialsForm {...props} input={input} />;
case 'cloudbeat/cis_eks':
return <EksCredentialsForm {...props} input={input} />;
case 'cloudbeat/cis_gcp':
return <GcpCredentialsForm {...props} input={input} />;
default:
return null;
}

View file

@ -2152,6 +2152,12 @@
"data.sessions.management.flyoutTitle": "Inspecter la session de recherche",
"data.triggers.applyFilterDescription": "Lorsque le filtre Kibana est appliqué. Peut être un filtre simple ou un filtre de plage.",
"data.triggers.applyFilterTitle": "Appliquer le filtre",
"savedSearch.kibana_context.filters.help": "Spécifier des filtres génériques Kibana",
"savedSearch.kibana_context.help": "Met à jour le contexte général de Kibana.",
"savedSearch.kibana_context.q.help": "Spécifier une recherche en texte libre Kibana",
"savedSearch.kibana_context.savedSearchId.help": "Spécifier l'ID de recherche enregistrée à utiliser pour les requêtes et les filtres",
"savedSearch.kibana_context.timeRange.help": "Spécifier le filtre de plage temporelle Kibana",
"savedSearch.legacyURLConflict.errorMessage": "Cette recherche a la même URL qu'un alias hérité. Désactiver l'alias pour résoudre cette erreur : {json}",
"dataViews.deprecations.scriptedFieldsMessage": "Vous avez {numberOfIndexPatternsWithScriptedFields} vues de données ({titlesPreview}…) qui utilisent des champs scriptés. Les champs scriptés sont déclassés et seront supprimés à l'avenir. Utilisez plutôt des champs d'exécution.",
"dataViews.fetchFieldErrorTitle": "Erreur lors de l'extraction des champs pour la vue de données {title} (ID : {id})",
"dataViews.aliasLabel": "Alias",
@ -4991,7 +4997,6 @@
"savedObjectsManagement.view.savedObjectProblemErrorMessage": "Un problème est survenu avec cet objet enregistré.",
"savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "La recherche enregistrée associée à cet objet n'existe plus.",
"savedSearch.legacyURLConflict.errorMessage": "Cette recherche a la même URL qu'un alias hérité. Désactiver l'alias pour résoudre cette erreur : {json}",
"savedSearch.contentManagementType": "Recherche enregistrée",
"securitySolutionPackages.dataTable.eventsTab.unit": "{totalCount, plural, =1 {alerte} one {alertes} many {alertes} other {alertes}}",
"securitySolutionPackages.dataTable.unit": "{totalCount, plural, =1 {alerte} one {alertes} many {alertes} other {alertes}}",
"securitySolutionPackages.ecsDataQualityDashboard.allTab.allFieldsTableTitle": "Tous les champs  {indexName}",
@ -11442,7 +11447,6 @@
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "Bientôt disponible",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.gcpOption.tooltipContent": "Bientôt disponible",
"xpack.csp.cspmIntegration.integration.nameTitle": "Gestion du niveau de sécurité du cloud",
"xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM",
"xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "Afficher tous les résultats pour ",

View file

@ -2166,6 +2166,12 @@
"data.sessions.management.flyoutTitle": "検索セッションの検査",
"data.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。",
"data.triggers.applyFilterTitle": "フィルターを適用",
"savedSearch.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します",
"savedSearch.kibana_context.help": "Kibana グローバルコンテキストを更新します",
"savedSearch.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します",
"savedSearch.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。",
"savedSearch.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します",
"savedSearch.legacyURLConflict.errorMessage": "この検索にはレガシーエイリアスと同じURLがあります。このエラーを解決するには、エイリアスを無効にしてください{json}",
"dataViews.deprecations.scriptedFieldsMessage": "スクリプト化されたフィールドを使用する{numberOfIndexPatternsWithScriptedFields}データビュー({titlesPreview}...)があります。スクリプト化されたフィールドは廃止予定であり、今後は削除されます。ランタイムフィールドを使用してください。",
"dataViews.fetchFieldErrorTitle": "データビューのフィールド取得中にエラーが発生 {title}ID{id}",
"dataViews.aliasLabel": "エイリアス",
@ -11456,7 +11462,6 @@
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "まもなくリリース",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.gcpOption.tooltipContent": "まもなくリリース",
"xpack.csp.cspmIntegration.integration.nameTitle": "クラウドセキュリティ態勢管理",
"xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM",
"xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "すべての調査結果を表示 ",

View file

@ -2166,6 +2166,12 @@
"data.sessions.management.flyoutTitle": "检查搜索会话",
"data.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。",
"data.triggers.applyFilterTitle": "应用筛选",
"savedSearch.kibana_context.filters.help": "指定 Kibana 常规筛选",
"savedSearch.kibana_context.help": "更新 kibana 全局上下文",
"savedSearch.kibana_context.q.help": "指定 Kibana 自由格式文本查询",
"savedSearch.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID",
"savedSearch.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选",
"savedSearch.legacyURLConflict.errorMessage": "此搜索具有与旧版别名相同的 URL。请禁用别名以解决此错误{json}",
"dataViews.deprecations.scriptedFieldsMessage": "您具有 {numberOfIndexPatternsWithScriptedFields} 个使用脚本字段的数据视图 ({titlesPreview}...)。脚本字段已过时,将在未来移除。请改为使用运行时脚本。",
"dataViews.fetchFieldErrorTitle": "提取数据视图 {title}ID{id})的字段时出错",
"dataViews.aliasLabel": "别名",
@ -5004,7 +5010,6 @@
"savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "与此对象关联的数据视图不再存在。",
"savedObjectsManagement.view.savedObjectProblemErrorMessage": "此已保存对象有问题",
"savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "与此对象关联的已保存搜索已不存在。",
"savedSearch.legacyURLConflict.errorMessage": "此搜索具有与旧版别名相同的 URL。请禁用别名以解决此错误{json}",
"savedSearch.contentManagementType": "已保存搜索",
"securitySolutionPackages.dataTable.eventsTab.unit": "{totalCount, plural, =1 {告警} other {告警}}",
"securitySolutionPackages.dataTable.unit": "{totalCount, plural, =1 {告警} other {告警}}",
@ -11456,7 +11461,6 @@
"xpack.csp.cspmIntegration.azureOption.tooltipContent": "即将推出",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.gcpOption.tooltipContent": "即将推出",
"xpack.csp.cspmIntegration.integration.nameTitle": "云安全态势管理",
"xpack.csp.cspmIntegration.integration.shortNameTitle": "CSPM",
"xpack.csp.dashboard.benchmarkSection.clusterTitleTooltip.clusterPrefixTitle": "显示以下所有结果 ",