diff --git a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/SlackEventNotification.java b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/SlackEventNotification.java index 2a8ae770fe42..9f6a98f8d11c 100644 --- a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/SlackEventNotification.java +++ b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/SlackEventNotification.java @@ -34,6 +34,7 @@ import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.system.NodeId; import org.graylog2.shared.utilities.StringUtils; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +58,7 @@ public class SlackEventNotification implements EventNotification { private final SlackClient slackClient; private final TemplateModelProvider templateModelProvider; private final EventProcedureProvider eventProcedureProvider; + private final UrlAllowlistValidator urlAllowlistValidator; @Inject public SlackEventNotification(EventNotificationService notificationCallbackService, @@ -64,7 +66,8 @@ public SlackEventNotification(EventNotificationService notificationCallbackServi NotificationService notificationService, NodeId nodeId, SlackClient slackClient, TemplateModelProvider templateModelProvider, - EventProcedureProvider eventProcedureProvider) { + EventProcedureProvider eventProcedureProvider, + UrlAllowlistValidator urlAllowlistValidator) { this.notificationCallbackService = notificationCallbackService; this.templateEngine = requireNonNull(templateEngine); this.notificationService = requireNonNull(notificationService); @@ -72,6 +75,7 @@ public SlackEventNotification(EventNotificationService notificationCallbackServi this.slackClient = requireNonNull(slackClient); this.templateModelProvider = templateModelProvider; this.eventProcedureProvider = eventProcedureProvider; + this.urlAllowlistValidator = requireNonNull(urlAllowlistValidator); } /** @@ -84,6 +88,7 @@ public void execute(EventNotificationContext ctx) throws EventNotificationExcept LOG.debug("SlackEventNotification backlog size in method execute is [{}]", config.backlogSize()); try { + urlAllowlistValidator.validateUrl(config.webhookUrl(), ctx); SlackMessage slackMessage = createSlackMessage(ctx, config); slackClient.send(slackMessage, config.webhookUrl()); } catch (JsonProcessingException ex) { diff --git a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotification.java b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotification.java index 366ccfd11098..f9b6c5aaf0cf 100644 --- a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotification.java +++ b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotification.java @@ -33,6 +33,7 @@ import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.system.NodeId; import org.graylog2.shared.bindings.providers.ObjectMapperProvider; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.graylog2.web.customization.CustomizationConfig; import org.joda.time.DateTimeZone; import org.slf4j.Logger; @@ -59,6 +60,7 @@ public class TeamsEventNotification implements EventNotification { private final RequestClient requestClient; private final TemplateModelProvider templateModelProvider; private final CustomizationConfig customizationConfig; + private final UrlAllowlistValidator urlAllowlistValidator; @Inject public TeamsEventNotification(EventNotificationService notificationCallbackService, @@ -67,7 +69,8 @@ public TeamsEventNotification(EventNotificationService notificationCallbackServi NotificationService notificationService, NodeId nodeId, RequestClient requestClient, TemplateModelProvider templateModelProvider, - CustomizationConfig customizationConfig) { + CustomizationConfig customizationConfig, + UrlAllowlistValidator urlAllowlistValidator) { this.notificationCallbackService = notificationCallbackService; this.objectMapperProvider = requireNonNull(objectMapperProvider); this.templateEngine = requireNonNull(templateEngine); @@ -76,6 +79,7 @@ public TeamsEventNotification(EventNotificationService notificationCallbackServi this.requestClient = requireNonNull(requestClient); this.templateModelProvider = templateModelProvider; this.customizationConfig = customizationConfig; + this.urlAllowlistValidator = requireNonNull(urlAllowlistValidator); } /** @@ -88,6 +92,7 @@ public void execute(EventNotificationContext ctx) throws EventNotificationExcept LOG.debug("TeamsEventNotification backlog size in method execute is [{}]", config.backlogSize()); try { + urlAllowlistValidator.validateUrl(config.webhookUrl(), ctx); TeamsMessage teamsMessage = createTeamsMessage(ctx, config); requestClient.send(objectMapperProvider.getForTimeZone(config.timeZone()).writeValueAsString(teamsMessage), config.webhookUrl()); } catch (TemporaryEventNotificationException exp) { diff --git a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2.java b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2.java index be51205a11aa..215e63afa91c 100644 --- a/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2.java +++ b/graylog2-server/src/main/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2.java @@ -35,6 +35,7 @@ import org.graylog2.plugin.Tools; import org.graylog2.plugin.system.NodeId; import org.graylog2.shared.utilities.StringUtils; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,19 +56,22 @@ public class TeamsEventNotificationV2 implements EventNotification { private final NodeId nodeId; private final RequestClient requestClient; private final TemplateModelProvider templateModelProvider; + private final UrlAllowlistValidator urlAllowlistValidator; @Inject public TeamsEventNotificationV2(EventNotificationService notificationCallbackService, @Named("JsonSafe") Engine jsonTemplateEngine, NotificationService notificationService, NodeId nodeId, RequestClient requestClient, - TemplateModelProvider templateModelProvider) { + TemplateModelProvider templateModelProvider, + UrlAllowlistValidator urlAllowlistValidator) { this.notificationCallbackService = notificationCallbackService; this.jsonTemplateEngine = requireNonNull(jsonTemplateEngine); this.notificationService = requireNonNull(notificationService); this.nodeId = requireNonNull(nodeId); this.requestClient = requireNonNull(requestClient); this.templateModelProvider = templateModelProvider; + this.urlAllowlistValidator = requireNonNull(urlAllowlistValidator); } /** @@ -82,6 +86,7 @@ public void execute(EventNotificationContext ctx) throws EventNotificationExcept LOG.debug("TeamsEventNotificationV2 backlog size in method execute is [{}]", config.backlogSize()); try { + urlAllowlistValidator.validateUrl(config.webhookUrl(), ctx); final String requestBody = generateBody(ctx, config); requestClient.send(requestBody, config.webhookUrl()); } catch (TemporaryEventNotificationException exp) { diff --git a/graylog2-server/src/main/java/org/graylog2/system/urlallowlist/UrlAllowlistValidator.java b/graylog2-server/src/main/java/org/graylog2/system/urlallowlist/UrlAllowlistValidator.java new file mode 100644 index 000000000000..2467d971ff70 --- /dev/null +++ b/graylog2-server/src/main/java/org/graylog2/system/urlallowlist/UrlAllowlistValidator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +package org.graylog2.system.urlallowlist; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.graylog.events.notifications.EventNotificationContext; +import org.graylog.events.notifications.NotificationTestData; +import org.graylog.events.notifications.TemporaryEventNotificationException; +import org.graylog.events.processor.EventDefinitionDto; + +@Singleton +public class UrlAllowlistValidator { + private final UrlAllowlistService allowlistService; + private final UrlAllowlistNotificationService allowlistNotificationService; + + @Inject + public UrlAllowlistValidator(UrlAllowlistService allowlistService, + UrlAllowlistNotificationService allowlistNotificationService) { + this.allowlistService = allowlistService; + this.allowlistNotificationService = allowlistNotificationService; + } + + public void validateUrl(String url, EventNotificationContext ctx) throws TemporaryEventNotificationException { + if (!allowlistService.isAllowlisted(url)) { + if (!NotificationTestData.TEST_NOTIFICATION_ID.equals(ctx.notificationId())) { + final String eventDefTitle = ctx.eventDefinition().map(EventDefinitionDto::title).orElse("Unnamed"); + final String description = "The alert notification \"" + eventDefTitle + + "\" is trying to access a URL which is not allowlisted. Please check your configuration. [url: \"" + + url + "\"]"; + allowlistNotificationService.publishAllowlistFailure(description); + } + throw new TemporaryEventNotificationException("URL <" + url + "> is not allowlisted."); + } + } +} diff --git a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/SlackEventNotificationTest.java b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/SlackEventNotificationTest.java index a812fb5f1760..0284a075fe1d 100644 --- a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/SlackEventNotificationTest.java +++ b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/SlackEventNotificationTest.java @@ -36,6 +36,7 @@ import org.graylog2.configuration.HttpConfiguration; import org.graylog2.notifications.NotificationImpl; import org.graylog2.notifications.NotificationService; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.graylog2.plugin.MessageFactory; import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.TestMessageFactory; @@ -89,6 +90,9 @@ public class SlackEventNotificationTest { @Mock EventProcedureProvider mockEventProcedureProvider; + @Mock + UrlAllowlistValidator mockUrlAllowlistValidator; + private SlackEventNotificationConfig slackEventNotificationConfig; private EventNotificationContext eventNotificationContext; private final MessageFactory messageFactory = new TestMessageFactory(); @@ -113,7 +117,8 @@ public void setUp() { nodeId, mockSlackClient, new TemplateModelProvider(CustomizationConfig.empty(), new ObjectMapperProvider(), new HttpConfiguration()), - mockEventProcedureProvider); + mockEventProcedureProvider, + mockUrlAllowlistValidator); } private void getDummySlackNotificationConfig() { diff --git a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationTest.java b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationTest.java index 6f621e9a14dc..75e279cf7192 100644 --- a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationTest.java +++ b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationTest.java @@ -35,6 +35,7 @@ import org.graylog2.configuration.HttpConfiguration; import org.graylog2.notifications.NotificationImpl; import org.graylog2.notifications.NotificationService; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.graylog2.plugin.MessageFactory; import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.TestMessageFactory; @@ -85,6 +86,9 @@ public class TeamsEventNotificationTest { @Mock EventNotificationService notificationCallbackService; + @Mock + UrlAllowlistValidator mockUrlAllowlistValidator; + private TeamsEventNotificationConfig teamsEventNotificationConfig; private EventNotificationContext eventNotificationContext; private final MessageFactory messageFactory = new TestMessageFactory(); @@ -108,7 +112,8 @@ public void setUp() { nodeId, mockrequestClient, new TemplateModelProvider(customizationConfig, new ObjectMapperProvider(), new HttpConfiguration()), - customizationConfig); + customizationConfig, + mockUrlAllowlistValidator); } private void getDummyTeamsNotificationConfig() { @@ -354,7 +359,8 @@ public void testProductNameInDefaultMessage() throws EventNotificationException nodeId, mockrequestClient, new TemplateModelProvider(customizationConfig, new ObjectMapperProvider(), new HttpConfiguration()), - customizationConfig); + customizationConfig, + mockUrlAllowlistValidator); TeamsMessage actual = teamsEventNotification.createTeamsMessage(eventNotificationContext, TeamsEventNotificationConfig.builder().iconUrl(expectedImage).build()); assertThat(actual.sections()) diff --git a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2Test.java b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2Test.java index fcf47cb46226..265d747f9497 100644 --- a/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2Test.java +++ b/graylog2-server/src/test/java/org/graylog/integrations/notifications/types/microsoftteams/TeamsEventNotificationV2Test.java @@ -34,6 +34,7 @@ import org.graylog2.configuration.HttpConfiguration; import org.graylog2.notifications.NotificationImpl; import org.graylog2.notifications.NotificationService; +import org.graylog2.system.urlallowlist.UrlAllowlistValidator; import org.graylog2.plugin.MessageFactory; import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.TestMessageFactory; @@ -183,6 +184,9 @@ public class TeamsEventNotificationV2Test { @Mock EventNotificationService notificationCallbackService; + @Mock + UrlAllowlistValidator mockUrlAllowlistValidator; + private TeamsEventNotificationConfigV2 notificationConfig; private EventNotificationContext eventNotificationContext; @@ -198,7 +202,8 @@ public void setUp() { mockNotificationService, nodeId, mockrequestClient, - new TemplateModelProvider(CustomizationConfig.empty(), new ObjectMapperProvider(), new HttpConfiguration())); + new TemplateModelProvider(CustomizationConfig.empty(), new ObjectMapperProvider(), new HttpConfiguration()), + mockUrlAllowlistValidator); } @Test diff --git a/graylog2-web-interface/src/integrations/event-notifications/event-notification-types/SlackNotificationForm.tsx b/graylog2-web-interface/src/integrations/event-notifications/event-notification-types/SlackNotificationForm.tsx index 26fec4bd861b..37c32187da05 100644 --- a/graylog2-web-interface/src/integrations/event-notifications/event-notification-types/SlackNotificationForm.tsx +++ b/graylog2-web-interface/src/integrations/event-notifications/event-notification-types/SlackNotificationForm.tsx @@ -30,7 +30,7 @@ import { InputGroup, Row, } from 'components/bootstrap'; -import { ColorPickerPopover, TimezoneSelect } from 'components/common'; +import { ColorPickerPopover, TimezoneSelect, URLAllowListInput } from 'components/common'; import ColorLabel from 'components/sidecars/common/ColorLabel'; import DocumentationLink from 'components/support/DocumentationLink'; import usePluggableLicenseCheck from 'hooks/usePluggableLicenseCheck'; @@ -154,6 +154,10 @@ const SlackNotificationForm = ({ config, validation, onChange }: Props) => { propagateChange(name, getValueFromInput(event.target)); }; + const handleWebhookUrlChange = (event) => { + propagateChange('webhook_url', getValueFromInput(event.target)); + }; + const handleTimeZoneChange = (nextValue) => { propagateChange('time_zone', nextValue); }; @@ -183,16 +187,13 @@ const SlackNotificationForm = ({ config, validation, onChange }: Props) => { Choose a color to use for this configuration. - { + propagateChange('webhook_url', getValueFromInput(event.target)); + }; + const element = (

Custom message to be appended below the alert title. See{' '} @@ -143,16 +147,13 @@ const TeamsNotificationForm = ({ config, validation, onChange }: TeamsNotificati Choose a color to use for this configuration. -