[8.10] [Cloud Security][CIS GCP]cis gcp now use updated gcp field name + small last minute changes (#164792) (#165072)

# Backport

This will backport the following commits from `main` to `8.10`:
- [[Cloud Security][CIS GCP]cis gcp now use updated gcp field name +
small last minute changes
(#164792)](https://github.com/elastic/kibana/pull/164792)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Rickyanto
Ang","email":"rickyangwyn@gmail.com"},"sourceCommit":{"committedDate":"2023-08-28T22:43:03Z","message":"[Cloud
Security][CIS GCP]cis gcp now use updated gcp field name + small last
minute changes (#164792)\n\nThis PR is for updating GCP forms \r\n-
Manifest for GCP has been changed (this PR updates the field name
that\r\nwas updated on the manifest)\r\n- EuiCodeBlock for CloudShell is
interactive now\r\n- Static hover name for AWS and GCP icon on
dashboard\r\n- MIN_VERSION for CIS GCP has now been update to 1.5.2 from
1.5.0\r\n\r\n<img width=\"739\" alt=\"Screenshot 2023-08-24 at 10 58 44
PM\"\r\nsrc=\"b88f4787-8396-4913-a475-5a381c802021\">\r\n##
Summary\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"145fefee3f2ac114009ed71148f04faa5c316621","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","Team:Cloud
Security","backport:prev-minor","v8.10.0","v8.11.0"],"number":164792,"url":"https://github.com/elastic/kibana/pull/164792","mergeCommit":{"message":"[Cloud
Security][CIS GCP]cis gcp now use updated gcp field name + small last
minute changes (#164792)\n\nThis PR is for updating GCP forms \r\n-
Manifest for GCP has been changed (this PR updates the field name
that\r\nwas updated on the manifest)\r\n- EuiCodeBlock for CloudShell is
interactive now\r\n- Static hover name for AWS and GCP icon on
dashboard\r\n- MIN_VERSION for CIS GCP has now been update to 1.5.2 from
1.5.0\r\n\r\n<img width=\"739\" alt=\"Screenshot 2023-08-24 at 10 58 44
PM\"\r\nsrc=\"b88f4787-8396-4913-a475-5a381c802021\">\r\n##
Summary\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"145fefee3f2ac114009ed71148f04faa5c316621"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/164792","number":164792,"mergeCommit":{"message":"[Cloud
Security][CIS GCP]cis gcp now use updated gcp field name + small last
minute changes (#164792)\n\nThis PR is for updating GCP forms \r\n-
Manifest for GCP has been changed (this PR updates the field name
that\r\nwas updated on the manifest)\r\n- EuiCodeBlock for CloudShell is
interactive now\r\n- Static hover name for AWS and GCP icon on
dashboard\r\n- MIN_VERSION for CIS GCP has now been update to 1.5.2 from
1.5.0\r\n\r\n<img width=\"739\" alt=\"Screenshot 2023-08-24 at 10 58 44
PM\"\r\nsrc=\"b88f4787-8396-4913-a475-5a381c802021\">\r\n##
Summary\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"145fefee3f2ac114009ed71148f04faa5c316621"}}]}]
BACKPORT-->
This commit is contained in:
Rickyanto Ang 2023-08-29 03:48:35 -07:00 committed by GitHub
parent c76997894e
commit 608855df48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 216 additions and 79 deletions

View file

@ -5,7 +5,12 @@
* 2.0.
*/
import { PostureTypes, VulnSeverity, AwsCredentialsTypeFieldMap } from './types';
import {
PostureTypes,
VulnSeverity,
AwsCredentialsTypeFieldMap,
GcpCredentialsTypeFieldMap,
} from './types';
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
export const STATUS_API_CURRENT_VERSION = '1';
@ -143,3 +148,8 @@ export const SETUP_ACCESS_CLOUD_SHELL = 'google_cloud_shell';
export const SETUP_ACCESS_MANUAL = 'manual';
export const DETECTION_ENGINE_ALERTS_INDEX_DEFAULT = '.alerts-security.alerts-default';
export const GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP: GcpCredentialsTypeFieldMap = {
'credentials-file': ['gcp.credentials.file'],
'credentials-json': ['gcp.credentials.json'],
};

View file

