From 62f9b56c4cb0061682d857c3fb35150b13d91c0a Mon Sep 17 00:00:00 2001 From: Maxim Kholod Date: Tue, 26 Sep 2023 10:28:13 +0200 Subject: [PATCH] [Cloud Security] use only available agent versions for Cloudformation and Cloud Shell parameters (#166198) ## Summary fixes - https://github.com/elastic/security-team/issues/7557 instead of using Kibana version for Cloudformation and Cloud Shell params in CNVM and CSPM integrations, check if the version of an agent that matches the current Kibana version actually available as an artifact. Relevant for serverless, where Kibana version points to not-yet released versions of Elastic Agent. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../public/hooks/use_agent_version.test.ts | 125 ++++++++++++++++++ .../fleet/public/hooks/use_agent_version.ts | 27 +++- 2 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts b/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts new file mode 100644 index 0000000000000..6cb1c8ee42248 --- /dev/null +++ b/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; + +import { useAgentVersion } from './use_agent_version'; +import { useKibanaVersion } from './use_kibana_version'; +import { sendGetAgentsAvailableVersions } from './use_request'; + +jest.mock('./use_kibana_version'); +jest.mock('./use_request'); + +describe('useAgentVersion', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return agent version that matches Kibana version if released', async () => { + const mockKibanaVersion = '8.8.1'; + const mockAvailableVersions = ['8.9.0', '8.8.1', '8.8.0', '8.7.0']; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockResolvedValue({ + data: { items: mockAvailableVersions }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + + await waitForNextUpdate(); + + expect(result.current).toEqual(mockKibanaVersion); + }); + + it('should return the latest availeble agent version if a version that matches Kibana version is not released', async () => { + const mockKibanaVersion = '8.11.0'; + const mockAvailableVersions = ['8.8.0', '8.7.0', '8.9.2', '7.16.0']; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockResolvedValue({ + data: { items: mockAvailableVersions }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + + await waitForNextUpdate(); + + expect(result.current).toEqual('8.9.2'); + }); + + it('should return the agent version that is <= Kibana version if an agent version that matches Kibana version is not released', async () => { + const mockKibanaVersion = '8.8.3'; + const mockAvailableVersions = ['8.8.0', '8.8.1', '8.8.2', '8.7.0', '8.9.2', '7.16.0']; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockResolvedValue({ + data: { items: mockAvailableVersions }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + + await waitForNextUpdate(); + + expect(result.current).toEqual('8.8.2'); + }); + + it('should return the latest availeble agent version if a snapshot version', async () => { + const mockKibanaVersion = '8.10.0-SNAPSHOT'; + const mockAvailableVersions = ['8.8.0', '8.7.0', '8.9.2', '7.16.0']; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockResolvedValue({ + data: { items: mockAvailableVersions }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + + await waitForNextUpdate(); + + expect(result.current).toEqual('8.9.2'); + }); + + it('should return kibana version if no agent versions available', async () => { + const mockKibanaVersion = '8.11.0'; + const mockAvailableVersions: string[] = []; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockResolvedValue({ + data: { items: mockAvailableVersions }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + + await waitForNextUpdate(); + + expect(result.current).toEqual('8.11.0'); + }); + + it('should return kibana version if the list of available agent versions is not available', async () => { + const mockKibanaVersion = '8.11.0'; + + (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); + (sendGetAgentsAvailableVersions as jest.Mock).mockRejectedValue(new Error('Fetching error')); + + const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + + expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); + await waitForNextUpdate(); + + expect(result.current).toEqual(mockKibanaVersion); + }); +}); diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_version.ts b/x-pack/plugins/fleet/public/hooks/use_agent_version.ts index 32d0ee128ddcc..8c198dbc7773e 100644 --- a/x-pack/plugins/fleet/public/hooks/use_agent_version.ts +++ b/x-pack/plugins/fleet/public/hooks/use_agent_version.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { useEffect, useState } from 'react'; +import semverRcompare from 'semver/functions/rcompare'; +import semverLt from 'semver/functions/lt'; import { useKibanaVersion } from './use_kibana_version'; import { sendGetAgentsAvailableVersions } from './use_request'; /** - * @returns The most recent agent version available to install or upgrade to. + * @returns The most compatible agent version available to install or upgrade to. */ export const useAgentVersion = (): string | undefined => { const kibanaVersion = useKibanaVersion(); @@ -21,12 +22,26 @@ export const useAgentVersion = (): string | undefined => { const getVersions = async () => { try { const res = await sendGetAgentsAvailableVersions(); - // if the endpoint returns an error, use the fallback versions - const versionsList = res?.data?.items ? res.data.items : [kibanaVersion]; + const availableVersions = res?.data?.items; + let agentVersionToUse; + + if ( + availableVersions && + availableVersions.length > 0 && + availableVersions.indexOf(kibanaVersion) === -1 + ) { + availableVersions.sort(semverRcompare); + agentVersionToUse = + availableVersions.find((version) => { + return semverLt(version, kibanaVersion); + }) || availableVersions[0]; + } else { + agentVersionToUse = kibanaVersion; + } - setAgentVersion(versionsList[0]); + setAgentVersion(agentVersionToUse); } catch (err) { - return; + setAgentVersion(kibanaVersion); } };