Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Monitoring] Added a case for Alerting if security/ssl is disabled #71846

Merged
merged 6 commits into from
Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion x-pack/plugins/monitoring/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"kibanaLegacy",
"triggers_actions_ui",
"alerts",
"actions"
"actions",
"encryptedSavedObjects"
],
"optionalPlugins": ["infra", "telemetryCollectionManager", "usageCollection", "home", "cloud"],
"server": true,
Expand Down
137 changes: 137 additions & 0 deletions x-pack/plugins/monitoring/public/alerts/lib/security_toasts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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 React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiSpacer, EuiLink, EuiCode, EuiText } from '@elastic/eui';
import { Legacy } from '../../legacy_shims';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';

export interface AlertingFrameworkHealth {
isSufficientlySecure: boolean;
hasPermanentEncryptionKey: boolean;
}

const showTlsAndEncryptionError = () => {
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = Legacy.shims.docLinks;

Legacy.shims.toastNotifications.addWarning({
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.healthCheck.tlsAndEncryptionErrorTitle"
defaultMessage="Additional setup required"
/>
),
text: toMountPoint(
<div>
<p>
{i18n.translate('xpack.monitoring.healthCheck.tlsAndEncryptionError', {
defaultMessage: `You must enable Transport Layer Security between Kibana and Elasticsearch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably want to modify this message slightly since the user didn't do any explicit actions to enable alerting and will probably have no context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! What do you think about:

Alerting features are not working, because your security is currently insufficient. Please enable Transport Layer Security between Kibana and Elasticsearch, and configure an encryption key in your kibana.yml file.

Any suggestions would be helpful

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure honestly. cc @ravikesarwani @lcawl @gchaps for help here

and configure an encryption key in your kibana.yml file to use the Alerting feature.`,
})}
</p>
<EuiSpacer />
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html#general-alert-action-settings`}
external
target="_blank"
>
{i18n.translate('xpack.monitoring.healthCheck.encryptionErrorAction', {
defaultMessage: 'Learn how.',
})}
</EuiLink>
</div>
),
});
};

