From e2af848ef257de536f605c1fe45a8efa451aa319 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Thu, 3 Sep 2020 17:34:53 -0400 Subject: [PATCH] [Ingest Manager] Improve agent vs kibana version checks (#76238) (#76488) * Add logic spec'd in issue comments. Tests pass. * Change fn to accept 1 (opt 2) string vs object * Add tests based on issue comments * Change expected error message in test * Capitalize Kibana in error message Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../server/services/agents/enroll.test.ts | 70 ++++++++----------- .../server/services/agents/enroll.ts | 61 +++++++++++----- .../apis/fleet/agents/enroll.ts | 2 +- 3 files changed, 75 insertions(+), 58 deletions(-) diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts index 764564cfa49f5..44473bf2d8d79 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.test.ts @@ -5,62 +5,52 @@ */ import { validateAgentVersion } from './enroll'; -import { appContextService } from '../app_context'; -import { IngestManagerAppContext } from '../../plugin'; describe('validateAgentVersion', () => { it('should throw with agent > kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - expect(() => - validateAgentVersion({ - local: { elastic: { agent: { version: '8.8.0' } } }, - userProvided: {}, - }) - ).toThrowError(/Agent version is not compatible with kibana version/); + expect(() => validateAgentVersion('8.8.0', '8.0.0')).toThrowError('not compatible'); }); it('should work with agent < kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ local: { elastic: { agent: { version: '7.8.0' } } }, userProvided: {} }); + validateAgentVersion('7.8.0', '8.0.0'); }); it('should work with agent = kibana version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ local: { elastic: { agent: { version: '8.0.0' } } }, userProvided: {} }); + validateAgentVersion('8.0.0', '8.0.0'); }); it('should work with SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '8.0.0-SNAPSHOT', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '8.0.0-SNAPSHOT' } } }, - userProvided: {}, - }); + validateAgentVersion('8.0.0-SNAPSHOT', '8.0.0-SNAPSHOT'); }); it('should work with a agent using SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '7.8.0', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '7.8.0-SNAPSHOT' } } }, - userProvided: {}, - }); + validateAgentVersion('7.8.0-SNAPSHOT', '7.8.0'); }); it('should work with a kibana using SNAPSHOT version', () => { - appContextService.start(({ - kibanaVersion: '7.8.0-SNAPSHOT', - } as unknown) as IngestManagerAppContext); - validateAgentVersion({ - local: { elastic: { agent: { version: '7.8.0' } } }, - userProvided: {}, - }); + validateAgentVersion('7.8.0', '7.8.0-SNAPSHOT'); + }); + + it('very close versions, e.g. patch/prerelease - all combos should work', () => { + validateAgentVersion('7.9.1', '7.9.2'); + validateAgentVersion('7.8.1', '7.8.2'); + validateAgentVersion('7.6.99', '7.6.2'); + validateAgentVersion('7.6.2', '7.6.99'); + validateAgentVersion('5.94.3', '5.94.1234-SNAPSHOT'); + validateAgentVersion('5.94.3-SNAPSHOT', '5.94.1'); + }); + + it('somewhat close versions, minor release is 1 or 2 versions back and is older than the stack', () => { + validateAgentVersion('7.9.1', '7.10.2'); + validateAgentVersion('7.9.9', '7.11.1'); + validateAgentVersion('7.6.99', '7.6.2'); + validateAgentVersion('7.6.2', '7.6.99'); + expect(() => validateAgentVersion('5.94.3-SNAPSHOT', '5.93.1')).toThrowError('not compatible'); + expect(() => validateAgentVersion('5.94.3', '5.92.99-SNAPSHOT')).toThrowError('not compatible'); + }); + + it('versions where Agent is a minor version or major version greater (newer) than the stack should not work', () => { + expect(() => validateAgentVersion('7.10.1', '7.9.99')).toThrowError('not compatible'); + expect(() => validateAgentVersion('7.9.9', '6.11.1')).toThrowError('not compatible'); + expect(() => validateAgentVersion('5.94.3', '5.92.99-SNAPSHOT')).toThrowError('not compatible'); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index 606d5c4dcbb90..3c5850c722e97 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -20,7 +20,8 @@ export async function enroll( metadata?: { local: any; userProvided: any }, sharedId?: string ): Promise { - validateAgentVersion(metadata); + const agentVersion = metadata?.local?.elastic?.agent?.version; + validateAgentVersion(agentVersion); const existingAgent = sharedId ? await getAgentBySharedId(soClient, sharedId) : null; @@ -89,24 +90,50 @@ async function getAgentBySharedId(soClient: SavedObjectsClientContract, sharedId return null; } -export function validateAgentVersion(metadata?: { local: any; userProvided: any }) { - const kibanaVersion = semver.parse(appContextService.getKibanaVersion()); - if (!kibanaVersion) { - throw Boom.badRequest('Kibana version is not set'); - } - const version = semver.parse(metadata?.local?.elastic?.agent?.version); - if (!version) { - throw Boom.badRequest('Agent version not provided in metadata.'); +export function validateAgentVersion( + agentVersion: string, + kibanaVersion = appContextService.getKibanaVersion() +) { + const agentVersionParsed = semver.parse(agentVersion); + if (!agentVersionParsed) { + throw Boom.badRequest('Agent version not provided'); } - if (!version || !semver.lte(formatVersion(version), formatVersion(kibanaVersion))) { - throw Boom.badRequest('Agent version is not compatible with kibana version'); + const kibanaVersionParsed = semver.parse(kibanaVersion); + if (!kibanaVersionParsed) { + throw Boom.badRequest('Kibana version is not set or provided'); } -} -/** - * used to remove prelease from version as includePrerelease in not working as expected - */ -function formatVersion(version: semver.SemVer) { - return `${version.major}.${version.minor}.${version.patch}`; + const diff = semver.diff(agentVersion, kibanaVersion); + switch (diff) { + // section 1) very close versions, only patch release differences - all combos should work + // Agent a.b.1 < Kibana a.b.2 + // Agent a.b.2 > Kibana a.b.1 + case null: + case 'prerelease': + case 'prepatch': + case 'patch': + return; // OK + + // section 2) somewhat close versions, Agent minor release is 1 or 2 versions back and is older than the stack: + // Agent a.9.x < Kibana a.10.x + // Agent a.9.x < Kibana a.11.x + case 'preminor': + case 'minor': + if ( + agentVersionParsed.minor < kibanaVersionParsed.minor && + kibanaVersionParsed.minor - agentVersionParsed.minor <= 2 + ) + return; + + // section 3) versions where Agent is a minor version or major version greater (newer) than the stack should not work: + // Agent 7.10.x > Kibana 7.9.x + // Agent 8.0.x > Kibana 7.9.x + default: + if (semver.lte(agentVersionParsed, kibanaVersionParsed)) return; + else + throw Boom.badRequest( + `Agent version ${agentVersion} is not compatible with Kibana version ${kibanaVersion}` + ); + } } diff --git a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts index ec1f81ed1dd2b..eef0d3beb69d0 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/fleet/agents/enroll.ts @@ -115,7 +115,7 @@ export default function (providerContext: FtrProviderContext) { }, }) .expect(400); - expect(apiResponse.message).to.match(/Agent version is not compatible with kibana/); + expect(apiResponse.message).to.match(/is not compatible/); }); it('should allow to enroll an agent with a valid enrollment token', async () => {