mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add ability to specify CORS accepted origins (#84316)
* add settings * update abab package to version with types * add test case for CORS * add tests for cors config * fix jest tests * add deprecation message * tweak deprecation * make test runable on Cloud * add docs * fix type error * add test to throw on invalid URL * address comments * Update src/core/server/http/http_config.test.ts Co-authored-by: Larry Gregory <lgregorydev@gmail.com> * Update docs/setup/settings.asciidoc Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com> * allow kbn-xsrf headers to be set on CORS request Co-authored-by: Larry Gregory <lgregorydev@gmail.com> Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com>
This commit is contained in:
parent
60b96d6e1d
commit
44688d9595
26 changed files with 450 additions and 16 deletions
|
@ -450,6 +450,15 @@ deprecation warning at startup. This setting cannot end in a slash (`/`).
|
|||
| [[server-compression]] `server.compression.enabled:`
|
||||
| Set to `false` to disable HTTP compression for all responses. *Default: `true`*
|
||||
|
||||
| `server.cors.enabled:`
|
||||
| experimental[] Set to `true` to allow cross-origin API calls. *Default:* `false`
|
||||
|
||||
| `server.cors.credentials:`
|
||||
| experimental[] Set to `true` to allow browser code to access response body whenever request performed with user credentials. *Default:* `false`
|
||||
|
||||
| `server.cors.origin:`
|
||||
| experimental[] List of origins permitted to access resources. You must specify explicit hostnames and not use `*` for `server.cors.origin` when `server.cors.credentials: true`. *Default:* "*"
|
||||
|
||||
| `server.compression.referrerWhitelist:`
|
||||
| Specifies an array of trusted hostnames, such as the {kib} host, or a reverse
|
||||
proxy sitting in front of it. This determines whether HTTP compression may be used for responses, based on the request `Referer` header.
|
||||
|
|
|
@ -573,7 +573,7 @@
|
|||
"@typescript-eslint/parser": "^4.8.1",
|
||||
"@welldone-software/why-did-you-render": "^5.0.0",
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"abab": "^1.0.4",
|
||||
"abab": "^2.0.4",
|
||||
"angular-aria": "^1.8.0",
|
||||
"angular-mocks": "^1.7.9",
|
||||
"angular-recursion": "^1.0.5",
|
||||
|
|
|
@ -94,6 +94,32 @@ describe('core deprecations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('server.cors', () => {
|
||||
it('renames server.cors to server.cors.enabled', () => {
|
||||
const { migrated } = applyCoreDeprecations({
|
||||
server: { cors: true },
|
||||
});
|
||||
expect(migrated.server.cors).toEqual({ enabled: true });
|
||||
});
|
||||
it('logs a warning message about server.cors renaming', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
server: { cors: true },
|
||||
});
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"server.cors\\" is deprecated and has been replaced by \\"server.cors.enabled\\"",
|
||||
]
|
||||
`);
|
||||
});
|
||||
it('does not log deprecation message when server.cors.enabled set', () => {
|
||||
const { migrated, messages } = applyCoreDeprecations({
|
||||
server: { cors: { enabled: true } },
|
||||
});
|
||||
expect(migrated.server.cors).toEqual({ enabled: true });
|
||||
expect(messages.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rewriteBasePath', () => {
|
||||
it('logs a warning is server.basePath is set and server.rewriteBasePath is not', () => {
|
||||
const { messages } = applyCoreDeprecations({
|
||||
|
|
|
@ -50,6 +50,17 @@ const rewriteBasePathDeprecation: ConfigDeprecation = (settings, fromPath, log)
|
|||
return settings;
|
||||
};
|
||||
|
||||
const rewriteCorsSettings: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
const corsSettings = get(settings, 'server.cors');
|
||||
if (typeof get(settings, 'server.cors') === 'boolean') {
|
||||
log('"server.cors" is deprecated and has been replaced by "server.cors.enabled"');
|
||||
settings.server.cors = {
|
||||
enabled: corsSettings,
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
|
||||
const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
|
||||
const NONCE_STRING = `{nonce}`;
|
||||
// Policies that should include the 'self' source
|
||||
|
@ -131,6 +142,7 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu
|
|||
rename('cpu.cgroup.path.override', 'ops.cGroupOverrides.cpuPath'),
|
||||
rename('cpuacct.cgroup.path.override', 'ops.cGroupOverrides.cpuAcctPath'),
|
||||
rename('server.xsrf.whitelist', 'server.xsrf.allowlist'),
|
||||
rewriteCorsSettings,
|
||||
configPathDeprecation,
|
||||
dataPathDeprecation,
|
||||
rewriteBasePathDeprecation,
|
||||
|
|
|
@ -38,7 +38,11 @@ Object {
|
|||
"compression": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
"cors": false,
|
||||
"cors": Object {
|
||||
"credentials": false,
|
||||
"enabled": false,
|
||||
"origin": "*",
|
||||
},
|
||||
"customResponseHeaders": Object {},
|
||||
"host": "localhost",
|
||||
"keepaliveTimeout": 120000,
|
||||
|
|
|
@ -68,6 +68,9 @@ configService.atPath.mockImplementation((path) => {
|
|||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
if (path === 'externalUrl') {
|
||||
|
|
|
@ -330,6 +330,59 @@ describe('with compression', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('cors', () => {
|
||||
describe('origin', () => {
|
||||
it('list cannot be empty', () => {
|
||||
expect(() =>
|
||||
config.schema.validate({
|
||||
cors: {
|
||||
origin: [],
|
||||
},
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[cors.origin]: types that failed validation:
|
||||
- [cors.origin.0]: expected value to equal [*]
|
||||
- [cors.origin.1]: array size is [0], but cannot be smaller than [1]"
|
||||
`);
|
||||
});
|
||||
|
||||
it('list of valid URLs', () => {
|
||||
const origin = ['http://127.0.0.1:3000', 'https://elastic.co'];
|
||||
expect(
|
||||
config.schema.validate({
|
||||
cors: { origin },
|
||||
}).cors.origin
|
||||
).toStrictEqual(origin);
|
||||
|
||||
expect(() =>
|
||||
config.schema.validate({
|
||||
cors: {
|
||||
origin: ['*://elastic.co/*'],
|
||||
},
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('can be configured as "*" wildcard', () => {
|
||||
expect(config.schema.validate({ cors: { origin: '*' } }).cors.origin).toBe('*');
|
||||
});
|
||||
});
|
||||
describe('credentials', () => {
|
||||
it('cannot use wildcard origin if "credentials: true"', () => {
|
||||
expect(
|
||||
() => config.schema.validate({ cors: { credentials: true, origin: '*' } }).cors.origin
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."`
|
||||
);
|
||||
expect(
|
||||
() => config.schema.validate({ cors: { credentials: true } }).cors.origin
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HttpConfig', () => {
|
||||
it('converts customResponseHeaders to strings or arrays of strings', () => {
|
||||
const httpSchema = config.schema;
|
||||
|
|
|
@ -27,7 +27,7 @@ import { SslConfig, sslSchema } from './ssl_config';
|
|||
|
||||
const validBasePathRegex = /^\/.*[^\/]$/;
|
||||
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
|
||||
const match = (regex: RegExp, errorMsg: string) => (str: string) =>
|
||||
regex.test(str) ? undefined : errorMsg;
|
||||
|
||||
|
@ -45,7 +45,25 @@ export const config = {
|
|||
validate: match(validBasePathRegex, "must start with a slash, don't end with one"),
|
||||
})
|
||||
),
|
||||
cors: schema.boolean({ defaultValue: false }),
|
||||
cors: schema.object(
|
||||
{
|
||||
enabled: schema.boolean({ defaultValue: false }),
|
||||
credentials: schema.boolean({ defaultValue: false }),
|
||||
origin: schema.oneOf(
|
||||
[schema.literal('*'), schema.arrayOf(hostURISchema, { minSize: 1 })],
|
||||
{
|
||||
defaultValue: '*',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
validate(value) {
|
||||
if (value.credentials === true && value.origin === '*') {
|
||||
return 'Cannot specify wildcard origin "*" with "credentials: true". Please provide a list of allowed origins.';
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
customResponseHeaders: schema.recordOf(schema.string(), schema.any(), {
|
||||
defaultValue: {},
|
||||
}),
|
||||
|
@ -148,7 +166,11 @@ export class HttpConfig {
|
|||
public keepaliveTimeout: number;
|
||||
public socketTimeout: number;
|
||||
public port: number;
|
||||
public cors: boolean | { origin: string[] };
|
||||
public cors: {
|
||||
enabled: boolean;
|
||||
credentials: boolean;
|
||||
origin: '*' | string[];
|
||||
};
|
||||
public customResponseHeaders: Record<string, string | string[]>;
|
||||
public maxPayload: ByteSizeValue;
|
||||
public basePath?: string;
|
||||
|
|
|
@ -72,6 +72,9 @@ beforeEach(() => {
|
|||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
} as any;
|
||||
|
||||
configWithSSL = {
|
||||
|
|
|
@ -102,6 +102,9 @@ describe('timeouts', () => {
|
|||
host: '127.0.0.1',
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
ssl: {},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
compression: { enabled: true },
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
|
@ -187,6 +190,26 @@ describe('getServerOptions', () => {
|
|||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('properly configures CORS when cors enabled', () => {
|
||||
const httpConfig = new HttpConfig(
|
||||
config.schema.validate({
|
||||
cors: {
|
||||
enabled: true,
|
||||
credentials: false,
|
||||
origin: '*',
|
||||
},
|
||||
}),
|
||||
{} as any,
|
||||
{} as any
|
||||
);
|
||||
|
||||
expect(getServerOptions(httpConfig).routes?.cors).toEqual({
|
||||
credentials: false,
|
||||
origin: '*',
|
||||
headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequestId', () => {
|
||||
|
|
|
@ -16,19 +16,34 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from '@hapi/hapi';
|
||||
import { Server } from '@hapi/hapi';
|
||||
import type {
|
||||
Lifecycle,
|
||||
Request,
|
||||
ResponseToolkit,
|
||||
RouteOptionsCors,
|
||||
ServerOptions,
|
||||
Util,
|
||||
} from '@hapi/hapi';
|
||||
import Hoek from '@hapi/hoek';
|
||||
import { ServerOptions as TLSOptions } from 'https';
|
||||
import { ValidationError } from 'joi';
|
||||
import type { ServerOptions as TLSOptions } from 'https';
|
||||
import type { ValidationError } from 'joi';
|
||||
import uuid from 'uuid';
|
||||
import { HttpConfig } from './http_config';
|
||||
import { validateObject } from './prototype_pollution';
|
||||
|
||||
const corsAllowedHeaders = ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'];
|
||||
/**
|
||||
* Converts Kibana `HttpConfig` into `ServerOptions` that are accepted by the Hapi server.
|
||||
*/
|
||||
export function getServerOptions(config: HttpConfig, { configureTLS = true } = {}) {
|
||||
const cors: RouteOptionsCors | false = config.cors.enabled
|
||||
? {
|
||||
credentials: config.cors.credentials,
|
||||
origin: config.cors.origin,
|
||||
headers: corsAllowedHeaders,
|
||||
}
|
||||
: false;
|
||||
// Note that all connection options configured here should be exactly the same
|
||||
// as in the legacy platform server (see `src/legacy/server/http/index`). Any change
|
||||
// SHOULD BE applied in both places. The only exception is TLS-specific options,
|
||||
|
@ -41,7 +56,7 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = {
|
|||
privacy: 'private',
|
||||
otherwise: 'private, no-cache, no-store, must-revalidate',
|
||||
},
|
||||
cors: config.cors,
|
||||
cors,
|
||||
payload: {
|
||||
maxBytes: config.maxPayload.getValueInBytes(),
|
||||
},
|
||||
|
|
|
@ -48,6 +48,9 @@ beforeEach(() => {
|
|||
enabled: true,
|
||||
redirectHttpFromPort: chance.integer({ min: 20000, max: 30000 }),
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
} as HttpConfig;
|
||||
|
||||
server = new HttpsRedirectServer(loggingSystemMock.create().get());
|
||||
|
|
|
@ -59,6 +59,9 @@ describe('core lifecycle handlers', () => {
|
|||
ssl: {
|
||||
enabled: false,
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
compression: { enabled: true },
|
||||
name: kibanaName,
|
||||
customResponseHeaders: {
|
||||
|
|
|
@ -41,6 +41,9 @@ configService.atPath.mockImplementation((path) => {
|
|||
ssl: {
|
||||
enabled: false,
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
compression: { enabled: true },
|
||||
xsrf: {
|
||||
disableProtection: true,
|
||||
|
|
|
@ -15,6 +15,7 @@ const alwaysImportedTests = [
|
|||
require.resolve('../test/security_functional/oidc.config.ts'),
|
||||
require.resolve('../test/security_functional/saml.config.ts'),
|
||||
require.resolve('../test/functional_embedded/config.ts'),
|
||||
require.resolve('../test/functional_cors/config.ts'),
|
||||
require.resolve('../test/functional_enterprise_search/without_host_configured.config.ts'),
|
||||
];
|
||||
const onlyNotInCoverageTests = [
|
||||
|
|
63
x-pack/test/functional_cors/config.ts
Normal file
63
x-pack/test/functional_cors/config.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 Path from 'path';
|
||||
import getPort from 'get-port';
|
||||
import type { FtrConfigProviderContext } from '@kbn/test/types/ftr';
|
||||
import { kbnTestConfig } from '@kbn/test';
|
||||
import { pageObjects } from '../functional/page_objects';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const kibanaFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js'));
|
||||
|
||||
const corsTestPlugin = Path.resolve(__dirname, './plugins/kibana_cors_test');
|
||||
|
||||
const servers = {
|
||||
...kibanaFunctionalConfig.get('servers'),
|
||||
elasticsearch: {
|
||||
...kibanaFunctionalConfig.get('servers.elasticsearch'),
|
||||
},
|
||||
kibana: {
|
||||
...kibanaFunctionalConfig.get('servers.kibana'),
|
||||
},
|
||||
};
|
||||
|
||||
const { protocol, hostname } = kbnTestConfig.getUrlParts();
|
||||
const pluginPort = await getPort({ port: 9000 });
|
||||
const originUrl = Url.format({
|
||||
protocol,
|
||||
hostname,
|
||||
port: pluginPort,
|
||||
});
|
||||
|
||||
return {
|
||||
testFiles: [require.resolve('./tests')],
|
||||
servers,
|
||||
services: kibanaFunctionalConfig.get('services'),
|
||||
pageObjects,
|
||||
junit: {
|
||||
reportName: 'Kibana CORS with X-Pack Security',
|
||||
},
|
||||
|
||||
esTestCluster: kibanaFunctionalConfig.get('esTestCluster'),
|
||||
apps: {
|
||||
...kibanaFunctionalConfig.get('apps'),
|
||||
},
|
||||
|
||||
kbnTestServer: {
|
||||
...kibanaFunctionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...kibanaFunctionalConfig.get('kbnTestServer.serverArgs'),
|
||||
`--plugin-path=${corsTestPlugin}`,
|
||||
`--test.cors.port=${pluginPort}`,
|
||||
'--server.cors.enabled=true',
|
||||
'--server.cors.credentials=true',
|
||||
`--server.cors.origin=["${originUrl}"]`,
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
12
x-pack/test/functional_cors/ftr_provider_context.d.ts
vendored
Normal file
12
x-pack/test/functional_cors/ftr_provider_context.d.ts
vendored
Normal file
|
@ -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<typeof services, typeof pageObjects>;
|
||||
export { pageObjects };
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "kibana_cors_test",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["test", "cors"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "kiban_cors_test",
|
||||
"version": "0.0.0",
|
||||
"kibana": {
|
||||
"version": "kibana"
|
||||
},
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && ../../../../../node_modules/.bin/tsc"
|
||||
}
|
||||
}
|
|
@ -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 { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const configSchema = schema.object({
|
||||
port: schema.number(),
|
||||
});
|
||||
|
||||
export type ConfigSchema = TypeOf<typeof configSchema>;
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 type { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server';
|
||||
import { CorsTestPlugin } from './plugin';
|
||||
import { configSchema, ConfigSchema } from './config';
|
||||
|
||||
export const plugin = (initContext: PluginInitializerContext) => new CorsTestPlugin(initContext);
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigSchema> = {
|
||||
schema: configSchema,
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 Hapi from '@hapi/hapi';
|
||||
import { kbnTestConfig } from '@kbn/test';
|
||||
import { take } from 'rxjs/operators';
|
||||
import Url from 'url';
|
||||
import abab from 'abab';
|
||||
|
||||
import type { Plugin, CoreSetup, CoreStart, PluginInitializerContext } from 'src/core/server';
|
||||
import type { ConfigSchema } from './config';
|
||||
|
||||
const apiToken = abab.btoa(kbnTestConfig.getUrlParts().auth!);
|
||||
|
||||
function renderBody(kibanaUrl: string) {
|
||||
const url = Url.resolve(kibanaUrl, '/cors-test');
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Request to CORS Kibana</title>
|
||||
</head>
|
||||
<script>
|
||||
fetch('${url}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Basic ${apiToken}',
|
||||
'kbn-xsrf': 'kibana',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((res) => {
|
||||
const element = window.document.createElement('p');
|
||||
element.innerText = res;
|
||||
window.document.body.appendChild(element);
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
export class CorsTestPlugin implements Plugin {
|
||||
private server?: Hapi.Server;
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
async setup(core: CoreSetup) {
|
||||
const router = core.http.createRouter();
|
||||
router.post({ path: '/cors-test', validate: false }, (context, req, res) =>
|
||||
res.ok({ body: 'content from kibana' })
|
||||
);
|
||||
}
|
||||
|
||||
async start(core: CoreStart) {
|
||||
const config = await this.initializerContext.config
|
||||
.create<ConfigSchema>()
|
||||
.pipe(take(1))
|
||||
.toPromise();
|
||||
|
||||
const server = new Hapi.Server({
|
||||
port: config.port,
|
||||
});
|
||||
this.server = server;
|
||||
|
||||
const { protocol, port, hostname } = core.http.getServerInfo();
|
||||
|
||||
const kibanaUrl = Url.format({ protocol, hostname, port });
|
||||
|
||||
server.route({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
handler(_, h) {
|
||||
return h.response(renderBody(kibanaUrl));
|
||||
},
|
||||
});
|
||||
await server.start();
|
||||
}
|
||||
public stop() {
|
||||
if (this.server) {
|
||||
this.server.stop();
|
||||
}
|
||||
}
|
||||
}
|
9
x-pack/test/functional_cors/services.ts
Normal file
9
x-pack/test/functional_cors/services.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { services as functionalServices } from '../functional/services';
|
||||
|
||||
export const services = functionalServices;
|
30
x-pack/test/functional_cors/tests/cors.ts
Normal file
30
x-pack/test/functional_cors/tests/cors.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const config = getService('config');
|
||||
const find = getService('find');
|
||||
|
||||
describe('CORS', () => {
|
||||
it('Communicates to Kibana with configured CORS', async () => {
|
||||
const args: string[] = config.get('kbnTestServer.serverArgs');
|
||||
const originSetting = args.find((str) => str.includes('server.cors.origin'));
|
||||
if (!originSetting) {
|
||||
throw new Error('Cannot find "server.cors.origin" argument');
|
||||
}
|
||||
const [, value] = originSetting.split('=');
|
||||
const url = JSON.parse(value);
|
||||
|
||||
await browser.navigateTo(url[0]);
|
||||
const element = await find.byCssSelector('p');
|
||||
expect(await element.getVisibleText()).to.be('content from kibana');
|
||||
});
|
||||
});
|
||||
}
|
14
x-pack/test/functional_cors/tests/index.ts
Normal file
14
x-pack/test/functional_cors/tests/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Kibana cors', function () {
|
||||
this.tags('ciGroup2');
|
||||
loadTestFile(require.resolve('./cors'));
|
||||
});
|
||||
}
|
|
@ -6318,12 +6318,7 @@ JSONStream@1.3.5, JSONStream@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57"
|
||||
integrity sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=
|
||||
|
||||
abab@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
|
||||
integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=
|
||||
|
||||
abab@^2.0.0, abab@^2.0.3:
|
||||
abab@^2.0.0, abab@^2.0.3, abab@^2.0.4:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
|
||||
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue