Create GCP Logging implementation of AdminEventLogger

This commit is contained in:
Ehren Kret 2022-08-26 16:49:05 -05:00
parent a4508ec84f
commit abd0f9630c
8 changed files with 87 additions and 11 deletions

View File

@ -5,11 +5,36 @@
package org.signal.event 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<String, String>?) fun logEvent(event: Event, labels: Map<String, String>?)
fun logEvent(event: Event) = logEvent(event, null) fun logEvent(event: Event) = logEvent(event, null)
} }
class NoOpLogger : Logger { class NoOpAdminEventLogger : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {} override fun logEvent(event: Event, labels: Map<String, String>?) {}
} }
class GoogleCloudAdminEventLogger(private val logging: Logging, private val logName: String) : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {
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()))
}
}

View File

@ -136,6 +136,13 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>26.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency> <dependency>
<groupId>com.eatthepath</groupId> <groupId>com.eatthepath</groupId>

View File

@ -3,6 +3,13 @@
# `unset` values will need to be set to work properly. # `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. # 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: stripe:
apiKey: unset apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash

View File

@ -14,6 +14,7 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration; import org.whispersystems.textsecuregcm.configuration.AbusiveMessageFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration; import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
import org.whispersystems.textsecuregcm.configuration.AdminEventLoggingConfiguration;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration; import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration; import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration; import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
@ -53,6 +54,11 @@ import org.whispersystems.websocket.configuration.WebSocketConfiguration;
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */ /** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
public class WhisperServerConfiguration extends Configuration { public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private AdminEventLoggingConfiguration adminEventLoggingConfiguration;
@NotNull @NotNull
@Valid @Valid
@JsonProperty @JsonProperty
@ -257,6 +263,10 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty @JsonProperty
private AbusiveMessageFilterConfiguration abusiveMessageFilter; private AbusiveMessageFilterConfiguration abusiveMessageFilter;
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
return adminEventLoggingConfiguration;
}
public StripeConfiguration getStripe() { public StripeConfiguration getStripe() {
return stripe; return stripe;
} }

View File

@ -14,6 +14,8 @@ import com.codahale.metrics.SharedMetricRegistries;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature; 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.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; 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.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.datadog.DatadogMeterRegistry; import io.micrometer.datadog.DatadogMeterRegistry;
import java.io.ByteArrayInputStream;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,7 +59,8 @@ import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration; import javax.servlet.ServletRegistration;
import org.eclipse.jetty.servlets.CrossOriginFilter; import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.ServerProperties; 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.i18n.HeaderControlledResourceBundleLookup;
import org.signal.libsignal.zkgroup.ServerSecretParams; import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations; import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
@ -407,6 +412,13 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
.rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()) .rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())
.build(); .build();
final AdminEventLogger adminEventLogger = new GoogleCloudAdminEventLogger(
LoggingOptions.newBuilder().setProjectId(config.getAdminEventLoggingConfiguration().projectId())
.setCredentials(GoogleCredentials.fromStream(new ByteArrayInputStream(
config.getAdminEventLoggingConfiguration().credentials().getBytes(StandardCharsets.UTF_8))))
.build().getService(),
config.getAdminEventLoggingConfiguration().logName());
StripeManager stripeManager = new StripeManager(config.getStripe().getApiKey(), stripeExecutor, StripeManager stripeManager = new StripeManager(config.getStripe().getApiKey(), stripeExecutor,
config.getStripe().getIdempotencyKeyGenerator(), config.getStripe().getBoostDescription()); config.getStripe().getIdempotencyKeyGenerator(), config.getStripe().getBoostDescription());
@ -645,7 +657,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new PaymentsController(currencyManager, paymentsCredentialsGenerator), new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor), new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager), new ProvisioningController(rateLimiters, provisioningManager),
new RemoteConfigController(remoteConfigsManager, new NoOpLogger(), config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig()), new RemoteConfigController(remoteConfigsManager, adminEventLogger, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig()),
new SecureBackupController(backupCredentialsGenerator), new SecureBackupController(backupCredentialsGenerator),
new SecureStorageController(storageCredentialsGenerator), new SecureStorageController(storageCredentialsGenerator),
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(),

View File

@ -0,0 +1,15 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public record AdminEventLoggingConfiguration(
@NotNull @NotEmpty String credentials,
@NotNull @NotEmpty String projectId,
@NotNull @NotEmpty String logName) {
}

View File

@ -32,7 +32,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.signal.event.Logger; import org.signal.event.AdminEventLogger;
import org.signal.event.RemoteConfigSetEvent; import org.signal.event.RemoteConfigSetEvent;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.entities.UserRemoteConfig; import org.whispersystems.textsecuregcm.entities.UserRemoteConfig;
@ -45,15 +45,15 @@ import org.whispersystems.textsecuregcm.util.Conversions;
public class RemoteConfigController { public class RemoteConfigController {
private final RemoteConfigsManager remoteConfigsManager; private final RemoteConfigsManager remoteConfigsManager;
private final Logger eventLogger; private final AdminEventLogger adminEventLogger;
private final List<String> configAuthTokens; private final List<String> configAuthTokens;
private final Map<String, String> globalConfig; private final Map<String, String> globalConfig;
private static final String GLOBAL_CONFIG_PREFIX = "global."; private static final String GLOBAL_CONFIG_PREFIX = "global.";
public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, Logger eventLogger, List<String> configAuthTokens, Map<String, String> globalConfig) { public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, AdminEventLogger adminEventLogger, List<String> configAuthTokens, Map<String, String> globalConfig) {
this.remoteConfigsManager = remoteConfigsManager; this.remoteConfigsManager = remoteConfigsManager;
this.eventLogger = Objects.requireNonNull(eventLogger); this.adminEventLogger = Objects.requireNonNull(adminEventLogger);
this.configAuthTokens = configAuthTokens; this.configAuthTokens = configAuthTokens;
this.globalConfig = globalConfig; this.globalConfig = globalConfig;
} }
@ -93,7 +93,7 @@ public class RemoteConfigController {
throw new WebApplicationException(Response.Status.FORBIDDEN); throw new WebApplicationException(Response.Status.FORBIDDEN);
} }
eventLogger.logEvent( adminEventLogger.logEvent(
new RemoteConfigSetEvent( new RemoteConfigSetEvent(
configToken, configToken,
config.getName(), config.getName(),

View File

@ -35,7 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; 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.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.controllers.RemoteConfigController; import org.whispersystems.textsecuregcm.controllers.RemoteConfigController;
@ -58,7 +58,7 @@ class RemoteConfigControllerTest {
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class))) ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
.setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addProvider(new DeviceLimitExceededExceptionMapper()) .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(); .build();