[Response Ops][Connectors][Email] xpack.actions.email.services.ses.host/port kibana config (#221389)

## Summary

Closes https://github.com/elastic/kibana/issues/220286

## Release note

New AWS SES Email configuration options
`xpack.actions.email.services.ses.host` and
`xpack.actions.email.services.ses.port`.

---------

Co-authored-by: Lisa Cawley <lcawley@elastic.co>
This commit is contained in:
Julian Gernun 2025-06-12 17:38:08 +02:00 committed by GitHub
parent 52e3d1f228
commit f5b6aa241f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 533 additions and 25 deletions

View file

@ -191,6 +191,7 @@ enabled:
- x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/config.ts
- x-pack/test/functional_with_es_ssl/apps/embeddable_alerts_table/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors/with_aws_ses_kibana_config/config.ts
- x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/shared/config.ts
- x-pack/test/functional/apps/advanced_settings/config.ts
- x-pack/test/functional/apps/aiops/config.ts

View file

@ -134,6 +134,22 @@ $$$action-config-email-domain-allowlist$$$
Data type: `string`
`xpack.actions.email.services.ses.host` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
: The SMTP endpoint for an Amazon Simple Email Service (SES) service provider that can be used by email connectors.
::::{warning}
This setting alone is insufficient for overriding system defaults for the SES SMTP endpoint. You must also configure the `xpack.actions.email.services.ses.port` setting
::::
Data type: `string`
Default: `email-smtp.us-east-1.amazonaws.com`
`xpack.actions.email.services.ses.port` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
: The port number for an Amazon Simple Email Service (SES) service provider that can be used by email connectors.
Data type: `int`
Default: `465`
`xpack.actions.enableFooterInEmail` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}")
: A boolean value indicating that a footer with a relevant link should be added to emails sent as alerting actions.

View file

@ -281,6 +281,50 @@ groups:
self: all
ess: all
# example: |
- setting: xpack.actions.email.services.ses.host
id: action-config-email-services-ses-host
description: |
The SMTP endpoint for an Amazon Simple Email Service (SES) service provider that can be used by email connectors.
# state: deprecated/hidden/tech-preview
# deprecation_details: ""
# note: ""
# tip: ""
warning: "This setting alone is insufficient for overriding system defaults for the SES SMTP endpoint. You must also configure the `xpack.actions.email.services.ses.port` setting"
# important: ""
datatype: string
default: "email-smtp.us-east-1.amazonaws.com"
# options:
# - option:
# description: ""
# type: static/dynamic
applies_to:
deployment:
self: all
ess: all
# example: |
- setting: xpack.actions.email.services.ses.port
id: action-config-email-services-ses-port
description: |
The port number for an Amazon Simple Email Service (SES) service provider that can be used by email connectors.
# state: deprecated/hidden/tech-preview
# deprecation_details: ""
# note: ""
# tip: ""
# warning: ""
# important: ""
datatype: int
default: 465
# options:
# - option:
# description: ""
# type: static/dynamic
applies_to:
deployment:
self: all
ess: all
# example: |
- setting: xpack.actions.enableFooterInEmail
# id:

View file

@ -212,6 +212,8 @@ kibana_vars=(
xpack.actions.allowedHosts
xpack.actions.customHostSettings
xpack.actions.email.domain_allowlist
xpack.actions.email.services.ses.host
xpack.actions.email.services.ses.port
xpack.actions.enableFooterInEmail
xpack.actions.enabledActionTypes
xpack.actions.maxResponseContentLength

View file

@ -202,7 +202,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'vis_type_vislib.readOnly (boolean?|never)',
'vis_type_xy.readOnly (boolean?|never)',
'vis_type_vega.enableExternalUrls (boolean?)',
'xpack.actions.email.domain_allowlist (array)',
'xpack.actions.email.domain_allowlist (array?)',
'xpack.apm.serviceMapEnabled (boolean?)',
'xpack.apm.ui.enabled (boolean?)',
'xpack.apm.ui.maxTraceItems (number?)',

View file

@ -36,6 +36,7 @@ const createActionsConfigMock = () => {
getMaxAttempts: jest.fn().mockReturnValue(3),
enableFooterInEmail: jest.fn().mockReturnValue(true),
getMaxQueued: jest.fn().mockReturnValue(1000),
getAwsSesConfig: jest.fn().mockReturnValue(null),
};
return mocked;
};

