From c521487b305ead743459a5c75590f75df21993b0 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 18 Jun 2020 16:23:56 +0300 Subject: [PATCH] [7.x] Add functional test for Kibana embedded in iframe (#68544) (#69487) * Add functional test for Kibana embedded in iframe (#68544) * convert kbn test config into TS * add test for Kibana embedded in iframe * run embedded tests in functional suite * ignore tls errors in functional tests by default * switch test to https * remove env vars mutation * allow to pass ssl config to Kibana * pass ssl config to axios * adopt KbnClient interfaces * adopt KibanaServer * use KbnRequester in security service * set sameSiteCookies:None in test * acceptInsecureCerts in chrome * remove leftovers * fix type error * remove unnecessary field * address comments * refactor plugin * refactor test * make acceptInsecureCerts configurable * run firefox tests on ci * up TS version * fix firefox.sh script * fix path Co-authored-by: Elastic Machine # Conflicts: # test/functional/services/remote/remote.ts # test/functional/services/remote/webdriver.ts # x-pack/scripts/functional_tests.js * create browser config. lost during conflict resolution --- .../src/kbn_client/kbn_client.ts | 8 +-- .../src/kbn_client/kbn_client_requester.ts | 38 ++++++++--- .../kbn_client/kbn_client_saved_objects.ts | 16 +++-- .../src/kbn_client/kbn_client_status.ts | 3 +- .../src/kbn_client/kbn_client_ui_settings.ts | 7 +- .../lib/config/schema.ts | 9 +++ .../kbn-test/src/kbn/{index.js => index.ts} | 0 ...{kbn_test_config.js => kbn_test_config.ts} | 24 ++++--- .../kbn-test/src/kbn/{users.js => users.ts} | 0 src/es_archiver/es_archiver.ts | 2 +- .../services/kibana_server/kibana_server.ts | 4 +- test/common/services/security/role.ts | 28 ++++---- .../common/services/security/role_mappings.ts | 32 +++------ test/common/services/security/security.ts | 12 ++-- test/common/services/security/user.ts | 34 ++++------ test/functional/services/common/browser.ts | 5 ++ test/functional/services/remote/remote.ts | 15 +++-- test/functional/services/remote/webdriver.ts | 18 +++-- test/scripts/jenkins_xpack_firefox_smoke.sh | 3 +- x-pack/scripts/functional_tests.js | 1 + .../functional_embedded/config.firefox.ts | 27 ++++++++ x-pack/test/functional_embedded/config.ts | 67 +++++++++++++++++++ .../ftr_provider_context.d.ts | 12 ++++ .../plugins/iframe_embedded/kibana.json | 7 ++ .../plugins/iframe_embedded/package.json | 14 ++++ .../plugins/iframe_embedded/server/index.ts | 11 +++ .../plugins/iframe_embedded/server/plugin.ts | 45 +++++++++++++ x-pack/test/functional_embedded/services.ts | 9 +++ .../tests/iframe_embedded.ts | 42 ++++++++++++ .../test/functional_embedded/tests/index.ts | 14 ++++ 30 files changed, 394 insertions(+), 113 deletions(-) rename packages/kbn-test/src/kbn/{index.js => index.ts} (100%) rename packages/kbn-test/src/kbn/{kbn_test_config.js => kbn_test_config.ts} (76%) rename packages/kbn-test/src/kbn/{users.js => users.ts} (100%) create mode 100644 x-pack/test/functional_embedded/config.firefox.ts create mode 100644 x-pack/test/functional_embedded/config.ts create mode 100644 x-pack/test/functional_embedded/ftr_provider_context.d.ts create mode 100644 x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json create mode 100644 x-pack/test/functional_embedded/plugins/iframe_embedded/package.json create mode 100644 x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts create mode 100644 x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts create mode 100644 x-pack/test/functional_embedded/services.ts create mode 100644 x-pack/test/functional_embedded/tests/iframe_embedded.ts create mode 100644 x-pack/test/functional_embedded/tests/index.ts diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts index 2eb6c6cc5aac6..861ea0988692c 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client.ts @@ -18,7 +18,7 @@ */ import { ToolingLog } from '../tooling_log'; -import { KbnClientRequester, ReqOptions } from './kbn_client_requester'; +import { KibanaConfig, KbnClientRequester, ReqOptions } from './kbn_client_requester'; import { KbnClientStatus } from './kbn_client_status'; import { KbnClientPlugins } from './kbn_client_plugins'; import { KbnClientVersion } from './kbn_client_version'; @@ -26,7 +26,7 @@ import { KbnClientSavedObjects } from './kbn_client_saved_objects'; import { KbnClientUiSettings, UiSettingValues } from './kbn_client_ui_settings'; export class KbnClient { - private readonly requester = new KbnClientRequester(this.log, this.kibanaUrls); + private readonly requester = new KbnClientRequester(this.log, this.kibanaConfig); readonly status = new KbnClientStatus(this.requester); readonly plugins = new KbnClientPlugins(this.status); readonly version = new KbnClientVersion(this.status); @@ -43,10 +43,10 @@ export class KbnClient { */ constructor( private readonly log: ToolingLog, - private readonly kibanaUrls: string[], + private readonly kibanaConfig: KibanaConfig, private readonly uiSettingDefaults?: UiSettingValues ) { - if (!kibanaUrls.length) { + if (!kibanaConfig.url) { throw new Error('missing Kibana urls'); } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts index ea4159de55749..2aba2be56f277 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_requester.ts @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - import Url from 'url'; - -import Axios from 'axios'; +import Https from 'https'; +import Axios, { AxiosResponse } from 'axios'; import { isAxiosRequestError, isAxiosResponseError } from '../axios'; import { ToolingLog } from '../tooling_log'; @@ -70,20 +69,38 @@ const delay = (ms: number) => setTimeout(resolve, ms); }); +export interface KibanaConfig { + url: string; + ssl?: { + enabled: boolean; + key: string; + certificate: string; + certificateAuthorities: string; + }; +} + export class KbnClientRequester { - constructor(private readonly log: ToolingLog, private readonly kibanaUrls: string[]) {} + private readonly httpsAgent: Https.Agent | null; + constructor(private readonly log: ToolingLog, private readonly kibanaConfig: KibanaConfig) { + this.httpsAgent = + kibanaConfig.ssl && kibanaConfig.ssl.enabled + ? new Https.Agent({ + cert: kibanaConfig.ssl.certificate, + key: kibanaConfig.ssl.key, + ca: kibanaConfig.ssl.certificateAuthorities, + }) + : null; + } private pickUrl() { - const url = this.kibanaUrls.shift()!; - this.kibanaUrls.push(url); - return url; + return this.kibanaConfig.url; } public resolveUrl(relativeUrl: string = '/') { return Url.resolve(this.pickUrl(), relativeUrl); } - async request(options: ReqOptions): Promise { + async request(options: ReqOptions): Promise> { const url = Url.resolve(this.pickUrl(), options.path); const description = options.description || `${options.method} ${url}`; let attempt = 0; @@ -93,7 +110,7 @@ export class KbnClientRequester { attempt += 1; try { - const response = await Axios.request({ + const response = await Axios.request({ method: options.method, url, data: options.body, @@ -101,9 +118,10 @@ export class KbnClientRequester { headers: { 'kbn-xsrf': 'kbn-client', }, + httpsAgent: this.httpsAgent, }); - return response.data; + return response; } catch (error) { const conflictOnGet = isConcliftOnGetError(error); const requestedRetries = options.retries !== undefined; diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts index e671061b34352..7334c6353debf 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_saved_objects.ts @@ -71,12 +71,13 @@ export class KbnClientSavedObjects { public async migrate() { this.log.debug('Migrating saved objects'); - return await this.requester.request({ + const { data } = await this.requester.request({ description: 'migrate saved objects', path: uriencode`/internal/saved_objects/_migrate`, method: 'POST', body: {}, }); + return data; } /** @@ -85,11 +86,12 @@ export class KbnClientSavedObjects { public async get>(options: GetOptions) { this.log.debug('Gettings saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'get saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'GET', }); + return data; } /** @@ -98,7 +100,7 @@ export class KbnClientSavedObjects { public async create>(options: IndexOptions) { this.log.debug('Creating saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'update saved object', path: options.id ? uriencode`/api/saved_objects/${options.type}/${options.id}` @@ -113,6 +115,7 @@ export class KbnClientSavedObjects { references: options.references, }, }); + return data; } /** @@ -121,7 +124,7 @@ export class KbnClientSavedObjects { public async update>(options: UpdateOptions) { this.log.debug('Updating saved object: %j', options); - return await this.requester.request>({ + const { data } = await this.requester.request>({ description: 'update saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, query: { @@ -134,6 +137,7 @@ export class KbnClientSavedObjects { references: options.references, }, }); + return data; } /** @@ -142,10 +146,12 @@ export class KbnClientSavedObjects { public async delete(options: GetOptions) { this.log.debug('Deleting saved object %s/%s', options); - return await this.requester.request({ + const { data } = await this.requester.request({ description: 'delete saved object', path: uriencode`/api/saved_objects/${options.type}/${options.id}`, method: 'DELETE', }); + + return data; } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts index 22baf4a330416..4f203e73620f3 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts @@ -52,10 +52,11 @@ export class KbnClientStatus { * Get the full server status */ async get() { - return await this.requester.request({ + const { data } = await this.requester.request({ method: 'GET', path: 'api/status', }); + return data; } /** diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts index dbfa87e70032b..6ee2d3bfe59b0 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts @@ -57,10 +57,11 @@ export class KbnClientUiSettings { * Unset a uiSetting */ async unset(setting: string) { - return await this.requester.request({ + const { data } = await this.requester.request({ path: uriencode`/api/kibana/settings/${setting}`, method: 'DELETE', }); + return data; } /** @@ -105,11 +106,11 @@ export class KbnClientUiSettings { } private async getAll() { - const resp = await this.requester.request({ + const { data } = await this.requester.request({ path: '/api/kibana/settings', method: 'GET', }); - return resp.settings; + return data.settings; } } diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 29ec28175a851..e9aeee87f1a3b 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -38,6 +38,14 @@ const urlPartsSchema = () => password: Joi.string(), pathname: Joi.string().regex(/^\//, 'start with a /'), hash: Joi.string().regex(/^\//, 'start with a /'), + ssl: Joi.object() + .keys({ + enabled: Joi.boolean().default(false), + certificate: Joi.string().optional(), + certificateAuthorities: Joi.string().optional(), + key: Joi.string().optional(), + }) + .default(), }) .default(); @@ -122,6 +130,7 @@ export const schema = Joi.object() type: Joi.string().valid('chrome', 'firefox', 'ie', 'msedge').default('chrome'), logPollingMs: Joi.number().default(100), + acceptInsecureCerts: Joi.boolean().default(false), }) .default(), diff --git a/packages/kbn-test/src/kbn/index.js b/packages/kbn-test/src/kbn/index.ts similarity index 100% rename from packages/kbn-test/src/kbn/index.js rename to packages/kbn-test/src/kbn/index.ts diff --git a/packages/kbn-test/src/kbn/kbn_test_config.js b/packages/kbn-test/src/kbn/kbn_test_config.ts similarity index 76% rename from packages/kbn-test/src/kbn/kbn_test_config.js rename to packages/kbn-test/src/kbn/kbn_test_config.ts index c43efabb4b747..909c94098cf5d 100644 --- a/packages/kbn-test/src/kbn/kbn_test_config.js +++ b/packages/kbn-test/src/kbn/kbn_test_config.ts @@ -16,26 +16,34 @@ * specific language governing permissions and limitations * under the License. */ - -import { kibanaTestUser } from './users'; import url from 'url'; +import { kibanaTestUser } from './users'; + +interface UrlParts { + protocol?: string; + hostname?: string; + port?: number; + auth?: string; + username?: string; + password?: string; +} export const kbnTestConfig = new (class KbnTestConfig { getPort() { return this.getUrlParts().port; } - getUrlParts() { + getUrlParts(): UrlParts { // allow setting one complete TEST_KIBANA_URL for ES like https://elastic:changeme@example.com:9200 if (process.env.TEST_KIBANA_URL) { const testKibanaUrl = url.parse(process.env.TEST_KIBANA_URL); return { - protocol: testKibanaUrl.protocol.slice(0, -1), + protocol: testKibanaUrl.protocol?.slice(0, -1), hostname: testKibanaUrl.hostname, - port: parseInt(testKibanaUrl.port, 10), + port: testKibanaUrl.port ? parseInt(testKibanaUrl.port, 10) : undefined, auth: testKibanaUrl.auth, - username: testKibanaUrl.auth.split(':')[0], - password: testKibanaUrl.auth.split(':')[1], + username: testKibanaUrl.auth?.split(':')[0], + password: testKibanaUrl.auth?.split(':')[1], }; } @@ -44,7 +52,7 @@ export const kbnTestConfig = new (class KbnTestConfig { return { protocol: process.env.TEST_KIBANA_PROTOCOL || 'http', hostname: process.env.TEST_KIBANA_HOSTNAME || 'localhost', - port: parseInt(process.env.TEST_KIBANA_PORT, 10) || 5620, + port: process.env.TEST_KIBANA_PORT ? parseInt(process.env.TEST_KIBANA_PORT, 10) : 5620, auth: `${username}:${password}`, username, password, diff --git a/packages/kbn-test/src/kbn/users.js b/packages/kbn-test/src/kbn/users.ts similarity index 100% rename from packages/kbn-test/src/kbn/users.js rename to packages/kbn-test/src/kbn/users.ts diff --git a/src/es_archiver/es_archiver.ts b/src/es_archiver/es_archiver.ts index f36cbb3f516b9..e335652195b86 100644 --- a/src/es_archiver/es_archiver.ts +++ b/src/es_archiver/es_archiver.ts @@ -49,7 +49,7 @@ export class EsArchiver { this.client = client; this.dataDir = dataDir; this.log = log; - this.kbnClient = new KbnClient(log, [kibanaUrl]); + this.kbnClient = new KbnClient(log, { url: kibanaUrl }); } /** diff --git a/test/common/services/kibana_server/kibana_server.ts b/test/common/services/kibana_server/kibana_server.ts index 16039d6fee833..4a251cca044d3 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/test/common/services/kibana_server/kibana_server.ts @@ -27,9 +27,9 @@ export function KibanaServerProvider({ getService }: FtrProviderContext) { const config = getService('config'); const lifecycle = getService('lifecycle'); const url = Url.format(config.get('servers.kibana')); + const ssl = config.get('servers.kibana').ssl; const defaults = config.get('uiSettings.defaults'); - - const kbn = new KbnClient(log, [url], defaults); + const kbn = new KbnClient(log, { url, ssl }, defaults); if (defaults) { lifecycle.beforeTests.add(async () => { diff --git a/test/common/services/security/role.ts b/test/common/services/security/role.ts index dfc6ff9b164e5..caa5549a70f0c 100644 --- a/test/common/services/security/role.ts +++ b/test/common/services/security/role.ts @@ -17,27 +17,20 @@ * under the License. */ -import axios, { AxiosInstance } from 'axios'; import util from 'util'; -import { ToolingLog } from '@kbn/dev-utils'; +import { KbnClient, ToolingLog } from '@kbn/dev-utils'; export class Role { - private log: ToolingLog; - private axios: AxiosInstance; - - constructor(url: string, log: ToolingLog) { - this.log = log; - this.axios = axios.create({ - headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/role' }, - baseURL: url, - maxRedirects: 0, - validateStatus: () => true, // we do our own validation below and throw better error messages - }); - } + constructor(private log: ToolingLog, private kibanaServer: KbnClient) {} public async create(name: string, role: any) { this.log.debug(`creating role ${name}`); - const { data, status, statusText } = await this.axios.put(`/api/security/role/${name}`, role); + const { data, status, statusText } = await this.kibanaServer.request({ + path: `/api/security/role/${name}`, + method: 'PUT', + body: role, + retries: 0, + }); if (status !== 204) { throw new Error( `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` @@ -47,7 +40,10 @@ export class Role { public async delete(name: string) { this.log.debug(`deleting role ${name}`); - const { data, status, statusText } = await this.axios.delete(`/api/security/role/${name}`); + const { data, status, statusText } = await this.kibanaServer.request({ + path: `/api/security/role/${name}`, + method: 'DELETE', + }); if (status !== 204 && status !== 404) { throw new Error( `Expected status code of 204 or 404, received ${status} ${statusText}: ${util.inspect( diff --git a/test/common/services/security/role_mappings.ts b/test/common/services/security/role_mappings.ts index cc2fa23825498..7951d4b5b47b2 100644 --- a/test/common/services/security/role_mappings.ts +++ b/test/common/services/security/role_mappings.ts @@ -17,30 +17,19 @@ * under the License. */ -import axios, { AxiosInstance } from 'axios'; import util from 'util'; -import { ToolingLog } from '@kbn/dev-utils'; +import { KbnClient, ToolingLog } from '@kbn/dev-utils'; export class RoleMappings { - private log: ToolingLog; - private axios: AxiosInstance; - - constructor(url: string, log: ToolingLog) { - this.log = log; - this.axios = axios.create({ - headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/role_mappings' }, - baseURL: url, - maxRedirects: 0, - validateStatus: () => true, // we do our own validation below and throw better error messages - }); - } + constructor(private log: ToolingLog, private kbnClient: KbnClient) {} public async create(name: string, roleMapping: Record) { this.log.debug(`creating role mapping ${name}`); - const { data, status, statusText } = await this.axios.post( - `/internal/security/role_mapping/${name}`, - roleMapping - ); + const { data, status, statusText } = await this.kbnClient.request({ + path: `/internal/security/role_mapping/${name}`, + method: 'POST', + body: roleMapping, + }); if (status !== 200) { throw new Error( `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` @@ -51,9 +40,10 @@ export class RoleMappings { public async delete(name: string) { this.log.debug(`deleting role mapping ${name}`); - const { data, status, statusText } = await this.axios.delete( - `/internal/security/role_mapping/${name}` - ); + const { data, status, statusText } = await this.kbnClient.request({ + path: `/internal/security/role_mapping/${name}`, + method: 'DELETE', + }); if (status !== 200 && status !== 404) { throw new Error( `Expected status code of 200 or 404, received ${status} ${statusText}: ${util.inspect( diff --git a/test/common/services/security/security.ts b/test/common/services/security/security.ts index 6ad0933a2a5a2..fae4c9198cab6 100644 --- a/test/common/services/security/security.ts +++ b/test/common/services/security/security.ts @@ -17,8 +17,6 @@ * under the License. */ -import { format as formatUrl } from 'url'; - import { Role } from './role'; import { User } from './user'; import { RoleMappings } from './role_mappings'; @@ -28,14 +26,14 @@ import { createTestUserService } from './test_user'; export async function SecurityServiceProvider(context: FtrProviderContext) { const { getService } = context; const log = getService('log'); - const config = getService('config'); - const url = formatUrl(config.get('servers.kibana')); - const role = new Role(url, log); - const user = new User(url, log); + const kibanaServer = getService('kibanaServer'); + + const role = new Role(log, kibanaServer); + const user = new User(log, kibanaServer); const testUser = await createTestUserService(role, user, context); return new (class SecurityService { - roleMappings = new RoleMappings(url, log); + roleMappings = new RoleMappings(log, kibanaServer); testUser = testUser; role = role; user = user; diff --git a/test/common/services/security/user.ts b/test/common/services/security/user.ts index ae02127043234..58c4d0f1cf34e 100644 --- a/test/common/services/security/user.ts +++ b/test/common/services/security/user.ts @@ -17,33 +17,22 @@ * under the License. */ -import axios, { AxiosInstance } from 'axios'; import util from 'util'; -import { ToolingLog } from '@kbn/dev-utils'; +import { KbnClient, ToolingLog } from '@kbn/dev-utils'; export class User { - private log: ToolingLog; - private axios: AxiosInstance; - - constructor(url: string, log: ToolingLog) { - this.log = log; - this.axios = axios.create({ - headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/user' }, - baseURL: url, - maxRedirects: 0, - validateStatus: () => true, // we do our own validation below and throw better error messages - }); - } + constructor(private log: ToolingLog, private kbnClient: KbnClient) {} public async create(username: string, user: any) { this.log.debug(`creating user ${username}`); - const { data, status, statusText } = await this.axios.post( - `/internal/security/users/${username}`, - { + const { data, status, statusText } = await this.kbnClient.request({ + path: `/internal/security/users/${username}`, + method: 'POST', + body: { username, ...user, - } - ); + }, + }); if (status !== 200) { throw new Error( `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` @@ -54,9 +43,10 @@ export class User { public async delete(username: string) { this.log.debug(`deleting user ${username}`); - const { data, status, statusText } = await this.axios.delete( - `/internal/security/users/${username}` - ); + const { data, status, statusText } = await await this.kbnClient.request({ + path: `/internal/security/users/${username}`, + method: 'DELETE', + }); if (status !== 204) { throw new Error( `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index e809a0d69f7b6..35cfc4e258510 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -527,5 +527,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { await driver.executeScript('document.body.scrollLeft = ' + scrollSize); return this.getScrollLeft(); } + + public async switchToFrame(idOrElement: number | WebElementWrapper) { + const _id = idOrElement instanceof WebElementWrapper ? idOrElement._webElement : idOrElement; + await driver.switchTo().frame(_id); + } })(); } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 2e5266712d949..7e271dbc9bc5b 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -18,7 +18,7 @@ */ import { FtrProviderContext } from '../../ftr_provider_context'; -import { initWebDriver } from './webdriver'; +import { initWebDriver, BrowserConfig } from './webdriver'; import { Browsers } from './browsers'; export async function RemoteProvider({ getService }: FtrProviderContext) { @@ -38,12 +38,13 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { } }; - const { driver, consoleLog$ } = await initWebDriver( - log, - browserType, - lifecycle, - config.get('browser.logPollingMs') - ); + const browserConfig: BrowserConfig = { + logPollingMs: config.get('browser.logPollingMs'), + acceptInsecureCerts: config.get('browser.acceptInsecureCerts'), + }; + + const { driver, consoleLog$ } = await initWebDriver(log, browserType, lifecycle, browserConfig); + const isW3CEnabled = (driver as any).executor_.w3c; const caps = await driver.getCapabilities(); diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index b2858ed7c51c8..a0b070ebcab4b 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -73,13 +73,18 @@ Executor.prototype.execute = preventParallelCalls( (command: { getName: () => string }) => NO_QUEUE_COMMANDS.includes(command.getName()) ); +export interface BrowserConfig { + logPollingMs: number; + acceptInsecureCerts: boolean; +} + let attemptCounter = 0; let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( log: ToolingLog, browserType: Browsers, lifecycle: Lifecycle, - logPollingMs: number + config: BrowserConfig ) { const attemptId = ++attemptCounter; log.debug('[webdriver] Creating session'); @@ -114,6 +119,7 @@ async function attemptToCreateCommand( if (certValidation === '0') { chromeOptions.push('ignore-certificate-errors'); } + if (remoteDebug === '1') { // Visit chrome://inspect in chrome to remotely view/debug chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222'); @@ -125,6 +131,7 @@ async function attemptToCreateCommand( }); chromeCapabilities.set('unexpectedAlertBehaviour', 'accept'); chromeCapabilities.set('goog:loggingPrefs', { browser: 'ALL' }); + chromeCapabilities.setAcceptInsecureCerts(config.acceptInsecureCerts); const session = await new Builder() .forBrowser(browserType) @@ -134,7 +141,7 @@ async function attemptToCreateCommand( return { session, - consoleLog$: pollForLogEntry$(session, logging.Type.BROWSER, logPollingMs).pipe( + consoleLog$: pollForLogEntry$(session, logging.Type.BROWSER, config.logPollingMs).pipe( takeUntil(lifecycle.cleanup.after$), map(({ message, level: { name: level } }) => ({ message: message.replace(/\\n/g, '\n'), @@ -166,7 +173,7 @@ async function attemptToCreateCommand( .build(); return { session, - consoleLog$: pollForLogEntry$(session, logging.Type.BROWSER, logPollingMs).pipe( + consoleLog$: pollForLogEntry$(session, logging.Type.BROWSER, config.logPollingMs).pipe( takeUntil(lifecycle.cleanup.after$), map(({ message, level: { name: level } }) => ({ message: message.replace(/\\n/g, '\n'), @@ -196,6 +203,7 @@ async function attemptToCreateCommand( 'browser.helperApps.neverAsk.saveToDisk', 'application/comma-separated-values, text/csv, text/plain' ); + firefoxOptions.setAcceptInsecureCerts(config.acceptInsecureCerts); if (headlessBrowser === '1') { // See: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode @@ -307,7 +315,7 @@ export async function initWebDriver( log: ToolingLog, browserType: Browsers, lifecycle: Lifecycle, - logPollingMs: number + config: BrowserConfig ) { const logger = getLogger('webdriver.http.Executor'); logger.setLevel(logging.Level.FINEST); @@ -338,7 +346,7 @@ export async function initWebDriver( while (true) { const command = await Promise.race([ delay(30 * SECOND), - attemptToCreateCommand(log, browserType, lifecycle, logPollingMs), + attemptToCreateCommand(log, browserType, lifecycle, config), ]); if (!command) { diff --git a/test/scripts/jenkins_xpack_firefox_smoke.sh b/test/scripts/jenkins_xpack_firefox_smoke.sh index fdaee76cafa9d..ae924a5e10552 100755 --- a/test/scripts/jenkins_xpack_firefox_smoke.sh +++ b/test/scripts/jenkins_xpack_firefox_smoke.sh @@ -7,4 +7,5 @@ checks-reporter-with-killswitch "X-Pack firefox smoke test" \ --debug --bail \ --kibana-install-dir "$KIBANA_INSTALL_DIR" \ --include-tag "includeFirefox" \ - --config test/functional/config.firefox.js; + --config test/functional/config.firefox.js \ + --config test/functional_embedded/config.firefox.ts; diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 87d43366ba1c8..76ff00d18a8fd 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -51,4 +51,5 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/licensing_plugin/config.legacy'), require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'), require.resolve('../test/reporting_api_integration/config.js'), + require.resolve('../test/functional_embedded/config.ts'), ]); diff --git a/x-pack/test/functional_embedded/config.firefox.ts b/x-pack/test/functional_embedded/config.firefox.ts new file mode 100644 index 0000000000000..2051d1afd4ab3 --- /dev/null +++ b/x-pack/test/functional_embedded/config.firefox.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const chromeConfig = await readConfigFile(require.resolve('./config')); + + return { + ...chromeConfig.getAll(), + + browser: { + type: 'firefox', + acceptInsecureCerts: true, + }, + + suiteTags: { + exclude: ['skipFirefox'], + }, + + junit: { + reportName: 'Firefox Kibana Embedded in iframe with X-Pack Security', + }, + }; +} diff --git a/x-pack/test/functional_embedded/config.ts b/x-pack/test/functional_embedded/config.ts new file mode 100644 index 0000000000000..95b290ece7db2 --- /dev/null +++ b/x-pack/test/functional_embedded/config.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Fs from 'fs'; +import { resolve } from 'path'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const iframeEmbeddedPlugin = resolve(__dirname, './plugins/iframe_embedded'); + + const servers = { + ...kibanaFunctionalConfig.get('servers'), + elasticsearch: { + ...kibanaFunctionalConfig.get('servers.elasticsearch'), + }, + kibana: { + ...kibanaFunctionalConfig.get('servers.kibana'), + protocol: 'https', + ssl: { + enabled: true, + key: Fs.readFileSync(KBN_KEY_PATH).toString('utf8'), + certificate: Fs.readFileSync(KBN_CERT_PATH).toString('utf8'), + certificateAuthorities: Fs.readFileSync(CA_CERT_PATH).toString('utf8'), + }, + }, + }; + + return { + testFiles: [require.resolve('./tests')], + servers, + services: kibanaFunctionalConfig.get('services'), + pageObjects, + browser: { + acceptInsecureCerts: true, + }, + junit: { + reportName: 'Kibana Embedded in iframe with X-Pack Security', + }, + + esTestCluster: kibanaFunctionalConfig.get('esTestCluster'), + apps: { + ...kibanaFunctionalConfig.get('apps'), + }, + + kbnTestServer: { + ...kibanaFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'), + `--plugin-path=${iframeEmbeddedPlugin}`, + '--server.ssl.enabled=true', + `--server.ssl.key=${KBN_KEY_PATH}`, + `--server.ssl.certificate=${KBN_CERT_PATH}`, + `--server.ssl.certificateAuthorities=${CA_CERT_PATH}`, + + '--xpack.security.sameSiteCookies=None', + '--xpack.security.secureCookies=true', + ], + }, + }; +} diff --git a/x-pack/test/functional_embedded/ftr_provider_context.d.ts b/x-pack/test/functional_embedded/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..5646c06a3cd30 --- /dev/null +++ b/x-pack/test/functional_embedded/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export { pageObjects }; diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json b/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json new file mode 100644 index 0000000000000..ea9f55bd21c6e --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "iframe_embedded", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false +} diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json b/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json new file mode 100644 index 0000000000000..9fa1554e5312b --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/package.json @@ -0,0 +1,14 @@ +{ + "name": "iframe_embedded", + "version": "0.0.0", + "kibana": { + "version": "kibana" + }, + "scripts": { + "kbn": "node ../../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.9.5" + } +} diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts new file mode 100644 index 0000000000000..976ef19d4d8a7 --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'kibana/server'; +import { IframeEmbeddedPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new IframeEmbeddedPlugin(initContext); diff --git a/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts new file mode 100644 index 0000000000000..890fe14cf03cf --- /dev/null +++ b/x-pack/test/functional_embedded/plugins/iframe_embedded/server/plugin.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import Url from 'url'; +import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; + +function renderBody(iframeUrl: string) { + return ` + + + + + Kibana embedded in iframe + + +