From abd0f9630c1c5b8259abafed64ba435b3e4ddc7f Mon Sep 17 00:00:00 2001 From: Ehren Kret Date: Fri, 26 Aug 2022 16:49:05 -0500 Subject: [PATCH] Create GCP Logging implementation of AdminEventLogger --- event-logger/src/main/kotlin/loggers.kt | 29 +++++++++++++++++-- pom.xml | 7 +++++ service/config/sample.yml | 7 +++++ .../WhisperServerConfiguration.java | 10 +++++++ .../textsecuregcm/WhisperServerService.java | 16 ++++++++-- .../AdminEventLoggingConfiguration.java | 15 ++++++++++ .../controllers/RemoteConfigController.java | 10 +++---- .../RemoteConfigControllerTest.java | 4 +-- 8 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/configuration/AdminEventLoggingConfiguration.java diff --git a/event-logger/src/main/kotlin/loggers.kt b/event-logger/src/main/kotlin/loggers.kt index 02d94cdb3..9ff57f0e7 100644 --- a/event-logger/src/main/kotlin/loggers.kt +++ b/event-logger/src/main/kotlin/loggers.kt @@ -5,11 +5,36 @@ package org.signal.event -interface Logger { +import com.google.cloud.logging.LogEntry +import com.google.cloud.logging.Logging +import com.google.cloud.logging.Payload.JsonPayload +import com.google.cloud.logging.Severity +import com.google.protobuf.Struct +import com.google.protobuf.util.JsonFormat +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +interface AdminEventLogger { fun logEvent(event: Event, labels: Map?) fun logEvent(event: Event) = logEvent(event, null) } -class NoOpLogger : Logger { +class NoOpAdminEventLogger : AdminEventLogger { override fun logEvent(event: Event, labels: Map?) {} } + +class GoogleCloudAdminEventLogger(private val logging: Logging, private val logName: String) : AdminEventLogger { + override fun logEvent(event: Event, labels: Map?) { + val structBuilder = Struct.newBuilder() + JsonFormat.parser().merge(Json.encodeToString(event), structBuilder) + val struct = structBuilder.build() + + val logEntryBuilder = LogEntry.newBuilder(JsonPayload.of(struct)) + .setLogName(logName) + .setSeverity(Severity.NOTICE); + if (labels != null) { + logEntryBuilder.setLabels(labels); + } + logging.write(listOf(logEntryBuilder.build())) + } +} diff --git a/pom.xml b/pom.xml index 2fcda57e4..7290acebc 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,13 @@ pom import + + com.google.cloud + libraries-bom + 26.1.0 + pom + import + com.eatthepath diff --git a/service/config/sample.yml b/service/config/sample.yml index dee2414a7..b851b80ec 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -3,6 +3,13 @@ # `unset` values will need to be set to work properly. # Most other values are technically valid for a local/demonstration environment, but are probably not production-ready. +adminEventLoggingConfiguration: + credentials: | + Some credentials text + blah blah blah + projectId: some-project-id + logName: some-log-name + stripe: apiKey: unset idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index 7e87e4806..e95accaa0 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -14,6 +14,7 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration; import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration; +import org.whispersystems.textsecuregcm.configuration.AdminEventLoggingConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration; @@ -53,6 +54,11 @@ import org.whispersystems.websocket.configuration.WebSocketConfiguration; /** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */ public class WhisperServerConfiguration extends Configuration { + @NotNull + @Valid + @JsonProperty + private AdminEventLoggingConfiguration adminEventLoggingConfiguration; + @NotNull @Valid @JsonProperty @@ -257,6 +263,10 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private AbusiveMessageFilterConfiguration abusiveMessageFilter; + public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() { + return adminEventLoggingConfiguration; + } + public StripeConfiguration getStripe() { return stripe; } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index d314408b2..3678190f2 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -14,6 +14,8 @@ import com.codahale.metrics.SharedMetricRegistries; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.logging.LoggingOptions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; @@ -34,7 +36,9 @@ import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.datadog.DatadogMeterRegistry; +import java.io.ByteArrayInputStream; import java.net.http.HttpClient; +import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; @@ -55,7 +59,8 @@ import javax.servlet.FilterRegistration; import javax.servlet.ServletRegistration; import org.eclipse.jetty.servlets.CrossOriginFilter; import org.glassfish.jersey.server.ServerProperties; -import org.signal.event.NoOpLogger; +import org.signal.event.AdminEventLogger; +import org.signal.event.GoogleCloudAdminEventLogger; import org.signal.i18n.HeaderControlledResourceBundleLookup; import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations; @@ -407,6 +412,13 @@ public class WhisperServerService extends Application configAuthTokens; private final Map globalConfig; private static final String GLOBAL_CONFIG_PREFIX = "global."; - public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, Logger eventLogger, List configAuthTokens, Map globalConfig) { + public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, AdminEventLogger adminEventLogger, List configAuthTokens, Map globalConfig) { this.remoteConfigsManager = remoteConfigsManager; - this.eventLogger = Objects.requireNonNull(eventLogger); + this.adminEventLogger = Objects.requireNonNull(adminEventLogger); this.configAuthTokens = configAuthTokens; this.globalConfig = globalConfig; } @@ -93,7 +93,7 @@ public class RemoteConfigController { throw new WebApplicationException(Response.Status.FORBIDDEN); } - eventLogger.logEvent( + adminEventLogger.logEvent( new RemoteConfigSetEvent( configToken, config.getName(), diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/RemoteConfigControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/RemoteConfigControllerTest.java index 36174a3d2..2956b5300 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/RemoteConfigControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/RemoteConfigControllerTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.signal.event.NoOpLogger; +import org.signal.event.NoOpAdminEventLogger; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.controllers.RemoteConfigController; @@ -58,7 +58,7 @@ class RemoteConfigControllerTest { ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addProvider(new DeviceLimitExceededExceptionMapper()) - .addResource(new RemoteConfigController(remoteConfigsManager, new NoOpLogger(), remoteConfigsAuth, Map.of("maxGroupSize", "42"))) + .addResource(new RemoteConfigController(remoteConfigsManager, new NoOpAdminEventLogger(), remoteConfigsAuth, Map.of("maxGroupSize", "42"))) .build();