View file

@ -571,3 +571,34 @@ describe('getMaxQueued()', () => {
expect(max).toEqual(1000000);
});
});
describe('getAwsSesConfig()', () => {
test('returns null when no email config set', () => {
const acu = getActionsConfigurationUtilities(defaultActionsConfig);
expect(acu.getAwsSesConfig()).toEqual(null);
});
test('returns null when no email.services config set', () => {
const acu = getActionsConfigurationUtilities({ ...defaultActionsConfig, email: {} });
expect(acu.getAwsSesConfig()).toEqual(null);
});
test('returns config if set', () => {
const acu = getActionsConfigurationUtilities({
...defaultActionsConfig,
email: {
services: {
ses: {
host: 'https://email.us-east-1.amazonaws.com',
port: 1234,
},
},
},
});
expect(acu.getAwsSesConfig()).toEqual({
host: 'https://email.us-east-1.amazonaws.com',
port: 1234,
secure: true,
});
});
});

View file

@ -15,7 +15,7 @@ import type { ActionsConfig, CustomHostSettings } from './config';
import { AllowedHosts, EnabledActionTypes, DEFAULT_QUEUED_MAX } from './config';
import { getCanonicalCustomHostUrl } from './lib/custom_host_settings';
import { ActionTypeDisabledError } from './lib';
import type { ProxySettings, ResponseSettings, SSLSettings } from './types';
import type { AwsSesConfig, ProxySettings, ResponseSettings, SSLSettings } from './types';
import { getSSLSettingsFromConfig } from './lib/get_node_ssl_options';
import type { ValidateEmailAddressesOptions } from '../common';
import { validateEmailAddresses, invalidEmailsAsMessage } from '../common';
@ -55,6 +55,7 @@ export interface ActionsConfigurationUtilities {
): string | undefined;
enableFooterInEmail: () => boolean;
getMaxQueued: () => number;
getAwsSesConfig: () => AwsSesConfig;
}
function allowListErrorMessage(field: AllowListingField, value: string) {
@ -168,7 +169,7 @@ function validateEmails(
addresses: string[],
options: ValidateEmailAddressesOptions
): string | undefined {
if (config.email == null) {
if (config.email?.domain_allowlist == null) {
return;
}
@ -225,5 +226,16 @@ export function getActionsConfigurationUtilities(
},
enableFooterInEmail: () => config.enableFooterInEmail,
getMaxQueued: () => config.queued?.max || DEFAULT_QUEUED_MAX,
getAwsSesConfig: () => {
if (config.email?.services?.ses.host && config.email?.services?.ses.port) {
return {
host: config.email?.services?.ses.host,
port: config.email?.services?.ses.port,
secure: true,
};
}
return null;
},
};
}

View file

@ -238,7 +238,7 @@ describe('config validation', () => {
config.email = {};
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email.domain_allowlist]: expected value of type [array] but got [undefined]"`
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
);
config.email = { domain_allowlist: [] };
@ -249,6 +249,54 @@ describe('config validation', () => {
result = configSchema.validate(config);
expect(result.email?.domain_allowlist).toEqual(['a.com', 'b.c.com', 'd.e.f.com']);
});
describe('email.services.ses', () => {
const config: Record<string, unknown> = {};
test('validates no email config at all', () => {
expect(configSchema.validate(config).email).toBe(undefined);
});
test('validates empty email config', () => {
config.email = {};
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
);
});
test('validates email config with empty services', () => {
config.email = { services: {} };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
);
});
test('validates email config with empty ses service', () => {
config.email = { services: { ses: {} } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires either domain_allowlist or services.ses to be specified"`
);
});
test('validates ses config with host only', () => {
config.email = { services: { ses: { host: 'ses.host.com' } } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"`
);
});
test('validates ses config with port only', () => {
config.email = { services: { ses: { port: 1 } } };
expect(() => configSchema.validate(config)).toThrowErrorMatchingInlineSnapshot(
`"[email]: Email configuration requires both services.ses.host and services.ses.port to be specified"`
);
});
test('validates ses service', () => {
config.email = { services: { ses: { host: 'ses.host.com', port: 1 } } };
const result = configSchema.validate(config);
expect(result.email?.services?.ses).toEqual({ host: 'ses.host.com', port: 1 });
});
});
});
// object creator that ensures we can create a property named __proto__ on an

View file

