diff --git a/alerting-config-service-api/build.gradle.kts b/alerting-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/alerting-config-service-api/build.gradle.kts +++ b/alerting-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/config-proto-converter/build.gradle.kts b/config-proto-converter/build.gradle.kts index c33a7433..3fe246c2 100644 --- a/config-proto-converter/build.gradle.kts +++ b/config-proto-converter/build.gradle.kts @@ -11,4 +11,9 @@ dependencies { because("https://snyk.io/vuln/SNYK-JAVA-COMGOOGLECODEGSON-1730327") } } + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } diff --git a/config-service-api/build.gradle.kts b/config-service-api/build.gradle.kts index 06c510ca..8e65d4f9 100644 --- a/config-service-api/build.gradle.kts +++ b/config-service-api/build.gradle.kts @@ -73,4 +73,9 @@ dependencies { testFixturesImplementation(libs.guava) testFixturesAnnotationProcessor(libs.lombok) testFixturesCompileOnly(libs.lombok) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } diff --git a/config-service-factory/src/main/java/org/hypertrace/config/service/ConfigServiceFactory.java b/config-service-factory/src/main/java/org/hypertrace/config/service/ConfigServiceFactory.java index 9638de4a..26f17cd7 100644 --- a/config-service-factory/src/main/java/org/hypertrace/config/service/ConfigServiceFactory.java +++ b/config-service-factory/src/main/java/org/hypertrace/config/service/ConfigServiceFactory.java @@ -65,7 +65,8 @@ public List buildServices( localChannel, config, configChangeEventGenerator), new EventConditionConfigServiceImpl(localChannel, configChangeEventGenerator), new NotificationRuleConfigServiceImpl(localChannel, configChangeEventGenerator), - new NotificationChannelConfigServiceImpl(localChannel, configChangeEventGenerator), + new NotificationChannelConfigServiceImpl( + localChannel, config, configChangeEventGenerator), SpanProcessingConfigServiceFactory.build(localChannel, config)) .map(GrpcPlatformService::new) .collect(Collectors.toUnmodifiableList()); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 225e36fd..071a85d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -protoc = "3.21.12" -grpc = "1.56.0" +protoc = "3.23.2" +grpc = "1.56.1" gson = "2.9.0" hypertrace-grpcutils = "0.12.1" hypertrace-framework = "0.1.52" diff --git a/label-application-rule-config-service-api/build.gradle.kts b/label-application-rule-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/label-application-rule-config-service-api/build.gradle.kts +++ b/label-application-rule-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/labels-config-service-api/build.gradle.kts b/labels-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/labels-config-service-api/build.gradle.kts +++ b/labels-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/notification-channel-config-service-api/build.gradle.kts b/notification-channel-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/notification-channel-config-service-api/build.gradle.kts +++ b/notification-channel-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImpl.java b/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImpl.java index a07fb548..834808a7 100644 --- a/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImpl.java +++ b/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImpl.java @@ -1,5 +1,6 @@ package org.hypertrace.notification.config.service; +import com.typesafe.config.Config; import io.grpc.Channel; import io.grpc.Status; import io.grpc.stub.StreamObserver; @@ -27,11 +28,18 @@ public class NotificationChannelConfigServiceImpl extends NotificationChannelConfigServiceGrpc.NotificationChannelConfigServiceImplBase { + static final String NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG = + "notification.channel.config.service"; + private final NotificationChannelStore notificationChannelStore; private final NotificationChannelConfigServiceRequestValidator validator; + private Config notificationChannelConfig = null; public NotificationChannelConfigServiceImpl( - Channel channel, ConfigChangeEventGenerator configChangeEventGenerator) { + Channel channel, Config config, ConfigChangeEventGenerator configChangeEventGenerator) { + if (config.hasPath(NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG)) { + this.notificationChannelConfig = config.getConfig(NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG); + } this.notificationChannelStore = new NotificationChannelStore(channel, configChangeEventGenerator); this.validator = new NotificationChannelConfigServiceRequestValidator(); @@ -43,7 +51,8 @@ public void createNotificationChannel( StreamObserver responseObserver) { try { RequestContext requestContext = RequestContext.CURRENT.get(); - validator.validateCreateNotificationChannelRequest(requestContext, request); + validator.validateCreateNotificationChannelRequest( + requestContext, request, notificationChannelConfig); NotificationChannel.Builder builder = NotificationChannel.newBuilder() .setId(UUID.randomUUID().toString()) @@ -66,7 +75,8 @@ public void updateNotificationChannel( StreamObserver responseObserver) { try { RequestContext requestContext = RequestContext.CURRENT.get(); - validator.validateUpdateNotificationChannelRequest(requestContext, request); + validator.validateUpdateNotificationChannelRequest( + requestContext, request, notificationChannelConfig); responseObserver.onNext( UpdateNotificationChannelResponse.newBuilder() .setNotificationChannel( diff --git a/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidator.java b/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidator.java index 5856d9d9..0ccd7d20 100644 --- a/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidator.java +++ b/notification-channel-config-service-impl/src/main/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidator.java @@ -3,7 +3,11 @@ import static org.hypertrace.config.validation.GrpcValidatorUtils.validateNonDefaultPresenceOrThrow; import static org.hypertrace.config.validation.GrpcValidatorUtils.validateRequestContextOrThrow; +import com.typesafe.config.Config; import io.grpc.Status; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; import org.hypertrace.core.grpcutils.context.RequestContext; import org.hypertrace.notification.config.service.v1.AwsS3BucketChannelConfig; import org.hypertrace.notification.config.service.v1.AwsS3BucketChannelConfig.WebIdentityAuthenticationCredential; @@ -20,17 +24,86 @@ public class NotificationChannelConfigServiceRequestValidator { + public static final String WEBHOOK_EXCLUSION_DOMAINS = "webhook.exclusion.domains"; + public static final String WEBHOOK_HTTP_SUPPORT_ENABLED = "webhook.http.support.enabled"; + public void validateCreateNotificationChannelRequest( - RequestContext requestContext, CreateNotificationChannelRequest request) { + RequestContext requestContext, + CreateNotificationChannelRequest request, + Config notificationChannelConfig) { validateRequestContextOrThrow(requestContext); validateNotificationChannelMutableData(request.getNotificationChannelMutableData()); + validateWebhookConfigExclusionDomains( + request.getNotificationChannelMutableData(), notificationChannelConfig); + validateWebhookHttpSupport( + request.getNotificationChannelMutableData(), notificationChannelConfig); + } + + void validateWebhookHttpSupport( + NotificationChannelMutableData notificationChannelMutableData, + Config notificationChannelConfig) { + if (notificationChannelConfig == null + || notificationChannelMutableData.getWebhookChannelConfigList().isEmpty()) { + return; + } + for (WebhookChannelConfig webhookChannelConfig : + notificationChannelMutableData.getWebhookChannelConfigList()) { + if (notificationChannelConfig.hasPath(WEBHOOK_HTTP_SUPPORT_ENABLED) + && notificationChannelConfig.getBoolean(WEBHOOK_HTTP_SUPPORT_ENABLED)) { + continue; + } + validateHttpsUrl(webhookChannelConfig.getUrl()); + } + } + + private void validateHttpsUrl(String urlString) { + try { + URL url = new URL(urlString); + String protocol = url.getProtocol(); + if (!protocol.equals("https")) { + throw Status.INVALID_ARGUMENT + .withDescription("URL configured in webhook is not https ") + .asRuntimeException(); + } + } catch (MalformedURLException e) { + throw Status.INVALID_ARGUMENT + .withDescription("URL configured in webhook is malformed ") + .asRuntimeException(); + } + } + + void validateWebhookConfigExclusionDomains( + NotificationChannelMutableData notificationChannelMutableData, + Config notificationChannelConfig) { + if (notificationChannelConfig == null + || notificationChannelMutableData.getWebhookChannelConfigList().isEmpty()) { + return; + } + if (notificationChannelConfig.hasPath(WEBHOOK_EXCLUSION_DOMAINS)) { + List exclusionDomains = + notificationChannelConfig.getStringList(WEBHOOK_EXCLUSION_DOMAINS); + for (WebhookChannelConfig webhookChannelConfig : + notificationChannelMutableData.getWebhookChannelConfigList()) { + for (String exclusionDomain : exclusionDomains) { + if (webhookChannelConfig.getUrl().contains(exclusionDomain)) { + throw Status.INVALID_ARGUMENT + .withDescription("URL configured in webhook contains excluded domain") + .asRuntimeException(); + } + } + } + } } public void validateUpdateNotificationChannelRequest( - RequestContext requestContext, UpdateNotificationChannelRequest request) { + RequestContext requestContext, + UpdateNotificationChannelRequest request, + Config notificationChannelConfig) { validateRequestContextOrThrow(requestContext); validateNonDefaultPresenceOrThrow(request, UpdateNotificationChannelRequest.ID_FIELD_NUMBER); validateNotificationChannelMutableData(request.getNotificationChannelMutableData()); + validateWebhookConfigExclusionDomains( + request.getNotificationChannelMutableData(), notificationChannelConfig); } private void validateNotificationChannelMutableData(NotificationChannelMutableData data) { diff --git a/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImplTest.java b/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImplTest.java index 37e2db99..2cbcf9df 100644 --- a/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImplTest.java +++ b/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceImplTest.java @@ -5,6 +5,7 @@ import com.google.protobuf.Value; import com.google.protobuf.util.JsonFormat; +import com.typesafe.config.Config; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -38,7 +39,7 @@ void beforeEach() { mockGenericConfigService .addService( new NotificationChannelConfigServiceImpl( - mockGenericConfigService.channel(), configChangeEventGenerator)) + mockGenericConfigService.channel(), mock(Config.class), configChangeEventGenerator)) .start(); channelStub = diff --git a/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidatorTest.java b/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidatorTest.java new file mode 100644 index 00000000..64a2147d --- /dev/null +++ b/notification-channel-config-service-impl/src/test/java/org/hypertrace/notification/config/service/NotificationChannelConfigServiceRequestValidatorTest.java @@ -0,0 +1,92 @@ +package org.hypertrace.notification.config.service; + +import static org.hypertrace.notification.config.service.NotificationChannelConfigServiceImpl.NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG; +import static org.hypertrace.notification.config.service.NotificationChannelConfigServiceRequestValidator.WEBHOOK_HTTP_SUPPORT_ENABLED; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigValueFactory; +import java.io.File; +import org.hypertrace.notification.config.service.v1.NotificationChannelMutableData; +import org.hypertrace.notification.config.service.v1.WebhookChannelConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class NotificationChannelConfigServiceRequestValidatorTest { + @Test + public void testValidateWebhookExclusions() { + NotificationChannelConfigServiceRequestValidator + notificationChannelConfigServiceRequestValidator = + new NotificationChannelConfigServiceRequestValidator(); + File configFile = new File(ClassLoader.getSystemResource("application.conf").getPath()); + Config config = ConfigFactory.parseFile(configFile); + NotificationChannelMutableData notificationChannelMutableDataWithExcludedDomain = + getNotificationChannelMutableData("http://localhost:9000/test"); + Assertions.assertThrows( + RuntimeException.class, + () -> { + notificationChannelConfigServiceRequestValidator.validateWebhookConfigExclusionDomains( + notificationChannelMutableDataWithExcludedDomain, + config.getConfig(NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG)); + }, + "RuntimeException was expected"); + NotificationChannelMutableData notificationChannelMutableDataWithValidDomain = + getNotificationChannelMutableData("http://testHost:9000/test"); + notificationChannelConfigServiceRequestValidator.validateWebhookConfigExclusionDomains( + notificationChannelMutableDataWithValidDomain, + config.getConfig(NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG)); + } + + @Test + public void testValidateWebhookHttpsSupport() { + NotificationChannelConfigServiceRequestValidator + notificationChannelConfigServiceRequestValidator = + new NotificationChannelConfigServiceRequestValidator(); + File configFile = new File(ClassLoader.getSystemResource("application.conf").getPath()); + Config config = ConfigFactory.parseFile(configFile); + Config notificationChannelConfig = config.getConfig(NOTIFICATION_CHANNEL_CONFIG_SERVICE_CONFIG); + NotificationChannelMutableData notificationChannelWithHttpUrl = + getNotificationChannelMutableData("http://localhost:9000/test"); + // As http support disabled RuntimeException should be thrown. + Assertions.assertThrows( + RuntimeException.class, + () -> { + notificationChannelConfigServiceRequestValidator.validateWebhookHttpSupport( + notificationChannelWithHttpUrl, notificationChannelConfig); + }, + "RuntimeException was expected"); + + // In valid URl not accepted + NotificationChannelMutableData notificationChannelWithInvalidUrl = + getNotificationChannelMutableData("localhost"); + Assertions.assertThrows( + RuntimeException.class, + () -> { + notificationChannelConfigServiceRequestValidator.validateWebhookHttpSupport( + notificationChannelWithInvalidUrl, notificationChannelConfig); + }, + "RuntimeException was expected"); + + // Valid webhook config with https url. + NotificationChannelMutableData notificationChannelMutableDataWithHttpsUrl = + getNotificationChannelMutableData("https://localhost:9000/test"); + notificationChannelConfigServiceRequestValidator.validateWebhookHttpSupport( + notificationChannelMutableDataWithHttpsUrl, notificationChannelConfig); + + // Update config with http support enabled and verify no exceptions for http url + Config updatedNotificationChannelConfig = + config.withValue(WEBHOOK_HTTP_SUPPORT_ENABLED, ConfigValueFactory.fromAnyRef("true")); + + NotificationChannelMutableData notificationChannelMutableDataWithHttpUrl = + getNotificationChannelMutableData("http://localhost:9000/test"); + notificationChannelConfigServiceRequestValidator.validateWebhookHttpSupport( + notificationChannelMutableDataWithHttpUrl, updatedNotificationChannelConfig); + } + + private static NotificationChannelMutableData getNotificationChannelMutableData(String url) { + return NotificationChannelMutableData.newBuilder() + .setChannelName("testChannel") + .addWebhookChannelConfig(WebhookChannelConfig.newBuilder().setUrl(url)) + .build(); + } +} diff --git a/notification-channel-config-service-impl/src/test/resources/application.conf b/notification-channel-config-service-impl/src/test/resources/application.conf new file mode 100644 index 00000000..b78adb90 --- /dev/null +++ b/notification-channel-config-service-impl/src/test/resources/application.conf @@ -0,0 +1,4 @@ +notification.channel.config.service { + webhook.exclusion.domains = ["localhost"] + webhook.http.support.enabled = false +} \ No newline at end of file diff --git a/notification-rule-config-service-api/build.gradle.kts b/notification-rule-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/notification-rule-config-service-api/build.gradle.kts +++ b/notification-rule-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/partitioner-config-service-api/build.gradle.kts b/partitioner-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/partitioner-config-service-api/build.gradle.kts +++ b/partitioner-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/spaces-config-service-api/build.gradle.kts b/spaces-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/spaces-config-service-api/build.gradle.kts +++ b/spaces-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets { diff --git a/span-processing-config-service-api/build.gradle.kts b/span-processing-config-service-api/build.gradle.kts index f710c5d2..990787e3 100644 --- a/span-processing-config-service-api/build.gradle.kts +++ b/span-processing-config-service-api/build.gradle.kts @@ -31,6 +31,11 @@ protobuf { dependencies { api(libs.bundles.grpc.api) + constraints { + implementation("com.google.guava:guava:32.0.1-jre") { + because("https://nvd.nist.gov/vuln/detail/CVE-2023-2976") + } + } } sourceSets {