From bf8c7f9785b0850236a1c5b75fcc0e5880e7069c Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 6 Dec 2023 14:25:57 +0100 Subject: [PATCH 01/13] [ftr] move SAML auth to kbn-test --- packages/kbn-test/index.ts | 71 +++----- packages/kbn-test/src/auth/helper.ts | 37 ++++ packages/kbn-test/src/auth/index.ts | 9 + .../kbn-test/src/auth}/saml_auth.ts | 44 ++--- packages/kbn-test/src/auth/session_manager.ts | 134 ++++++++++++++ packages/kbn-test/src/auth/types.ts | 33 ++++ .../test_serverless/shared/services/index.ts | 4 +- .../shared/services/svl_user_manager.ts | 19 ++ .../services/user_manager/svl_user_manager.ts | 168 ------------------ 9 files changed, 280 insertions(+), 239 deletions(-) create mode 100644 packages/kbn-test/src/auth/helper.ts create mode 100644 packages/kbn-test/src/auth/index.ts rename {x-pack/test_serverless/shared/services/user_manager => packages/kbn-test/src/auth}/saml_auth.ts (90%) create mode 100644 packages/kbn-test/src/auth/session_manager.ts create mode 100644 packages/kbn-test/src/auth/types.ts create mode 100644 x-pack/test_serverless/shared/services/svl_user_manager.ts delete mode 100644 x-pack/test_serverless/shared/services/user_manager/svl_user_manager.ts diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 5be415161a4a5..2b63d45a4c393 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -6,58 +6,43 @@ * Side Public License, v 1. */ -export { KbnClientRequesterError } from './src/kbn_client/kbn_client_requester_error'; - -// @internal -export { startServersCli, startServers } from './src/functional_tests/start_servers'; - -// @internal -export { runTestsCli, runTests } from './src/functional_tests/run_tests'; - -export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; -export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; - +export { getDockerFileMountPath } from '@kbn/es'; +export { kbnTestConfig } from './kbn_test_config'; +export { SAMLSessionManager } from './src/auth'; +export { CI_PARALLEL_PROCESS_PREFIX } from './src/ci_parallel_process_prefix'; +export { + createEsClientForFtrConfig, + createEsClientForTesting, + createRemoteEsClientForFtrConfig, + createTestEsCluster, + esTestConfig, +} from './src/es'; export type { CreateTestEsClusterOptions, + EsClientForTestingOptions, EsTestCluster, ICluster, - EsClientForTestingOptions, -} from './src/es'; -export { - esTestConfig, - createTestEsCluster, - createEsClientForTesting, - createEsClientForFtrConfig, - createRemoteEsClientForFtrConfig, } from './src/es'; - -export { kbnTestConfig } from './kbn_test_config'; - +export * from './src/find_test_plugin_paths'; +export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; +export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; +// @internal +export { runTests, runTestsCli } from './src/functional_tests/run_tests'; +// @internal +export { startServers, startServersCli } from './src/functional_tests/start_servers'; +export * from './src/functional_test_runner'; +export { getUrl } from './src/jest/get_url'; +export { runJest } from './src/jest/run'; +export { runCheckJestConfigsCli } from './src/jest/run_check_jest_configs_cli'; export { + adminTestUser, kibanaServerTestUser, + kibanaTestSuperuserServerless, kibanaTestUser, - adminTestUser, systemIndicesSuperuser, - kibanaTestSuperuserServerless, } from './src/kbn'; - -// @internal -export { setupJUnitReportGeneration, escapeCdata } from './src/mocha'; - -export { CI_PARALLEL_PROCESS_PREFIX } from './src/ci_parallel_process_prefix'; - -export * from './src/functional_test_runner'; - -export { getUrl } from './src/jest/get_url'; - -export { runCheckJestConfigsCli } from './src/jest/run_check_jest_configs_cli'; - -export { runJest } from './src/jest/run'; - export * from './src/kbn_archiver_cli'; - export * from './src/kbn_client'; - -export * from './src/find_test_plugin_paths'; - -export { getDockerFileMountPath } from '@kbn/es'; +export { KbnClientRequesterError } from './src/kbn_client/kbn_client_requester_error'; +// @internal +export { escapeCdata, setupJUnitReportGeneration } from './src/mocha'; diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts new file mode 100644 index 0000000000000..cfaaaaa5197dc --- /dev/null +++ b/packages/kbn-test/src/auth/helper.ts @@ -0,0 +1,37 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { REPO_ROOT } from '@kbn/repo-info'; +import * as fs from 'fs'; +import { resolve } from 'path'; +import { User } from './session_manager'; + +export const getProjectType = (serverArgs: string[]) => { + const svlArg = serverArgs.filter((arg) => arg.startsWith('--serverless')); + if (svlArg.length === 0) { + throw new Error('--serverless argument is missing in kbnTestServer.serverArgs'); + } + return svlArg[0].split('=')[1]; +}; + +/** + * Loads cloud users from '.ftr/role_users.json' + * QAF prepares the file for CI pipelines, make sure to add it manually for local run + */ +export const readCloudUsersFromFile = (): Array<[string, User]> => { + const cloudRoleUsersFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); + if (!fs.existsSync(cloudRoleUsersFilePath)) { + throw new Error(`Please define user roles with email/password in ${cloudRoleUsersFilePath}`); + } + const data = fs.readFileSync(cloudRoleUsersFilePath, 'utf8'); + if (data.length === 0) { + throw new Error(`'${cloudRoleUsersFilePath}' is empty: no roles are defined`); + } + + return Object.entries(JSON.parse(data)) as Array<[string, User]>; +}; diff --git a/packages/kbn-test/src/auth/index.ts b/packages/kbn-test/src/auth/index.ts new file mode 100644 index 0000000000000..86229439a3dcb --- /dev/null +++ b/packages/kbn-test/src/auth/index.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { SAMLSessionManager } from './session_manager'; diff --git a/x-pack/test_serverless/shared/services/user_manager/saml_auth.ts b/packages/kbn-test/src/auth/saml_auth.ts similarity index 90% rename from x-pack/test_serverless/shared/services/user_manager/saml_auth.ts rename to packages/kbn-test/src/auth/saml_auth.ts index ac69ec402fa7c..996f16eace38a 100644 --- a/x-pack/test_serverless/shared/services/user_manager/saml_auth.ts +++ b/packages/kbn-test/src/auth/saml_auth.ts @@ -1,40 +1,32 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { createSAMLResponse as createMockedSAMLResponse } from '@kbn/mock-idp-plugin/common'; import { ToolingLog } from '@kbn/tooling-log'; import axios, { AxiosResponse } from 'axios'; import * as cheerio from 'cheerio'; -import { parse as parseCookie } from 'tough-cookie'; +import { Cookie, parse as parseCookie } from 'tough-cookie'; import Url from 'url'; -import { Session } from './svl_user_manager'; - -export interface CloudSamlSessionParams { - email: string; - password: string; - kbnHost: string; - kbnVersion: string; - log: ToolingLog; -} - -export interface LocalSamlSessionParams { - username: string; - email: string; - fullname: string; - role: string; - kbnHost: string; - log: ToolingLog; -} +import { CloudSamlSessionParams, CreateSamlSessionParams, LocalSamlSessionParams } from './types'; + +export class Session { + readonly cookie; + readonly email; + readonly fullname; + constructor(cookie: Cookie, email: string, fullname: string) { + this.cookie = cookie; + this.email = email; + this.fullname = fullname; + } -export interface CreateSamlSessionParams { - hostname: string; - email: string; - password: string; - log: ToolingLog; + getCookieValue() { + return this.cookie.value; + } } const cleanException = (url: string, ex: any) => { diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts new file mode 100644 index 0000000000000..724e04be5777e --- /dev/null +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -0,0 +1,134 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ToolingLog } from '@kbn/tooling-log'; +import Url from 'url'; +import type { Config } from '../functional_test_runner'; +import { KbnClient } from '../kbn_client'; +import { getProjectType, readCloudUsersFromFile } from './helper'; +import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; + +export interface User { + readonly email: string; + readonly password: string; +} + +export type Role = string; + +export class SAMLSessionManager { + private readonly config: Config; + private readonly log: ToolingLog; + private readonly sessionCache; + private readonly isCloud: boolean; + private readonly kbnHost: string; + private readonly kbnClient: KbnClient; + private readonly roleToUserMap: Map; + private readonly svlProjectType: string; + constructor(config: Config, log: ToolingLog, isCloud: boolean) { + this.config = config; + this.log = log; + this.sessionCache = new Map(); + this.isCloud = isCloud; + this.roleToUserMap = new Map(); + + const hostOptions = { + protocol: this.config.get('servers.kibana.protocol'), + hostname: this.config.get('servers.kibana.hostname'), + port: isCloud ? undefined : this.config.get('servers.kibana.port'), + }; + this.kbnHost = Url.format(hostOptions); + this.kbnClient = new KbnClient({ + log: this.log, + url: Url.format({ + ...hostOptions, + auth: `${this.config.get('servers.kibana.username')}:${this.config.get( + 'servers.kibana.password' + )}`, + }), + }); + this.svlProjectType = getProjectType(this.config.get('kbnTestServer.serverArgs')); + } + + // we should split packages/kbn-es/src/serverless_resources/roles.yml into 3 different files + + // getLocalUsers = () => { + // const rolesDefinitionFilePath = resolve( + // REPO_ROOT, + // 'packages/kbn-es/src/serverless_resources/roles.yml' + // ); + // const roles: string[] = Object.keys(loadYaml(fs.readFileSync(rolesDefinitionFilePath, 'utf8'))); + // }; + + private getCloudUsers = () => { + // Loading cloud users on the first call + if (this.roleToUserMap.size === 0) { + const data = readCloudUsersFromFile(); + for (const [roleName, user] of data) { + this.roleToUserMap.set(roleName, user); + } + } + + return this.roleToUserMap; + }; + + private getCloudUserByRole = (role: string) => { + if (this.getCloudUsers().has(role)) { + return this.getCloudUsers().get(role)!; + } else { + throw new Error(`User with '${role}' role is not defined for ${this.svlProjectType} project`); + } + }; + + private getSessionByRole = async (role: string) => { + if (this.sessionCache.has(role)) { + return this.sessionCache.get(role)!; + } + + let session: Session; + const kbnVersion = await this.kbnClient.version.get(); + if (this.isCloud) { + this.log.debug(`new SAML authentication with '${role}' role`); + const { email, password } = this.getCloudUserByRole(role); + session = await createCloudSAMLSession({ + email, + password, + kbnHost: this.kbnHost, + kbnVersion, + log: this.log, + }); + } else { + this.log.debug(`new fake SAML authentication with '${role}' role`); + session = await createLocalSAMLSession({ + username: `elastic_${role}`, + email: `elastic_${role}@elastic.co`, + fullname: `test ${role}`, + role, + kbnHost: this.kbnHost, + log: this.log, + }); + } + + this.sessionCache.set(role, session); + return session; + }; + + async getApiCredentialsForRole(role: string) { + const session = await this.getSessionByRole(role); + return { Cookie: `sid=${session.getCookieValue()}` }; + } + + async getSessionCookieForRole(role: string) { + const session = await this.getSessionByRole(role); + return session.getCookieValue(); + } + + async getUserData(role: string) { + const { email, fullname } = await this.getSessionByRole(role); + return { email, fullname }; + } +} diff --git a/packages/kbn-test/src/auth/types.ts b/packages/kbn-test/src/auth/types.ts new file mode 100644 index 0000000000000..6eb3812dc6a9b --- /dev/null +++ b/packages/kbn-test/src/auth/types.ts @@ -0,0 +1,33 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ToolingLog } from '@kbn/tooling-log'; + +export interface CloudSamlSessionParams { + kbnHost: string; + kbnVersion: string; + email: string; + password: string; + log: ToolingLog; +} + +export interface LocalSamlSessionParams { + kbnHost: string; + email: string; + username: string; + fullname: string; + role: string; + log: ToolingLog; +} + +export interface CreateSamlSessionParams { + hostname: string; + email: string; + password: string; + log: ToolingLog; +} diff --git a/x-pack/test_serverless/shared/services/index.ts b/x-pack/test_serverless/shared/services/index.ts index 3803ca84490e5..90c9460213379 100644 --- a/x-pack/test_serverless/shared/services/index.ts +++ b/x-pack/test_serverless/shared/services/index.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { SvlReportingServiceProvider } from './svl_reporting'; import { SupertestProvider, SupertestWithoutAuthProvider } from './supertest'; import { SvlCommonApiServiceProvider } from './svl_common_api'; -import { SvlUserManagerProvider } from './user_manager/svl_user_manager'; +import { SvlReportingServiceProvider } from './svl_reporting'; +import { SvlUserManagerProvider } from './svl_user_manager'; export const services = { supertest: SupertestProvider, diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/x-pack/test_serverless/shared/services/svl_user_manager.ts new file mode 100644 index 0000000000000..89672fa8fc9f0 --- /dev/null +++ b/x-pack/test_serverless/shared/services/svl_user_manager.ts @@ -0,0 +1,19 @@ +/* + * 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 { SAMLSessionManager } from '@kbn/test'; +import { FtrProviderContext } from '../../functional/ftr_provider_context'; + +export function SvlUserManagerProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + const log = getService('log'); + const isCloud = !!process.env.TEST_CLOUD; + // Sharing the instance within FTR config run means cookies are persistent for each role between tests. + const sessionManager = new SAMLSessionManager(config, log, isCloud); + + return sessionManager; +} diff --git a/x-pack/test_serverless/shared/services/user_manager/svl_user_manager.ts b/x-pack/test_serverless/shared/services/user_manager/svl_user_manager.ts deleted file mode 100644 index fd9560e7a7a80..0000000000000 --- a/x-pack/test_serverless/shared/services/user_manager/svl_user_manager.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 { REPO_ROOT } from '@kbn/repo-info'; -import * as fs from 'fs'; -import { load as loadYaml } from 'js-yaml'; -import { resolve } from 'path'; -import { Cookie } from 'tough-cookie'; -import Url from 'url'; -import { FtrProviderContext } from '../../../functional/ftr_provider_context'; -import { createCloudSAMLSession, createLocalSAMLSession } from './saml_auth'; - -export interface User { - readonly email: string; - readonly password: string; -} - -export type Role = string; - -export class Session { - readonly cookie; - readonly email; - readonly fullname; - constructor(cookie: Cookie, email: string, fullname: string) { - this.cookie = cookie; - this.email = email; - this.fullname = fullname; - } - - getCookieValue() { - return this.cookie.value; - } -} - -export function SvlUserManagerProvider({ getService }: FtrProviderContext) { - const kibanaServer = getService('kibanaServer'); - const config = getService('config'); - const log = getService('log'); - const isServerless = config.get('serverless'); - const isCloud = !!process.env.TEST_CLOUD; - const cloudRoleUsersFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); - const rolesDefinitionFilePath = resolve( - REPO_ROOT, - 'packages/kbn-es/src/serverless_resources/roles.yml' - ); - const roles: string[] = Object.keys(loadYaml(fs.readFileSync(rolesDefinitionFilePath, 'utf8'))); - const roleToUserMap: Map = new Map(); - - if (!isServerless) { - throw new Error(`'svlUserManager' service can't be used in non-serverless FTR context`); - } - - if (isCloud) { - // QAF should prepare the '.ftr/role_users.json' file for MKI pipelines - if (!fs.existsSync(cloudRoleUsersFilePath)) { - throw new Error( - `svlUserManager service requires user roles to be defined in ${cloudRoleUsersFilePath}` - ); - } - - const data = fs.readFileSync(cloudRoleUsersFilePath, 'utf8'); - if (data.length === 0) { - throw new Error(`'${cloudRoleUsersFilePath}' is empty: no roles are defined`); - } - for (const [roleName, user] of Object.entries(JSON.parse(data)) as Array<[string, User]>) { - roleToUserMap.set(roleName, user); - } - } - // to be re-used within FTR config run - const sessionCache = new Map(); - - const getCloudUserByRole = (role: string) => { - if (!roles.includes(role)) { - log.warning(`Role '${role}' is not listed in 'kbn-es/src/serverless_resources/roles.yml'`); - } - if (roleToUserMap.has(role)) { - return roleToUserMap.get(role)!; - } else { - throw new Error(`User with '${role}' role is not defined`); - } - }; - - const getSessionByRole = async (role: string) => { - if (sessionCache.has(role)) { - return sessionCache.get(role)!; - } - - const kbnHost = Url.format({ - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: isCloud ? undefined : config.get('servers.kibana.port'), - }); - let session: Session; - - if (isCloud) { - log.debug(`new SAML authentication with '${role}' role`); - const kbnVersion = await kibanaServer.version.get(); - session = await createCloudSAMLSession({ - ...getCloudUserByRole(role), - kbnHost, - kbnVersion, - log, - }); - } else { - log.debug(`new fake SAML authentication with '${role}' role`); - session = await createLocalSAMLSession({ - username: `elastic_${role}`, - email: `elastic_${role}@elastic.co`, - fullname: `test ${role}`, - role, - kbnHost, - log, - }); - } - - sessionCache.set(role, session); - return session; - }; - - return { - /* - * Returns auth header to do API calls with 'supertestWithoutAuth' service - * - * @example Create API call as a user with viewer role - * - * ```ts - * const credentials = await svlUserManager.getApiCredentialsForRole('viewer'); - * const response = await supertestWithoutAuth - * .get('/api/status') - * .set(credentials) - * .set('kbn-xsrf', 'kibana'); - * ``` - */ - async getApiCredentialsForRole(role: string) { - const session = await getSessionByRole(role); - return { Cookie: `sid=${session.getCookieValue()}` }; - }, - - /** - * Returns sid cookie that can be added to browser context for authentication - * - * @example Set cookie in browser context to login with specific role - * - * ```ts - * const sidCookie = await svlUserManager.getSessionCookieForRole(role); - * Loading bootstrap.js in order to be on the domain that the cookie will be set for. - * await browser.get(deployment.getHostPort() + '/bootstrap.js'); - * await browser.setCookie('sid', sidCookie); - * ``` - */ - async getSessionCookieForRole(role: string) { - const session = await getSessionByRole(role); - return session.getCookieValue(); - }, - - /** - * Returns SAML user email and full name - */ - async getUserData(role: string) { - const { email, fullname } = await getSessionByRole(role); - return { email, fullname }; - }, - }; -} From 3ecba70ada0d7b59276b256190e53b862e002f12 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 6 Dec 2023 14:29:22 +0100 Subject: [PATCH 02/13] fetch kibana version only for cloud saml session --- packages/kbn-test/src/auth/session_manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 724e04be5777e..3befa8997071f 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -90,9 +90,10 @@ export class SAMLSessionManager { } let session: Session; - const kbnVersion = await this.kbnClient.version.get(); + if (this.isCloud) { this.log.debug(`new SAML authentication with '${role}' role`); + const kbnVersion = await this.kbnClient.version.get(); const { email, password } = this.getCloudUserByRole(role); session = await createCloudSAMLSession({ email, From cdbf959544e91566e21dbc6dc256148229b74f3b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:43:01 +0000 Subject: [PATCH 03/13] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-test/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 7d73db67a0b92..abf0a35c4438a 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -32,5 +32,6 @@ "@kbn/babel-register", "@kbn/repo-packages", "@kbn/core-saved-objects-api-server", + "@kbn/mock-idp-plugin", ] } From 30a9fa14bc839494f560395157dd3ede8d3e47f8 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 6 Dec 2023 17:04:15 +0100 Subject: [PATCH 04/13] revert local prettier change --- packages/kbn-test/index.ts | 69 +++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 2b63d45a4c393..3ad2e590ffb58 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -6,43 +6,58 @@ * Side Public License, v 1. */ -export { getDockerFileMountPath } from '@kbn/es'; -export { kbnTestConfig } from './kbn_test_config'; +export { KbnClientRequesterError } from './src/kbn_client/kbn_client_requester_error'; + +// @internal +export { startServersCli, startServers } from './src/functional_tests/start_servers'; + +// @internal +export { runTestsCli, runTests } from './src/functional_tests/run_tests'; export { SAMLSessionManager } from './src/auth'; -export { CI_PARALLEL_PROCESS_PREFIX } from './src/ci_parallel_process_prefix'; -export { - createEsClientForFtrConfig, - createEsClientForTesting, - createRemoteEsClientForFtrConfig, - createTestEsCluster, - esTestConfig, -} from './src/es'; +export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; +export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; + export type { CreateTestEsClusterOptions, - EsClientForTestingOptions, EsTestCluster, ICluster, + EsClientForTestingOptions, } from './src/es'; -export * from './src/find_test_plugin_paths'; -export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; -export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; -// @internal -export { runTests, runTestsCli } from './src/functional_tests/run_tests'; -// @internal -export { startServers, startServersCli } from './src/functional_tests/start_servers'; -export * from './src/functional_test_runner'; -export { getUrl } from './src/jest/get_url'; -export { runJest } from './src/jest/run'; -export { runCheckJestConfigsCli } from './src/jest/run_check_jest_configs_cli'; export { - adminTestUser, + esTestConfig, + createTestEsCluster, + createEsClientForTesting, + createEsClientForFtrConfig, + createRemoteEsClientForFtrConfig, +} from './src/es'; + +export { kbnTestConfig } from './kbn_test_config'; + +export { kibanaServerTestUser, - kibanaTestSuperuserServerless, kibanaTestUser, + adminTestUser, systemIndicesSuperuser, + kibanaTestSuperuserServerless, } from './src/kbn'; + +// @internal +export { setupJUnitReportGeneration, escapeCdata } from './src/mocha'; + +export { CI_PARALLEL_PROCESS_PREFIX } from './src/ci_parallel_process_prefix'; + +export * from './src/functional_test_runner'; + +export { getUrl } from './src/jest/get_url'; + +export { runCheckJestConfigsCli } from './src/jest/run_check_jest_configs_cli'; + +export { runJest } from './src/jest/run'; + export * from './src/kbn_archiver_cli'; + export * from './src/kbn_client'; -export { KbnClientRequesterError } from './src/kbn_client/kbn_client_requester_error'; -// @internal -export { escapeCdata, setupJUnitReportGeneration } from './src/mocha'; + +export * from './src/find_test_plugin_paths'; + +export { getDockerFileMountPath } from '@kbn/es'; \ No newline at end of file From d217a4cf8dfd9d2ad52f9b62f4f69477af00c6bb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:49:47 +0000 Subject: [PATCH 05/13] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- packages/kbn-test/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 3ad2e590ffb58..307d620c9799b 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -60,4 +60,4 @@ export * from './src/kbn_client'; export * from './src/find_test_plugin_paths'; -export { getDockerFileMountPath } from '@kbn/es'; \ No newline at end of file +export { getDockerFileMountPath } from '@kbn/es'; From 82f750b027c14b67999105c0a8c44c5722660fae Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 09:41:33 +0100 Subject: [PATCH 06/13] change interface to not rely on FTR config --- packages/kbn-test/index.ts | 2 +- packages/kbn-test/src/auth/helper.ts | 17 +--- packages/kbn-test/src/auth/index.ts | 2 +- packages/kbn-test/src/auth/session_manager.ts | 94 ++++++++++--------- .../shared/services/svl_user_manager.ts | 15 ++- 5 files changed, 70 insertions(+), 60 deletions(-) diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 3ad2e590ffb58..4e85a00f81349 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -13,7 +13,7 @@ export { startServersCli, startServers } from './src/functional_tests/start_serv // @internal export { runTestsCli, runTests } from './src/functional_tests/run_tests'; -export { SAMLSessionManager } from './src/auth'; +export { SamlSessionManager, type SamlSessionManagerOptions, type HostOptions } from './src/auth'; export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts index cfaaaaa5197dc..dcd9ce01d8b5d 100644 --- a/packages/kbn-test/src/auth/helper.ts +++ b/packages/kbn-test/src/auth/helper.ts @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import { REPO_ROOT } from '@kbn/repo-info'; import * as fs from 'fs'; -import { resolve } from 'path'; import { User } from './session_manager'; export const getProjectType = (serverArgs: string[]) => { @@ -19,18 +17,13 @@ export const getProjectType = (serverArgs: string[]) => { return svlArg[0].split('=')[1]; }; -/** - * Loads cloud users from '.ftr/role_users.json' - * QAF prepares the file for CI pipelines, make sure to add it manually for local run - */ -export const readCloudUsersFromFile = (): Array<[string, User]> => { - const cloudRoleUsersFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); - if (!fs.existsSync(cloudRoleUsersFilePath)) { - throw new Error(`Please define user roles with email/password in ${cloudRoleUsersFilePath}`); +export const readCloudUsersFromFile = (filePath: string): Array<[string, User]> => { + if (!fs.existsSync(filePath)) { + throw new Error(`Please define user roles with email/password in ${filePath}`); } - const data = fs.readFileSync(cloudRoleUsersFilePath, 'utf8'); + const data = fs.readFileSync(filePath, 'utf8'); if (data.length === 0) { - throw new Error(`'${cloudRoleUsersFilePath}' is empty: no roles are defined`); + throw new Error(`'${filePath}' is empty: no roles are defined`); } return Object.entries(JSON.parse(data)) as Array<[string, User]>; diff --git a/packages/kbn-test/src/auth/index.ts b/packages/kbn-test/src/auth/index.ts index 86229439a3dcb..80a1de986dee9 100644 --- a/packages/kbn-test/src/auth/index.ts +++ b/packages/kbn-test/src/auth/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { SAMLSessionManager } from './session_manager'; +export { SamlSessionManager, type SamlSessionManagerOptions, type HostOptions } from './session_manager'; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 3befa8997071f..82f8432c73eee 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -6,11 +6,12 @@ * Side Public License, v 1. */ +import { REPO_ROOT } from '@kbn/repo-info'; import { ToolingLog } from '@kbn/tooling-log'; +import { resolve } from 'path'; import Url from 'url'; -import type { Config } from '../functional_test_runner'; import { KbnClient } from '../kbn_client'; -import { getProjectType, readCloudUsersFromFile } from './helper'; +import { readCloudUsersFromFile } from './helper'; import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; export interface User { @@ -20,54 +21,59 @@ export interface User { export type Role = string; -export class SAMLSessionManager { - private readonly config: Config; - private readonly log: ToolingLog; - private readonly sessionCache; +export interface HostOptions { + protocol: 'http' | 'https'; + hostname: string; + port?: number; + username: string; + password: string; +} + +export interface SamlSessionManagerOptions { + hostOptions: HostOptions; + isCloud: boolean; + log: ToolingLog; +} + +/** + * Manages cookies assosiated with user roles + */ +export class SamlSessionManager { private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; - private readonly roleToUserMap: Map; - private readonly svlProjectType: string; - constructor(config: Config, log: ToolingLog, isCloud: boolean) { - this.config = config; - this.log = log; - this.sessionCache = new Map(); - this.isCloud = isCloud; - this.roleToUserMap = new Map(); - - const hostOptions = { - protocol: this.config.get('servers.kibana.protocol'), - hostname: this.config.get('servers.kibana.hostname'), - port: isCloud ? undefined : this.config.get('servers.kibana.port'), - }; - this.kbnHost = Url.format(hostOptions); + private readonly log: ToolingLog; + private readonly roleToUserMap: Map; + private readonly sessionCache: Map; + private readonly userRoleFilePath = resolve(REPO_ROOT, '.ftr', 'role_users.json'); + + constructor(options: SamlSessionManagerOptions) { + this.isCloud = options.isCloud; + this.log = options.log; + const hostOptionsWithoutAuth = { + protocol: options.hostOptions.protocol, + hostname: options.hostOptions.hostname, + port: options.hostOptions.port, + } + this.kbnHost = Url.format(hostOptionsWithoutAuth); this.kbnClient = new KbnClient({ - log: this.log, - url: Url.format({ - ...hostOptions, - auth: `${this.config.get('servers.kibana.username')}:${this.config.get( - 'servers.kibana.password' - )}`, - }), - }); - this.svlProjectType = getProjectType(this.config.get('kbnTestServer.serverArgs')); + log: this.log, + url: Url.format({ + ...hostOptionsWithoutAuth, + auth: `${options.hostOptions.username}:${options.hostOptions.password}`, + }), + }); + this.sessionCache = new Map(); + this.roleToUserMap = new Map(); } - // we should split packages/kbn-es/src/serverless_resources/roles.yml into 3 different files - - // getLocalUsers = () => { - // const rolesDefinitionFilePath = resolve( - // REPO_ROOT, - // 'packages/kbn-es/src/serverless_resources/roles.yml' - // ); - // const roles: string[] = Object.keys(loadYaml(fs.readFileSync(rolesDefinitionFilePath, 'utf8'))); - // }; - + /** + * Loads cloud users from '.ftr/role_users.json' + * QAF prepares the file for CI pipelines, make sure to add it manually for local run + */ private getCloudUsers = () => { - // Loading cloud users on the first call if (this.roleToUserMap.size === 0) { - const data = readCloudUsersFromFile(); + const data = readCloudUsersFromFile(this.userRoleFilePath); for (const [roleName, user] of data) { this.roleToUserMap.set(roleName, user); } @@ -80,7 +86,7 @@ export class SAMLSessionManager { if (this.getCloudUsers().has(role)) { return this.getCloudUsers().get(role)!; } else { - throw new Error(`User with '${role}' role is not defined for ${this.svlProjectType} project`); + throw new Error(`User with '${role}' role is not defined`); } }; @@ -92,7 +98,7 @@ export class SAMLSessionManager { let session: Session; if (this.isCloud) { - this.log.debug(`new SAML authentication with '${role}' role`); + this.log.debug(`new cloud SAML authentication with '${role}' role`); const kbnVersion = await this.kbnClient.version.get(); const { email, password } = this.getCloudUserByRole(role); session = await createCloudSAMLSession({ diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/x-pack/test_serverless/shared/services/svl_user_manager.ts index 89672fa8fc9f0..38f8a1a087d5f 100644 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ b/x-pack/test_serverless/shared/services/svl_user_manager.ts @@ -5,15 +5,26 @@ * 2.0. */ -import { SAMLSessionManager } from '@kbn/test'; +import { SamlSessionManager } from '@kbn/test'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; export function SvlUserManagerProvider({ getService }: FtrProviderContext) { const config = getService('config'); const log = getService('log'); const isCloud = !!process.env.TEST_CLOUD; + // Sharing the instance within FTR config run means cookies are persistent for each role between tests. - const sessionManager = new SAMLSessionManager(config, log, isCloud); + const sessionManager = new SamlSessionManager({ + hostOptions: { + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: isCloud ? undefined : config.get('servers.kibana.port'), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), + }, + log, + isCloud + }); return sessionManager; } From 3f464666651e9fede95d8ecf910ffd05220bdec7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 7 Dec 2023 09:25:54 +0000 Subject: [PATCH 07/13] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- packages/kbn-test/src/auth/index.ts | 6 +++++- packages/kbn-test/src/auth/session_manager.ts | 14 +++++++------- .../shared/services/svl_user_manager.ts | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/kbn-test/src/auth/index.ts b/packages/kbn-test/src/auth/index.ts index 80a1de986dee9..00631c3ab2b0a 100644 --- a/packages/kbn-test/src/auth/index.ts +++ b/packages/kbn-test/src/auth/index.ts @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export { SamlSessionManager, type SamlSessionManagerOptions, type HostOptions } from './session_manager'; +export { + SamlSessionManager, + type SamlSessionManagerOptions, + type HostOptions, +} from './session_manager'; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 82f8432c73eee..0e92210691f50 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -54,15 +54,15 @@ export class SamlSessionManager { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, port: options.hostOptions.port, - } + }; this.kbnHost = Url.format(hostOptionsWithoutAuth); this.kbnClient = new KbnClient({ - log: this.log, - url: Url.format({ - ...hostOptionsWithoutAuth, - auth: `${options.hostOptions.username}:${options.hostOptions.password}`, - }), - }); + log: this.log, + url: Url.format({ + ...hostOptionsWithoutAuth, + auth: `${options.hostOptions.username}:${options.hostOptions.password}`, + }), + }); this.sessionCache = new Map(); this.roleToUserMap = new Map(); } diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/x-pack/test_serverless/shared/services/svl_user_manager.ts index 38f8a1a087d5f..042cee2ffed3e 100644 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ b/x-pack/test_serverless/shared/services/svl_user_manager.ts @@ -23,7 +23,7 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { password: config.get('servers.kibana.password'), }, log, - isCloud + isCloud, }); return sessionManager; From fac981eafd25e44b70840fcd275991cf8fc596b7 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 14:24:13 +0100 Subject: [PATCH 08/13] clean code --- packages/kbn-test/src/auth/helper.ts | 14 +++----------- packages/kbn-test/src/auth/session_manager.ts | 8 +------- packages/kbn-test/src/auth/types.ts | 7 +++++++ .../functional/page_objects/svl_common_page.ts | 2 +- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts index dcd9ce01d8b5d..d13e3ef69f37b 100644 --- a/packages/kbn-test/src/auth/helper.ts +++ b/packages/kbn-test/src/auth/helper.ts @@ -7,17 +7,9 @@ */ import * as fs from 'fs'; -import { User } from './session_manager'; +import { Role, User } from './types'; -export const getProjectType = (serverArgs: string[]) => { - const svlArg = serverArgs.filter((arg) => arg.startsWith('--serverless')); - if (svlArg.length === 0) { - throw new Error('--serverless argument is missing in kbnTestServer.serverArgs'); - } - return svlArg[0].split('=')[1]; -}; - -export const readCloudUsersFromFile = (filePath: string): Array<[string, User]> => { +export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => { if (!fs.existsSync(filePath)) { throw new Error(`Please define user roles with email/password in ${filePath}`); } @@ -26,5 +18,5 @@ export const readCloudUsersFromFile = (filePath: string): Array<[string, User]> throw new Error(`'${filePath}' is empty: no roles are defined`); } - return Object.entries(JSON.parse(data)) as Array<[string, User]>; + return Object.entries(JSON.parse(data)) as Array<[Role, User]>; }; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 82f8432c73eee..ece9d3b8362d0 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -13,13 +13,7 @@ import Url from 'url'; import { KbnClient } from '../kbn_client'; import { readCloudUsersFromFile } from './helper'; import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; - -export interface User { - readonly email: string; - readonly password: string; -} - -export type Role = string; +import { Role, User } from './types'; export interface HostOptions { protocol: 'http' | 'https'; diff --git a/packages/kbn-test/src/auth/types.ts b/packages/kbn-test/src/auth/types.ts index 6eb3812dc6a9b..45e5b78b0ba38 100644 --- a/packages/kbn-test/src/auth/types.ts +++ b/packages/kbn-test/src/auth/types.ts @@ -31,3 +31,10 @@ export interface CreateSamlSessionParams { password: string; log: ToolingLog; } + +export interface User { + readonly email: string; + readonly password: string; +} + +export type Role = string; diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts index 3709efe36ea1d..ab7eef0b32799 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts @@ -54,7 +54,7 @@ export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProvide const { body } = await supertestWithoutAuth .get('/internal/security/me') .set(svlCommonApi.getInternalRequestHeader()) - .set('Cookie', `sid=${browserCookies[0].value}`); + .set({Cookie: `sid=${browserCookies[0].value}`}); const userData = await svlUserManager.getUserData(role); // email returned from API call must match the email for the specified role From 7ef742e19d19008cb0e038f93918f084c739bdc5 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 18:35:34 +0100 Subject: [PATCH 09/13] add unit tests --- .../kbn-test/src/auth/sesson_manager.test.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 packages/kbn-test/src/auth/sesson_manager.test.ts diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts new file mode 100644 index 0000000000000..a8dfd42fc73dc --- /dev/null +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -0,0 +1,156 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ToolingLog } from '@kbn/tooling-log'; +import { Cookie } from 'tough-cookie'; +import { Session } from './saml_auth'; +import { SamlSessionManager } from './session_manager'; +import * as samlAuth from './saml_auth'; +import * as helper from './helper'; +import { Role, User } from './types'; + +const log = new ToolingLog(); + +const cookieInstance = Cookie.parse('sid=kbn_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT')!; +cookieInstance.cookieString; +const email = 'testuser@elastic.com'; +const fullname = 'Test User'; + +const cloudCookieInstance = Cookie.parse('sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT')!; +const cloudEmail = 'viewer@elastic.co'; +const cloudFullname = 'Test Viewer'; + +const cloudUsers = new Array<[Role, User]>(); +cloudUsers.push(['viewer', {email: "viewer@elastic.co", password: "p1234"}]) +cloudUsers.push(['admin', {email: "admin@elastic.co", password: "p1234"}]) + +const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); +const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); +const readCloudUsersFromFileMock = jest.spyOn(helper, 'readCloudUsersFromFile'); + +jest.mock('../kbn_client/kbn_client', () => { + return { + KbnClient : jest.fn(), + }; +}) +const get = jest.fn(); + +beforeEach(() => { + jest.resetAllMocks(); + + jest.requireMock('../kbn_client/kbn_client').KbnClient.mockImplementation(() => ({ version: { get } })); + get.mockImplementationOnce(() => Promise.resolve('8.12.0')); + + createLocalSAMLSessionMock.mockResolvedValue(new Session(cookieInstance, email, fullname)); + createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail, cloudFullname)); + readCloudUsersFromFileMock.mockReturnValue(cloudUsers); + +}); + +describe('SamlSessionManager', () => { + describe('for local session', () => { + const hostOptions = { + protocol: 'http' as "http" | "https", + hostname: 'localhost', + port: 5620, + username: 'elastic', + password: 'changeme', + } + const isCloud = false; + test('should create an instance of SamlSessionManager', () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); + }); + + test(`'getSessionCookieForRole' should return the actual cookie value`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + const cookie = await samlSessionManager.getSessionCookieForRole('tester'); + expect(cookie).toBe(cookieInstance.value); + }); + + test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + const credentials = await samlSessionManager.getApiCredentialsForRole('tester') + expect(credentials).toEqual({Cookie: `${cookieInstance.cookieString()}`}); + }); + + test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' only once for the same role`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + await samlSessionManager.getSessionCookieForRole('tester'); + await samlSessionManager.getSessionCookieForRole('admin'); + await samlSessionManager.getSessionCookieForRole('tester'); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(2); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + + }); + + test(`'getUserData' should return the correct email & fullname`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + const data = await samlSessionManager.getUserData('tester'); + expect(data).toEqual({email, fullname}) + }); + }) + + describe('for cloud session', () => { + const hostOptions = { + protocol: 'https' as "http" | "https", + hostname: 'cloud', + username: 'elastic', + password: 'changeme', + } + const isCloud = true; + test('should create an instance of SamlSessionManager', () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); + }); + + test(`'getSessionCookieForRole' should return the actual cookie value`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail, cloudFullname)); + const cookie = await samlSessionManager.getSessionCookieForRole('viewer'); + expect(cookie).toBe(cloudCookieInstance.value); + }); + + test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + const credentials = await samlSessionManager.getApiCredentialsForRole('viewer') + expect(credentials).toEqual({Cookie: `${cloudCookieInstance.cookieString()}`}); + }); + + test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' only once for the same role`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + await samlSessionManager.getSessionCookieForRole('viewer'); + await samlSessionManager.getSessionCookieForRole('admin'); + await samlSessionManager.getSessionCookieForRole('viewer'); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(0); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); + }); + + test(`'getUserData' should return the correct email & fullname`, async () => { + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + const data = await samlSessionManager.getUserData('viewer'); + expect(data).toEqual({email: cloudEmail, fullname: cloudFullname}) + }); + + test(`'getSessionCookieForRole' throws error when roles does not exist`, async () => { + const nonExistingRole = 'tester'; + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + await expect(samlSessionManager.getSessionCookieForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); + }); + + test(`throws error when roles does not exist`, async () => { + const nonExistingRole = 'tester'; + const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); + await expect(samlSessionManager.getSessionCookieForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); + await expect(samlSessionManager.getApiCredentialsForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); + await expect(samlSessionManager.getUserData(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + }); + }) +}); + From 63537f901804991b984e50c484642150e2f4940e Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 18:37:59 +0100 Subject: [PATCH 10/13] Update packages/kbn-test/src/auth/session_manager.ts Co-authored-by: Aleh Zasypkin --- packages/kbn-test/src/auth/session_manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index c539fc09ccf9f..2e498d2bc9c74 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -30,7 +30,7 @@ export interface SamlSessionManagerOptions { } /** - * Manages cookies assosiated with user roles + * Manages cookies associated with user roles */ export class SamlSessionManager { private readonly isCloud: boolean; From 568d02adc5a9e99e53765c750aec3bb23c6404b1 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 23:26:35 +0100 Subject: [PATCH 11/13] update tests --- packages/kbn-test/src/auth/sesson_manager.test.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index a8dfd42fc73dc..5ee7acaa2dc3c 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -17,7 +17,6 @@ import { Role, User } from './types'; const log = new ToolingLog(); const cookieInstance = Cookie.parse('sid=kbn_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT')!; -cookieInstance.cookieString; const email = 'testuser@elastic.com'; const fullname = 'Test User'; @@ -42,7 +41,6 @@ const get = jest.fn(); beforeEach(() => { jest.resetAllMocks(); - jest.requireMock('../kbn_client/kbn_client').KbnClient.mockImplementation(() => ({ version: { get } })); get.mockImplementationOnce(() => Promise.resolve('8.12.0')); @@ -122,7 +120,7 @@ describe('SamlSessionManager', () => { expect(credentials).toEqual({Cookie: `${cloudCookieInstance.cookieString()}`}); }); - test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' only once for the same role`, async () => { + test(`'getSessionCookieForRole' should call 'createCloudSAMLSession' only once for the same role`, async () => { const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); await samlSessionManager.getSessionCookieForRole('viewer'); await samlSessionManager.getSessionCookieForRole('admin'); @@ -137,12 +135,6 @@ describe('SamlSessionManager', () => { expect(data).toEqual({email: cloudEmail, fullname: cloudFullname}) }); - test(`'getSessionCookieForRole' throws error when roles does not exist`, async () => { - const nonExistingRole = 'tester'; - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - await expect(samlSessionManager.getSessionCookieForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); - }); - test(`throws error when roles does not exist`, async () => { const nonExistingRole = 'tester'; const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); From 93664d5c0cce08d264aa06b438ab8a9d5d3801df Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 7 Dec 2023 23:40:10 +0100 Subject: [PATCH 12/13] update api integration test example --- .../platform_security/request_as_viewer.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts index e04a98b4ff7e7..7f48d98d5afad 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts @@ -9,8 +9,9 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { - describe('security/request as viewer', () => { + describe('security/me as viewer', () => { const svlUserManager = getService('svlUserManager'); + const svlCommonApi = getService('svlCommonApi'); const supertestWithoutAuth = getService('supertestWithoutAuth'); let credentials: { Cookie: string }; @@ -19,15 +20,17 @@ export default function ({ getService }: FtrProviderContext) { credentials = await svlUserManager.getApiCredentialsForRole('viewer'); }); - it('returns full status payload for authenticated request', async () => { - const { body } = await supertestWithoutAuth - .get('/api/status') - .set(credentials) - .set('kbn-xsrf', 'kibana'); + it('returns valid user data for authenticated request', async () => { + const { body, status } = await supertestWithoutAuth + .get('/internal/security/me') + .set(svlCommonApi.getInternalRequestHeader()) + .set(credentials); - expect(body.name).to.be.a('string'); - expect(body.uuid).to.be.a('string'); - expect(body.version.number).to.be.a('string'); + const userData = await svlUserManager.getUserData('viewer'); + + expect(status).to.be(200); + expect(body.full_name).to.be(userData.fullname); + expect(body.email).to.be(userData.email); }); }); } From c4ead200111ac176bc14f52320fc2e8c1a868bcd Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:16:18 +0000 Subject: [PATCH 13/13] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../kbn-test/src/auth/sesson_manager.test.ts | 193 ++++++++++-------- .../platform_security/request_as_viewer.ts | 6 +- .../page_objects/svl_common_page.ts | 2 +- 3 files changed, 107 insertions(+), 94 deletions(-) diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index 5ee7acaa2dc3c..88049acddb2e0 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -16,133 +16,146 @@ import { Role, User } from './types'; const log = new ToolingLog(); -const cookieInstance = Cookie.parse('sid=kbn_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT')!; +const cookieInstance = Cookie.parse( + 'sid=kbn_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' +)!; const email = 'testuser@elastic.com'; const fullname = 'Test User'; -const cloudCookieInstance = Cookie.parse('sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT')!; +const cloudCookieInstance = Cookie.parse( + 'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' +)!; const cloudEmail = 'viewer@elastic.co'; const cloudFullname = 'Test Viewer'; const cloudUsers = new Array<[Role, User]>(); -cloudUsers.push(['viewer', {email: "viewer@elastic.co", password: "p1234"}]) -cloudUsers.push(['admin', {email: "admin@elastic.co", password: "p1234"}]) +cloudUsers.push(['viewer', { email: 'viewer@elastic.co', password: 'p1234' }]); +cloudUsers.push(['admin', { email: 'admin@elastic.co', password: 'p1234' }]); const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); const readCloudUsersFromFileMock = jest.spyOn(helper, 'readCloudUsersFromFile'); jest.mock('../kbn_client/kbn_client', () => { - return { - KbnClient : jest.fn(), - }; -}) + return { + KbnClient: jest.fn(), + }; +}); const get = jest.fn(); beforeEach(() => { jest.resetAllMocks(); - jest.requireMock('../kbn_client/kbn_client').KbnClient.mockImplementation(() => ({ version: { get } })); + jest + .requireMock('../kbn_client/kbn_client') + .KbnClient.mockImplementation(() => ({ version: { get } })); get.mockImplementationOnce(() => Promise.resolve('8.12.0')); createLocalSAMLSessionMock.mockResolvedValue(new Session(cookieInstance, email, fullname)); - createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail, cloudFullname)); + createCloudSAMLSessionMock.mockResolvedValue( + new Session(cloudCookieInstance, cloudEmail, cloudFullname) + ); readCloudUsersFromFileMock.mockReturnValue(cloudUsers); - }); describe('SamlSessionManager', () => { describe('for local session', () => { const hostOptions = { - protocol: 'http' as "http" | "https", - hostname: 'localhost', - port: 5620, - username: 'elastic', - password: 'changeme', - } + protocol: 'http' as 'http' | 'https', + hostname: 'localhost', + port: 5620, + username: 'elastic', + password: 'changeme', + }; const isCloud = false; test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); test(`'getSessionCookieForRole' should return the actual cookie value`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - const cookie = await samlSessionManager.getSessionCookieForRole('tester'); - expect(cookie).toBe(cookieInstance.value); - }); - - test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - const credentials = await samlSessionManager.getApiCredentialsForRole('tester') - expect(credentials).toEqual({Cookie: `${cookieInstance.cookieString()}`}); - }); - - test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' only once for the same role`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - await samlSessionManager.getSessionCookieForRole('tester'); - await samlSessionManager.getSessionCookieForRole('admin'); - await samlSessionManager.getSessionCookieForRole('tester'); - expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(2); - expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); - - }); - - test(`'getUserData' should return the correct email & fullname`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - const data = await samlSessionManager.getUserData('tester'); - expect(data).toEqual({email, fullname}) - }); - }) + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const cookie = await samlSessionManager.getSessionCookieForRole('tester'); + expect(cookie).toBe(cookieInstance.value); + }); + + test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const credentials = await samlSessionManager.getApiCredentialsForRole('tester'); + expect(credentials).toEqual({ Cookie: `${cookieInstance.cookieString()}` }); + }); + + test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' only once for the same role`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + await samlSessionManager.getSessionCookieForRole('tester'); + await samlSessionManager.getSessionCookieForRole('admin'); + await samlSessionManager.getSessionCookieForRole('tester'); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(2); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + }); + + test(`'getUserData' should return the correct email & fullname`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const data = await samlSessionManager.getUserData('tester'); + expect(data).toEqual({ email, fullname }); + }); + }); describe('for cloud session', () => { const hostOptions = { - protocol: 'https' as "http" | "https", - hostname: 'cloud', - username: 'elastic', - password: 'changeme', - } + protocol: 'https' as 'http' | 'https', + hostname: 'cloud', + username: 'elastic', + password: 'changeme', + }; const isCloud = true; test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); test(`'getSessionCookieForRole' should return the actual cookie value`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail, cloudFullname)); - const cookie = await samlSessionManager.getSessionCookieForRole('viewer'); - expect(cookie).toBe(cloudCookieInstance.value); - }); - - test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - const credentials = await samlSessionManager.getApiCredentialsForRole('viewer') - expect(credentials).toEqual({Cookie: `${cloudCookieInstance.cookieString()}`}); - }); - - test(`'getSessionCookieForRole' should call 'createCloudSAMLSession' only once for the same role`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - await samlSessionManager.getSessionCookieForRole('viewer'); - await samlSessionManager.getSessionCookieForRole('admin'); - await samlSessionManager.getSessionCookieForRole('viewer'); - expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(0); - expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); - }); - - test(`'getUserData' should return the correct email & fullname`, async () => { - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - const data = await samlSessionManager.getUserData('viewer'); - expect(data).toEqual({email: cloudEmail, fullname: cloudFullname}) - }); - - test(`throws error when roles does not exist`, async () => { - const nonExistingRole = 'tester'; - const samlSessionManager = new SamlSessionManager({hostOptions, log, isCloud}); - await expect(samlSessionManager.getSessionCookieForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); - await expect(samlSessionManager.getApiCredentialsForRole(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); - await expect(samlSessionManager.getUserData(nonExistingRole)).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); - expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); - }); - }) -}); + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + createCloudSAMLSessionMock.mockResolvedValue( + new Session(cloudCookieInstance, cloudEmail, cloudFullname) + ); + const cookie = await samlSessionManager.getSessionCookieForRole('viewer'); + expect(cookie).toBe(cloudCookieInstance.value); + }); + test(`'getApiCredentialsForRole' should return {Cookie: }`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const credentials = await samlSessionManager.getApiCredentialsForRole('viewer'); + expect(credentials).toEqual({ Cookie: `${cloudCookieInstance.cookieString()}` }); + }); + + test(`'getSessionCookieForRole' should call 'createCloudSAMLSession' only once for the same role`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + await samlSessionManager.getSessionCookieForRole('viewer'); + await samlSessionManager.getSessionCookieForRole('admin'); + await samlSessionManager.getSessionCookieForRole('viewer'); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(0); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); + }); + + test(`'getUserData' should return the correct email & fullname`, async () => { + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const data = await samlSessionManager.getUserData('viewer'); + expect(data).toEqual({ email: cloudEmail, fullname: cloudFullname }); + }); + + test(`throws error when roles does not exist`, async () => { + const nonExistingRole = 'tester'; + const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + await expect(samlSessionManager.getSessionCookieForRole(nonExistingRole)).rejects.toThrow( + `User with '${nonExistingRole}' role is not defined` + ); + await expect(samlSessionManager.getApiCredentialsForRole(nonExistingRole)).rejects.toThrow( + `User with '${nonExistingRole}' role is not defined` + ); + await expect(samlSessionManager.getUserData(nonExistingRole)).rejects.toThrow( + `User with '${nonExistingRole}' role is not defined` + ); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + }); + }); +}); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts index 7f48d98d5afad..7931f28f55df7 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts @@ -22,9 +22,9 @@ export default function ({ getService }: FtrProviderContext) { it('returns valid user data for authenticated request', async () => { const { body, status } = await supertestWithoutAuth - .get('/internal/security/me') - .set(svlCommonApi.getInternalRequestHeader()) - .set(credentials); + .get('/internal/security/me') + .set(svlCommonApi.getInternalRequestHeader()) + .set(credentials); const userData = await svlUserManager.getUserData('viewer'); diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts index ab7eef0b32799..681f024b8f837 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts @@ -54,7 +54,7 @@ export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProvide const { body } = await supertestWithoutAuth .get('/internal/security/me') .set(svlCommonApi.getInternalRequestHeader()) - .set({Cookie: `sid=${browserCookies[0].value}`}); + .set({ Cookie: `sid=${browserCookies[0].value}` }); const userData = await svlUserManager.getUserData(role); // email returned from API call must match the email for the specified role