From 29400555583d397b48e72dfe13574c0d8b093fc2 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Fri, 1 Nov 2024 20:10:28 +0100 Subject: [PATCH] [kbn-test] validate isCloud by checking kbnHost (#198025) ## Summary Recently we had issues investigating SAML auth failures against MKI. The issue was caused by missing `TEST_CLOUD` env var that led to `isCloud` property to be set to false. This PR adds `isCloud` validation by checking if `kbnHost` is pointing to Cloud instance and throwing error about misconfiguration **How to test:** Try to run FTR tests against MKI without defining `TEST_CLOUD` env var --------- Co-authored-by: Aleh Zasypkin (cherry picked from commit 413f7c10b69aa01394beeeb1760a9b085d06d1e4) --- packages/kbn-test/src/auth/saml_auth.ts | 26 ++++++++++------- .../kbn-test/src/auth/session_manager.test.ts | 29 ++++++++++++++++++- packages/kbn-test/src/auth/session_manager.ts | 18 +++++++++++- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/packages/kbn-test/src/auth/saml_auth.ts b/packages/kbn-test/src/auth/saml_auth.ts index 0ec264d66d425..601fcf8301d9a 100644 --- a/packages/kbn-test/src/auth/saml_auth.ts +++ b/packages/kbn-test/src/auth/saml_auth.ts @@ -10,6 +10,7 @@ import { createSAMLResponse as createMockedSAMLResponse } from '@kbn/mock-idp-utils'; import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosResponse } from 'axios'; +import util from 'util'; import * as cheerio from 'cheerio'; import { Cookie, parse as parseCookie } from 'tough-cookie'; import Url from 'url'; @@ -253,23 +254,26 @@ export const finishSAMLHandshake = async ({ }) => { const encodedResponse = encodeURIComponent(samlResponse); const url = kbnHost + '/api/security/saml/callback'; + const request = { + url, + method: 'post', + data: `SAMLResponse=${encodedResponse}`, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + ...(sid ? { Cookie: `sid=${sid}` } : {}), + }, + validateStatus: () => true, + maxRedirects: 0, + }; let authResponse: AxiosResponse; try { - authResponse = await axios.request({ - url, - method: 'post', - data: `SAMLResponse=${encodedResponse}`, - headers: { - 'content-type': 'application/x-www-form-urlencoded', - ...(sid ? { Cookie: `sid=${sid}` } : {}), - }, - validateStatus: () => true, - maxRedirects: 0, - }); + authResponse = await axios.request(request); } catch (ex) { log.error('Failed to call SAML callback'); cleanException(url, ex); + // Logging the `Cookie: sid=xxxx` header is safe here since it’s an intermediate, non-authenticated cookie that cannot be reused if leaked. + log.error(`Request sent: ${util.inspect(request)}`); throw ex; } diff --git a/packages/kbn-test/src/auth/session_manager.test.ts b/packages/kbn-test/src/auth/session_manager.test.ts index 98c0181141e54..4b20581eced4c 100644 --- a/packages/kbn-test/src/auth/session_manager.test.ts +++ b/packages/kbn-test/src/auth/session_manager.test.ts @@ -172,7 +172,7 @@ describe('SamlSessionManager', () => { describe('for cloud session', () => { const hostOptions = { protocol: 'https' as 'http' | 'https', - hostname: 'cloud', + hostname: 'my-test-deployment.test.elastic.cloud', username: 'elastic', password: 'changeme', }; @@ -328,4 +328,31 @@ describe('SamlSessionManager', () => { expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); }); }); + + describe(`for cloud session with 'isCloud' set to false`, () => { + const hostOptions = { + protocol: 'http' as 'http' | 'https', + hostname: 'my-test-deployment.test.elastic.cloud', + username: 'elastic', + password: 'changeme', + }; + const samlSessionManagerOptions = { + hostOptions, + isCloud: false, + log, + cloudUsersFilePath, + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('should throw an error when kbnHost points to a Cloud instance', () => { + const kbnHost = `${hostOptions.protocol}://${hostOptions.hostname}`; + expect(() => new SamlSessionManager(samlSessionManagerOptions)).toThrow( + `SamlSessionManager: 'isCloud' was set to false, but 'kbnHost' appears to be a Cloud instance: ${kbnHost} +Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment` + ); + }); + }); }); diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index e376135296bd7..ba411aaa21891 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -54,7 +54,6 @@ export class SamlSessionManager { private readonly cloudUsersFilePath: string; constructor(options: SamlSessionManagerOptions) { - this.isCloud = options.isCloud; this.log = options.log; const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, @@ -62,6 +61,7 @@ export class SamlSessionManager { port: options.hostOptions.port, }; this.kbnHost = Url.format(hostOptionsWithoutAuth); + this.isCloud = options.isCloud; this.kbnClient = new KbnClient({ log: this.log, url: Url.format({ @@ -73,6 +73,22 @@ export class SamlSessionManager { this.sessionCache = new Map(); this.roleToUserMap = new Map(); this.supportedRoles = options.supportedRoles; + this.validateCloudSetting(); + } + + /** + * Validates if the 'kbnHost' points to Cloud, even if 'isCloud' was set to false + */ + private validateCloudSetting() { + const cloudSubDomains = ['elastic.cloud', 'foundit.no', 'cloud.es.io', 'elastic-cloud.com']; + const isCloudHost = cloudSubDomains.some((domain) => this.kbnHost.endsWith(domain)); + + if (!this.isCloud && isCloudHost) { + throw new Error( + `SamlSessionManager: 'isCloud' was set to false, but 'kbnHost' appears to be a Cloud instance: ${this.kbnHost} +Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment` + ); + } } /**