const showEncryptionError = () => {
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = Legacy.shims.docLinks;

Legacy.shims.toastNotifications.addWarning(
{
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.healthCheck.encryptionErrorTitle"
defaultMessage="You must set an encryption key"
/>
),
text: toMountPoint(
<div role="banner">
{i18n.translate('xpack.monitoring.healthCheck.encryptionErrorBeforeKey', {
defaultMessage: 'To create an alert, set a value for ',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, like before, the user will not understand what this means because they aren't consciously enabling or interacting with alerts

Copy link
Contributor Author

@igoristic igoristic Jul 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you're right!

Wondering if we should just use one generic message from https://github.com/elastic/kibana/pull/71846/files#r455776054 if any of the two requirements (isSufficientlySecure, and hasPermanentEncryptionKey) are false?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I'm not sure what we want to say here. Something like "out of the box alerts require x, y, z. See {link} on how to configure this". Honestly, I'd default to @ravikesarwani @lcawl @gchaps for help here on the messaging

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worked with @lcawl on the text. Does this work:

Alerting requires Transport Layer Security between Kibana and Elasticsearch and an encryption key in your kibana.yml file. Learn how to enable TLS.

where "Lean how to enable TLS" links to the documentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think that works. i'll open a new PR for this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #72310

})}
<EuiText size="xs">
<EuiCode>{'xpack.encryptedSavedObjects.encryptionKey'}</EuiCode>
</EuiText>
{i18n.translate('xpack.monitoring.healthCheck.encryptionErrorAfterKey', {
defaultMessage: ' in your kibana.yml file. ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html#general-alert-action-settings`}
external
target="_blank"
>
{i18n.translate('xpack.monitoring.healthCheck.encryptionErrorAction', {
defaultMessage: 'Learn how.',
})}
</EuiLink>
</div>
),
},
{}
);
};

const showTlsError = () => {
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = Legacy.shims.docLinks;

Legacy.shims.toastNotifications.addWarning({
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.healthCheck.tlsErrorTitle"
defaultMessage="You must enable Transport Layer Security"
/>
),
text: toMountPoint(
<div role="banner">
{i18n.translate('xpack.monitoring.healthCheck.tlsError', {
defaultMessage:
'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`}
external
target="_blank"
>
{i18n.translate('xpack.monitoring.healthCheck.tlsErrorAction', {
defaultMessage: 'Learn how to enable TLS.',
})}
</EuiLink>
</div>
),
});
};

export const showSecurityToast = (alertingHealth: AlertingFrameworkHealth) => {
const { isSufficientlySecure, hasPermanentEncryptionKey } = alertingHealth;
if (
Array.isArray(alertingHealth) ||
(!alertingHealth.hasOwnProperty('isSufficientlySecure') &&
!alertingHealth.hasOwnProperty('hasPermanentEncryptionKey'))
) {
return;
}

if (!isSufficientlySecure && !hasPermanentEncryptionKey) {
showTlsAndEncryptionError();
} else if (!isSufficientlySecure) {
showTlsError();
} else if (!hasPermanentEncryptionKey) {
showEncryptionError();
}
};
4 changes: 3 additions & 1 deletion x-pack/plugins/monitoring/public/services/clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { ajaxErrorHandlersProvider } from '../lib/ajax_error_handler';
import { Legacy } from '../legacy_shims';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants';
import { showSecurityToast } from '../alerts/lib/security_toasts';

function formatClusters(clusters) {
return clusters.map(formatCluster);
Expand Down Expand Up @@ -66,7 +67,8 @@ export function monitoringClustersProvider($injector) {
return getClusters().then((clusters) => {
if (clusters.length) {
return ensureAlertsEnabled()
.then(() => {
.then(({ data }) => {
showSecurityToast(data);
once = true;
return clusters;
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { RequestHandlerContext } from 'kibana/server';
import { EncryptedSavedObjectsPluginSetup } from '../../../../encrypted_saved_objects/server';

export interface AlertingFrameworkHealth {
isSufficientlySecure: boolean;
hasPermanentEncryptionKey: boolean;
}

export interface XPackUsageSecurity {
security?: {
enabled?: boolean;
ssl?: {
http?: {
enabled?: boolean;
};
};
};
}

export class AlertingSecurity {
public static readonly getSecurityHealth = async (
context: RequestHandlerContext,
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
): Promise<AlertingFrameworkHealth> => {
const {
security: {
enabled: isSecurityEnabled = false,
ssl: { http: { enabled: isTLSEnabled = false } = {} } = {},
} = {},
}: XPackUsageSecurity = await context.core.elasticsearch.legacy.client.callAsInternalUser(
'transport.request',
{
method: 'GET',
path: '/_xpack/usage',
}
);

return {
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
};
};
}
1 change: 1 addition & 0 deletions x-pack/plugins/monitoring/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export class Plugin {
requireUIRoutes(this.monitoringCore, {
router,
licenseService: this.licenseService,
encryptedSavedObjects: plugins.encryptedSavedObjects,
});
initInfraSource(config, plugins.infra);
}
Expand Down
25 changes: 21 additions & 4 deletions x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,37 @@ import { AlertsFactory } from '../../../../alerts';
import { RouteDependencies } from '../../../../types';
import { ALERT_ACTION_TYPE_LOG } from '../../../../../common/constants';
import { ActionResult } from '../../../../../../actions/common';
// import { fetchDefaultEmailAddress } from '../../../../lib/alerts/fetch_default_email_address';
import { AlertingSecurity } from '../../../../lib/elasticsearch/verify_alerting_security';

const DEFAULT_SERVER_LOG_NAME = 'Monitoring: Write to Kibana log';

export function enableAlertsRoute(server: any, npRoute: RouteDependencies) {
export function enableAlertsRoute(_server: unknown, npRoute: RouteDependencies) {
npRoute.router.post(
{
path: '/api/monitoring/v1/alerts/enable',
options: { tags: ['access:monitoring'] },
validate: false,
},
async (context, request, response) => {
async (context, _request, response) => {
try {
const alerts = AlertsFactory.getAll().filter((a) => a.isEnabled(npRoute.licenseService));

if (alerts.length) {
const {
isSufficientlySecure,
hasPermanentEncryptionKey,
} = await AlertingSecurity.getSecurityHealth(context, npRoute.encryptedSavedObjects);

if (!isSufficientlySecure || !hasPermanentEncryptionKey) {
return response.ok({
body: {
isSufficientlySecure,
hasPermanentEncryptionKey,
},
});
}
}

const alertsClient = context.alerting?.getAlertsClient();
const actionsClient = context.actions?.getActionsClient();
const types = context.actions?.listTypes();
Expand Down Expand Up @@ -58,7 +76,6 @@ export function enableAlertsRoute(server: any, npRoute: RouteDependencies) {
},
];

const alerts = AlertsFactory.getAll().filter((a) => a.isEnabled(npRoute.licenseService));
const createdAlerts = await Promise.all(
alerts.map(
async (alert) => await alert.createIfDoesNotExist(alertsClient, actionsClient, actions)
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/monitoring/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { InfraPluginSetup } from '../../infra/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server';
import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server';

export interface MonitoringLicenseService {
refresh: () => Promise<any>;
Expand All @@ -36,6 +37,7 @@ export interface LegacyAPI {
}

export interface PluginsSetup {
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
telemetryCollectionManager?: TelemetryCollectionManagerPluginSetup;
usageCollection?: UsageCollectionSetup;
licensing: LicensingPluginSetup;
Expand All @@ -56,6 +58,7 @@ export interface MonitoringCoreConfig {
export interface RouteDependencies {
router: IRouter;
licenseService: MonitoringLicenseService;
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
}

export interface MonitoringCore {
Expand Down