@ -24,6 +24,12 @@ export type AwsCredentialsTypeFieldMap = {
[key in AwsCredentialsType]: string[];
};
export type GcpCredentialsType = 'credentials-file' | 'credentials-json';
export type GcpCredentialsTypeFieldMap = {
[key in GcpCredentialsType]: string[];
};
export type Evaluation = 'passed' | 'failed' | 'NA';
export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all';

View file

@ -185,5 +185,109 @@ describe('test helper methods', () => {
shared_credential_file: { value: undefined, type: 'text' },
});
});
it('when aws credential type is undefined, return unchanged policy', () => {
const mockPackagePolicy = createPackagePolicyMock();
mockPackagePolicy.inputs = [
{
type: 'cloudbeat/cis_eks',
enabled: true,
streams: [
{
id: 'findings',
enabled: true,
data_stream: {
dataset: 'cloud_security_posture.findings',
type: 'logs',
},
vars: {
'aws.credentials.type': { value: undefined },
access_key_id: { value: 'used', type: 'text' },
credential_profile_name: { value: 'unused', type: 'text' },
role_arn: { value: 'unused' },
secret_access_key: { value: 'used', type: 'text' },
session_token: { value: 'unused', type: 'text' },
shared_credential_file: { value: 'unused', type: 'text' },
},
},
],
},
];
const cleanedPackage = cleanupCredentials(mockPackagePolicy);
expect(cleanedPackage.inputs[0].streams[0].vars).toEqual({
'aws.credentials.type': { value: undefined },
access_key_id: { value: 'used', type: 'text' },
credential_profile_name: { value: 'unused', type: 'text' },
role_arn: { value: 'unused' },
secret_access_key: { value: 'used', type: 'text' },
session_token: { value: 'unused', type: 'text' },
shared_credential_file: { value: 'unused', type: 'text' },
});
});
it('cleans unused gcp credential methods, when using credentials-file method ', () => {
const mockPackagePolicy = createPackagePolicyMock();
mockPackagePolicy.inputs = [
{
type: 'cloudbeat/cis_gcp',
enabled: true,
streams: [
{
id: 'findings',
enabled: true,
data_stream: {
dataset: 'cloud_security_posture.findings',
type: 'logs',
},
vars: {
'gcp.credentials.type': { value: 'credentials-file' },
'gcp.credentials.file': { value: 'used' },
'gcp.credentials.json': { value: 'unused' },
},
},
],
},
];
const cleanedPackage = cleanupCredentials(mockPackagePolicy);
expect(cleanedPackage.inputs[0].streams[0].vars).toEqual({
'gcp.credentials.type': { value: 'credentials-file' },
'gcp.credentials.file': { value: 'used' },
'gcp.credentials.json': { value: undefined },
});
});
it('when gcp credential type is undefined, return unchanged policy', () => {
const mockPackagePolicy = createPackagePolicyMock();
mockPackagePolicy.inputs = [
{
type: 'cloudbeat/cis_gcp',
enabled: true,
streams: [
{
id: 'findings',
enabled: true,
data_stream: {
dataset: 'cloud_security_posture.findings',
type: 'logs',
},
vars: {
'gcp.credentials.type': { value: undefined },
'gcp.credentials.file': { value: 'used' },
'gcp.credentials.json': { value: 'unused' },
},
},
],
},
];
const cleanedPackage = cleanupCredentials(mockPackagePolicy);
expect(cleanedPackage.inputs[0].streams[0].vars).toEqual({
'gcp.credentials.type': { value: undefined },
'gcp.credentials.file': { value: 'used' },
'gcp.credentials.json': { value: 'unused' },
});
});
});
});

View file

