[alerting] add mustache variable kibanaBaseUrl for Kibana's publicly exposed base URL (#90525)
resolves https://github.com/elastic/kibana/issues/49392 Adds the top-level mustache variable `kibanaBaseUrl` for action parameter mustache templates. The value comes from Kibana config, which, if not set will result in this variable having the value `undefined` which will be rendered as an empty string.
This commit is contained in:
parent
073cd4d508
commit
20e16bd9a4
|
@ -161,6 +161,7 @@ export class AlertingPlugin {
|
|||
private eventLogService?: IEventLogService;
|
||||
private eventLogger?: IEventLogger;
|
||||
private readonly kibanaIndexConfig: Observable<{ kibana: { index: string } }>;
|
||||
private kibanaBaseUrl: string | undefined;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.config = initializerContext.config.create<AlertsConfig>().pipe(first()).toPromise();
|
||||
|
@ -176,6 +177,7 @@ export class AlertingPlugin {
|
|||
core: CoreSetup<AlertingPluginsStart, unknown>,
|
||||
plugins: AlertingPluginsSetup
|
||||
): PluginSetupContract {
|
||||
this.kibanaBaseUrl = core.http.basePath.publicBaseUrl;
|
||||
this.licenseState = new LicenseState(plugins.licensing.license$);
|
||||
this.security = plugins.security;
|
||||
|
||||
|
@ -371,6 +373,7 @@ export class AlertingPlugin {
|
|||
eventLogger: this.eventLogger!,
|
||||
internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']),
|
||||
alertTypeRegistry: this.alertTypeRegistry!,
|
||||
kibanaBaseUrl: this.kibanaBaseUrl,
|
||||
});
|
||||
|
||||
this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
|
||||
|
|
|
@ -72,6 +72,7 @@ const createExecutionHandlerParams: jest.Mocked<
|
|||
alertName: 'name-of-alert',
|
||||
tags: ['tag-A', 'tag-B'],
|
||||
apiKey: 'MTIzOmFiYw==',
|
||||
kibanaBaseUrl: 'http://localhost:5601',
|
||||
alertType,
|
||||
logger: loggingSystemMock.create().get(),
|
||||
eventLogger: mockEventLogger,
|
||||
|
|
|
@ -39,6 +39,7 @@ export interface CreateExecutionHandlerOptions<
|
|||
actions: AlertAction[];
|
||||
spaceId: string;
|
||||
apiKey: RawAlert['apiKey'];
|
||||
kibanaBaseUrl: string | undefined;
|
||||
alertType: NormalizedAlertType<
|
||||
Params,
|
||||
State,
|
||||
|
@ -82,6 +83,7 @@ export function createExecutionHandler<
|
|||
spaceId,
|
||||
apiKey,
|
||||
alertType,
|
||||
kibanaBaseUrl,
|
||||
eventLogger,
|
||||
request,
|
||||
alertParams,
|
||||
|
@ -126,6 +128,7 @@ export function createExecutionHandler<
|
|||
context,
|
||||
actionParams: action.params,
|
||||
state,
|
||||
kibanaBaseUrl,
|
||||
alertParams,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -97,6 +97,7 @@ describe('Task Runner', () => {
|
|||
eventLogger: eventLoggerMock.create(),
|
||||
internalSavedObjectsRepository: savedObjectsRepositoryMock.create(),
|
||||
alertTypeRegistry,
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
};
|
||||
|
||||
const mockedAlertTypeSavedObject: Alert<AlertTypeParams> = {
|
||||
|
|
|
@ -160,6 +160,7 @@ export class TaskRunner<
|
|||
tags: string[] | undefined,
|
||||
spaceId: string,
|
||||
apiKey: RawAlert['apiKey'],
|
||||
kibanaBaseUrl: string | undefined,
|
||||
actions: Alert<Params>['actions'],
|
||||
alertParams: Params
|
||||
) {
|
||||
|
@ -180,6 +181,7 @@ export class TaskRunner<
|
|||
actions,
|
||||
spaceId,
|
||||
alertType: this.alertType,
|
||||
kibanaBaseUrl,
|
||||
eventLogger: this.context.eventLogger,
|
||||
request: this.getFakeKibanaRequest(spaceId, apiKey),
|
||||
alertParams,
|
||||
|
@ -388,6 +390,7 @@ export class TaskRunner<
|
|||
alert.tags,
|
||||
spaceId,
|
||||
apiKey,
|
||||
this.context.kibanaBaseUrl,
|
||||
alert.actions,
|
||||
alert.params
|
||||
);
|
||||
|
|
|
@ -77,6 +77,7 @@ describe('Task Runner Factory', () => {
|
|||
eventLogger: eventLoggerMock.create(),
|
||||
internalSavedObjectsRepository: savedObjectsRepositoryMock.create(),
|
||||
alertTypeRegistry: alertTypeRegistryMock.create(),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface TaskRunnerContext {
|
|||
basePathService: IBasePath;
|
||||
internalSavedObjectsRepository: ISavedObjectsRepository;
|
||||
alertTypeRegistry: AlertTypeRegistry;
|
||||
kibanaBaseUrl: string | undefined;
|
||||
}
|
||||
|
||||
export class TaskRunnerFactory {
|
||||
|
|
|
@ -27,6 +27,7 @@ interface TransformActionParamsOptions {
|
|||
actionParams: AlertActionParams;
|
||||
alertParams: AlertTypeParams;
|
||||
state: AlertInstanceState;
|
||||
kibanaBaseUrl?: string;
|
||||
context: AlertInstanceContext;
|
||||
}
|
||||
|
||||
|
@ -44,6 +45,7 @@ export function transformActionParams({
|
|||
context,
|
||||
actionParams,
|
||||
state,
|
||||
kibanaBaseUrl,
|
||||
alertParams,
|
||||
}: TransformActionParamsOptions): AlertActionParams {
|
||||
// when the list of variables we pass in here changes,
|
||||
|
@ -61,6 +63,7 @@ export function transformActionParams({
|
|||
context,
|
||||
date: new Date().toISOString(),
|
||||
state,
|
||||
kibanaBaseUrl,
|
||||
params: alertParams,
|
||||
};
|
||||
return actionsPlugin.renderActionParameterTemplates(actionTypeId, actionParams, variables);
|
||||
|
|
|
@ -44,10 +44,18 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The alert action subgroup that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionSubgroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "The configured server.publicBaseUrl value or empty string if not configured.",
|
||||
"name": "kibanaBaseUrl",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
@ -91,10 +99,18 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The alert action subgroup that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionSubgroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "The configured server.publicBaseUrl value or empty string if not configured.",
|
||||
"name": "kibanaBaseUrl",
|
||||
},
|
||||
Object {
|
||||
"description": "foo-description",
|
||||
"name": "context.foo",
|
||||
|
@ -146,10 +162,18 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The alert action subgroup that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionSubgroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "The configured server.publicBaseUrl value or empty string if not configured.",
|
||||
"name": "kibanaBaseUrl",
|
||||
},
|
||||
Object {
|
||||
"description": "foo-description",
|
||||
"name": "state.foo",
|
||||
|
@ -204,10 +228,18 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The alert action subgroup that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionSubgroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "The configured server.publicBaseUrl value or empty string if not configured.",
|
||||
"name": "kibanaBaseUrl",
|
||||
},
|
||||
Object {
|
||||
"description": "fooC-description",
|
||||
"name": "context.fooC",
|
||||
|
@ -280,10 +312,18 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The alert action subgroup that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionSubgroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "The configured server.publicBaseUrl value or empty string if not configured.",
|
||||
"name": "kibanaBaseUrl",
|
||||
},
|
||||
Object {
|
||||
"description": "fooC-description",
|
||||
"name": "context.fooC",
|
||||
|
|
|
@ -88,6 +88,17 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
|
|||
}),
|
||||
});
|
||||
|
||||
result.push({
|
||||
name: 'alertActionSubgroup',
|
||||
description: i18n.translate(
|
||||
'xpack.triggersActionsUI.actionVariables.alertActionSubgroupLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'The alert action subgroup that was used to scheduled actions for the alert.',
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
result.push({
|
||||
name: 'alertActionGroupName',
|
||||
description: i18n.translate(
|
||||
|
@ -99,5 +110,13 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
|
|||
),
|
||||
});
|
||||
|
||||
result.push({
|
||||
name: 'kibanaBaseUrl',
|
||||
description: i18n.translate('xpack.triggersActionsUI.actionVariables.kibanaBaseUrlLabel', {
|
||||
defaultMessage:
|
||||
'The configured server.publicBaseUrl value or empty string if not configured.',
|
||||
}),
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ interface CreateTestConfigOptions {
|
|||
ssl?: boolean;
|
||||
enableActionsProxy: boolean;
|
||||
rejectUnauthorized?: boolean;
|
||||
publicBaseUrl?: boolean;
|
||||
}
|
||||
|
||||
// test.not-enabled is specifically not enabled
|
||||
|
@ -97,7 +98,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
...xPackApiIntegrationTestsConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...xPackApiIntegrationTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--server.publicBaseUrl=https://localhost:5601',
|
||||
...(options.publicBaseUrl ? ['--server.publicBaseUrl=https://localhost:5601'] : []),
|
||||
`--xpack.actions.allowedHosts=${JSON.stringify(['localhost', 'some.non.existent.com'])}`,
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
|
||||
'--xpack.alerts.invalidateApiKeysTask.interval="15s"',
|
||||
|
|
|
@ -13,4 +13,5 @@ export default createTestConfig('security_and_spaces', {
|
|||
license: 'trial',
|
||||
ssl: true,
|
||||
enableActionsProxy: true,
|
||||
publicBaseUrl: true,
|
||||
});
|
||||
|
|
|
@ -51,6 +51,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
|
|||
loadTestFile(require.resolve('./update_api_key'));
|
||||
loadTestFile(require.resolve('./alerts'));
|
||||
loadTestFile(require.resolve('./event_log'));
|
||||
loadTestFile(require.resolve('./mustache_templates'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These tests ensure that the per-action mustache template escaping works
|
||||
* for actions we have simulators for. It arranges to have an alert that
|
||||
* schedules an action that will contain "escapable" characters in it, and
|
||||
* then validates that the simulator receives the escaped versions.
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import getPort from 'get-port';
|
||||
import axios from 'axios';
|
||||
import httpProxy from 'http-proxy';
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { Spaces } from '../../scenarios';
|
||||
import { getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { getSlackServer } from '../../../common/fixtures/plugins/actions_simulators/server/plugin';
|
||||
import { getHttpProxyServer } from '../../../common/lib/get_proxy_server';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function executionStatusAlertTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const retry = getService('retry');
|
||||
const configService = getService('config');
|
||||
|
||||
describe('mustacheTemplates', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
let slackSimulatorURL: string = '';
|
||||
let slackServer: http.Server;
|
||||
let proxyServer: httpProxy | undefined;
|
||||
|
||||
before(async () => {
|
||||
slackServer = await getSlackServer();
|
||||
const availablePort = await getPort({ port: getPort.makeRange(9000, 9100) });
|
||||
if (!slackServer.listening) {
|
||||
slackServer.listen(availablePort);
|
||||
}
|
||||
slackSimulatorURL = `http://localhost:${availablePort}`;
|
||||
|
||||
proxyServer = await getHttpProxyServer(
|
||||
slackSimulatorURL,
|
||||
configService.get('kbnTestServer.serverArgs'),
|
||||
() => {}
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await objectRemover.removeAll();
|
||||
slackServer.close();
|
||||
|
||||
if (proxyServer) {
|
||||
proxyServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('should render kibanaBaseUrl as non-empty string since configured', async () => {
|
||||
const actionResponse = await supertest
|
||||
.post(`${getUrlPrefix(Spaces[0].id)}/api/actions/action`)
|
||||
.set('kbn-xsrf', 'test')
|
||||
.send({
|
||||
name: 'testing context variable expansion',
|
||||
actionTypeId: '.slack',
|
||||
secrets: {
|
||||
webhookUrl: slackSimulatorURL,
|
||||
},
|
||||
});
|
||||
expect(actionResponse.status).to.eql(200);
|
||||
const createdAction = actionResponse.body;
|
||||
objectRemover.add(Spaces[0].id, createdAction.id, 'action', 'actions');
|
||||
|
||||
const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"';
|
||||
|
||||
const alertResponse = await supertest
|
||||
.post(`${getUrlPrefix(Spaces[0].id)}/api/alerts/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestAlertData({
|
||||
name: 'testing context variable kibanaBaseUrl',
|
||||
alertTypeId: 'test.patternFiring',
|
||||
params: {
|
||||
pattern: { instance: [true, true] },
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {
|
||||
message: `message {{alertId}} - ${varsTemplate}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
expect(alertResponse.status).to.eql(200);
|
||||
const createdAlert = alertResponse.body;
|
||||
objectRemover.add(Spaces[0].id, createdAlert.id, 'alert', 'alerts');
|
||||
|
||||
const body = await retry.try(async () =>
|
||||
waitForActionBody(slackSimulatorURL, createdAlert.id)
|
||||
);
|
||||
expect(body).to.be('kibanaBaseUrl: "https://localhost:5601"');
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForActionBody(url: string, id: string): Promise<string> {
|
||||
const response = await axios.get<string[]>(url);
|
||||
expect(response.status).to.eql(200);
|
||||
|
||||
for (const datum of response.data) {
|
||||
const match = datum.match(/^(.*) - (.*)$/);
|
||||
if (match == null) continue;
|
||||
|
||||
if (match[1] === id) return match[2];
|
||||
}
|
||||
|
||||
throw new Error(`no action body posted yet for id ${id}`);
|
||||
}
|
||||
}
|
|
@ -217,6 +217,54 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon
|
|||
'{"objectA":{"stringB":"B","arrayC":[{"stringD":"D1","numberE":42},{"stringD":"D2","numberE":43}],"objectF":{"stringG":"G","nullG":null}},"stringH":"H","arrayI":[44,45],"nullJ":null}'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render kibanaBaseUrl as empty string since not configured', async () => {
|
||||
const actionResponse = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action`)
|
||||
.set('kbn-xsrf', 'test')
|
||||
.send({
|
||||
name: 'testing context variable expansion',
|
||||
actionTypeId: '.slack',
|
||||
secrets: {
|
||||
webhookUrl: slackSimulatorURL,
|
||||
},
|
||||
});
|
||||
expect(actionResponse.status).to.eql(200);
|
||||
const createdAction = actionResponse.body;
|
||||
objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions');
|
||||
|
||||
const varsTemplate = 'kibanaBaseUrl: "{{kibanaBaseUrl}}"';
|
||||
|
||||
const alertResponse = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(
|
||||
getTestAlertData({
|
||||
name: 'testing context variable kibanaBaseUrl',
|
||||
alertTypeId: 'test.patternFiring',
|
||||
params: {
|
||||
pattern: { instance: [true, true] },
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
id: createdAction.id,
|
||||
group: 'default',
|
||||
params: {
|
||||
message: `message {{alertId}} - ${varsTemplate}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
expect(alertResponse.status).to.eql(200);
|
||||
const createdAlert = alertResponse.body;
|
||||
objectRemover.add(Spaces.space1.id, createdAlert.id, 'alert', 'alerts');
|
||||
|
||||
const body = await retry.try(async () =>
|
||||
waitForActionBody(slackSimulatorURL, createdAlert.id)
|
||||
);
|
||||
expect(body).to.be('kibanaBaseUrl: ""');
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForActionBody(url: string, id: string): Promise<string> {
|
||||
|
|
Loading…
Reference in a new issue