Skip to content

Commit 21e34ea

Browse files
pmuellrkibanamachineelasticmachine
authored
[Actions] set max limit on size of emails sent (#239572)
resolves #235127 ## Summary ## ToDo - [ ] issue to update cloud for new config setting - [ ] issue to lower default value based on existing usage - [ ] issue to surface error in rule execution UX ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Elastic Machine <[email protected]>
1 parent 19a2a69 commit 21e34ea

File tree

16 files changed

+435
-24
lines changed

16 files changed

+435
-24
lines changed

docs/reference/cloud/elastic-cloud-kibana-settings.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ If you want to allow anonymous authentication in Kibana, these settings are supp
7474

7575
You can configure the following X-Pack settings from the Kibana **User Settings** editor.
7676

77+
### Version 9.3+ [ec_version_9_3]
78+
```{applies_to}
79+
stack: ga 9.3
80+
```
81+
82+
`xpack.actions.email.maximum_body_length`
83+
: The maximum length of an email body in bytes. Values longer than this length will be truncated. The default is 25MB, the maximum is 25MB.
84+
7785
### Version 9.2+ [ec_version_9_2]
7886
```{applies_to}
7987
stack: ga 9.2

docs/reference/configuration-reference/alerting-settings.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ $$$action-config-email-domain-allowlist$$$
151151

152152
Only "to", "cc", or "bcc" email addresses that match the listed patterns will be accepted. For example, "[email protected]" or "[email protected]".
153153

154+
`xpack.actions.email.maximum_body_length` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") {applies_to}`stack: ga 9.3`
155+
: The maximum length of an email body in bytes. Values longer than this length will be truncated. The default is 25MB, the maximum is 25MB.
156+
154157
`xpack.actions.email.services.ses.host` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") {applies_to}`stack: ga 9.1`
155158
: The SMTP endpoint for an Amazon Simple Email Service (SES) service provider that can be used by email connectors.
156159

src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ kibana_vars=(
219219
xpack.actions.customHostSettings
220220
xpack.actions.email.domain_allowlist
221221
xpack.actions.email.recipient_allowlist
222+
xpack.actions.email.maximum_body_length
222223
xpack.actions.email.services.ses.host
223224
xpack.actions.email.services.ses.port
224225
xpack.actions.email.services.enabled

x-pack/platform/plugins/shared/actions/common/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,9 @@ export const ACTIONS_FEATURE_ID = 'actions';
7878
export const DEFAULT_MICROSOFT_EXCHANGE_URL = 'https://login.microsoftonline.com';
7979
export const DEFAULT_MICROSOFT_GRAPH_API_URL = 'https://graph.microsoft.com/v1.0';
8080
export const DEFAULT_MICROSOFT_GRAPH_API_SCOPE = 'https://graph.microsoft.com/.default';
81+
82+
// Default == max, which we will change later when we have more
83+
// information on email sizes. Greater than 25MB does seem to cause
84+
// OOMs, so that seems like a safe limit for now.
85+
export const MAX_EMAIL_BODY_LENGTH = 25 * 1000 * 1000; // 25MB
86+
export const DEFAULT_EMAIL_BODY_LENGTH = MAX_EMAIL_BODY_LENGTH;

x-pack/platform/plugins/shared/actions/server/actions_config.mock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
DEFAULT_MICROSOFT_EXCHANGE_URL,
1010
DEFAULT_MICROSOFT_GRAPH_API_SCOPE,
1111
DEFAULT_MICROSOFT_GRAPH_API_URL,
12+
DEFAULT_EMAIL_BODY_LENGTH,
1213
} from '../common';
1314
import type { ActionsConfigurationUtilities } from './actions_config';
1415

@@ -45,6 +46,7 @@ const createActionsConfigMock = () => {
4546
}),
4647
getAwsSesConfig: jest.fn().mockReturnValue(null),
4748
getEnabledEmailServices: jest.fn().mockReturnValue(['*']),
49+
getMaxEmailBodyLength: jest.fn().mockReturnValue(DEFAULT_EMAIL_BODY_LENGTH),
4850
};
4951
return mocked;
5052
};

x-pack/platform/plugins/shared/actions/server/actions_config.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
DEFAULT_MICROSOFT_EXCHANGE_URL,
1212
DEFAULT_MICROSOFT_GRAPH_API_SCOPE,
1313
DEFAULT_MICROSOFT_GRAPH_API_URL,
14+
MAX_EMAIL_BODY_LENGTH,
15+
DEFAULT_EMAIL_BODY_LENGTH,
1416
} from '../common';
1517
import {
1618
getActionsConfigurationUtilities,
@@ -573,6 +575,52 @@ describe('validateEmailAddresses()', () => {
573575
});
574576
});
575577

578+
describe('getMaxEmailBodyLength() ', () => {
579+
function getAcu(maxEmailBodyLength?: number) {
580+
const actionsConfig =
581+
maxEmailBodyLength == null
582+
? defaultActionsConfig
583+
: {
584+
...defaultActionsConfig,
585+
email: {
586+
maximum_body_length: maxEmailBodyLength,
587+
},
588+
};
589+
590+
return getActionsConfigurationUtilities(actionsConfig);
591+
}
592+
593+
test('default value is as expected', async () => {
594+
const acu = getAcu();
595+
const actual = acu.getMaxEmailBodyLength();
596+
expect(actual).toBe(DEFAULT_EMAIL_BODY_LENGTH);
597+
});
598+
599+
test('value coerced to minimum of range if below', async () => {
600+
const acu = getAcu(-100);
601+
const actual = acu.getMaxEmailBodyLength();
602+
expect(actual).toBe(0);
603+
});
604+
605+
test('value of 0 accepted', async () => {
606+
const acu = getAcu(0);
607+
const actual = acu.getMaxEmailBodyLength();
608+
expect(actual).toBe(0);
609+
});
610+
611+
test('value within range is passed through', async () => {
612+
const acu = getAcu(100);
613+
const actual = acu.getMaxEmailBodyLength();
614+
expect(actual).toBe(100);
615+
});
616+
617+
test('value coerced to maximum of range if above', async () => {
618+
const acu = getAcu(MAX_EMAIL_BODY_LENGTH + 100);
619+
const actual = acu.getMaxEmailBodyLength();
620+
expect(actual).toBe(MAX_EMAIL_BODY_LENGTH);
621+
});
622+
});
623+
576624
describe('getMaxAttempts()', () => {
577625
test('returns the maxAttempts defined in config', () => {
578626
const acu = getActionsConfigurationUtilities({

x-pack/platform/plugins/shared/actions/server/actions_config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ import { ActionTypeDisabledError } from './lib';
1818
import type { AwsSesConfig, ProxySettings, ResponseSettings, SSLSettings } from './types';
1919
import { getSSLSettingsFromConfig } from './lib/get_node_ssl_options';
2020
import type { ValidateEmailAddressesOptions } from '../common';
21-
import { validateEmailAddresses, invalidEmailsAsMessage } from '../common';
21+
import {
22+
validateEmailAddresses,
23+
invalidEmailsAsMessage,
24+
DEFAULT_EMAIL_BODY_LENGTH,
25+
MAX_EMAIL_BODY_LENGTH,
26+
} from '../common';
2227
export { AllowedHosts, EnabledActionTypes } from './config';
2328

2429
enum AllowListingField {
@@ -64,6 +69,7 @@ export interface ActionsConfigurationUtilities {
6469
};
6570
getAwsSesConfig: () => AwsSesConfig;
6671
getEnabledEmailServices: () => string[];
72+
getMaxEmailBodyLength: () => number;
6773
}
6874

6975
function allowListErrorMessage(field: AllowListingField, value: string) {
@@ -267,5 +273,10 @@ export function getActionsConfigurationUtilities(
267273

268274
return ['*'];
269275
},
276+
getMaxEmailBodyLength() {
277+
const configuredLength = config.email?.maximum_body_length ?? DEFAULT_EMAIL_BODY_LENGTH;
278+
const nonNegativeLength = Math.max(0, configuredLength);
279+
return Math.min(nonNegativeLength, MAX_EMAIL_BODY_LENGTH);
280+
},
270281
};
271282
}

x-pack/platform/plugins/shared/actions/server/config.test.ts

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* 2.0.
66
*/
77

8+
import { MAX_EMAIL_BODY_LENGTH } from '../common';
89
import type { ActionsConfig } from './config';
910
import { configSchema, getValidatedConfig } from './config';
1011
import type { Logger } from '@kbn/core/server';
@@ -227,11 +228,6 @@ describe('config validation', () => {
227228
let result = configSchema.validate(config);
228229
expect(result.email === undefined);
229230

230-
config.email = {};
231-
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
232-
`"[email]: email.domain_allowlist, email.recipient_allowlist, or email.services must be defined"`
233-
);
234-
235231
config.email = { domain_allowlist: [] };
236232
result = configSchema.validate(config);
237233
expect(result.email?.domain_allowlist).toEqual([]);
@@ -248,13 +244,6 @@ describe('config validation', () => {
248244
expect(result.email === undefined);
249245
});
250246

251-
test('validates empty email config', () => {
252-
config.email = {};
253-
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
254-
`"[email]: email.domain_allowlist, email.recipient_allowlist, or email.services must be defined"`
255-
);
256-
});
257-
258247
test('validates email config with recipient_allowlist = null', () => {
259248
config.email = { recipient_allowlist: null };
260249
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
@@ -283,6 +272,74 @@ describe('config validation', () => {
283272
});
284273
});
285274

275+
describe('email.maximum_body_length', () => {
276+
// note, this just tests the current behavior, but if we change so the
277+
// default was ALWAYS the defined default, it's fine to change it.
278+
test('validates that the value is undefined with no email parent', () => {
279+
const validatedConfig = configSchema.validate({});
280+
expect(validatedConfig.email?.maximum_body_length).toBe(undefined);
281+
});
282+
283+
test('validates with value 0', () => {
284+
const validatedConfig = configSchema.validate({
285+
email: {
286+
maximum_body_length: 0,
287+
},
288+
});
289+
expect(validatedConfig.email?.maximum_body_length).toBe(0);
290+
});
291+
292+
test('validates valid value set', () => {
293+
const validatedConfig = configSchema.validate({
294+
email: {
295+
maximum_body_length: 42,
296+
},
297+
});
298+
expect(validatedConfig.email?.maximum_body_length).toBe(42);
299+
});
300+
301+
test('throws error on negative value', () => {
302+
const config = {
303+
email: {
304+
maximum_body_length: -42,
305+
},
306+
};
307+
308+
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
309+
`"[email.maximum_body_length]: Value must be equal to or greater than [0]."`
310+
);
311+
});
312+
313+
test('logs warning when value is greater than max', () => {
314+
const config = {
315+
email: {
316+
maximum_body_length: MAX_EMAIL_BODY_LENGTH + 1,
317+
},
318+
};
319+
const validatedConfig = getValidatedConfig(mockLogger, configSchema.validate(config));
320+
321+
// value is still > max here, fixed in actionConfigUtils getMaxEmailBodyLength()
322+
expect(validatedConfig.email?.maximum_body_length).toBe(MAX_EMAIL_BODY_LENGTH + 1);
323+
expect(mockLogger.warn.mock.calls[0][0]).toBe(
324+
'The configuration xpack.actions.email.maximum_body_length value 25000001 is larger than the maximum setting of 25000000 and the maximum value will be used instead'
325+
);
326+
});
327+
328+
test('logs warning on zero value', () => {
329+
const config = {
330+
email: {
331+
maximum_body_length: 0,
332+
},
333+
};
334+
const validatedConfig = getValidatedConfig(mockLogger, configSchema.validate(config));
335+
336+
expect(validatedConfig.email?.maximum_body_length).toBe(0);
337+
expect(mockLogger.warn.mock.calls[0][0]).toBe(
338+
'The configuration xpack.actions.email.maximum_body_length is set to 0 and will result in sending empty emails'
339+
);
340+
});
341+
});
342+
286343
test('throws when domain_allowlist and recipient_allowlist are used at the same time', () => {
287344
const config: Record<string, unknown> = {};
288345
const result = configSchema.validate(config);
@@ -370,13 +427,6 @@ describe('config validation', () => {
370427
expect(configSchema.validate(config).email).toBe(undefined);
371428
});
372429

373-
test('validates empty email config', () => {
374-
config.email = {};
375-
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
376-
`"[email]: email.domain_allowlist, email.recipient_allowlist, or email.services must be defined"`
377-
);
378-
});
379-
380430
test('validates email config with empty services', () => {
381431
config.email = { services: {} };
382432
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(

x-pack/platform/plugins/shared/actions/server/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import {
1212
DEFAULT_MICROSOFT_EXCHANGE_URL,
1313
DEFAULT_MICROSOFT_GRAPH_API_SCOPE,
1414
DEFAULT_MICROSOFT_GRAPH_API_URL,
15+
DEFAULT_EMAIL_BODY_LENGTH,
16+
MAX_EMAIL_BODY_LENGTH,
1517
} from '../common';
18+
1619
import { validateDuration } from './lib/parse_date';
1720

1821
export enum AllowedHosts {
@@ -142,6 +145,9 @@ export const configSchema = schema.object({
142145
{
143146
domain_allowlist: schema.maybe(schema.arrayOf(schema.string())),
144147
recipient_allowlist: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
148+
maximum_body_length: schema.maybe(
149+
schema.number({ min: 0, defaultValue: DEFAULT_EMAIL_BODY_LENGTH })
150+
),
145151
services: schema.maybe(
146152
schema.object(
147153
{
@@ -253,6 +259,21 @@ export function getValidatedConfig(logger: Logger, originalConfig: ActionsConfig
253259
return tmp as ActionsConfig;
254260
}
255261

262+
if (originalConfig.email && originalConfig.email.maximum_body_length != null) {
263+
const emailMaximumBodyLength = originalConfig.email.maximum_body_length;
264+
if (emailMaximumBodyLength === 0) {
265+
logger.warn(
266+
`The configuration xpack.actions.email.maximum_body_length is set to 0 and will result in sending empty emails`
267+
);
268+
}
269+
270+
if (emailMaximumBodyLength > MAX_EMAIL_BODY_LENGTH) {
271+
logger.warn(
272+
`The configuration xpack.actions.email.maximum_body_length value ${emailMaximumBodyLength} is larger than the maximum setting of ${MAX_EMAIL_BODY_LENGTH} and the maximum value will be used instead`
273+
);
274+
}
275+
}
276+
256277
return originalConfig;
257278
}
258279

0 commit comments

Comments
 (0)