@ -19,8 +19,15 @@ import {
CLOUDBEAT_VANILLA,
CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE,
AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP,
GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP,
} from '../constants';
import type { BenchmarkId, Score, BaseCspSetupStatus, AwsCredentialsType } from '../types';
import type {
BenchmarkId,
Score,
BaseCspSetupStatus,
AwsCredentialsType,
GcpCredentialsType,
} from '../types';
/**
* @example
@ -103,12 +110,21 @@ export const getStatusForIndexName = (indexName: string, status?: BaseCspSetupSt
export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePackagePolicy) => {
const enabledInput = packagePolicy.inputs.find((i) => i.enabled);
const credentialType: AwsCredentialsType | undefined =
const awsCredentialType: AwsCredentialsType | undefined =
enabledInput?.streams?.[0].vars?.['aws.credentials.type']?.value;
const gcpCredentialType: GcpCredentialsType | undefined =
enabledInput?.streams?.[0].vars?.['gcp.credentials.type']?.value;
if (credentialType) {
const credsToKeep = AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP[credentialType];
const credFields = Object.values(AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
if (awsCredentialType || gcpCredentialType) {
let credsToKeep: string[] = [' '];
let credFields: string[] = [' '];
if (awsCredentialType) {
credsToKeep = AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP[awsCredentialType];
credFields = Object.values(AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
} else if (gcpCredentialType) {
credsToKeep = GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP[gcpCredentialType];
credFields = Object.values(GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
}
if (credsToKeep) {
// we need to return a copy of the policy with the unused

View file

@ -214,7 +214,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
},
};
export const FINDINGS_DOCS_URL = 'https://ela.st/findings';
export const MIN_VERSION_GCP_CIS = '1.5.0';
export const MIN_VERSION_GCP_CIS = '1.5.2';
export const NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS = 10000;

View file

@ -33,8 +33,8 @@ export const AccountsEvaluatedWidget = ({
const cisAwsClusterAmount = filterClustersById(CIS_AWS).length;
const cisGcpClusterAmount = filterClustersById(CIS_GCP).length;
const cisAwsBenchmarkName = filterClustersById(CIS_AWS)[0]?.meta.benchmark.name || '';
const cisGcpBenchmarkName = filterClustersById(CIS_GCP)[0]?.meta.benchmark.name || '';
const cisAwsBenchmarkName = 'Amazon Web Services (AWS)';
const cisGcpBenchmarkName = 'Google Cloud Platform (GCP)';
return (
<>

View file

@ -110,41 +110,6 @@ const GoogleCloudShellSetup = () => {
);
};
type GcpFields = Record<string, { label: string; type?: 'password' | 'text' }>;
interface GcpInputFields {
fields: GcpFields;
}
export 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.findings.gcpIntegration.gcpInputText.credentialFileText', {
defaultMessage: 'Path to JSON file containing the credentials and key used to subscribe',
}),
type: 'text',
},
credentials_json: {
label: i18n.translate('xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText', {
defaultMessage: 'JSON blob containing the credentials and key used to subscribe',
}),
type: 'text',
},
credentials_type: {
label: i18n.translate(
'xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle',
{ defaultMessage: 'Credential' }
),
type: 'text',
},
},
};
const credentialOptionsList = [
{
text: i18n.translate('xpack.csp.gcpIntegration.credentialsFileOption', {
@ -160,6 +125,43 @@ const credentialOptionsList = [
},
];
type GcpFields = Record<string, { label: string; type?: 'password' | 'text'; value?: string }>;
interface GcpInputFields {
fields: GcpFields;
}
export const gcpField: GcpInputFields = {
fields: {
'gcp.project_id': {
label: i18n.translate('xpack.csp.gcpIntegration.projectidFieldLabel', {
defaultMessage: 'Project ID',
}),
type: 'text',
},
'gcp.credentials.file': {
label: i18n.translate('xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText', {
defaultMessage: 'Path to JSON file containing the credentials and key used to subscribe',
}),
type: 'text',
},
'gcp.credentials.json': {
label: i18n.translate('xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText', {
defaultMessage: 'JSON blob containing the credentials and key used to subscribe',
}),
type: 'text',
},
'gcp.credentials.type': {
label: i18n.translate(
'xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle',
{
defaultMessage: 'Credential',
}
),
type: 'text',
},
},
};
const getSetupFormatOptions = (): Array<{
id: SetupFormatGCP;
label: string;
@ -193,10 +195,7 @@ interface GcpFormProps {
onChange: any;
}
const getInputVarsFields = (
input: NewPackagePolicyInput,
fields: GcpInputFields[keyof GcpInputFields]
) =>
const getInputVarsFields = (input: NewPackagePolicyInput, fields: GcpFields) =>
Object.entries(input.streams[0].vars || {})
.filter(([id]) => id in fields)
.map(([id, inputVar]) => {
@ -348,7 +347,7 @@ export const GcpCredentialsForm = ({
// Integration is Invalid IF Version is not at least 1.5.0 OR Setup Access is manual but Project ID is empty
useEffect(() => {
const isProjectIdEmpty =
setupFormat === SETUP_ACCESS_MANUAL && !getFieldById('project_id')?.value;
setupFormat === SETUP_ACCESS_MANUAL && !getFieldById('gcp.project_id')?.value;
const isInvalidPolicy = isInvalid || isProjectIdEmpty;
setIsValid(!isInvalidPolicy);
@ -411,19 +410,21 @@ const GcpInputVarFields = ({
const getFieldById = (id: keyof GcpInputFields['fields']) => {
return fields.find((element) => element.id === id);
};
const projectIdFields = getFieldById('project_id');
const credentialsTypeFields = getFieldById('credentials_type') || credentialOptionsList[0];
const credentialFilesFields = getFieldById('credentials_file');
const credentialJSONFields = getFieldById('credentials_json');
const projectIdFields = getFieldById('gcp.project_id');
const credentialsTypeFields = getFieldById('gcp.credentials.type');
const credentialFilesFields = getFieldById('gcp.credentials.file');
const credentialJSONFields = getFieldById('gcp.credentials.json');
const credentialFieldValue = credentialOptionsList[0].value;
const credentialJSONValue = credentialOptionsList[1].value;
const credentialsTypeValue = credentialsTypeFields?.value || credentialOptionsList[0].value;
return (
<div>
<EuiForm component="form">
{projectIdFields && (
<EuiFormRow fullWidth label={gcpField.fields.project_id.label}>
<EuiFormRow fullWidth label={gcpField.fields['gcp.project_id'].label}>
<EuiFieldText
data-test-subj={CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.PROJECT_ID}
id={projectIdFields.id}
@ -433,22 +434,21 @@ const GcpInputVarFields = ({
/>
</EuiFormRow>
)}
{credentialFilesFields && credentialJSONFields && (
<EuiFormRow fullWidth label={gcpField.fields.credentials_type.label}>
{credentialsTypeFields && credentialFilesFields && credentialJSONFields && (
<EuiFormRow fullWidth label={gcpField.fields['gcp.credentials.type'].label}>
<EuiSelect
data-test-subj={CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.CREDENTIALS_TYPE}
fullWidth
options={credentialOptionsList}
value={credentialsTypeFields?.value || credentialOptionsList[0].value}
onChange={(optionElem) => {
onChange('credentials_type', optionElem.target.value);
onChange(credentialsTypeFields?.id, optionElem.target.value);
}}
/>
</EuiFormRow>
)}
{credentialsTypeFields.value === credentialFieldValue && credentialFilesFields && (
<EuiFormRow fullWidth label={gcpField.fields.credentials_file.label}>
{credentialsTypeValue === credentialFieldValue && credentialFilesFields && (
<EuiFormRow fullWidth label={gcpField.fields['gcp.credentials.file'].label}>
<EuiFieldText
data-test-subj={CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.CREDENTIALS_FILE}
id={credentialFilesFields.id}
@ -458,8 +458,8 @@ const GcpInputVarFields = ({
/>
</EuiFormRow>
)}
{credentialsTypeFields?.value === credentialJSONValue && credentialJSONFields && (
<EuiFormRow fullWidth label={gcpField.fields.credentials_json.label}>
{credentialsTypeValue === credentialJSONValue && credentialJSONFields && (
<EuiFormRow fullWidth label={gcpField.fields['gcp.credentials.json'].label}>
<EuiTextArea
data-test-subj={CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.CREDENTIALS_JSON}
id={credentialJSONFields.id}

View file

@ -80,7 +80,7 @@ export const getMockPackageInfoCspmAWS = (packageVersion = '1.5.0') => {
} as PackageInfo;
};
export const getMockPackageInfoCspmGCP = (packageVersion = '1.5.0') => {
export const getMockPackageInfoCspmGCP = (packageVersion = '1.5.2') => {
return {
version: packageVersion,
name: 'cspm',
@ -130,9 +130,10 @@ const getPolicyMock = (
};
const gcpVarsMock = {
project_id: { type: 'text' },
credentials_file: { type: 'text' },
credentials_json: { type: 'text' },
'gcp.project_id': { type: 'text' },
'gcp.credentials.file': { type: 'text' },
'gcp.credentials.json': { type: 'text' },
'gcp.credentials.type': { type: 'text' },
};
const dataStream = { type: 'logs', dataset: 'cloud_security_posture.findings' };

View file

@ -979,7 +979,7 @@ describe('<CspPolicyTemplateForm />', () => {
});
describe('GCP Credentials input fields', () => {
it(`renders ${CLOUDBEAT_GCP} Not supported when version is not at least version 1.5.0`, () => {
it(`renders ${CLOUDBEAT_GCP} Not supported when version is not at least version 1.5.2`, () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
credentials_type: { value: 'credentials-file' },
@ -1024,7 +1024,7 @@ describe('<CspPolicyTemplateForm />', () => {
it(`project ID is required for Manual users`, () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
project_id: { value: undefined },
'gcp.project_id': { value: undefined },
setup_access: { value: 'manual' },
});
@ -1036,7 +1036,7 @@ describe('<CspPolicyTemplateForm />', () => {
updatedPolicy: policy,
});
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
project_id: { value: '' },
'gcp.project_id': { value: '' },
setup_access: { value: 'manual' },
});
rerender(<WrappedComponent newPolicy={policy} packageInfo={getMockPackageInfoCspmGCP()} />);
@ -1049,7 +1049,7 @@ describe('<CspPolicyTemplateForm />', () => {
it(`renders ${CLOUDBEAT_GCP} Credentials File fields`, () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
credentials_type: { value: 'credentials-file' },
'gcp.credentials.type': { value: 'credentials-file' },
setup_access: { value: 'manual' },
});
@ -1067,8 +1067,8 @@ describe('<CspPolicyTemplateForm />', () => {
it(`updates ${CLOUDBEAT_GCP} Credentials File fields`, () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
project_id: { value: 'a' },
credentials_type: { value: 'credentials-file' },
'gcp.project_id': { value: 'a' },
'gcp.credentials.type': { value: 'credentials-file' },
setup_access: { value: 'manual' },
});
@ -1079,7 +1079,7 @@ describe('<CspPolicyTemplateForm />', () => {
userEvent.type(getByTestId(CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.CREDENTIALS_FILE), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
credentials_file: { value: 'b' },
'gcp.credentials.file': { value: 'b' },
});
expect(onChange).toHaveBeenCalledWith({
@ -1092,7 +1092,7 @@ describe('<CspPolicyTemplateForm />', () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
setup_access: { value: 'manual' },
credentials_type: { value: 'credentials-json' },
'gcp.credentials.type': { value: 'credentials-json' },
});
const { getByRole, getByLabelText } = render(
@ -1109,8 +1109,8 @@ describe('<CspPolicyTemplateForm />', () => {
it(`updates ${CLOUDBEAT_GCP} Credentials JSON fields`, () => {
let policy = getMockPolicyGCP();
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
project_id: { value: 'a' },
credentials_type: { value: 'credentials-json' },
'gcp.project_id': { value: 'a' },
'gcp.credentials.type': { value: 'credentials-json' },
setup_access: { value: 'manual' },
});
@ -1121,7 +1121,7 @@ describe('<CspPolicyTemplateForm />', () => {
userEvent.type(getByTestId(CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS.CREDENTIALS_JSON), 'b');
policy = getPosturePolicy(policy, CLOUDBEAT_GCP, {
credentials_json: { value: 'b' },
'gcp.credentials.json': { value: 'b' },
});
expect(onChange).toHaveBeenCalledWith({

View file

@ -50,10 +50,10 @@ export const GoogleCloudShellGuide = (props: { commandText: string }) => {
<>
<FormattedMessage
id="xpack.fleet.googleCloudShell.guide.steps.copy"
defaultMessage="Copy the following command and replace <PROJECT_ID> with your project ID."
defaultMessage="Replace <PROJECT_ID> in the following command with your project ID and copy the command"
/>
<EuiSpacer size="m" />
<EuiCodeBlock language="bash" isCopyable>
<EuiCodeBlock language="bash" isCopyable contentEditable="true">
{props.commandText}
</EuiCodeBlock>
</>