mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ResponseOps] provide config to turn off email action footer (#154919)
resolves https://github.com/elastic/kibana/issues/135402 Allows deployments to not have the default footer added to alerting emails via the new `xpack.actions.enableFooterInEmail` config setting. The default value is `true`, which renders the footer. Setting the value to `false` will cause no footer to be rendered. Also changes the footer separator from `--` to `---`, which renders nicer in HTML, as a `<hr>` element.
This commit is contained in:
parent
70d5dad847
commit
bcfe4b0005
18 changed files with 96 additions and 20 deletions
|
@ -130,6 +130,9 @@ A list of allowed email domains which can be used with the email connector. When
|
|||
|
||||
WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such, this setting should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly.
|
||||
|
||||
`xpack.actions.enableFooterInEmail` {ess-icon}::
|
||||
A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions. Default: true.
|
||||
|
||||
`xpack.actions.enabledActionTypes` {ess-icon}::
|
||||
A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.torq`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types.
|
||||
+
|
||||
|
|
|
@ -580,6 +580,7 @@ describe('create()', () => {
|
|||
verificationMode: 'full',
|
||||
proxyVerificationMode: 'full',
|
||||
},
|
||||
enableFooterInEmail: true,
|
||||
});
|
||||
|
||||
const localActionTypeRegistryParams = {
|
||||
|
|
|
@ -27,6 +27,7 @@ const createActionsConfigMock = () => {
|
|||
getMicrosoftGraphApiUrl: jest.fn().mockReturnValue(undefined),
|
||||
validateEmailAddresses: jest.fn().mockReturnValue(undefined),
|
||||
getMaxAttempts: jest.fn().mockReturnValue(3),
|
||||
enableFooterInEmail: jest.fn().mockReturnValue(true),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
|
|
@ -33,6 +33,7 @@ const defaultActionsConfig: ActionsConfig = {
|
|||
proxyVerificationMode: 'full',
|
||||
verificationMode: 'full',
|
||||
},
|
||||
enableFooterInEmail: true,
|
||||
};
|
||||
|
||||
describe('ensureUriAllowed', () => {
|
||||
|
|
|
@ -53,6 +53,7 @@ export interface ActionsConfigurationUtilities {
|
|||
addresses: string[],
|
||||
options?: ValidateEmailAddressesOptions
|
||||
): string | undefined;
|
||||
enableFooterInEmail: () => boolean;
|
||||
}
|
||||
|
||||
function allowListErrorMessage(field: AllowListingField, value: string) {
|
||||
|
@ -215,5 +216,6 @@ export function getActionsConfigurationUtilities(
|
|||
DEFAULT_MAX_ATTEMPTS
|
||||
);
|
||||
},
|
||||
enableFooterInEmail: () => config.enableFooterInEmail,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('config validation', () => {
|
|||
"allowedHosts": Array [
|
||||
"*",
|
||||
],
|
||||
"enableFooterInEmail": true,
|
||||
"enabledActionTypes": Array [
|
||||
"*",
|
||||
],
|
||||
|
@ -57,6 +58,7 @@ describe('config validation', () => {
|
|||
"allowedHosts": Array [
|
||||
"*",
|
||||
],
|
||||
"enableFooterInEmail": true,
|
||||
"enabledActionTypes": Array [
|
||||
"*",
|
||||
],
|
||||
|
@ -198,6 +200,7 @@ describe('config validation', () => {
|
|||
"allowedHosts": Array [
|
||||
"*",
|
||||
],
|
||||
"enableFooterInEmail": true,
|
||||
"enabledActionTypes": Array [
|
||||
"*",
|
||||
],
|
||||
|
|
|
@ -125,6 +125,7 @@ export const configSchema = schema.object({
|
|||
connectorTypeOverrides: schema.maybe(schema.arrayOf(connectorTypeSchema)),
|
||||
})
|
||||
),
|
||||
enableFooterInEmail: schema.boolean({ defaultValue: true }),
|
||||
});
|
||||
|
||||
export type ActionsConfig = TypeOf<typeof configSchema>;
|
||||
|
|
|
@ -580,6 +580,7 @@ const BaseActionsConfig: ActionsConfig = {
|
|||
maxResponseContentLength: ByteSizeValue.parse('1mb'),
|
||||
responseTimeout: momentDuration(1000 * 30),
|
||||
customHostSettings: undefined,
|
||||
enableFooterInEmail: true,
|
||||
};
|
||||
|
||||
function getACUfromConfig(config: Partial<ActionsConfig> = {}): ActionsConfigurationUtilities {
|
||||
|
|
|
@ -592,6 +592,7 @@ const BaseActionsConfig: ActionsConfig = {
|
|||
maxResponseContentLength: ByteSizeValue.parse('1mb'),
|
||||
responseTimeout: momentDuration(1000 * 30),
|
||||
customHostSettings: undefined,
|
||||
enableFooterInEmail: true,
|
||||
};
|
||||
|
||||
function getACUfromConfig(config: Partial<ActionsConfig> = {}): ActionsConfigurationUtilities {
|
||||
|
|
|
@ -73,6 +73,7 @@ describe('custom_host_settings', () => {
|
|||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration(60000),
|
||||
enableFooterInEmail: true,
|
||||
};
|
||||
|
||||
test('ensure it copies over the config parts that it does not touch', () => {
|
||||
|
|
|
@ -46,6 +46,7 @@ describe('Actions Plugin', () => {
|
|||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration(60000),
|
||||
enableFooterInEmail: true,
|
||||
});
|
||||
plugin = new ActionsPlugin(context);
|
||||
coreSetup = coreMock.createSetup();
|
||||
|
@ -218,6 +219,7 @@ describe('Actions Plugin', () => {
|
|||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration('60s'),
|
||||
enableFooterInEmail: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
@ -273,6 +275,7 @@ describe('Actions Plugin', () => {
|
|||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration(60000),
|
||||
enableFooterInEmail: true,
|
||||
});
|
||||
plugin = new ActionsPlugin(context);
|
||||
coreSetup = coreMock.createSetup();
|
||||
|
@ -341,6 +344,7 @@ describe('Actions Plugin', () => {
|
|||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration('60s'),
|
||||
enableFooterInEmail: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -523,6 +523,10 @@ describe('execute()', () => {
|
|||
logger: mockedLogger,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
executorOptions.configurationUtilities = actionsConfigMock.create();
|
||||
});
|
||||
|
||||
test('ensure parameters are as expected', async () => {
|
||||
sendEmailMock.mockReset();
|
||||
const result = await connectorType.executor(executorOptions);
|
||||
|
@ -540,7 +544,7 @@ describe('execute()', () => {
|
|||
"content": Object {
|
||||
"message": "a message to you
|
||||
|
||||
--
|
||||
---
|
||||
|
||||
This message was sent by Elastic.",
|
||||
"subject": "the subject",
|
||||
|
@ -591,7 +595,7 @@ describe('execute()', () => {
|
|||
"content": Object {
|
||||
"message": "a message to you
|
||||
|
||||
--
|
||||
---
|
||||
|
||||
This message was sent by Elastic.",
|
||||
"subject": "the subject",
|
||||
|
@ -642,7 +646,7 @@ describe('execute()', () => {
|
|||
"content": Object {
|
||||
"message": "a message to you
|
||||
|
||||
--
|
||||
---
|
||||
|
||||
This message was sent by Elastic.",
|
||||
"subject": "the subject",
|
||||
|
@ -738,6 +742,28 @@ describe('execute()', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('provides no footer link when enableFooterInEmail is false', async () => {
|
||||
const customExecutorOptions: EmailConnectorTypeExecutorOptions = {
|
||||
...executorOptions,
|
||||
configurationUtilities: {
|
||||
...configurationUtilities,
|
||||
enableFooterInEmail: jest.fn().mockReturnValue(false),
|
||||
},
|
||||
};
|
||||
|
||||
const connectorTypeWithPublicUrl = getConnectorType({
|
||||
publicBaseUrl: 'https://localhost:1234/foo/bar',
|
||||
});
|
||||
|
||||
await connectorTypeWithPublicUrl.executor(customExecutorOptions);
|
||||
|
||||
expect(customExecutorOptions.configurationUtilities.enableFooterInEmail).toHaveBeenCalledTimes(
|
||||
1
|
||||
);
|
||||
const sendMailCall = sendEmailMock.mock.calls[0][1];
|
||||
expect(sendMailCall.content.message).toMatchInlineSnapshot(`"a message to you"`);
|
||||
});
|
||||
|
||||
test('provides a footer link to Elastic when publicBaseUrl is defined', async () => {
|
||||
const connectorTypeWithPublicUrl = getConnectorType({
|
||||
publicBaseUrl: 'https://localhost:1234/foo/bar',
|
||||
|
@ -750,7 +776,7 @@ describe('execute()', () => {
|
|||
expect(sendMailCall.content.message).toMatchInlineSnapshot(`
|
||||
"a message to you
|
||||
|
||||
--
|
||||
---
|
||||
|
||||
This message was sent by Elastic. [Go to Elastic](https://localhost:1234/foo/bar)."
|
||||
`);
|
||||
|
@ -779,7 +805,7 @@ describe('execute()', () => {
|
|||
expect(sendMailCall.content.message).toMatchInlineSnapshot(`
|
||||
"a message to you
|
||||
|
||||
--
|
||||
---
|
||||
|
||||
This message was sent by Elastic. [View this in Elastic](https://localhost:1234/foo/bar/my/app)."
|
||||
`);
|
||||
|
|
|
@ -54,7 +54,7 @@ export const ELASTIC_CLOUD_SERVICE: SMTPConnection.Options = {
|
|||
secure: false,
|
||||
};
|
||||
|
||||
const EMAIL_FOOTER_DIVIDER = '\n\n--\n\n';
|
||||
const EMAIL_FOOTER_DIVIDER = '\n\n---\n\n';
|
||||
|
||||
const ConfigSchemaProps = {
|
||||
service: schema.string({ defaultValue: 'other' }),
|
||||
|
@ -319,10 +319,14 @@ async function executor(
|
|||
transport.service = config.service;
|
||||
}
|
||||
|
||||
const footerMessage = getFooterMessage({
|
||||
publicBaseUrl,
|
||||
kibanaFooterLink: params.kibanaFooterLink,
|
||||
});
|
||||
let actualMessage = params.message;
|
||||
if (configurationUtilities.enableFooterInEmail()) {
|
||||
const footerMessage = getFooterMessage({
|
||||
publicBaseUrl,
|
||||
kibanaFooterLink: params.kibanaFooterLink,
|
||||
});
|
||||
actualMessage = `${params.message}${EMAIL_FOOTER_DIVIDER}${footerMessage}`;
|
||||
}
|
||||
|
||||
const sendEmailOptions: SendEmailOptions = {
|
||||
connectorId: actionId,
|
||||
|
@ -335,7 +339,7 @@ async function executor(
|
|||
},
|
||||
content: {
|
||||
subject: params.subject,
|
||||
message: `${params.message}${EMAIL_FOOTER_DIVIDER}${footerMessage}`,
|
||||
message: actualMessage,
|
||||
},
|
||||
hasAuth: config.hasAuth,
|
||||
configurationUtilities,
|
||||
|
|
|
@ -27,6 +27,7 @@ interface CreateTestConfigOptions {
|
|||
testFiles?: string[];
|
||||
reportName?: string;
|
||||
useDedicatedTaskRunner: boolean;
|
||||
enableFooterInEmail?: boolean;
|
||||
}
|
||||
|
||||
// test.not-enabled is specifically not enabled
|
||||
|
@ -75,6 +76,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
testFiles = undefined,
|
||||
reportName = undefined,
|
||||
useDedicatedTaskRunner,
|
||||
enableFooterInEmail = true,
|
||||
} = options;
|
||||
|
||||
return async ({ readConfigFile }: FtrConfigProviderContext) => {
|
||||
|
@ -173,6 +175,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
|
|||
'some.non.existent.com',
|
||||
'smtp.live.com',
|
||||
])}`,
|
||||
`--xpack.actions.enableFooterInEmail=${enableFooterInEmail}`,
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
|
||||
'--xpack.alerting.invalidateApiKeysTask.interval="15s"',
|
||||
'--xpack.alerting.healthCheck.interval="1s"',
|
||||
|
|
|
@ -43,7 +43,7 @@ export function initPlugin(router: IRouter, path: string) {
|
|||
cc: null,
|
||||
bcc: null,
|
||||
subject: 'email-subject',
|
||||
html: `<p>email-message</p>\n<p>--</p>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
html: `<p>email-message</p>\n<hr>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
text: 'email-message\n\n--\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).',
|
||||
headers: {},
|
||||
},
|
||||
|
|
|
@ -124,8 +124,8 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
cc: null,
|
||||
bcc: null,
|
||||
subject: 'email-subject',
|
||||
html: `<p>email-message</p>\n<p>--</p>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
text: 'email-message\n\n--\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).',
|
||||
html: `<p>email-message</p>\n<hr>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
text: 'email-message\n\n---\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).',
|
||||
headers: {},
|
||||
},
|
||||
});
|
||||
|
@ -147,10 +147,10 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
.then((resp: any) => {
|
||||
const { text, html } = resp.body.data.message;
|
||||
expect(text).to.eql(
|
||||
'_italic_ **bold** https://elastic.co link\n\n--\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).'
|
||||
'_italic_ **bold** https://elastic.co link\n\n---\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).'
|
||||
);
|
||||
expect(html).to.eql(
|
||||
`<p><em>italic</em> <strong>bold</strong> <a href="https://elastic.co">https://elastic.co</a> link</p>\n<p>--</p>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`
|
||||
`<p><em>italic</em> <strong>bold</strong> <a href="https://elastic.co">https://elastic.co</a> link</p>\n<hr>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -174,10 +174,10 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
.then((resp: any) => {
|
||||
const { text, html } = resp.body.data.message;
|
||||
expect(text).to.eql(
|
||||
'message\n\n--\n\nThis message was sent by Elastic. [View my path in Elastic](https://localhost:5601/my/path).'
|
||||
'message\n\n---\n\nThis message was sent by Elastic. [View my path in Elastic](https://localhost:5601/my/path).'
|
||||
);
|
||||
expect(html).to.eql(
|
||||
`<p>message</p>\n<p>--</p>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601/my/path\">View my path in Elastic</a>.</p>\n`
|
||||
`<p>message</p>\n<hr>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601/my/path\">View my path in Elastic</a>.</p>\n`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -325,8 +325,8 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
cc: null,
|
||||
bcc: null,
|
||||
subject: 'email-subject',
|
||||
html: `<p>email-message</p>\n<p>--</p>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
text: 'email-message\n\n--\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).',
|
||||
html: `<p>email-message</p>\n<hr>\n<p>This message was sent by Elastic. <a href=\"https://localhost:5601\">Go to Elastic</a>.</p>\n`,
|
||||
text: 'email-message\n\n---\n\nThis message was sent by Elastic. [Go to Elastic](https://localhost:5601).',
|
||||
headers: {},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -21,4 +21,5 @@ export default createTestConfig('spaces_only', {
|
|||
useDedicatedTaskRunner: true,
|
||||
testFiles: [require.resolve('.')],
|
||||
reportName: 'X-Pack Alerting API Integration Tests - Actions',
|
||||
enableFooterInEmail: false,
|
||||
});
|
||||
|
|
|
@ -54,6 +54,29 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
|
||||
it('does not have a footer', async () => {
|
||||
const from = `bob@${EmailDomainAllowed}`;
|
||||
const conn = await createConnector(from);
|
||||
expect(conn.status).to.be(200);
|
||||
|
||||
const { id } = conn.body;
|
||||
expect(id).to.be.a('string');
|
||||
|
||||
const to = EmailDomainsAllowed.map((domain) => `jeb@${domain}`).sort();
|
||||
const cc = EmailDomainsAllowed.map((domain) => `jim@${domain}`).sort();
|
||||
const bcc = EmailDomainsAllowed.map((domain) => `joe@${domain}`).sort();
|
||||
|
||||
const ccNames = cc.map((email) => `Jimmy Jack <${email}>`);
|
||||
|
||||
const run = await runConnector(id, to, ccNames, bcc);
|
||||
expect(run.status).to.be(200);
|
||||
|
||||
const { status } = run.body || {};
|
||||
expect(status).to.be('ok');
|
||||
|
||||
expect(run.body.data.message.text).to.be('email-message');
|
||||
});
|
||||
|
||||
describe('fails for invalid email domains', () => {
|
||||
it('in create when invalid "from" used', async () => {
|
||||
const from = `bob@not.allowed`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue