Skip to content

Commit

Permalink
replace security callout with action prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
gmmorris committed Apr 7, 2020
1 parent 0b776a1 commit 61ee568
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 153 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface ActionGroup {

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

export const BASE_ALERT_API_PATH = '/api/alert';
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/server/routes/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function healthRoute(

const frameworkHealth: AlertingFrameworkHealth = {
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
isESOUsingEphemeralEncryptionKey: encryptedSavedObjects.usingEphemeralEncryptionKey,
hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
};

return res.ok({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* 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 { render } from '@testing-library/react';

import { HealthCheck } from './health_check';

import { act } from 'react-dom/test-utils';
import { httpServiceMock } from '../../../../../../src/core/public/mocks';

const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' };

const http = httpServiceMock.createStartContract();

describe('health check', () => {
test('renders spinner while health is loading', async () => {
http.get.mockImplementationOnce(() => new Promise(() => {}));

const { queryByText, container } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'shouldnt render'}</p>
</HealthCheck>
);
await act(async () => {
// wait for useEffect to run
});

expect(container.getElementsByClassName('euiLoadingSpinner').length).toBe(1);
expect(queryByText('shouldnt render')).not.toBeInTheDocument();
});

it('renders children if keys are enabled', async () => {
http.get.mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: true });

const { queryByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
);
await act(async () => {
// wait for useEffect to run
});
expect(queryByText('should render')).toBeInTheDocument();
});

test('renders warning if keys are disabled', async () => {
http.get.mockImplementationOnce(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: true,
}));

const { queryAllByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
);
await act(async () => {
// wait for useEffect to run
});

const [description, action] = queryAllByText(/TLS/i);

expect(description.textContent).toMatchInlineSnapshot(
`"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to enable TLS"`
);

expect(action.textContent).toMatchInlineSnapshot(`"enable TLS"`);

expect(action.getAttribute('href')).toMatchInlineSnapshot(
`"elastic.co/guide/en/kibana/current/configuring-tls.html"`
);
});

test('renders warning if encryption key is ephemeral', async () => {
http.get.mockImplementationOnce(async () => ({
isSufficientlySecure: true,
hasPermanentEncryptionKey: false,
}));

const { queryAllByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
);
await act(async () => {
// wait for useEffect to run
});

const [description, action] = queryAllByText(/Encryption/i);

expect(description.textContent).toMatchInlineSnapshot(
`"Alerting relies on API keys, which requires a permanent Encryption Key. Learn how to set a permanent Encryption Key"`
);

expect(action.textContent).toMatchInlineSnapshot(`"set a permanent Encryption Key"`);

expect(action.getAttribute('href')).toMatchInlineSnapshot(
`"elastic.co/guide/en/kibana/current/alert-action-settings-kb.html#general-alert-action-settings"`
);
});

test('renders warning if encryption key is ephemeral and keys are disabled', async () => {
http.get.mockImplementationOnce(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: false,
}));

const { queryAllByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
);
await act(async () => {
// wait for useEffect to run
});

const [description, action] = queryAllByText(/TLS/i);

expect(description.textContent).toMatchInlineSnapshot(
`"Alerting relies on API keys, which require TLS between Elasticsearch and Kibana, and a permanent Encryption Key. Learn how to enable TLS and a permanent Encryption Key"`
);

expect(action.textContent).toMatchInlineSnapshot(`"enable TLS and a permanent Encryption Key"`);

expect(action.getAttribute('href')).toMatchInlineSnapshot(
`"elastic.co/guide/en/kibana/current/alerting-getting-started.html#alerting-setup-prerequisites"`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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, { Fragment } from 'react';
import { Option, none, some, fold } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';

import { EuiLink, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { DocLinksStart, HttpSetup } from 'kibana/public';

import { AlertingFrameworkHealth } from '../../types';
import { health } from '../lib/alert_api';

import { ActionNeededPrompt } from './prompts/action_needed_prompt';

interface Props {
docLinks: Pick<DocLinksStart, 'ELASTIC_WEBSITE_URL' | 'DOC_LINK_VERSION'>;
http: HttpSetup;
}

export const HealthCheck: React.FunctionComponent<Props> = ({ docLinks, http, children }) => {
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;

const [alertingHealth, setAlertingHealth] = React.useState<Option<AlertingFrameworkHealth>>(none);

React.useEffect(() => {
(async function() {
setAlertingHealth(some(await health({ http })));
})();
}, [http]);

return pipe(
alertingHealth,
fold(
() => <EuiLoadingSpinner size="m" />,
healthCheck => {
return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? (
<Fragment>{children}</Fragment>
) : (
<ActionNeededPrompt>
{!healthCheck.isSufficientlySecure && !healthCheck.hasPermanentEncryptionKey ? (
<p role="banner">
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError',
{
defaultMessage:
'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana, and a permanent Encryption Key. Learn how to ',
}
)}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alerting-getting-started.html#alerting-setup-prerequisites`}
external
target="_blank"
>
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction',
{
defaultMessage: 'enable TLS and a permanent Encryption Key',
}
)}
</EuiLink>
</p>
) : !healthCheck.hasPermanentEncryptionKey ? (
<p role="banner">
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.encryptionError', {
defaultMessage:
'Alerting relies on API keys, which requires a permanent Encryption Key. Learn how to ',
})}
<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.triggersActionsUI.components.healthCheck.encryptionErrorAction',
{
defaultMessage: 'set a permanent Encryption Key',
}
)}
</EuiLink>
</p>
) : (
<p role="banner">
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsError', {
defaultMessage:
'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. Learn how to ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`}
external
target="_blank"
>
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsErrorAction', {
defaultMessage: 'enable TLS',
})}
</EuiLink>
</p>
)}
</ActionNeededPrompt>
);
}
)
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { FormattedMessage } from '@kbn/i18n/react';
import React, { FunctionComponent } from 'react';
import { EuiEmptyPrompt } from '@elastic/eui';

export const ActionNeededPrompt: FunctionComponent = ({ children }) => (
<EuiEmptyPrompt
iconType="watchesApp"
data-test-subj="createFirstAlertEmptyPrompt"
title={
<h2>
<FormattedMessage
id="xpack.triggersActionsUI.sections.actionNeededPrompt.title"
defaultMessage="Action needed"
/>
</h2>
}
body={children}
/>
);

This file was deleted.

Loading

0 comments on commit 61ee568

Please sign in to comment.