diff --git a/src/dev/build/tasks/fetch_agent_versions_list.ts b/src/dev/build/tasks/fetch_agent_versions_list.ts
index bc4143f5000da..560c52fad07ba 100644
--- a/src/dev/build/tasks/fetch_agent_versions_list.ts
+++ b/src/dev/build/tasks/fetch_agent_versions_list.ts
@@ -19,29 +19,22 @@ const getAvailableVersions = async (log: ToolingLog) => {
};
// Endpoint maintained by the web-team and hosted on the elastic website
// See https://github.com/elastic/website-development/issues/9331
- const url = 'https://www.elastic.co/content/product_versions';
- log.info('Fetching Elastic Agent versions list');
- const results = await fetch(url, options);
- const rawBody = await results.text();
-
+ const url = 'https://www.elastic.co/api/product_versions';
try {
- const jsonBody = JSON.parse(rawBody);
+ log.info('Fetching Elastic Agent versions list');
+ const results = await fetch(url, options);
+
+ const jsonBody = await results.json();
const versions: string[] = (jsonBody.length ? jsonBody[0] : [])
.filter((item: any) => item?.title?.includes('Elastic Agent'))
.map((item: any) => item?.version_number);
- log.info(`Retrieved available Elastic Agent versions`);
+ log.info(`Retrieved available versions`);
return versions;
} catch (error) {
- log.warning(`Failed to fetch Elastic Agent versions list`);
- log.info(`Status: ${results.status}`);
- log.info(rawBody);
- if (process.env.BUILDKITE_PULL_REQUEST === 'true') {
- log.warning(error);
- } else {
- throw new Error(error);
- }
+ log.warning(`Failed to fetch versions list`);
+ log.warning(error);
}
return [];
};
@@ -54,8 +47,8 @@ export const FetchAgentVersionsList: Task = {
const versionsList = await getAvailableVersions(log);
const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json';
- if (versionsList.length !== 0) {
- log.info(`Writing Elastic Agent versions list to ${AGENT_VERSION_BUILD_FILE}`);
+ if (versionsList !== []) {
+ log.info(`Writing versions list to ${AGENT_VERSION_BUILD_FILE}`);
await write(
build.resolvePath(AGENT_VERSION_BUILD_FILE),
JSON.stringify(versionsList, null, ' ')
diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts
index 37453e1364f8d..fd99edd8705e7 100644
--- a/x-pack/plugins/cloud_security_posture/common/constants.ts
+++ b/x-pack/plugins/cloud_security_posture/common/constants.ts
@@ -63,6 +63,8 @@ export const CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE = 'csp-rule-template';
export const CLOUDBEAT_VANILLA = 'cloudbeat/cis_k8s';
export const CLOUDBEAT_EKS = 'cloudbeat/cis_eks';
+export const CLOUDBEAT_AKS = 'cloudbeat/cis_aks';
+export const CLOUDBEAT_GKE = 'cloudbeat/cis_gke';
export const CLOUDBEAT_AWS = 'cloudbeat/cis_aws';
export const CLOUDBEAT_GCP = 'cloudbeat/cis_gcp';
export const CLOUDBEAT_AZURE = 'cloudbeat/cis_azure';
diff --git a/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_aks_logo.svg b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_aks_logo.svg
new file mode 100644
index 0000000000000..f81d0ae7d0370
--- /dev/null
+++ b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_aks_logo.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_gke_logo.svg b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_gke_logo.svg
new file mode 100644
index 0000000000000..6dd28d61ebf0d
--- /dev/null
+++ b/x-pack/plugins/cloud_security_posture/public/assets/icons/cis_gke_logo.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
index 54e0b7467069a..185cd19f200cf 100644
--- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts
+++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
@@ -20,9 +20,13 @@ import {
VULN_MGMT_POLICY_TEMPLATE,
CLOUDBEAT_VULN_MGMT_GCP,
CLOUDBEAT_VULN_MGMT_AZURE,
+ CLOUDBEAT_AKS,
+ CLOUDBEAT_GKE,
} from '../../common/constants';
import eksLogo from '../assets/icons/cis_eks_logo.svg';
+import aksLogo from '../assets/icons/cis_aks_logo.svg';
+import gkeLogo from '../assets/icons/cis_gke_logo.svg';
export const statusColors = {
passed: euiThemeVars.euiColorVis0,
@@ -48,7 +52,7 @@ export interface CloudPostureIntegrationProps {
name: string;
shortName: string;
options: Array<{
- type: PostureInput;
+ type: PostureInput | typeof CLOUDBEAT_AKS | typeof CLOUDBEAT_GKE;
name: string;
benchmark: string;
disabled?: boolean;
@@ -119,7 +123,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
{
type: CLOUDBEAT_VANILLA,
name: i18n.translate('xpack.csp.kspmIntegration.vanillaOption.nameTitle', {
- defaultMessage: 'Self-Managed/Vanilla Kubernetes',
+ defaultMessage: 'Self-Managed',
}),
benchmark: i18n.translate('xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle', {
defaultMessage: 'CIS Kubernetes',
@@ -129,12 +133,43 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
{
type: CLOUDBEAT_EKS,
name: i18n.translate('xpack.csp.kspmIntegration.eksOption.nameTitle', {
- defaultMessage: 'EKS (Elastic Kubernetes Service)',
+ defaultMessage: 'EKS',
}),
benchmark: i18n.translate('xpack.csp.kspmIntegration.eksOption.benchmarkTitle', {
defaultMessage: 'CIS EKS',
}),
icon: eksLogo,
+ tooltip: i18n.translate('xpack.csp.kspmIntegration.eksOption.tooltipContent', {
+ defaultMessage: 'Elastic Kubernetes Service',
+ }),
+ },
+ {
+ type: CLOUDBEAT_AKS,
+ name: i18n.translate('xpack.csp.kspmIntegration.aksOption.nameTitle', {
+ defaultMessage: 'AKS',
+ }),
+ benchmark: i18n.translate('xpack.csp.kspmIntegration.aksOption.benchmarkTitle', {
+ defaultMessage: 'CIS AKS',
+ }),
+ disabled: true,
+ icon: aksLogo,
+ tooltip: i18n.translate('xpack.csp.kspmIntegration.aksOption.tooltipContent', {
+ defaultMessage: 'Azure Kubernetes Service - Coming soon',
+ }),
+ },
+ {
+ type: CLOUDBEAT_GKE,
+ name: i18n.translate('xpack.csp.kspmIntegration.gkeOption.nameTitle', {
+ defaultMessage: 'GKE',
+ }),
+ benchmark: i18n.translate('xpack.csp.kspmIntegration.gkeOption.benchmarkTitle', {
+ defaultMessage: 'CIS GKE',
+ }),
+ disabled: true,
+ icon: gkeLogo,
+ tooltip: i18n.translate('xpack.csp.kspmIntegration.gkeOption.tooltipContent', {
+ defaultMessage: 'Google Kubernetes Engine - Coming soon',
+ }),
},
],
},
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
index 7f3b7dc51319e..3427c25e49c19 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
@@ -101,8 +101,8 @@ describe('', () => {
it('renders KSPM input selector', () => {
const { getByLabelText } = render();
- const option1 = getByLabelText('Self-Managed/Vanilla Kubernetes');
- const option2 = getByLabelText('EKS (Elastic Kubernetes Service)');
+ const option1 = getByLabelText('Self-Managed');
+ const option2 = getByLabelText('EKS');
expect(option1).toBeInTheDocument();
expect(option2).toBeInTheDocument();
@@ -116,7 +116,7 @@ describe('', () => {
const eksPolicy = getMockPolicyEKS();
const { getByLabelText } = render();
- const option = getByLabelText('EKS (Elastic Kubernetes Service)');
+ const option = getByLabelText('EKS');
userEvent.click(option);
// Listen to the 2nd triggered by the test.
@@ -148,8 +148,8 @@ describe('', () => {
);
- const option1 = getByLabelText('Self-Managed/Vanilla Kubernetes');
- const option2 = getByLabelText('EKS (Elastic Kubernetes Service)');
+ const option1 = getByLabelText('Self-Managed');
+ const option2 = getByLabelText('EKS');
expect(option1).toBeInTheDocument();
expect(option2).toBeInTheDocument();
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
index 360becd415646..855dd3a3fe439 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
@@ -328,7 +328,7 @@ export class EndpointActionGenerator extends BaseDataGenerator {
stderr_truncated: true,
shell_code: 0,
shell: 'bash',
- cwd: '/some/path',
+ cwd: this.randomChoice(['/some/path', '/a-very/long/path'.repeat(30)]),
output_file_id: 'some-output-file-id',
output_file_stdout_truncated: this.randomChoice([true, false]),
output_file_stderr_truncated: this.randomChoice([true, false]),
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx
index b0c33c5d3ba39..956118e1ac297 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response.test.tsx
@@ -43,17 +43,42 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
};
});
+ const outputSuffix = 'output';
+
+ it('should show shell info and shell code', async () => {
+ render();
+ const { queryByTestId } = renderResult;
+ expect(queryByTestId(`test-executeResponseOutput-context`)).toBeInTheDocument();
+ expect(queryByTestId(`test-executeResponseOutput-shell`)).toBeInTheDocument();
+ expect(queryByTestId(`test-executeResponseOutput-cwd`)).toBeInTheDocument();
+ });
+
+ it('should show execute context accordion as `closed`', async () => {
+ render();
+ expect(renderResult.getByTestId('test-executeResponseOutput-context').className).toEqual(
+ 'euiAccordion'
+ );
+ });
+
+ it('should show current working directory', async () => {
+ render();
+ const { queryByTestId } = renderResult;
+ expect(queryByTestId(`test-executeResponseOutput-context`)).toBeInTheDocument();
+ expect(queryByTestId(`test-executeResponseOutput-cwd`)).toBeInTheDocument();
+ });
+
it('should show execute output and execute errors', async () => {
render();
- expect(renderResult.getByTestId('test-executeResponseOutput')).toBeTruthy();
+ const { queryByTestId } = renderResult;
+ expect(queryByTestId(`test-executeResponseOutput-${outputSuffix}`)).toBeInTheDocument();
+ expect(queryByTestId(`test-executeResponseOutput-error`)).toBeInTheDocument();
});
it('should show execute output accordion as `open`', async () => {
render();
- const accordionOutputButton = Array.from(
- renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
- )[0];
- expect(accordionOutputButton.className).toContain('isOpen');
+ expect(
+ renderResult.getByTestId(`test-executeResponseOutput-${outputSuffix}`).className
+ ).toContain('isOpen');
});
it('should show `-` in output accordion when no output content', async () => {
@@ -66,13 +91,11 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
},
},
};
+
render();
- const accordionOutputButton = Array.from(
- renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
- )[0];
- expect(accordionOutputButton.textContent).toContain(
- `Execution output (truncated)${getEmptyValue()}`
- );
+ expect(
+ renderResult.getByTestId(`test-executeResponseOutput-${outputSuffix}`).textContent
+ ).toContain(`Execution output (truncated)${getEmptyValue()}`);
});
it('should show `-` in error accordion when no error content', async () => {
@@ -85,18 +108,19 @@ describe('When using the `ExecuteActionHostResponse` component', () => {
},
},
};
+
render();
- const accordionErrorButton = Array.from(
- renderResult.getByTestId('test-executeResponseOutput').querySelectorAll('.euiAccordion')
- )[1];
- expect(accordionErrorButton.textContent).toContain(
+ expect(renderResult.getByTestId('test-executeResponseOutput-error').textContent).toContain(
`Execution error (truncated)${getEmptyValue()}`
);
});
it('should not show execute output accordions when no output in action details', () => {
(renderProps.action as ActionDetails).outputs = undefined;
+
render();
- expect(renderResult.queryByTestId('test-executeResponseOutput')).toBeNull();
+ const { queryByTestId } = renderResult;
+ expect(queryByTestId(`test-executeResponseOutput-context`)).not.toBeInTheDocument();
+ expect(queryByTestId(`test-executeResponseOutput-${outputSuffix}`)).not.toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx
index 1db3c4cd04983..85f3444d6ef3d 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_execute_action/execute_action_host_response_output.tsx
@@ -5,16 +5,30 @@
* 2.0.
*/
-import React, { memo } from 'react';
-import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiText, useGeneratedHtmlId } from '@elastic/eui';
+import React, { memo, useMemo } from 'react';
+import {
+ EuiAccordion,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiText,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { euiStyled } from '@kbn/kibana-react-plugin/common';
import type { ResponseActionExecuteOutputContent } from '../../../../common/endpoint/types';
import { getEmptyValue } from '../../../common/components/empty_value';
const emptyValue = getEmptyValue();
const ACCORDION_BUTTON_TEXT = Object.freeze({
+ context: i18n.translate(
+ 'xpack.securitySolution.responseActionExecuteAccordion.executionContext',
+ {
+ defaultMessage: 'Execution context',
+ }
+ ),
output: {
regular: i18n.translate(
'xpack.securitySolution.responseActionExecuteAccordion.outputButtonTextRegular',
@@ -44,36 +58,95 @@ const ACCORDION_BUTTON_TEXT = Object.freeze({
),
},
});
+
+const SHELL_INFO = Object.freeze({
+ shell: i18n.translate('xpack.securitySolution.responseActionExecuteAccordion.shellInformation', {
+ defaultMessage: 'Shell',
+ }),
+
+ returnCode: i18n.translate(
+ 'xpack.securitySolution.responseActionExecuteAccordion.shellReturnCode',
+ {
+ defaultMessage: 'Return code',
+ }
+ ),
+ currentDir: i18n.translate(
+ 'xpack.securitySolution.responseActionExecuteAccordion.currentWorkingDirectory',
+ {
+ defaultMessage: 'Current working directory',
+ }
+ ),
+});
+
+const StyledEuiText = euiStyled(EuiText)`
+ white-space: pre-wrap;
+ line-break: anywhere;
+`;
+
+interface ShellInfoContentProps {
+ content: string | number;
+ textSize?: 's' | 'xs';
+ title: string;
+}
+const ShellInfoContent = memo(({ content, textSize, title }) => (
+
+
+ {title}
+ {': '}
+
+ {content}
+
+));
+
+ShellInfoContent.displayName = 'ShellInfoContent';
+
interface ExecuteActionOutputProps {
- content?: string;
+ content?: string | React.ReactNode;
initialIsOpen?: boolean;
isTruncated?: boolean;
textSize?: 's' | 'xs';
- type: 'error' | 'output';
+ type: 'error' | 'output' | 'context';
+ 'data-test-subj'?: string;
}
const ExecutionActionOutputAccordion = memo(
- ({ content = emptyValue, initialIsOpen = false, isTruncated = false, textSize, type }) => {
+ ({
+ content = emptyValue,
+ initialIsOpen = false,
+ isTruncated = false,
+ textSize,
+ type,
+ 'data-test-subj': dataTestSubj,
+ }) => {
const id = useGeneratedHtmlId({
prefix: 'executeActionOutputAccordions',
suffix: type,
});
+
+ const accordionButtonContent = useMemo(
+ () => (
+
+ {type !== 'context'
+ ? isTruncated
+ ? ACCORDION_BUTTON_TEXT[type].truncated
+ : ACCORDION_BUTTON_TEXT[type].regular
+ : ACCORDION_BUTTON_TEXT[type]}
+
+ ),
+ [isTruncated, textSize, type]
+ );
+
return (
-
- {content}
-
+
+ {typeof content === 'string' ? {content}
: content}
+
);
}
@@ -87,24 +160,70 @@ export interface ExecuteActionHostResponseOutputProps {
}
export const ExecuteActionHostResponseOutput = memo(
- ({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs' }) => (
-
-
-
-
-
-
- )
+ ({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs' }) => {
+ const contextContent = useMemo(
+ () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ [dataTestSubj, outputContent.cwd, outputContent.shell, outputContent.shell_code, textSize]
+ );
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
);
ExecuteActionHostResponseOutput.displayName = 'ExecuteActionHostResponseOutput';
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
index 321f04e5f4311..3750d70f6e020 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
@@ -406,9 +406,7 @@ describe('When using Actions service utilities', () => {
])
).toEqual({
...NOT_COMPLETED_OUTPUT,
- outputs: {
- '789': expect.any(Object),
- },
+ outputs: expect.any(Object),
agentState: {
'123': {
completedAt: '2022-01-05T19:27:23.816Z',
@@ -444,10 +442,7 @@ describe('When using Actions service utilities', () => {
completedAt: COMPLETED_AT,
wasSuccessful: true,
errors: undefined,
- outputs: {
- 456: expect.any(Object),
- 789: expect.any(Object),
- },
+ outputs: expect.any(Object),
agentState: {
'123': {
completedAt: '2022-01-05T19:27:23.816Z',
@@ -489,9 +484,7 @@ describe('When using Actions service utilities', () => {
errors: ['Fleet action response error: something is no good'],
isCompleted: true,
wasSuccessful: false,
- outputs: {
- 789: expect.any(Object),
- },
+ outputs: expect.any(Object),
agentState: {
'123': {
completedAt: '2022-01-05T19:27:23.816Z',