From 64b8b88c649506a465d775b56cb2e16b7e217c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 14 Aug 2020 23:12:01 +0200 Subject: [PATCH 1/5] [ILM] TS conversion of Edit policy components (#74747) * [ILM] Convert node allocation component to TS and use hooks * [ILM] Fix jest tests * [ILM] Fix i18n check * [ILM] Implement code review suggestions * [ILM] Fix type check, docs link and button maxWidth in NodeAllocation component * Fix internaliation error * [ILM] Convert node details flyout to TS * [ILM] Fix useState declaration * [ILM] Fix useState declaration * [ILM] Fix jest test * [ILM] Change error message when unable to load node attributes * [ILM] Change error message when unable to load node details * [ILM] Delete a period in error callout * [ILM] Delete a period in error callout * [ILM] Convert node details flyout to TS * [ILM] Fix i18n check * [ILM] Fix useState declaration * [ILM] Fix useState declaration * [ILM] Fix jest test * [ILM] Change error message when unable to load node details * [ILM] Delete a period in error callout * [ILM] edit components * [ILM] Fix review suggestions Co-authored-by: Elastic Machine --- .../components/active_badge.tsx} | 0 .../cold_phase/cold_phase.container.js | 22 ----- .../components/cold_phase/index.js | 7 -- .../delete_phase/delete_phase.container.js | 21 ---- .../components/delete_phase/index.js | 7 -- .../{ => components}/form_errors.tsx | 0 .../hot_phase/hot_phase.container.js | 22 ----- .../edit_policy/components/hot_phase/index.js | 7 -- .../components/index.ts} | 9 +- .../components/learn_more_link.tsx | 2 +- .../{min_age_input.js => min_age_input.tsx} | 27 +++-- .../{node_allocation => }/node_allocation.tsx | 15 +-- .../node_attrs_details.tsx | 2 +- .../components/node_attrs_details/index.ts | 7 -- .../components/optional_label.tsx} | 0 .../components/phase_error_message.tsx} | 2 +- .../components/policy_json_flyout.js | 95 ------------------ .../components/policy_json_flyout.tsx | 98 +++++++++++++++++++ ...iority_input.js => set_priority_input.tsx} | 22 ++++- .../snapshot_policies.tsx | 2 +- .../components/snapshot_policies/index.ts | 7 -- .../components/warm_phase/index.js | 7 -- .../warm_phase/warm_phase.container.js | 22 ----- .../edit_policy/edit_policy.container.js | 4 + .../sections/edit_policy/edit_policy.js | 29 ++++-- .../cold_phase.js => phases/cold_phase.tsx} | 32 +++--- .../delete_phase.tsx} | 38 ++++--- .../hot_phase.js => phases/hot_phase.tsx} | 27 ++--- .../node_allocation => phases}/index.ts | 5 +- .../warm_phase.js => phases/warm_phase.tsx} | 34 ++++--- .../add_policy_to_template_confirm_modal.js | 2 +- .../public/application/store/actions/nodes.js | 3 - 32 files changed, 267 insertions(+), 310 deletions(-) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/active_badge.js => edit_policy/components/active_badge.tsx} (100%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{ => components}/form_errors.tsx (100%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/index.js => edit_policy/components/index.ts} (54%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{ => edit_policy}/components/learn_more_link.tsx (92%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{min_age_input.js => min_age_input.tsx} (91%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{node_allocation => }/node_allocation.tsx (93%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{node_attrs_details => }/node_attrs_details.tsx (97%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/optional_label.js => edit_policy/components/optional_label.tsx} (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/{components/phase_error_message.js => edit_policy/components/phase_error_message.tsx} (87%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.js create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{set_priority_input.js => set_priority_input.tsx} (80%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{snapshot_policies => }/snapshot_policies.tsx (98%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/snapshot_policies/index.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/index.js delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.container.js rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/cold_phase/cold_phase.js => phases/cold_phase.tsx} (92%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/delete_phase/delete_phase.js => phases/delete_phase.tsx} (88%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/hot_phase/hot_phase.js => phases/hot_phase.tsx} (96%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/node_allocation => phases}/index.ts (58%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/{components/warm_phase/warm_phase.js => phases/warm_phase.tsx} (95%) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/active_badge.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_badge.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/active_badge.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_badge.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js deleted file mode 100644 index d4605ceb43499..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.container.js +++ /dev/null @@ -1,22 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; - -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_COLD, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; -import { ColdPhase as PresentationComponent } from './cold_phase'; - -export const ColdPhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_COLD), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_COLD, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js deleted file mode 100644 index e0d70ceb57726..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { ColdPhase } from './cold_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js deleted file mode 100644 index 84bd17e3637e8..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.container.js +++ /dev/null @@ -1,21 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_DELETE, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; -import { DeletePhase as PresentationComponent } from './delete_phase'; - -export const DeletePhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_DELETE), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_DELETE, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js deleted file mode 100644 index 5f909ab2c0f79..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { DeletePhase } from './delete_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_errors.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js deleted file mode 100644 index 5f1451afdcc31..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.container.js +++ /dev/null @@ -1,22 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; - -import { getPhase } from '../../../../store/selectors'; -import { setPhaseData } from '../../../../store/actions'; -import { PHASE_HOT, PHASE_WARM, WARM_PHASE_ON_ROLLOVER } from '../../../../constants'; -import { HotPhase as PresentationComponent } from './hot_phase'; - -export const HotPhase = connect( - (state) => ({ - phaseData: getPhase(state, PHASE_HOT), - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_HOT, key, value), - setWarmPhaseOnRollover: (value) => setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js deleted file mode 100644 index 114e34c3ef4d0..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/index.js +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { HotPhase } from './hot_phase.container'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts similarity index 54% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index a2ae37780b9f9..e933c46e98491 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -5,6 +5,13 @@ */ export { ActiveBadge } from './active_badge'; +export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; -export { PhaseErrorMessage } from './phase_error_message'; +export { MinAgeInput } from './min_age_input'; +export { NodeAllocation } from './node_allocation'; +export { NodeAttrsDetails } from './node_attrs_details'; export { OptionalLabel } from './optional_label'; +export { PhaseErrorMessage } from './phase_error_message'; +export { PolicyJsonFlyout } from './policy_json_flyout'; +export { SetPriorityInput } from './set_priority_input'; +export { SnapshotPolicies } from './snapshot_policies'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx similarity index 92% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx index 623ff982438d7..5ada49b318018 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/learn_more_link.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/learn_more_link.tsx @@ -8,7 +8,7 @@ import React, { ReactNode } from 'react'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { createDocLink } from '../../services/documentation'; +import { createDocLink } from '../../../services/documentation'; interface Props { docPath: string; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx similarity index 91% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx index d90ad9378efd4..c9732f2311758 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx @@ -16,10 +16,10 @@ import { PHASE_COLD, PHASE_DELETE, } from '../../../constants'; -import { LearnMoreLink } from '../../components'; -import { ErrableFormRow } from '../form_errors'; +import { LearnMoreLink } from './learn_more_link'; +import { ErrableFormRow } from './form_errors'; -function getTimingLabelForPhase(phase) { +function getTimingLabelForPhase(phase: string) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. switch (phase) { case PHASE_WARM: @@ -39,7 +39,7 @@ function getTimingLabelForPhase(phase) { } } -function getUnitsAriaLabelForPhase(phase) { +function getUnitsAriaLabelForPhase(phase: string) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. switch (phase) { case PHASE_WARM: @@ -68,9 +68,24 @@ function getUnitsAriaLabelForPhase(phase) { } } -export const MinAgeInput = (props) => { - const { rolloverEnabled, errors, phaseData, phase, setPhaseData, isShowingErrors } = props; +interface Props { + rolloverEnabled: boolean; + errors: Record; + phase: string; + // TODO add types for phaseData and setPhaseData after policy is typed + phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; + isShowingErrors: boolean; +} +export const MinAgeInput: React.FunctionComponent = ({ + rolloverEnabled, + errors, + phaseData, + phase, + setPhaseData, + isShowingErrors, +}) => { let daysOptionLabel; let hoursOptionLabel; let minutesOptionLabel; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx similarity index 93% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx index 31261de45c743..576483a5ab9c2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx @@ -16,17 +16,18 @@ import { EuiButton, } from '@elastic/eui'; -import { PHASE_NODE_ATTRS } from '../../../../constants'; -import { LearnMoreLink } from '../../../components/learn_more_link'; -import { ErrableFormRow } from '../../form_errors'; -import { useLoadNodes } from '../../../../services/api'; -import { NodeAttrsDetails } from '../node_attrs_details'; +import { PHASE_NODE_ATTRS } from '../../../constants'; +import { LearnMoreLink } from './learn_more_link'; +import { ErrableFormRow } from './form_errors'; +import { useLoadNodes } from '../../../services/api'; +import { NodeAttrsDetails } from './node_attrs_details'; interface Props { phase: string; - setPhaseData: (dataKey: string, value: any) => void; - errors: any; + errors: Record; + // TODO add types for phaseData and setPhaseData after policy is typed phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; isShowingErrors: boolean; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx similarity index 97% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx index 6fcbd94dc5e9a..cd87cc324a414 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/node_attrs_details.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details.tsx @@ -20,7 +20,7 @@ import { EuiButton, } from '@elastic/eui'; -import { useLoadNodeDetails } from '../../../../services/api'; +import { useLoadNodeDetails } from '../../../services/api'; interface Props { close: () => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts deleted file mode 100644 index 056d2f2f600f3..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_attrs_details/index.ts +++ /dev/null @@ -1,7 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export { NodeAttrsDetails } from './node_attrs_details'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/optional_label.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/optional_label.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/optional_label.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/optional_label.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx similarity index 87% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx index 904ac7c25f2f9..750f68543f221 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/components/phase_error_message.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phase_error_message.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiBadge } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -export const PhaseErrorMessage = ({ isShowingErrors }) => { +export const PhaseErrorMessage = ({ isShowingErrors }: { isShowingErrors: boolean }) => { return isShowingErrors ? ( '}`; - const request = `${endpoint}\n${this.getEsJson(lifecycle)}`; - - return ( - - - -

- {policyName ? ( - - ) : ( - - )} -

-
-
- - - -

- -

-
- - - - - {request} - -
- - - - - - -
- ); - } -} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx new file mode 100644 index 0000000000000..aaf4aa6e6222d --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -0,0 +1,98 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +interface Props { + close: () => void; + // TODO add types for lifecycle after policy is typed + lifecycle: any; + policyName: string; +} + +export const PolicyJsonFlyout: React.FunctionComponent = ({ + close, + lifecycle, + policyName, +}) => { + // @ts-ignore until store is typed + const getEsJson = ({ phases }) => { + return JSON.stringify( + { + policy: { + phases, + }, + }, + null, + 2 + ); + }; + + const endpoint = `PUT _ilm/policy/${policyName || ''}`; + const request = `${endpoint}\n${getEsJson(lifecycle)}`; + + return ( + + + +

+ {policyName ? ( + + ) : ( + + )} +

+
+
+ + + +

+ +

+
+ + + + + {request} + +
+ + + + + + +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx similarity index 80% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx index bdcc1e23b4230..0034de85fce17 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx @@ -8,12 +8,26 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui'; import { PHASE_INDEX_PRIORITY } from '../../../constants'; -import { LearnMoreLink, OptionalLabel } from '../../components'; -import { ErrableFormRow } from '../form_errors'; -export const SetPriorityInput = (props) => { - const { errors, phaseData, phase, setPhaseData, isShowingErrors } = props; +import { LearnMoreLink } from './'; +import { OptionalLabel } from './'; +import { ErrableFormRow } from './'; +interface Props { + errors: Record; + // TODO add types for phaseData and setPhaseData after policy is typed + phase: string; + phaseData: any; + setPhaseData: (dataKey: string, value: any) => void; + isShowingErrors: boolean; +} +export const SetPriorityInput: React.FunctionComponent = ({ + errors, + phaseData, + phase, + setPhaseData, + isShowingErrors, +}) => { return ( ({ - phaseData: getPhase(state, PHASE_WARM), - hotPhaseRolloverEnabled: getPhase(state, PHASE_HOT)[PHASE_ROLLOVER_ENABLED], - }), - { - setPhaseData: (key, value) => setPhaseData(PHASE_WARM, key, value), - } -)(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js index 1c6ced8953211..e7f20a66d09f0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.container.js @@ -15,6 +15,7 @@ import { isPolicyListLoaded, getIsNewPolicy, getSelectedOriginalPolicyName, + getPhases, } from '../../store/selectors'; import { @@ -23,6 +24,7 @@ import { setSaveAsNewPolicy, saveLifecyclePolicy, fetchPolicies, + setPhaseData, } from '../../store/actions'; import { findFirstError } from '../../services/find_errors'; @@ -42,6 +44,7 @@ export const EditPolicy = connect( isPolicyListLoaded: isPolicyListLoaded(state), isNewPolicy: getIsNewPolicy(state), originalPolicyName: getSelectedOriginalPolicyName(state), + phases: getPhases(state), }; }, { @@ -50,5 +53,6 @@ export const EditPolicy = connect( setSaveAsNewPolicy, saveLifecyclePolicy, fetchPolicies, + setPhaseData, } )(PresentationComponent); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js index d9d8866a2e2cc..a29ecd07c5e45 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js @@ -33,17 +33,15 @@ import { PHASE_DELETE, PHASE_WARM, STRUCTURE_POLICY_NAME, + WARM_PHASE_ON_ROLLOVER, + PHASE_ROLLOVER_ENABLED, } from '../../constants'; import { toasts } from '../../services/notification'; import { findFirstError } from '../../services/find_errors'; -import { LearnMoreLink } from '../components'; -import { PolicyJsonFlyout } from './components/policy_json_flyout'; -import { ErrableFormRow } from './form_errors'; -import { HotPhase } from './components/hot_phase'; -import { WarmPhase } from './components/warm_phase'; -import { DeletePhase } from './components/delete_phase'; -import { ColdPhase } from './components/cold_phase'; +import { LearnMoreLink, PolicyJsonFlyout, ErrableFormRow } from './components'; + +import { HotPhase, WarmPhase, ColdPhase, DeletePhase } from './phases'; export class EditPolicy extends Component { static propTypes = { @@ -137,6 +135,8 @@ export class EditPolicy extends Component { isNewPolicy, lifecycle, originalPolicyName, + phases, + setPhaseData, } = this.props; const selectedPolicyName = selectedPolicy.name; const { isShowingErrors, isShowingPolicyJsonFlyout } = this.state; @@ -275,9 +275,13 @@ export class EditPolicy extends Component { setPhaseData(PHASE_HOT, key, value)} + phaseData={phases[PHASE_HOT]} + setWarmPhaseOnRollover={(value) => + setPhaseData(PHASE_WARM, WARM_PHASE_ON_ROLLOVER, value) + } /> @@ -285,6 +289,9 @@ export class EditPolicy extends Component { setPhaseData(PHASE_WARM, key, value)} + phaseData={phases[PHASE_WARM]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> @@ -292,6 +299,9 @@ export class EditPolicy extends Component { setPhaseData(PHASE_COLD, key, value)} + phaseData={phases[PHASE_COLD]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> @@ -300,6 +310,9 @@ export class EditPolicy extends Component { errors={errors[PHASE_DELETE]} isShowingErrors={isShowingErrors && !!findFirstError(errors[PHASE_DELETE], false)} getUrlForApp={this.props.getUrlForApp} + setPhaseData={(key, value) => setPhaseData(PHASE_DELETE, key, value)} + phaseData={phases[PHASE_DELETE]} + hotPhaseRolloverEnabled={phases[PHASE_HOT][PHASE_ROLLOVER_ENABLED]} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx similarity index 92% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx index 200bf0e767d9d..babbbf7638ebe 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/cold_phase/cold_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx @@ -5,7 +5,6 @@ */ import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -24,20 +23,27 @@ import { PHASE_ENABLED, PHASE_REPLICA_COUNT, PHASE_FREEZE_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, OptionalLabel } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { MinAgeInput } from '../min_age_input'; -import { NodeAllocation } from '../node_allocation'; -import { SetPriorityInput } from '../set_priority_input'; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + MinAgeInput, + NodeAllocation, + SetPriorityInput, +} from '../components'; -export class ColdPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; +} - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +export class ColdPhase extends PureComponent { render() { const { setPhaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx similarity index 88% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx index 2b12eec953e11..0143cc4af24e3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/delete_phase/delete_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx @@ -5,22 +5,35 @@ */ import React, { PureComponent, Fragment } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; -import { PHASE_DELETE, PHASE_ENABLED, PHASE_WAIT_FOR_SNAPSHOT_POLICY } from '../../../../constants'; -import { ActiveBadge, LearnMoreLink, OptionalLabel, PhaseErrorMessage } from '../../../components'; -import { MinAgeInput } from '../min_age_input'; -import { SnapshotPolicies } from '../snapshot_policies'; +import { PHASE_DELETE, PHASE_ENABLED, PHASE_WAIT_FOR_SNAPSHOT_POLICY } from '../../../constants'; +import { + ActiveBadge, + LearnMoreLink, + OptionalLabel, + PhaseErrorMessage, + MinAgeInput, + SnapshotPolicies, +} from '../components'; -export class DeletePhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; + getUrlForApp: ( + appId: string, + options?: { + path?: string; + absolute?: boolean; + } + ) => string; +} +export class DeletePhase extends PureComponent { render() { const { setPhaseData, @@ -28,6 +41,7 @@ export class DeletePhase extends PureComponent { errors, isShowingErrors, hotPhaseRolloverEnabled, + getUrlForApp, } = this.props; return ( @@ -123,7 +137,7 @@ export class DeletePhase extends PureComponent { setPhaseData(PHASE_WAIT_FOR_SNAPSHOT_POLICY, value)} - getUrlForApp={this.props.getUrlForApp} + getUrlForApp={getUrlForApp} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx similarity index 96% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx index b420442198712..dbd48f3a85634 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/hot_phase/hot_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx @@ -5,7 +5,6 @@ */ import React, { Fragment, PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -28,18 +27,24 @@ import { PHASE_ROLLOVER_MAX_SIZE_STORED, PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS, PHASE_ROLLOVER_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { SetPriorityInput } from '../set_priority_input'; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + ErrableFormRow, + SetPriorityInput, +} from '../components'; -export class HotPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +interface Props { + errors: Record; + isShowingErrors: boolean; + phaseData: any; + setPhaseData: (key: string, value: any) => void; + setWarmPhaseOnRollover: (value: boolean) => void; +} +export class HotPhase extends PureComponent { render() { const { setPhaseData, phaseData, isShowingErrors, errors, setWarmPhaseOnRollover } = this.props; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts similarity index 58% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts index 4675ab46ee501..8d1ace5950497 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { NodeAllocation } from './node_allocation'; +export { HotPhase } from './hot_phase'; +export { WarmPhase } from './warm_phase'; +export { ColdPhase } from './cold_phase'; +export { DeletePhase } from './delete_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx similarity index 95% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx index 60b5ab4781b6d..6ed81bf8f45d5 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/warm_phase/warm_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx @@ -5,7 +5,6 @@ */ import React, { Fragment, PureComponent } from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -28,21 +27,26 @@ import { PHASE_PRIMARY_SHARD_COUNT, PHASE_REPLICA_COUNT, PHASE_SHRINK_ENABLED, -} from '../../../../constants'; -import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, OptionalLabel } from '../../../components'; -import { ErrableFormRow } from '../../form_errors'; -import { SetPriorityInput } from '../set_priority_input'; -import { NodeAllocation } from '../node_allocation'; -import { MinAgeInput } from '../min_age_input'; - -export class WarmPhase extends PureComponent { - static propTypes = { - setPhaseData: PropTypes.func.isRequired, - - isShowingErrors: PropTypes.bool.isRequired, - errors: PropTypes.object.isRequired, - }; +} from '../../../constants'; +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + SetPriorityInput, + NodeAllocation, + MinAgeInput, +} from '../components'; +interface Props { + setPhaseData: (key: string, value: any) => void; + phaseData: any; + isShowingErrors: boolean; + errors: Record; + hotPhaseRolloverEnabled: boolean; +} +export class WarmPhase extends PureComponent { render() { const { setPhaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js index 8e53569047d8f..47134ad097720 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/add_policy_to_template_confirm_modal.js @@ -23,7 +23,7 @@ import { import { toasts } from '../../../../services/notification'; import { addLifecyclePolicyToTemplate, loadIndexTemplates } from '../../../../services/api'; import { showApiError } from '../../../../services/api_errors'; -import { LearnMoreLink } from '../../../components/learn_more_link'; +import { LearnMoreLink } from '../../../edit_policy/components'; export class AddPolicyToTemplateConfirmModal extends Component { state = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js index 3f1c00db621a7..45a8e63f70e83 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/store/actions/nodes.js @@ -4,8 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ import { createAction } from 'redux-actions'; -import { SET_SELECTED_NODE_ATTRS } from '../../constants'; - -export const setSelectedNodeAttrs = createAction(SET_SELECTED_NODE_ATTRS); export const setSelectedPrimaryShardCount = createAction('SET_SELECTED_PRIMARY_SHARED_COUNT'); export const setSelectedReplicaCount = createAction('SET_SELECTED_REPLICA_COUNT'); From 52bd6d98ea567fd266429e77412722888b5c5ef9 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Fri, 14 Aug 2020 14:20:12 -0700 Subject: [PATCH 2/5] Actions add proxy support (#74289) * Added proxy support for action types * Fixed tests * added rejectUnauthorizedCertificates config setting * removed slack not used code * Fixed Slack proxy * fixed typecheck errors * Cleanup code * Fixed slack * Added unit tests * added proxy server for test * Fixed build * Added functional tests * fixed due to comments * Fixed tests and some changes due to comments * Fixed functional tests * fixed circular deps * Added proxy unit test to action type --- x-pack/package.json | 12 ++- .../server/builtin_action_types/case/types.ts | 8 +- .../server/builtin_action_types/case/utils.ts | 16 ++- .../server/builtin_action_types/email.test.ts | 2 + .../server/builtin_action_types/email.ts | 1 + .../server/builtin_action_types/index.ts | 6 +- .../server/builtin_action_types/jira/index.ts | 32 ++++-- .../builtin_action_types/jira/service.test.ts | 62 +++++++---- .../builtin_action_types/jira/service.ts | 19 +++- .../lib/axios_utils.test.ts | 101 ++++++++++++------ .../builtin_action_types/lib/axios_utils.ts | 46 +++++--- .../lib/get_proxy_agent.test.ts | 30 ++++++ .../lib/get_proxy_agent.ts | 31 ++++++ .../lib/post_pagerduty.ts | 26 +++-- .../lib/send_email.test.ts | 63 ++++++++++- .../builtin_action_types/lib/send_email.ts | 14 ++- .../server/builtin_action_types/pagerduty.ts | 3 +- .../builtin_action_types/resilient/index.ts | 32 ++++-- .../resilient/service.test.ts | 63 +++++++---- .../builtin_action_types/resilient/service.ts | 19 +++- .../builtin_action_types/servicenow/index.ts | 12 ++- .../servicenow/service.test.ts | 50 ++++++--- .../servicenow/service.ts | 21 +++- .../server/builtin_action_types/slack.test.ts | 45 +++++++- .../server/builtin_action_types/slack.ts | 25 ++++- .../builtin_action_types/webhook.test.ts | 81 ++++++++++++-- .../server/builtin_action_types/webhook.ts | 12 ++- x-pack/plugins/actions/server/config.test.ts | 3 + x-pack/plugins/actions/server/config.ts | 3 + .../actions/server/lib/action_executor.ts | 4 + x-pack/plugins/actions/server/plugin.test.ts | 8 +- x-pack/plugins/actions/server/plugin.ts | 17 ++- x-pack/plugins/actions/server/types.ts | 7 ++ .../alerting_api_integration/basic/config.ts | 1 + .../alerting_api_integration/common/config.ts | 9 ++ .../common/lib/get_proxy_server.ts | 30 ++++++ .../security_and_spaces/config.ts | 1 + .../actions/builtin_action_types/jira.ts | 15 +++ .../actions/builtin_action_types/pagerduty.ts | 16 +++ .../actions/builtin_action_types/resilient.ts | 15 +++ .../builtin_action_types/servicenow.ts | 15 +++ .../actions/builtin_action_types/slack.ts | 12 +++ .../actions/builtin_action_types/webhook.ts | 14 ++- .../spaces_only/config.ts | 6 +- yarn.lock | 14 +++ 45 files changed, 827 insertions(+), 195 deletions(-) create mode 100644 x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts create mode 100644 x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts create mode 100644 x-pack/test/alerting_api_integration/common/lib/get_proxy_server.ts diff --git a/x-pack/package.json b/x-pack/package.json index 42fa74a3bc84e..57a0b88f8c2a5 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -44,9 +44,9 @@ "@storybook/addon-storyshots": "^5.3.19", "@storybook/react": "^5.3.19", "@storybook/theming": "^5.3.19", + "@testing-library/jest-dom": "^5.8.0", "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.2.1", - "@testing-library/jest-dom": "^5.8.0", "@types/angular": "^1.6.56", "@types/archiver": "^3.1.0", "@types/base64-js": "^1.2.5", @@ -72,8 +72,9 @@ "@types/gulp": "^4.0.6", "@types/hapi__wreck": "^15.0.1", "@types/he": "^1.1.1", - "@types/hoist-non-react-statics": "^3.3.1", "@types/history": "^4.7.3", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/http-proxy": "^1.17.4", "@types/jest": "^25.2.3", "@types/jest-specific-snapshot": "^0.5.4", "@types/joi": "^13.4.2", @@ -94,6 +95,7 @@ "@types/object-hash": "^1.3.0", "@types/papaparse": "^5.0.3", "@types/pngjs": "^3.3.2", + "@types/pretty-ms": "^5.0.0", "@types/prop-types": "^15.5.3", "@types/proper-lockfile": "^3.0.1", "@types/puppeteer": "^1.20.1", @@ -109,6 +111,7 @@ "@types/redux-actions": "^2.6.1", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", + "@types/stats-lite": "^2.2.0", "@types/styled-components": "^5.1.0", "@types/supertest": "^2.0.5", "@types/tar-fs": "^1.16.1", @@ -116,11 +119,9 @@ "@types/tinycolor2": "^1.4.1", "@types/use-resize-observer": "^6.0.0", "@types/uuid": "^3.4.4", + "@types/webpack-env": "^1.15.2", "@types/xml-crypto": "^1.4.0", "@types/xml2js": "^0.4.5", - "@types/stats-lite": "^2.2.0", - "@types/pretty-ms": "^5.0.0", - "@types/webpack-env": "^1.15.2", "@welldone-software/why-did-you-render": "^4.0.0", "abab": "^1.0.4", "autoprefixer": "^9.7.4", @@ -227,6 +228,7 @@ "@turf/circle": "6.0.1", "@turf/distance": "6.0.1", "@turf/helpers": "6.0.1", + "@types/http-proxy-agent": "^2.0.2", "angular": "^1.8.0", "angular-resource": "1.8.0", "angular-sanitize": "1.8.0", diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts index de96864d0b295..1030e3d9c5d8e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/types.ts @@ -9,6 +9,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { TypeOf } from '@kbn/config-schema'; +import { Logger } from '../../../../../../src/core/server'; import { ExternalIncidentServiceConfigurationSchema, @@ -122,7 +123,12 @@ export interface ExternalServiceApi { export interface CreateExternalServiceBasicArgs { api: ExternalServiceApi; - createExternalService: (credentials: ExternalServiceCredentials) => ExternalService; + createExternalService: ( + credentials: ExternalServiceCredentials, + logger: Logger, + proxySettings?: any + ) => ExternalService; + logger: Logger; } export interface CreateExternalServiceArgs extends CreateExternalServiceBasicArgs { diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts index 82dedb09c429e..d895bf386a367 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts @@ -67,6 +67,7 @@ export const mapParams = ( export const createConnectorExecutor = ({ api, createExternalService, + logger, }: CreateExternalServiceBasicArgs) => async ( execOptions: ActionTypeExecutorOptions< ExternalIncidentServiceConfiguration, @@ -83,10 +84,14 @@ export const createConnectorExecutor = ({ actionId, }; - const externalService = createExternalService({ - config, - secrets, - }); + const externalService = createExternalService( + { + config, + secrets, + }, + logger, + execOptions.proxySettings + ); if (!api[subAction]) { throw new Error('[Action][ExternalService] Unsupported subAction type.'); @@ -122,10 +127,11 @@ export const createConnector = ({ validate, createExternalService, validationSchema, + logger, }: CreateExternalServiceArgs) => { return ({ configurationUtilities, - executor = createConnectorExecutor({ api, createExternalService }), + executor = createConnectorExecutor({ api, createExternalService, logger }), }: CreateActionTypeArgs): ActionType => ({ ...config, validate: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 195f6db538ae5..62f369816d714 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -269,6 +269,7 @@ describe('execute()', () => { "message": "a message to you", "subject": "the subject", }, + "proxySettings": undefined, "routing": Object { "bcc": Array [ "jimmy@example.com", @@ -326,6 +327,7 @@ describe('execute()', () => { "message": "a message to you", "subject": "the subject", }, + "proxySettings": undefined, "routing": Object { "bcc": Array [ "jimmy@example.com", diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index a51a0432a01e0..e9dc4eea5dcfc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -184,6 +184,7 @@ async function executor( subject: params.subject, message: params.message, }, + proxySettings: execOptions.proxySettings, }; let result; diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts index 80a171cbe624d..3591e05fb3acf 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.ts @@ -31,9 +31,9 @@ export function registerBuiltInActionTypes({ actionTypeRegistry.register(getIndexActionType({ logger })); actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getServerLogActionType({ logger })); - actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); + actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getServiceNowActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getJiraActionType({ configurationUtilities })); - actionTypeRegistry.register(getResilientActionType({ configurationUtilities })); + actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities })); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts index a2d7bb5930a75..66be0bad02d7b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts @@ -4,21 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Logger } from '../../../../../../src/core/server'; import { createConnector } from '../case/utils'; +import { ActionType } from '../../types'; import { api } from './api'; import { config } from './config'; import { validate } from './validators'; import { createExternalService } from './service'; import { JiraSecretConfiguration, JiraPublicConfiguration } from './schema'; +import { ActionsConfigurationUtilities } from '../../actions_config'; -export const getActionType = createConnector({ - api, - config, - validate, - createExternalService, - validationSchema: { - config: JiraPublicConfiguration, - secrets: JiraSecretConfiguration, - }, -}); +export function getActionType({ + logger, + configurationUtilities, +}: { + logger: Logger; + configurationUtilities: ActionsConfigurationUtilities; +}): ActionType { + return createConnector({ + api, + config, + validate, + createExternalService, + validationSchema: { + config: JiraPublicConfiguration, + secrets: JiraSecretConfiguration, + }, + logger, + })({ configurationUtilities }); +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts index 3de3926b7d821..547595b4c183f 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts @@ -9,6 +9,9 @@ import axios from 'axios'; import { createExternalService } from './service'; import * as utils from '../lib/axios_utils'; import { ExternalService } from '../case/types'; +import { Logger } from '../../../../../../src/core/server'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); jest.mock('../lib/axios_utils', () => { @@ -26,10 +29,13 @@ describe('Jira service', () => { let service: ExternalService; beforeAll(() => { - service = createExternalService({ - config: { apiUrl: 'https://siem-kibana.atlassian.net', projectKey: 'CK' }, - secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, - }); + service = createExternalService( + { + config: { apiUrl: 'https://siem-kibana.atlassian.net', projectKey: 'CK' }, + secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, + }, + logger + ); }); beforeEach(() => { @@ -39,37 +45,49 @@ describe('Jira service', () => { describe('createExternalService', () => { test('throws without url', () => { expect(() => - createExternalService({ - config: { apiUrl: null, projectKey: 'CK' }, - secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, - }) + createExternalService( + { + config: { apiUrl: null, projectKey: 'CK' }, + secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, + }, + logger + ) ).toThrow(); }); test('throws without projectKey', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com', projectKey: null }, - secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, - }) + createExternalService( + { + config: { apiUrl: 'test.com', projectKey: null }, + secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, + }, + logger + ) ).toThrow(); }); test('throws without username', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com' }, - secrets: { apiToken: '', email: 'elastic@elastic.com' }, - }) + createExternalService( + { + config: { apiUrl: 'test.com' }, + secrets: { apiToken: '', email: 'elastic@elastic.com' }, + }, + logger + ) ).toThrow(); }); test('throws without password', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com' }, - secrets: { apiToken: '', email: undefined }, - }) + createExternalService( + { + config: { apiUrl: 'test.com' }, + secrets: { apiToken: '', email: undefined }, + }, + logger + ) ).toThrow(); }); }); @@ -92,6 +110,7 @@ describe('Jira service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1', + logger, }); }); @@ -146,6 +165,7 @@ describe('Jira service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue', + logger, method: 'post', data: { fields: { @@ -210,6 +230,7 @@ describe('Jira service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, + logger, method: 'put', url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1', data: { fields: { summary: 'title', description: 'desc' } }, @@ -272,6 +293,7 @@ describe('Jira service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, + logger, method: 'post', url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1/comment', data: { body: 'comment' }, diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts index 240b645c3a7dc..aec73cfb375ed 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts @@ -7,6 +7,7 @@ import axios from 'axios'; import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from '../case/types'; +import { Logger } from '../../../../../../src/core/server'; import { JiraPublicConfigurationType, JiraSecretConfigurationType, @@ -17,6 +18,7 @@ import { import * as i18n from './translations'; import { request, getErrorMessage } from '../lib/axios_utils'; +import { ProxySettings } from '../../types'; const VERSION = '2'; const BASE_URL = `rest/api/${VERSION}`; @@ -25,10 +27,11 @@ const COMMENT_URL = `comment`; const VIEW_INCIDENT_URL = `browse`; -export const createExternalService = ({ - config, - secrets, -}: ExternalServiceCredentials): ExternalService => { +export const createExternalService = ( + { config, secrets }: ExternalServiceCredentials, + logger: Logger, + proxySettings?: ProxySettings +): ExternalService => { const { apiUrl: url, projectKey } = config as JiraPublicConfigurationType; const { apiToken, email } = secrets as JiraSecretConfigurationType; @@ -55,6 +58,8 @@ export const createExternalService = ({ const res = await request({ axios: axiosInstance, url: `${incidentUrl}/${id}`, + logger, + proxySettings, }); const { fields, ...rest } = res.data; @@ -75,10 +80,12 @@ export const createExternalService = ({ const res = await request({ axios: axiosInstance, url: `${incidentUrl}`, + logger, method: 'post', data: { fields: { ...incident, project: { key: projectKey }, issuetype: { name: 'Task' } }, }, + proxySettings, }); const updatedIncident = await getIncident(res.data.id); @@ -102,7 +109,9 @@ export const createExternalService = ({ axios: axiosInstance, method: 'put', url: `${incidentUrl}/${incidentId}`, + logger, data: { fields: { ...incident } }, + proxySettings, }); const updatedIncident = await getIncident(incidentId); @@ -129,7 +138,9 @@ export const createExternalService = ({ axios: axiosInstance, method: 'post', url: getCommentsURL(incidentId), + logger, data: { body: comment.comment }, + proxySettings, }); return { diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts index 4a52ae60bcdda..844aa6d2de7ed 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts @@ -5,7 +5,11 @@ */ import axios from 'axios'; -import { addTimeZoneToDate, throwIfNotAlive, request, patch, getErrorMessage } from './axios_utils'; +import HttpProxyAgent from 'http-proxy-agent'; +import { Logger } from '../../../../../../src/core/server'; +import { addTimeZoneToDate, request, patch, getErrorMessage } from './axios_utils'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); const axiosMock = (axios as unknown) as jest.Mock; @@ -21,26 +25,6 @@ describe('addTimeZoneToDate', () => { }); }); -describe('throwIfNotAlive ', () => { - test('throws correctly when status is invalid', async () => { - expect(() => { - throwIfNotAlive(404, 'application/json'); - }).toThrow('Instance is not alive.'); - }); - - test('throws correctly when content is invalid', () => { - expect(() => { - throwIfNotAlive(200, 'application/html'); - }).toThrow('Instance is not alive.'); - }); - - test('do NOT throws with custom validStatusCodes', async () => { - expect(() => { - throwIfNotAlive(404, 'application/json', [404]); - }).not.toThrow('Instance is not alive.'); - }); -}); - describe('request', () => { beforeEach(() => { axiosMock.mockImplementation(() => ({ @@ -51,9 +35,22 @@ describe('request', () => { }); test('it fetch correctly with defaults', async () => { - const res = await request({ axios, url: '/test' }); + const res = await request({ + axios, + url: '/test', + logger, + }); - expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'get', data: {} }); + expect(axiosMock).toHaveBeenCalledWith('/test', { + method: 'get', + data: {}, + headers: undefined, + httpAgent: undefined, + httpsAgent: undefined, + params: undefined, + proxy: false, + validateStatus: undefined, + }); expect(res).toEqual({ status: 200, headers: { 'content-type': 'application/json' }, @@ -61,10 +58,27 @@ describe('request', () => { }); }); - test('it fetch correctly', async () => { - const res = await request({ axios, url: '/test', method: 'post', data: { id: '123' } }); + test('it have been called with proper proxy agent', async () => { + const res = await request({ + axios, + url: '/testProxy', + logger, + proxySettings: { + proxyUrl: 'http://localhost:1212', + rejectUnauthorizedCertificates: false, + }, + }); - expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'post', data: { id: '123' } }); + expect(axiosMock).toHaveBeenCalledWith('/testProxy', { + method: 'get', + data: {}, + headers: undefined, + httpAgent: new HttpProxyAgent('http://localhost:1212'), + httpsAgent: new HttpProxyAgent('http://localhost:1212'), + params: undefined, + proxy: false, + validateStatus: undefined, + }); expect(res).toEqual({ status: 200, headers: { 'content-type': 'application/json' }, @@ -72,14 +86,24 @@ describe('request', () => { }); }); - test('it throws correctly', async () => { - axiosMock.mockImplementation(() => ({ - status: 404, + test('it fetch correctly', async () => { + const res = await request({ axios, url: '/test', method: 'post', logger, data: { id: '123' } }); + + expect(axiosMock).toHaveBeenCalledWith('/test', { + method: 'post', + data: { id: '123' }, + headers: undefined, + httpAgent: undefined, + httpsAgent: undefined, + params: undefined, + proxy: false, + validateStatus: undefined, + }); + expect(res).toEqual({ + status: 200, headers: { 'content-type': 'application/json' }, data: { incidentId: '123' }, - })); - - await expect(request({ axios, url: '/test' })).rejects.toThrow(); + }); }); }); @@ -92,8 +116,17 @@ describe('patch', () => { }); test('it fetch correctly', async () => { - await patch({ axios, url: '/test', data: { id: '123' } }); - expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'patch', data: { id: '123' } }); + await patch({ axios, url: '/test', data: { id: '123' }, logger }); + expect(axiosMock).toHaveBeenCalledWith('/test', { + method: 'patch', + data: { id: '123' }, + headers: undefined, + httpAgent: undefined, + httpsAgent: undefined, + params: undefined, + proxy: false, + validateStatus: undefined, + }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts index d527cf632bace..e26a3b686179c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts @@ -4,50 +4,68 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AxiosInstance, Method, AxiosResponse } from 'axios'; - -export const throwIfNotAlive = ( - status: number, - contentType: string, - validStatusCodes: number[] = [200, 201, 204] -) => { - if (!validStatusCodes.includes(status) || !contentType.includes('application/json')) { - throw new Error('Instance is not alive.'); - } -}; +import { AxiosInstance, Method, AxiosResponse, AxiosBasicCredentials } from 'axios'; +import { Logger } from '../../../../../../src/core/server'; +import { ProxySettings } from '../../types'; +import { getProxyAgent } from './get_proxy_agent'; export const request = async ({ axios, url, + logger, method = 'get', data, params, + proxySettings, + headers, + validateStatus, + auth, }: { axios: AxiosInstance; url: string; + logger: Logger; method?: Method; data?: T; params?: unknown; + proxySettings?: ProxySettings; + headers?: Record | null; + validateStatus?: (status: number) => boolean; + auth?: AxiosBasicCredentials; }): Promise => { - const res = await axios(url, { method, data: data ?? {}, params }); - throwIfNotAlive(res.status, res.headers['content-type']); - return res; + return await axios(url, { + method, + data: data ?? {}, + params, + auth, + // use httpsAgent and embedded proxy: false, to be able to handle fail on invalid certs + httpsAgent: proxySettings ? getProxyAgent(proxySettings, logger) : undefined, + httpAgent: proxySettings ? getProxyAgent(proxySettings, logger) : undefined, + proxy: false, // the same way as it done for IncomingWebhook in + headers, + validateStatus, + }); }; export const patch = async ({ axios, url, data, + logger, + proxySettings, }: { axios: AxiosInstance; url: string; data: T; + logger: Logger; + proxySettings?: ProxySettings; }): Promise => { return request({ axios, url, + logger, method: 'patch', data, + proxySettings, }); }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts new file mode 100644 index 0000000000000..2468fab8c6ac5 --- /dev/null +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.test.ts @@ -0,0 +1,30 @@ +/* + * 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 HttpProxyAgent from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import { Logger } from '../../../../../../src/core/server'; +import { getProxyAgent } from './get_proxy_agent'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +const logger = loggingSystemMock.create().get() as jest.Mocked; + +describe('getProxyAgent', () => { + test('return HttpsProxyAgent for https proxy url', () => { + const agent = getProxyAgent( + { proxyUrl: 'https://someproxyhost', rejectUnauthorizedCertificates: false }, + logger + ); + expect(agent instanceof HttpsProxyAgent).toBeTruthy(); + }); + + test('return HttpProxyAgent for http proxy url', () => { + const agent = getProxyAgent( + { proxyUrl: 'http://someproxyhost', rejectUnauthorizedCertificates: false }, + logger + ); + expect(agent instanceof HttpProxyAgent).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts new file mode 100644 index 0000000000000..bb4dadd3a4698 --- /dev/null +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agent.ts @@ -0,0 +1,31 @@ +/* + * 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 HttpProxyAgent from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import { Logger } from '../../../../../../src/core/server'; +import { ProxySettings } from '../../types'; + +export function getProxyAgent( + proxySettings: ProxySettings, + logger: Logger +): HttpsProxyAgent | HttpProxyAgent { + logger.debug(`Create proxy agent for ${proxySettings.proxyUrl}.`); + + if (/^https/i.test(proxySettings.proxyUrl)) { + const proxyUrl = new URL(proxySettings.proxyUrl); + return new HttpsProxyAgent({ + host: proxyUrl.hostname, + port: Number(proxyUrl.port), + protocol: proxyUrl.protocol, + headers: proxySettings.proxyHeaders, + // do not fail on invalid certs if value is false + rejectUnauthorized: proxySettings.rejectUnauthorizedCertificates, + }); + } else { + return new HttpProxyAgent(proxySettings.proxyUrl); + } +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts index 92f88ebe0be22..d78237beb98a1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts @@ -5,22 +5,34 @@ */ import axios, { AxiosResponse } from 'axios'; -import { Services } from '../../types'; +import { Logger } from '../../../../../../src/core/server'; +import { Services, ProxySettings } from '../../types'; +import { request } from './axios_utils'; interface PostPagerdutyOptions { apiUrl: string; data: unknown; headers: Record; services: Services; + proxySettings?: ProxySettings; } // post an event to pagerduty -export async function postPagerduty(options: PostPagerdutyOptions): Promise { - const { apiUrl, data, headers } = options; - const axiosOptions = { +export async function postPagerduty( + options: PostPagerdutyOptions, + logger: Logger +): Promise { + const { apiUrl, data, headers, proxySettings } = options; + const axiosInstance = axios.create(); + + return await request({ + axios: axiosInstance, + url: apiUrl, + method: 'post', + logger, + data, + proxySettings, headers, validateStatus: () => true, - }; - - return axios.post(apiUrl, data, axiosOptions); + }); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index 3514bd4257b0f..8287ee944bca9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -12,6 +12,7 @@ import { Logger } from '../../../../../../src/core/server'; import { sendEmail } from './send_email'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import nodemailer from 'nodemailer'; +import { ProxySettings } from '../../types'; const createTransportMock = nodemailer.createTransport as jest.Mock; const sendMailMockResult = { result: 'does not matter' }; @@ -63,6 +64,59 @@ describe('send_email module', () => { }); test('handles unauthenticated email using not secure host/port', async () => { + const sendEmailOptions = getSendEmailOptions( + { + transport: { + host: 'example.com', + port: 1025, + }, + }, + { + proxyUrl: 'https://example.com', + rejectUnauthorizedCertificates: false, + } + ); + delete sendEmailOptions.transport.service; + delete sendEmailOptions.transport.user; + delete sendEmailOptions.transport.password; + const result = await sendEmail(mockLogger, sendEmailOptions); + expect(result).toBe(sendMailMockResult); + expect(createTransportMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "headers": undefined, + "host": "example.com", + "port": 1025, + "proxy": "https://example.com", + "secure": false, + "tls": Object { + "rejectUnauthorized": false, + }, + }, + ] + `); + expect(sendMailMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "bcc": Array [], + "cc": Array [ + "bob@example.com", + "robert@example.com", + ], + "from": "fred@example.com", + "html": "

a message

+ ", + "subject": "a subject", + "text": "a message", + "to": Array [ + "jim@example.com", + ], + }, + ] + `); + }); + + test('rejectUnauthorized default setting email using not secure host/port', async () => { const sendEmailOptions = getSendEmailOptions({ transport: { host: 'example.com', @@ -80,9 +134,6 @@ describe('send_email module', () => { "host": "example.com", "port": 1025, "secure": false, - "tls": Object { - "rejectUnauthorized": false, - }, }, ] `); @@ -161,7 +212,10 @@ describe('send_email module', () => { }); }); -function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {}) { +function getSendEmailOptions( + { content = {}, routing = {}, transport = {} } = {}, + proxySettings?: ProxySettings +) { return { content: { ...content, @@ -181,5 +235,6 @@ function getSendEmailOptions({ content = {}, routing = {}, transport = {} } = {} user: 'elastic', password: 'changeme', }, + proxySettings, }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index 869db34f034ae..a4f32f1880cb5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -6,10 +6,10 @@ // info on nodemailer: https://nodemailer.com/about/ import nodemailer from 'nodemailer'; - import { default as MarkdownIt } from 'markdown-it'; import { Logger } from '../../../../../../src/core/server'; +import { ProxySettings } from '../../types'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; @@ -18,6 +18,7 @@ export interface SendEmailOptions { transport: Transport; routing: Routing; content: Content; + proxySettings?: ProxySettings; } // config validation ensures either service is set or host/port are set @@ -44,7 +45,7 @@ export interface Content { // send an email export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { - const { transport, routing, content } = options; + const { transport, routing, content, proxySettings } = options; const { service, host, port, secure, user, password } = transport; const { from, to, cc, bcc } = routing; const { subject, message } = content; @@ -67,11 +68,16 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom transportConfig.host = host; transportConfig.port = port; transportConfig.secure = !!secure; - if (!transportConfig.secure) { + if (proxySettings && !transportConfig.secure) { transportConfig.tls = { - rejectUnauthorized: false, + // do not fail on invalid certs if value is false + rejectUnauthorized: proxySettings?.rejectUnauthorizedCertificates, }; } + if (proxySettings) { + transportConfig.proxy = proxySettings.proxyUrl; + transportConfig.headers = proxySettings.proxyHeaders; + } } const nodemailerTransport = nodemailer.createTransport(transportConfig); diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index b76e57419bc56..c0edfc530e738 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -161,6 +161,7 @@ async function executor( const secrets = execOptions.secrets; const params = execOptions.params; const services = execOptions.services; + const proxySettings = execOptions.proxySettings; const apiUrl = getPagerDutyApiUrl(config); const headers = { @@ -171,7 +172,7 @@ async function executor( let response; try { - response = await postPagerduty({ apiUrl, data, headers, services }); + response = await postPagerduty({ apiUrl, data, headers, services, proxySettings }, logger); } catch (err) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', { defaultMessage: 'error posting pagerduty event', diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts index e98bc71559d3f..1e9cb15589702 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Logger } from '../../../../../../src/core/server'; import { createConnector } from '../case/utils'; import { api } from './api'; @@ -11,14 +12,25 @@ import { config } from './config'; import { validate } from './validators'; import { createExternalService } from './service'; import { ResilientSecretConfiguration, ResilientPublicConfiguration } from './schema'; +import { ActionsConfigurationUtilities } from '../../actions_config'; +import { ActionType } from '../../types'; -export const getActionType = createConnector({ - api, - config, - validate, - createExternalService, - validationSchema: { - config: ResilientPublicConfiguration, - secrets: ResilientSecretConfiguration, - }, -}); +export function getActionType({ + logger, + configurationUtilities, +}: { + logger: Logger; + configurationUtilities: ActionsConfigurationUtilities; +}): ActionType { + return createConnector({ + api, + config, + validate, + createExternalService, + validationSchema: { + config: ResilientPublicConfiguration, + secrets: ResilientSecretConfiguration, + }, + logger, + })({ configurationUtilities }); +} diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts index 573885698014e..a9271671f68b9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts @@ -9,6 +9,9 @@ import axios from 'axios'; import { createExternalService, getValueTextContent, formatUpdateRequest } from './service'; import * as utils from '../lib/axios_utils'; import { ExternalService } from '../case/types'; +import { Logger } from '../../../../../../src/core/server'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); jest.mock('../lib/axios_utils', () => { @@ -72,10 +75,13 @@ describe('IBM Resilient service', () => { let service: ExternalService; beforeAll(() => { - service = createExternalService({ - config: { apiUrl: 'https://resilient.elastic.co', orgId: '201' }, - secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' }, - }); + service = createExternalService( + { + config: { apiUrl: 'https://resilient.elastic.co', orgId: '201' }, + secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' }, + }, + logger + ); }); afterAll(() => { @@ -138,37 +144,49 @@ describe('IBM Resilient service', () => { describe('createExternalService', () => { test('throws without url', () => { expect(() => - createExternalService({ - config: { apiUrl: null, orgId: '201' }, - secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, - }) + createExternalService( + { + config: { apiUrl: null, orgId: '201' }, + secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, + }, + logger + ) ).toThrow(); }); test('throws without orgId', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com', orgId: null }, - secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, - }) + createExternalService( + { + config: { apiUrl: 'test.com', orgId: null }, + secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, + }, + logger + ) ).toThrow(); }); test('throws without username', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com', orgId: '201' }, - secrets: { apiKeyId: '', apiKeySecret: 'secret' }, - }) + createExternalService( + { + config: { apiUrl: 'test.com', orgId: '201' }, + secrets: { apiKeyId: '', apiKeySecret: 'secret' }, + }, + logger + ) ).toThrow(); }); test('throws without password', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com', orgId: '201' }, - secrets: { apiKeyId: '', apiKeySecret: undefined }, - }) + createExternalService( + { + config: { apiUrl: 'test.com', orgId: '201' }, + secrets: { apiKeyId: '', apiKeySecret: undefined }, + }, + logger + ) ).toThrow(); }); }); @@ -197,6 +215,7 @@ describe('IBM Resilient service', () => { await service.getIncident('1'); expect(requestMock).toHaveBeenCalledWith({ axios, + logger, url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1', params: { text_content_output_format: 'objects_convert', @@ -256,6 +275,7 @@ describe('IBM Resilient service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, url: 'https://resilient.elastic.co/rest/orgs/201/incidents', + logger, method: 'post', data: { name: 'title', @@ -311,6 +331,7 @@ describe('IBM Resilient service', () => { // The second call to the API is the update call. expect(requestMock.mock.calls[1][0]).toEqual({ axios, + logger, method: 'patch', url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1', data: { @@ -392,7 +413,9 @@ describe('IBM Resilient service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, + logger, method: 'post', + proxySettings: undefined, url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1/comments', data: { text: { diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts index 8d0526ca3b571..b2150081f2c89 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts @@ -6,6 +6,7 @@ import axios from 'axios'; +import { Logger } from '../../../../../../src/core/server'; import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from '../case/types'; import { ResilientPublicConfigurationType, @@ -19,6 +20,7 @@ import { import * as i18n from './translations'; import { getErrorMessage, request } from '../lib/axios_utils'; +import { ProxySettings } from '../../types'; const BASE_URL = `rest`; const INCIDENT_URL = `incidents`; @@ -57,10 +59,11 @@ export const formatUpdateRequest = ({ }; }; -export const createExternalService = ({ - config, - secrets, -}: ExternalServiceCredentials): ExternalService => { +export const createExternalService = ( + { config, secrets }: ExternalServiceCredentials, + logger: Logger, + proxySettings?: ProxySettings +): ExternalService => { const { apiUrl: url, orgId } = config as ResilientPublicConfigurationType; const { apiKeyId, apiKeySecret } = secrets as ResilientSecretConfigurationType; @@ -88,9 +91,11 @@ export const createExternalService = ({ const res = await request({ axios: axiosInstance, url: `${incidentUrl}/${id}`, + logger, params: { text_content_output_format: 'objects_convert', }, + proxySettings, }); return { ...res.data, description: res.data.description?.content ?? '' }; @@ -107,6 +112,7 @@ export const createExternalService = ({ axios: axiosInstance, url: `${incidentUrl}`, method: 'post', + logger, data: { ...incident, description: { @@ -115,6 +121,7 @@ export const createExternalService = ({ }, discovered_date: Date.now(), }, + proxySettings, }); return { @@ -139,7 +146,9 @@ export const createExternalService = ({ axios: axiosInstance, method: 'patch', url: `${incidentUrl}/${incidentId}`, + logger, data, + proxySettings, }); if (!res.data.success) { @@ -170,7 +179,9 @@ export const createExternalService = ({ axios: axiosInstance, method: 'post', url: getCommentsURL(incidentId), + logger, data: { text: { format: 'text', content: comment.comment } }, + proxySettings, }); return { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index 109008b8fc9fb..3addbe7c54dac 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -76,10 +76,14 @@ async function executor( const { subAction, subActionParams } = params; let data: PushToServiceResponse | null = null; - const externalService = createExternalService({ - config, - secrets, - }); + const externalService = createExternalService( + { + config, + secrets, + }, + logger, + execOptions.proxySettings + ); if (!api[subAction]) { const errorMessage = `[Action][ExternalService] Unsupported subAction type ${subAction}.`; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts index 07d60ec9f7a05..2adcdf561ce17 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts @@ -9,6 +9,9 @@ import axios from 'axios'; import { createExternalService } from './service'; import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; +import { Logger } from '../../../../../../src/core/server'; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); jest.mock('../lib/axios_utils', () => { @@ -28,10 +31,13 @@ describe('ServiceNow service', () => { let service: ExternalService; beforeAll(() => { - service = createExternalService({ - config: { apiUrl: 'https://dev102283.service-now.com' }, - secrets: { username: 'admin', password: 'admin' }, - }); + service = createExternalService( + { + config: { apiUrl: 'https://dev102283.service-now.com' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger + ); }); beforeEach(() => { @@ -41,28 +47,37 @@ describe('ServiceNow service', () => { describe('createExternalService', () => { test('throws without url', () => { expect(() => - createExternalService({ - config: { apiUrl: null }, - secrets: { username: 'admin', password: 'admin' }, - }) + createExternalService( + { + config: { apiUrl: null }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger + ) ).toThrow(); }); test('throws without username', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com' }, - secrets: { username: '', password: 'admin' }, - }) + createExternalService( + { + config: { apiUrl: 'test.com' }, + secrets: { username: '', password: 'admin' }, + }, + logger + ) ).toThrow(); }); test('throws without password', () => { expect(() => - createExternalService({ - config: { apiUrl: 'test.com' }, - secrets: { username: '', password: undefined }, - }) + createExternalService( + { + config: { apiUrl: 'test.com' }, + secrets: { username: '', password: undefined }, + }, + logger + ) ).toThrow(); }); }); @@ -84,6 +99,7 @@ describe('ServiceNow service', () => { await service.getIncident('1'); expect(requestMock).toHaveBeenCalledWith({ axios, + logger, url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1', }); }); @@ -127,6 +143,7 @@ describe('ServiceNow service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, + logger, url: 'https://dev102283.service-now.com/api/now/v2/table/incident', method: 'post', data: { short_description: 'title', description: 'desc' }, @@ -179,6 +196,7 @@ describe('ServiceNow service', () => { expect(patchMock).toHaveBeenCalledWith({ axios, + logger, url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1', data: { short_description: 'title', description: 'desc' }, }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 2b5204af2eb7d..cf1c26e6462a2 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -9,8 +9,10 @@ import axios from 'axios'; import { ExternalServiceCredentials, ExternalService, ExternalServiceParams } from './types'; import * as i18n from './translations'; +import { Logger } from '../../../../../../src/core/server'; import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types'; import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils'; +import { ProxySettings } from '../../types'; const API_VERSION = 'v2'; const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; @@ -18,10 +20,11 @@ const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; // Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`; -export const createExternalService = ({ - config, - secrets, -}: ExternalServiceCredentials): ExternalService => { +export const createExternalService = ( + { config, secrets }: ExternalServiceCredentials, + logger: Logger, + proxySettings?: ProxySettings +): ExternalService => { const { apiUrl: url } = config as ServiceNowPublicConfigurationType; const { username, password } = secrets as ServiceNowSecretConfigurationType; @@ -43,6 +46,8 @@ export const createExternalService = ({ const res = await request({ axios: axiosInstance, url: `${incidentUrl}/${id}`, + logger, + proxySettings, }); return { ...res.data.result }; @@ -58,6 +63,8 @@ export const createExternalService = ({ const res = await request({ axios: axiosInstance, url: incidentUrl, + logger, + proxySettings, params, }); @@ -71,9 +78,13 @@ export const createExternalService = ({ const createIncident = async ({ incident }: ExternalServiceParams) => { try { + logger.warn(`incident error : ${JSON.stringify(proxySettings)}`); + logger.warn(`incident error : ${url}`); const res = await request({ axios: axiosInstance, url: `${incidentUrl}`, + logger, + proxySettings, method: 'post', data: { ...(incident as Record) }, }); @@ -96,7 +107,9 @@ export const createExternalService = ({ const res = await patch({ axios: axiosInstance, url: `${incidentUrl}/${incidentId}`, + logger, data: { ...(incident as Record) }, + proxySettings, }); return { diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index 6d4176067c3ba..812657138152c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -4,25 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Logger } from '../../../../../src/core/server'; import { Services, ActionTypeExecutorResult } from '../types'; import { validateParams, validateSecrets } from '../lib'; import { getActionType, SlackActionType, SlackActionTypeExecutorOptions } from './slack'; import { actionsConfigMock } from '../actions_config.mock'; import { actionsMock } from '../mocks'; +import { createActionTypeRegistry } from './index.test'; + +jest.mock('@slack/webhook', () => { + return { + IncomingWebhook: jest.fn().mockImplementation(() => { + return { send: (message: string) => {} }; + }), + }; +}); const ACTION_TYPE_ID = '.slack'; const services: Services = actionsMock.createServices(); let actionType: SlackActionType; +let mockedLogger: jest.Mocked; beforeAll(() => { + const { logger } = createActionTypeRegistry(); actionType = getActionType({ async executor(options) { return { status: 'ok', actionId: options.actionId }; }, configurationUtilities: actionsConfigMock.create(), + logger, }); + mockedLogger = logger; + expect(actionType).toBeTruthy(); }); describe('action registeration', () => { @@ -83,6 +98,7 @@ describe('validateActionTypeSecrets()', () => { test('should validate and pass when the slack webhookUrl is whitelisted', () => { actionType = getActionType({ + logger: mockedLogger, configurationUtilities: { ...actionsConfigMock.create(), ensureWhitelistedUri: (url) => { @@ -98,9 +114,10 @@ describe('validateActionTypeSecrets()', () => { test('config validation returns an error if the specified URL isnt whitelisted', () => { actionType = getActionType({ + logger: mockedLogger, configurationUtilities: { ...actionsConfigMock.create(), - ensureWhitelistedHostname: (url) => { + ensureWhitelistedHostname: () => { throw new Error(`target hostname is not whitelisted`); }, }, @@ -136,6 +153,7 @@ describe('execute()', () => { actionType = getActionType({ executor: mockSlackExecutor, + logger: mockedLogger, configurationUtilities: actionsConfigMock.create(), }); }); @@ -147,6 +165,10 @@ describe('execute()', () => { config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, + proxySettings: { + proxyUrl: 'https://someproxyhost', + rejectUnauthorizedCertificates: false, + }, }); expect(response).toMatchInlineSnapshot(` Object { @@ -170,4 +192,25 @@ describe('execute()', () => { `"slack mockExecutor failure: this invocation should fail"` ); }); + + test('calls the mock executor with success proxy', async () => { + const actionTypeProxy = getActionType({ + logger: mockedLogger, + configurationUtilities: actionsConfigMock.create(), + }); + await actionTypeProxy.executor({ + actionId: 'some-id', + services, + config: {}, + secrets: { webhookUrl: 'http://example.com' }, + params: { message: 'this invocation should succeed' }, + proxySettings: { + proxyUrl: 'https://someproxyhost', + rejectUnauthorizedCertificates: false, + }, + }); + expect(mockedLogger.info).toHaveBeenCalledWith( + 'IncomingWebhook was called with proxyUrl https://someproxyhost' + ); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index 209582585256b..293328c809435 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -6,11 +6,14 @@ import { URL } from 'url'; import { curry } from 'lodash'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import HttpProxyAgent from 'http-proxy-agent'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, getOrElse } from 'fp-ts/lib/Option'; +import { Logger } from '../../../../../src/core/server'; import { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header'; import { @@ -20,6 +23,7 @@ import { ExecutorType, } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; +import { getProxyAgent } from './lib/get_proxy_agent'; export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions< @@ -49,9 +53,11 @@ const ParamsSchema = schema.object({ // customizing executor is only used for tests export function getActionType({ + logger, configurationUtilities, - executor = slackExecutor, + executor = curry(slackExecutor)({ logger }), }: { + logger: Logger; configurationUtilities: ActionsConfigurationUtilities; executor?: ExecutorType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; }): SlackActionType { @@ -99,6 +105,7 @@ function valdiateActionTypeConfig( // action executor async function slackExecutor( + { logger }: { logger: Logger }, execOptions: SlackActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -109,10 +116,22 @@ async function slackExecutor( const { webhookUrl } = secrets; const { message } = params; + let proxyAgent: HttpsProxyAgent | HttpProxyAgent | undefined; + if (execOptions.proxySettings) { + proxyAgent = getProxyAgent(execOptions.proxySettings, logger); + logger.info(`IncomingWebhook was called with proxyUrl ${execOptions.proxySettings.proxyUrl}`); + } + try { - const webhook = new IncomingWebhook(webhookUrl); + // https://slack.dev/node-slack-sdk/webhook + // node-slack-sdk use Axios inside :) + const webhook = new IncomingWebhook(webhookUrl, { + agent: proxyAgent, + }); result = await webhook.send(message); } catch (err) { + logger.error(`error on ${actionId} slack event: ${err.message}`); + if (err.original == null || err.original.response == null) { return serviceErrorResult(actionId, err.message); } @@ -143,6 +162,8 @@ async function slackExecutor( }, } ); + logger.error(`error on ${actionId} slack action: ${errMessage}`); + return errorResult(actionId, errMessage); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 26dd8a1a1402a..ea9f30452918c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('axios', () => ({ - request: jest.fn(), -})); - import { Services } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; import { actionsConfigMock } from '../actions_config.mock'; @@ -24,7 +20,22 @@ import { WebhookMethods, } from './webhook'; -const axiosRequestMock = axios.request as jest.Mock; +import * as utils from './lib/axios_utils'; + +jest.mock('axios'); +jest.mock('./lib/axios_utils', () => { + const originalUtils = jest.requireActual('./lib/axios_utils'); + return { + ...originalUtils, + request: jest.fn(), + patch: jest.fn(), + }; +}); + +axios.create = jest.fn(() => axios); +const requestMock = utils.request as jest.Mock; + +axios.create = jest.fn(() => axios); const ACTION_TYPE_ID = '.webhook'; @@ -227,7 +238,7 @@ describe('params validation', () => { describe('execute()', () => { beforeAll(() => { - axiosRequestMock.mockReset(); + requestMock.mockReset(); actionType = getActionType({ logger: mockedLogger, configurationUtilities: actionsConfigMock.create(), @@ -235,8 +246,8 @@ describe('execute()', () => { }); beforeEach(() => { - axiosRequestMock.mockReset(); - axiosRequestMock.mockResolvedValue({ + requestMock.mockReset(); + requestMock.mockResolvedValue({ status: 200, statusText: '', data: '', @@ -261,17 +272,42 @@ describe('execute()', () => { params: { body: 'some data' }, }); - expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { "auth": Object { "password": "123", "username": "abc", }, + "axios": undefined, "data": "some data", "headers": Object { "aheader": "a value", }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from webhook action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, "method": "post", + "proxySettings": undefined, "url": "https://abc.def/my-webhook", } `); @@ -294,13 +330,38 @@ describe('execute()', () => { params: { body: 'some data' }, }); - expect(axiosRequestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` Object { + "axios": undefined, "data": "some data", "headers": Object { "aheader": "a value", }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from webhook action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, "method": "post", + "proxySettings": undefined, "url": "https://abc.def/my-webhook", } `); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index be75742fa882e..d9a005565498d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -15,6 +15,7 @@ import { isOk, promiseResult, Result } from './lib/result_type'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; import { Logger } from '../../../../../src/core/server'; +import { request } from './lib/axios_utils'; // config definition export enum WebhookMethods { @@ -136,13 +137,18 @@ export async function executor( ? { auth: { username: secrets.user, password: secrets.password } } : {}; + const axiosInstance = axios.create(); + const result: Result = await promiseResult( - axios.request({ + request({ + axios: axiosInstance, method, url, + logger, ...basicAuth, headers, data, + proxySettings: execOptions.proxySettings, }) ); @@ -159,7 +165,7 @@ export async function executor( if (error.response) { const { status, statusText, headers: responseHeaders } = error.response; const message = `[${status}] ${statusText}`; - logger.warn(`error on ${actionId} webhook event: ${message}`); + logger.error(`error on ${actionId} webhook event: ${message}`); // The request was made and the server responded with a status code // that falls out of the range of 2xx // special handling for 5xx @@ -178,7 +184,7 @@ export async function executor( return errorResultInvalid(actionId, message); } - logger.warn(`error on ${actionId} webhook action: unexpected error`); + logger.error(`error on ${actionId} webhook action: unexpected error`); return errorResultUnexpectedError(actionId); } } diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index e86f2d7832828..795fbbf84145b 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -15,6 +15,7 @@ describe('config validation', () => { "*", ], "preconfigured": Object {}, + "rejectUnauthorizedCertificates": true, "whitelistedHosts": Array [ "*", ], @@ -33,6 +34,7 @@ describe('config validation', () => { }, }, }, + rejectUnauthorizedCertificates: false, }; expect(configSchema.validate(config)).toMatchInlineSnapshot(` Object { @@ -50,6 +52,7 @@ describe('config validation', () => { "secrets": Object {}, }, }, + "rejectUnauthorizedCertificates": false, "whitelistedHosts": Array [ "*", ], diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index b2f3fa2680a9c..ba80915ebe243 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -32,6 +32,9 @@ export const configSchema = schema.object({ defaultValue: {}, validate: validatePreconfigured, }), + proxyUrl: schema.maybe(schema.string()), + proxyHeaders: schema.maybe(schema.recordOf(schema.string(), schema.string())), + rejectUnauthorizedCertificates: schema.boolean({ defaultValue: true }), }); export type ActionsConfig = TypeOf; diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index bce06c829b1bc..97c08124f5546 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -12,6 +12,7 @@ import { GetServicesFunction, RawAction, PreConfiguredAction, + ProxySettings, } from '../types'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { SpacesServiceSetup } from '../../../spaces/server'; @@ -28,6 +29,7 @@ export interface ActionExecutorContext { actionTypeRegistry: ActionTypeRegistryContract; eventLogger: IEventLogger; preconfiguredActions: PreConfiguredAction[]; + proxySettings?: ProxySettings; } export interface ExecuteOptions { @@ -78,6 +80,7 @@ export class ActionExecutor { eventLogger, preconfiguredActions, getActionsClientWithRequest, + proxySettings, } = this.actionExecutorContext!; const services = getServices(request); @@ -133,6 +136,7 @@ export class ActionExecutor { params: validatedParams, config: validatedConfig, secrets: validatedSecrets, + proxySettings, }); } catch (err) { rawResult = { diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index ca93e88d01203..341a17889923f 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -34,6 +34,7 @@ describe('Actions Plugin', () => { enabledActionTypes: ['*'], whitelistedHosts: ['*'], preconfigured: {}, + rejectUnauthorizedCertificates: true, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -194,6 +195,7 @@ describe('Actions Plugin', () => { secrets: {}, }, }, + rejectUnauthorizedCertificates: true, }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -217,7 +219,7 @@ describe('Actions Plugin', () => { // coreMock.createSetup doesn't support Plugin generics // eslint-disable-next-line @typescript-eslint/no-explicit-any await plugin.setup(coreSetup as any, pluginsSetup); - const pluginStart = plugin.start(coreStart, pluginsStart); + const pluginStart = await plugin.start(coreStart, pluginsStart); expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true); }); @@ -232,7 +234,7 @@ describe('Actions Plugin', () => { usingEphemeralEncryptionKey: false, }, }); - const pluginStart = plugin.start(coreStart, pluginsStart); + const pluginStart = await plugin.start(coreStart, pluginsStart); await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest()); }); @@ -241,7 +243,7 @@ describe('Actions Plugin', () => { // coreMock.createSetup doesn't support Plugin generics // eslint-disable-next-line @typescript-eslint/no-explicit-any await plugin.setup(coreSetup as any, pluginsSetup); - const pluginStart = plugin.start(coreStart, pluginsStart); + const pluginStart = await plugin.start(coreStart, pluginsStart); expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true); await expect( diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ee50ee81d507c..413e6663105b8 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -116,6 +116,7 @@ export class ActionsPlugin implements Plugin, Plugi private readonly config: Promise; private readonly logger: Logger; + private actionsConfig?: ActionsConfig; private serverBasePath?: string; private taskRunnerFactory?: TaskRunnerFactory; private actionTypeRegistry?: ActionTypeRegistry; @@ -173,12 +174,12 @@ export class ActionsPlugin implements Plugin, Plugi // get executions count const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); - const actionsConfig = (await this.config) as ActionsConfig; - const actionsConfigUtils = getActionsConfigurationUtilities(actionsConfig); + this.actionsConfig = (await this.config) as ActionsConfig; + const actionsConfigUtils = getActionsConfigurationUtilities(this.actionsConfig); - for (const preconfiguredId of Object.keys(actionsConfig.preconfigured)) { + for (const preconfiguredId of Object.keys(this.actionsConfig.preconfigured)) { this.preconfiguredActions.push({ - ...actionsConfig.preconfigured[preconfiguredId], + ...this.actionsConfig.preconfigured[preconfiguredId], id: preconfiguredId, isPreconfigured: true, }); @@ -317,6 +318,14 @@ export class ActionsPlugin implements Plugin, Plugi encryptedSavedObjectsClient, actionTypeRegistry: actionTypeRegistry!, preconfiguredActions, + proxySettings: + this.actionsConfig && this.actionsConfig.proxyUrl + ? { + proxyUrl: this.actionsConfig.proxyUrl, + proxyHeaders: this.actionsConfig.proxyHeaders, + rejectUnauthorizedCertificates: this.actionsConfig.rejectUnauthorizedCertificates, + } + : undefined, }); taskRunnerFactory!.initialize({ diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index ecec45ade0460..bf7bd709a4a88 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -58,6 +58,7 @@ export interface ActionTypeExecutorOptions { config: Config; secrets: Secrets; params: Params; + proxySettings?: ProxySettings; } export interface ActionResult { @@ -140,3 +141,9 @@ export interface ActionTaskExecutorParams { spaceId: string; actionTaskParamsId: string; } + +export interface ProxySettings { + proxyUrl: string; + proxyHeaders?: Record; + rejectUnauthorizedCertificates: boolean; +} diff --git a/x-pack/test/alerting_api_integration/basic/config.ts b/x-pack/test/alerting_api_integration/basic/config.ts index f9c248ec3d56f..f58b7753b74f7 100644 --- a/x-pack/test/alerting_api_integration/basic/config.ts +++ b/x-pack/test/alerting_api_integration/basic/config.ts @@ -11,4 +11,5 @@ export default createTestConfig('basic', { disabledPlugins: [], license: 'basic', ssl: true, + enableActionsProxy: false, }); diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 4947cdbf55484..34e23a2dba0b2 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -5,6 +5,7 @@ */ import path from 'path'; +import getPort from 'get-port'; import fs from 'fs'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; @@ -15,6 +16,7 @@ interface CreateTestConfigOptions { license: string; disabledPlugins?: string[]; ssl?: boolean; + enableActionsProxy: boolean; } // test.not-enabled is specifically not enabled @@ -56,6 +58,10 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) fs.statSync(path.resolve(__dirname, 'fixtures', 'plugins', file)).isDirectory() ); + const actionsProxyUrl = options.enableActionsProxy + ? [`--xpack.actions.proxyUrl=http://localhost:${await getPort()}`] + : []; + return { testFiles: [require.resolve(`../${name}/tests/`)], servers, @@ -85,6 +91,9 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ])}`, '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, + ...actionsProxyUrl, + '--xpack.actions.rejectUnauthorizedCertificates=false', + '--xpack.eventLog.logEntries=true', `--xpack.actions.preconfigured=${JSON.stringify({ 'my-slack1': { diff --git a/x-pack/test/alerting_api_integration/common/lib/get_proxy_server.ts b/x-pack/test/alerting_api_integration/common/lib/get_proxy_server.ts new file mode 100644 index 0000000000000..4540556e73c5f --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/lib/get_proxy_server.ts @@ -0,0 +1,30 @@ +/* + * 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 httpProxy from 'http-proxy'; + +export const getHttpProxyServer = ( + targetUrl: string, + onProxyResHandler: (proxyRes?: unknown, req?: unknown, res?: unknown) => void +): httpProxy => { + const proxyServer = httpProxy.createProxyServer({ + target: targetUrl, + secure: false, + selfHandleResponse: false, + }); + proxyServer.on('proxyRes', (proxyRes: unknown, req: unknown, res: unknown) => { + onProxyResHandler(proxyRes, req, res); + }); + return proxyServer; +}; + +export const getProxyUrl = (kbnTestServerConfig: any) => { + const proxyUrl = kbnTestServerConfig + .find((val: string) => val.startsWith('--xpack.actions.proxyUrl=')) + .replace('--xpack.actions.proxyUrl=', ''); + + return new URL(proxyUrl); +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/config.ts b/x-pack/test/alerting_api_integration/security_and_spaces/config.ts index 081b901c47fc3..97f53ae2c3664 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/config.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/config.ts @@ -11,4 +11,5 @@ export default createTestConfig('security_and_spaces', { disabledPlugins: [], license: 'trial', ssl: true, + enableActionsProxy: true, }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts index 24931f11d4999..a0ba5331105bc 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/jira.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -35,6 +36,7 @@ const mapping = [ export default function jiraTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); + const config = getService('config'); const mockJira = { config: { @@ -73,12 +75,19 @@ export default function jiraTest({ getService }: FtrProviderContext) { }; let jiraSimulatorURL: string = ''; + let proxyServer: any; + let proxyHaveBeenCalled = false; describe('Jira', () => { before(() => { jiraSimulatorURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.JIRA) ); + proxyServer = getHttpProxyServer(kibanaServer.resolveUrl('/'), () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(config.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); }); describe('Jira - Action Creation', () => { @@ -529,6 +538,8 @@ export default function jiraTest({ getService }: FtrProviderContext) { }) .expect(200); + expect(proxyHaveBeenCalled).to.equal(true); + expect(body).to.eql({ status: 'ok', actionId: simulatedActionId, @@ -542,5 +553,9 @@ export default function jiraTest({ getService }: FtrProviderContext) { }); }); }); + + after(() => { + proxyServer.close(); + }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts index f4fcbb65ab5a3..c697cf69bb4d5 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/pagerduty.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -17,16 +18,25 @@ import { export default function pagerdutyTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); + const config = getService('config'); describe('pagerduty action', () => { let simulatedActionId = ''; let pagerdutySimulatorURL: string = ''; + let proxyServer: any; + let proxyHaveBeenCalled = false; // need to wait for kibanaServer to settle ... before(() => { pagerdutySimulatorURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.PAGERDUTY) ); + + proxyServer = getHttpProxyServer(kibanaServer.resolveUrl('/'), () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(config.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); }); it('should return successfully when passed valid create parameters', async () => { @@ -144,6 +154,8 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { }, }) .expect(200); + expect(proxyHaveBeenCalled).to.equal(true); + expect(result).to.eql({ status: 'ok', actionId: simulatedActionId, @@ -202,5 +214,9 @@ export default function pagerdutyTest({ getService }: FtrProviderContext) { expect(result.message).to.match(/error posting pagerduty event: http status 502/); expect(result.retry).to.equal(true); }); + + after(() => { + proxyServer.close(); + }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts index 94feabb556a51..5085c87550d01 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/resilient.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -35,6 +36,7 @@ const mapping = [ export default function resilientTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); + const config = getService('config'); const mockResilient = { config: { @@ -73,12 +75,19 @@ export default function resilientTest({ getService }: FtrProviderContext) { }; let resilientSimulatorURL: string = ''; + let proxyServer: any; + let proxyHaveBeenCalled = false; describe('IBM Resilient', () => { before(() => { resilientSimulatorURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.RESILIENT) ); + proxyServer = getHttpProxyServer(kibanaServer.resolveUrl('/'), () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(config.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); }); describe('IBM Resilient - Action Creation', () => { @@ -529,6 +538,8 @@ export default function resilientTest({ getService }: FtrProviderContext) { }) .expect(200); + expect(proxyHaveBeenCalled).to.equal(true); + expect(body).to.eql({ status: 'ok', actionId: simulatedActionId, @@ -542,5 +553,9 @@ export default function resilientTest({ getService }: FtrProviderContext) { }); }); }); + + after(() => { + proxyServer.close(); + }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index d3b72d01216d0..70b6a8fe512e1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { @@ -35,6 +36,7 @@ const mapping = [ export default function servicenowTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); + const config = getService('config'); const mockServiceNow = { config: { @@ -72,12 +74,20 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }; let servicenowSimulatorURL: string = ''; + let proxyServer: any; + let proxyHaveBeenCalled = false; describe('ServiceNow', () => { before(() => { servicenowSimulatorURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.SERVICENOW) ); + + proxyServer = getHttpProxyServer(kibanaServer.resolveUrl('/'), () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(config.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); }); describe('ServiceNow - Action Creation', () => { @@ -448,6 +458,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }, }) .expect(200); + expect(proxyHaveBeenCalled).to.equal(true); expect(result).to.eql({ status: 'ok', @@ -462,5 +473,9 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }); }); }); + + after(() => { + proxyServer.close(); + }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts index c68bcaa0ad4e8..45f9ba369dc23 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/slack.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import http from 'http'; import getPort from 'get-port'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getSlackServer } from '../../../../common/fixtures/plugins/actions_simulators/server/plugin'; @@ -14,18 +15,27 @@ import { getSlackServer } from '../../../../common/fixtures/plugins/actions_simu // eslint-disable-next-line import/no-default-export export default function slackTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const config = getService('config'); describe('slack action', () => { let simulatedActionId = ''; let slackSimulatorURL: string = ''; let slackServer: http.Server; + let proxyServer: any; + let proxyHaveBeenCalled = false; // need to wait for kibanaServer to settle ... before(async () => { slackServer = await getSlackServer(); const availablePort = await getPort({ port: 9000 }); slackServer.listen(availablePort); slackSimulatorURL = `http://localhost:${availablePort}`; + + proxyServer = getHttpProxyServer(slackSimulatorURL, () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(config.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); }); it('should return 200 when creating a slack action successfully', async () => { @@ -155,6 +165,7 @@ export default function slackTest({ getService }: FtrProviderContext) { }) .expect(200); expect(result.status).to.eql('ok'); + expect(proxyHaveBeenCalled).to.equal(true); }); it('should handle an empty message error', async () => { @@ -222,6 +233,7 @@ export default function slackTest({ getService }: FtrProviderContext) { after(() => { slackServer.close(); + proxyServer.close(); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts index 8f17ab54184b5..896026611043f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/webhook.ts @@ -8,6 +8,7 @@ import http from 'http'; import getPort from 'get-port'; import expect from '@kbn/expect'; import { URL, format as formatUrl } from 'url'; +import { getHttpProxyServer, getProxyUrl } from '../../../../common/lib/get_proxy_server'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getExternalServiceSimulatorPath, @@ -31,6 +32,7 @@ function parsePort(url: Record): Record { webhookServer = await getWebhookServer(); @@ -76,6 +80,12 @@ export default function webhookTest({ getService }: FtrProviderContext) { webhookServer.listen(availablePort); webhookSimulatorURL = `http://localhost:${availablePort}`; + proxyServer = getHttpProxyServer(webhookSimulatorURL, () => { + proxyHaveBeenCalled = true; + }); + const proxyUrl = getProxyUrl(configService.get('kbnTestServer.serverArgs')); + proxyServer.listen(Number(proxyUrl.port)); + kibanaURL = kibanaServer.resolveUrl( getExternalServiceSimulatorPath(ExternalServiceSimulator.WEBHOOK) ); @@ -140,6 +150,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('ok'); + expect(proxyHaveBeenCalled).to.equal(true); }); it('should support the POST method against webhook target', async () => { @@ -218,7 +229,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { .expect(200); expect(result.status).to.eql('error'); - expect(result.message).to.match(/error calling webhook, unexpected error/); + expect(result.message).to.match(/error calling webhook, retry later/); }); it('should handle failing webhook targets', async () => { @@ -240,6 +251,7 @@ export default function webhookTest({ getService }: FtrProviderContext) { after(() => { webhookServer.close(); + proxyServer.close(); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/config.ts b/x-pack/test/alerting_api_integration/spaces_only/config.ts index c79c26ef68752..f9860b642f13a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/config.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/config.ts @@ -7,4 +7,8 @@ import { createTestConfig } from '../common/config'; // eslint-disable-next-line import/no-default-export -export default createTestConfig('spaces_only', { disabledPlugins: ['security'], license: 'trial' }); +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, +}); diff --git a/yarn.lock b/yarn.lock index 41323c5bb2761..81bb7338e615f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3913,6 +3913,20 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== +"@types/http-proxy-agent@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1" + integrity sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ== + dependencies: + "@types/node" "*" + +"@types/http-proxy@^1.17.4": + version "1.17.4" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b" + integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q== + dependencies: + "@types/node" "*" + "@types/inert@^5.1.2": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/inert/-/inert-5.1.2.tgz#2bb8bef3b2462f904c960654c9edfa39285a85c6" From df7221245d58840ceb768001864fbf2442b71d2a Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 14 Aug 2020 17:37:23 -0400 Subject: [PATCH 3/5] skip flaky suite (#74814) --- .../open_timeline/open_timeline_modal/index.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx index d3139601bfa17..365444032b402 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx @@ -55,7 +55,8 @@ jest.mock('react-virtualized-auto-sizer', () => { }) => children({ width: 100, height: 500 }); }); -describe('OpenTimelineModal', () => { +// Failing: See https://github.com/elastic/kibana/issues/74814 +describe.skip('OpenTimelineModal', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); const mockInstallPrepackagedTimelines = jest.fn(); beforeEach(() => { From 2129b13155592b7936c1b143d618bbce53616788 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 14 Aug 2020 15:30:24 -0700 Subject: [PATCH 4/5] remove .kbn-optimizer-cache upload (#75086) Co-authored-by: spalger --- vars/kibanaPipeline.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 173c5b7e11764..5f3e8d1a0660d 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -170,7 +170,6 @@ def uploadCoverageArtifacts(prefix, pattern) { def withGcsArtifactUpload(workerName, closure) { def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" def ARTIFACT_PATTERNS = [ - '**/target/public/.kbn-optimizer-cache', 'target/kibana-*', 'target/test-metrics/*', 'target/kibana-security-solution/**/*.png', From ef5c95ea37c0d804df0533dec97ad7199e43d0d5 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 14 Aug 2020 13:44:44 -0700 Subject: [PATCH 5/5] [Reporting/Flaky Test] Skip test for paging list of reports (#75075) --- .../test/functional/apps/reporting_management/report_listing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/reporting_management/report_listing.ts b/x-pack/test/functional/apps/reporting_management/report_listing.ts index ca5fb888e67e1..3a6a60f55db1f 100644 --- a/x-pack/test/functional/apps/reporting_management/report_listing.ts +++ b/x-pack/test/functional/apps/reporting_management/report_listing.ts @@ -67,7 +67,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('Paginates historical reports', async () => { + it.skip('Paginates historical reports', async () => { // wait for first row of page 1 await testSubjects.find('checkboxSelectRow-k9a9xlwl0gpe1457b10rraq3');