@ -121,9 +121,30 @@ export const configSchema = schema.object({
microsoftGraphApiScope: schema.string({ defaultValue: DEFAULT_MICROSOFT_GRAPH_API_SCOPE }),
microsoftExchangeUrl: schema.string({ defaultValue: DEFAULT_MICROSOFT_EXCHANGE_URL }),
email: schema.maybe(
schema.object({
domain_allowlist: schema.arrayOf(schema.string()),
})
schema.object(
{
domain_allowlist: schema.maybe(schema.arrayOf(schema.string())),
services: schema.maybe(
schema.object({
ses: schema.object({
host: schema.maybe(schema.string({ minLength: 1 })),
port: schema.maybe(schema.number({ min: 1, max: 65535 })),
}),
})
),
},
{
validate: (obj) => {
if (!obj.domain_allowlist && !obj.services?.ses.host && !obj.services?.ses.port) {
return 'Email configuration requires either domain_allowlist or services.ses to be specified';
}
if (obj.services?.ses && (!obj.services.ses.host || !obj.services.ses.port)) {
return 'Email configuration requires both services.ses.host and services.ses.port to be specified';
}
},
}
)
),
run: schema.maybe(
schema.object({

View file

@ -35,7 +35,9 @@ const createSetupMock = () => {
getSubActionConnectorClass: jest.fn(),
getCaseConnectorClass: jest.fn(),
getActionsHealth: jest.fn(),
getActionsConfigurationUtilities: jest.fn(),
getActionsConfigurationUtilities: jest.fn().mockReturnValue({
getAwsSesConfig: jest.fn(),
}),
setEnabledConnectorTypes: jest.fn(),
isActionTypeEnabled: jest.fn(),
};

View file

@ -281,3 +281,9 @@ export interface ConnectorToken extends SavedObjectAttributes {
// This unallowlist should only contain connector types that require a request or API key for
// execution.
export const UNALLOWED_FOR_UNSECURE_EXECUTION_CONNECTOR_TYPE_IDS = ['.index'];
export type AwsSesConfig = {
host: string;
port: number;
secure: boolean;
} | null;

View file

@ -10,6 +10,7 @@ export enum AdditionalEmailServices {
ELASTIC_CLOUD = 'elastic_cloud',
EXCHANGE = 'exchange_server',
OTHER = 'other',
AWS_SES = 'ses',
}
export const INTERNAL_BASE_STACK_CONNECTORS_API_PATH = '/internal/stack_connectors';

View file

@ -1,10 +0,0 @@
/*
* 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.
*/
export interface StackConnectorsConfigType {
enableExperimental: string[];
}

View file

@ -8,11 +8,11 @@
import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public';
import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public';
import type { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public';
import type { ConfigSchema as StackConnectorsConfigType } from '../server/config';
import { registerConnectorTypes } from './connector_types';
import { ExperimentalFeaturesService } from './common/experimental_features_service';
import type { ExperimentalFeatures } from '../common/experimental_features';
import { parseExperimentalConfigValue } from '../common/experimental_features';
import type { StackConnectorsConfigType } from '../common/types';
export type Setup = void;
export type Start = void;

View file

@ -33,6 +33,7 @@ import type {
import { getConnectorType } from '.';
import type { ValidateEmailAddressesOptions } from '@kbn/actions-plugin/common';
import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/types';
import { AdditionalEmailServices } from '../../../common';
const sendEmailMock = sendEmail as jest.Mock;
@ -485,6 +486,23 @@ describe('params validation', () => {
treatMustacheTemplatesAsValid: true,
});
});
test('doesnt throws if both host and port do not match AWS SES config', () => {
expect(() => {
validateConfig(
connectorType,
{
service: AdditionalEmailServices.AWS_SES,
from: 'bob@example.com',
host: 'wrong-host',
port: 123,
secure: true,
hasAuth: true,
},
{ configurationUtilities }
);
}).not.toThrowError();
});
});
describe('execute()', () => {
@ -1260,6 +1278,144 @@ describe('execute()', () => {
]
`);
});
test('parameters are as expected when using ses service without ses kbn config', async () => {
const mockedActionsConfig = actionsConfigMock.create();
const customExecutorOptions: EmailConnectorTypeExecutorOptions = {
...executorOptions,
configurationUtilities: mockedActionsConfig,
config: {
...config,
service: 'ses',
hasAuth: false,
},
secrets: {
...secrets,
user: null,
password: null,
},
};
sendEmailMock.mockReset();
await connectorType.executor(customExecutorOptions);
expect(sendEmailMock.mock.calls[0][1].transport).toStrictEqual({
service: 'ses',
});
});
test('parameters are as expected when using ses service and ses kbn config', async () => {
const mockedActionsConfig = actionsConfigMock.create();
mockedActionsConfig.getAwsSesConfig = jest.fn().mockReturnValue({
host: 'aws-ses-host',
port: 5555,
secure: true,
});
const customExecutorOptions: EmailConnectorTypeExecutorOptions = {
...executorOptions,
configurationUtilities: mockedActionsConfig,
config: {
...config,
service: 'ses',
hasAuth: false,
},
secrets: {
...secrets,
user: null,
password: null,
},
};
sendEmailMock.mockReset();
await connectorType.executor(customExecutorOptions);
expect(sendEmailMock.mock.calls[0][1].transport).toStrictEqual({
host: 'aws-ses-host',
port: 5555,
secure: true,
});
});
});
describe('validateConfig AWS SES specific checks', () => {
const awsSesHost = 'email-smtp.us-east-1.amazonaws.com';
const awsSesPort = 465;
const awsSesConfig = {
host: awsSesHost,
port: awsSesPort,
secure: true,
};
let configUtilsWithSes: jest.Mocked<ActionsConfigurationUtilities>;
beforeEach(() => {
configUtilsWithSes = {
...actionsConfigMock.create(),
getAwsSesConfig: jest.fn(() => awsSesConfig),
} as unknown as jest.Mocked<ActionsConfigurationUtilities>;
});
test('throws if both host and port do not match AWS SES config', () => {
const config = {
service: AdditionalEmailServices.AWS_SES,
from: 'bob@example.com',
host: 'wrong-host',
port: 123,
secure: true,
hasAuth: true,
};
expect(() => {
validateConfig(connectorType, config, { configurationUtilities: configUtilsWithSes });
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [ses.host]/[ses.port] does not match with the configured AWS SES host/port combination"`
);
});
test('throws if host does not match AWS SES config', () => {
const config = {
service: AdditionalEmailServices.AWS_SES,
from: 'bob@example.com',
host: 'wrong-host',
port: awsSesPort,
secure: true,
hasAuth: true,
};
expect(() => {
validateConfig(connectorType, config, { configurationUtilities: configUtilsWithSes });
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [ses.host] does not match with the configured AWS SES host"`
);
});
test('throws if port does not match AWS SES config', () => {
const config = {
service: AdditionalEmailServices.AWS_SES,
from: 'bob@example.com',
host: awsSesHost,
port: 123,
secure: true,
hasAuth: true,
};
expect(() => {
validateConfig(connectorType, config, { configurationUtilities: configUtilsWithSes });
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [ses.port] does not match with the configured AWS SES port"`
);
});
test('throws if secure is not true for AWS SES', () => {
const config = {
service: AdditionalEmailServices.AWS_SES,
from: 'bob@example.com',
host: awsSesHost,
port: awsSesPort,
secure: false,
hasAuth: true,
};
expect(() => {
validateConfig(connectorType, config, { configurationUtilities: configUtilsWithSes });
}).toThrowErrorMatchingInlineSnapshot(
`"error validating action type config: [ses.secure] must be true for AWS SES"`
);
});
});
function validateEmailAddressesImpl(

View file

@ -81,6 +81,7 @@ function validateConfig(
) {
const config = configObject;
const { configurationUtilities } = validatorServices;
const awsSesConfig = configurationUtilities.getAwsSesConfig();
const emails = [config.from];
const invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails);
@ -106,6 +107,21 @@ function validateConfig(
if (config.tenantId == null) {
throw new Error('[tenantId] is required');
}
} else if (awsSesConfig && config.service === AdditionalEmailServices.AWS_SES) {
if (awsSesConfig.host !== config.host && awsSesConfig.port !== config.port) {
throw new Error(
'[ses.host]/[ses.port] does not match with the configured AWS SES host/port combination'
);
}
if (awsSesConfig.host !== config.host) {
throw new Error('[ses.host] does not match with the configured AWS SES host');
}
if (awsSesConfig.port !== config.port) {
throw new Error('[ses.port] does not match with the configured AWS SES port');
}
if (awsSesConfig.secure !== config.secure) {
throw new Error(`[ses.secure] must be ${awsSesConfig.secure} for AWS SES`);
}
} else if (CUSTOM_HOST_PORT_SERVICES.indexOf(config.service) >= 0) {
// If configured `service` requires custom host/port/secure settings, validate that they are set
if (config.host == null && config.port == null) {
@ -297,6 +313,7 @@ async function executor(
connectorUsageCollector,
} = execOptions;
const connectorTokenClient = services.connectorTokenClient;
const awsSesConfig = configurationUtilities.getAwsSesConfig();
const emails = params.to.concat(params.cc).concat(params.bcc);
let invalidEmailsMessage = configurationUtilities.validateEmailAddresses(emails);
@ -348,6 +365,10 @@ async function executor(
if (config.oauthTokenUrl !== null) {
transport.oauthTokenUrl = config.oauthTokenUrl;
}
} else if (awsSesConfig && config.service === AdditionalEmailServices.AWS_SES) {
transport.host = awsSesConfig.host;
transport.port = awsSesConfig.port;
transport.secure = awsSesConfig.secure;
} else if (CUSTOM_HOST_PORT_SERVICES.indexOf(config.service) >= 0) {
// use configured host/port/secure values
// already validated service or host/port is not null ...

View file

@ -11,7 +11,7 @@ import { registerConnectorTypes } from './connector_types';
import { validSlackApiChannelsRoute, getWellKnownEmailServiceRoute } from './routes';
import type { ExperimentalFeatures } from '../common/experimental_features';
import { parseExperimentalConfigValue } from '../common/experimental_features';
import type { StackConnectorsConfigType } from '../common/types';
import type { ConfigSchema as StackConnectorsConfigType } from './config';
export interface ConnectorsPluginsSetup {
actions: ActionsPluginSetupContract;
}
@ -37,7 +37,8 @@ export class StackConnectorsPlugin
const router = core.http.createRouter();
const { actions } = plugins;
getWellKnownEmailServiceRoute(router);
const awsSesConfig = actions.getActionsConfigurationUtilities().getAwsSesConfig();
getWellKnownEmailServiceRoute(router, awsSesConfig);
validSlackApiChannelsRoute(router, actions.getActionsConfigurationUtilities(), this.logger);
registerConnectorTypes({

View file

@ -12,7 +12,7 @@ describe('getWellKnownEmailServiceRoute', () => {
it('returns config for well known email service', async () => {
const router = httpServiceMock.createRouter();
getWellKnownEmailServiceRoute(router);
getWellKnownEmailServiceRoute(router, null);
const [config, handler] = router.get.mock.calls[0];
expect(config.path).toMatchInlineSnapshot(
@ -37,7 +37,7 @@ describe('getWellKnownEmailServiceRoute', () => {
it('returns config for elastic cloud email service', async () => {
const router = httpServiceMock.createRouter();
getWellKnownEmailServiceRoute(router);
getWellKnownEmailServiceRoute(router, null);
const [config, handler] = router.get.mock.calls[0];
expect(config.path).toMatchInlineSnapshot(
@ -63,7 +63,7 @@ describe('getWellKnownEmailServiceRoute', () => {
it('returns empty for unknown service', async () => {
const router = httpServiceMock.createRouter();
getWellKnownEmailServiceRoute(router);
getWellKnownEmailServiceRoute(router, null);
const [config, handler] = router.get.mock.calls[0];
expect(config.path).toMatchInlineSnapshot(
@ -80,4 +80,29 @@ describe('getWellKnownEmailServiceRoute', () => {
body: {},
});
});
it('returns aws simple email service (ses) config if set', async () => {
const awsSesConfig = {
host: 'fake-email-smtp.us-east-1.amazonaws.com',
port: 1,
secure: true,
};
const router = httpServiceMock.createRouter();
getWellKnownEmailServiceRoute(router, awsSesConfig);
const [_, handler] = router.get.mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest({
params: { service: 'ses' },
});
await handler({}, mockRequest, mockResponse);
expect(mockResponse.ok).toHaveBeenCalledWith({
body: {
host: awsSesConfig.host,
port: awsSesConfig.port,
secure: true,
},
});
});
});

View file

@ -15,6 +15,7 @@ import type {
} from '@kbn/core/server';
import nodemailerGetService from 'nodemailer/lib/well-known';
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
import type { AwsSesConfig } from '@kbn/actions-plugin/server/types';
import { AdditionalEmailServices, INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../common';
import { ELASTIC_CLOUD_SERVICE } from '../connector_types/email';
@ -22,7 +23,7 @@ const paramSchema = schema.object({
service: schema.string(),
});
export const getWellKnownEmailServiceRoute = (router: IRouter) => {
export const getWellKnownEmailServiceRoute = (router: IRouter, awsSesConfig: AwsSesConfig) => {
router.get(
{
path: `${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_email_config/{service}`,
@ -53,6 +54,12 @@ export const getWellKnownEmailServiceRoute = (router: IRouter) => {
let response: SMTPConnection.Options = {};
if (service === AdditionalEmailServices.ELASTIC_CLOUD) {
response = ELASTIC_CLOUD_SERVICE;
} else if (awsSesConfig && service === AdditionalEmailServices.AWS_SES) {
response = {
host: awsSesConfig.host,
port: awsSesConfig.port,
secure: true,
};
} else {
const serviceEntry = nodemailerGetService(service);
if (serviceEntry) {

View file

@ -0,0 +1,33 @@
/*
* 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.
*/
export const awsSesConfig = {
host: 'email-fips.ca-central-1.amazonaws.com',
port: 25439,
};
import { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const baseConfig = await readConfigFile(require.resolve('../../../../config.base.ts'));
return {
...baseConfig.getAll(),
testFiles: [require.resolve('.')],
junit: {
reportName: 'Chrome X-Pack UI Functional Tests with ES SSL - AWS SES Kibana config',
},
kbnTestServer: {
...baseConfig.getAll().kbnTestServer,
serverArgs: [
...baseConfig.getAll().kbnTestServer.serverArgs,
`--xpack.actions.email.services.ses.host="${awsSesConfig.host}"`,
`--xpack.actions.email.services.ses.port=${awsSesConfig.port}`,
],
},
};
}

View file

@ -0,0 +1,33 @@
/*
* 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.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../ftr_provider_context';
import { awsSesConfig } from './config';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
describe('Email - with AWS SES Kibana config', () => {
beforeEach(async () => {
await pageObjects.common.navigateToApp('triggersActionsConnectors');
});
it('should use the kibana config for aws ses', async () => {
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
await testSubjects.click('.email-card');
await testSubjects.selectValue('emailServiceSelectInput', 'ses');
await testSubjects.waitForAttributeToChange('emailHostInput', 'value', awsSesConfig.host);
expect(await testSubjects.getAttribute('emailPortInput', 'value')).to.be(
awsSesConfig.port.toString()
);
expect(await testSubjects.getAttribute('emailSecureSwitch', 'aria-checked')).to.be('true');
});
});
};

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
import type { FtrProviderContext } from '../../../../../alerting_api_integration/common/ftr_provider_context';
import {
buildUp,
tearDown,
} from '../../../../../alerting_api_integration/spaces_only/tests/helpers';
export default function actionsTests({ loadTestFile, getService }: FtrProviderContext) {
describe('Connectors with AWS SES Kibana config', () => {
before(async () => buildUp(getService));
after(async () => tearDown(getService));
loadTestFile(require.resolve('./email'));
});
}

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
describe('Email', () => {
beforeEach(async () => {
await pageObjects.common.navigateToApp('triggersActionsConnectors');
});
it('should use the kibana config for aws ses defaults', async () => {
await new Promise((resolve) => setTimeout(resolve, 10000));
await pageObjects.triggersActionsUI.clickCreateConnectorButton();
await testSubjects.click('.email-card');
await testSubjects.selectValue('emailServiceSelectInput', 'ses');
await testSubjects.waitForAttributeToChange(
'emailHostInput',
'value',
'email-smtp.us-east-1.amazonaws.com'
);
expect(await testSubjects.getAttribute('emailPortInput', 'value')).to.be('465');
expect(await testSubjects.getAttribute('emailSecureSwitch', 'aria-checked')).to.be('true');
});
});
};

View file

@ -18,6 +18,7 @@ export default ({ loadTestFile }: FtrProviderContext) => {
loadTestFile(require.resolve('./rules_settings'));
loadTestFile(require.resolve('./stack_alerts_page'));
loadTestFile(require.resolve('./maintenance_windows'));
loadTestFile(require.resolve('./email'));
loadTestFile(require.resolve('./alert_deletion'));
});
};