mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
This is a 'red' Flogger migration CL. Red CLs contain changes which are likely not to work without manual intervention. Note that it may not even be possible to directly migrate the logger usage in this CL to the Flogger API and some additional refactoring may be required. If this is the case, please note that it should be safe to submit any outstanding 'green' and 'yellow' CLs prior to tackling this. If you feel that your use case is not covered by the existing Flogger API please raise a feature request at []and revert this CL. For more information, see [] Base CL: 197331037 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=197503952
318 lines
10 KiB
Java
318 lines
10 KiB
Java
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google.registry.proxy;
|
|
|
|
import static com.google.common.base.Suppliers.memoizeWithExpiration;
|
|
import static google.registry.proxy.ProxyConfig.getProxyConfig;
|
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
|
|
import com.beust.jcommander.JCommander;
|
|
import com.beust.jcommander.Parameter;
|
|
import com.beust.jcommander.ParameterException;
|
|
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
|
import com.google.api.client.googleapis.util.Utils;
|
|
import com.google.api.services.cloudkms.v1.CloudKMS;
|
|
import com.google.api.services.cloudkms.v1.model.DecryptRequest;
|
|
import com.google.api.services.storage.Storage;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.flogger.LoggerConfig;
|
|
import com.google.monitoring.metrics.MetricReporter;
|
|
import dagger.Component;
|
|
import dagger.Module;
|
|
import dagger.Provides;
|
|
import google.registry.proxy.EppProtocolModule.EppProtocol;
|
|
import google.registry.proxy.HealthCheckProtocolModule.HealthCheckProtocol;
|
|
import google.registry.proxy.Protocol.FrontendProtocol;
|
|
import google.registry.proxy.ProxyConfig.Environment;
|
|
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
|
|
import google.registry.util.Clock;
|
|
import google.registry.util.SystemClock;
|
|
import io.netty.handler.logging.LogLevel;
|
|
import io.netty.handler.logging.LoggingHandler;
|
|
import io.netty.handler.ssl.OpenSsl;
|
|
import io.netty.handler.ssl.SslProvider;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import java.util.function.Supplier;
|
|
import java.util.logging.ConsoleHandler;
|
|
import java.util.logging.Handler;
|
|
import java.util.logging.Level;
|
|
import javax.inject.Named;
|
|
import javax.inject.Singleton;
|
|
|
|
/**
|
|
* A module that provides the port-to-protocol map and other configs that are used to bootstrap the
|
|
* server.
|
|
*/
|
|
@Module
|
|
public class ProxyModule {
|
|
|
|
@Parameter(names = "--whois", description = "Port for WHOIS")
|
|
private Integer whoisPort;
|
|
|
|
@Parameter(names = "--epp", description = "Port for EPP")
|
|
private Integer eppPort;
|
|
|
|
@Parameter(names = "--health_check", description = "Port for health check protocol")
|
|
private Integer healthCheckPort;
|
|
|
|
@Parameter(names = "--env", description = "Environment to run the proxy in")
|
|
private Environment env = Environment.LOCAL;
|
|
|
|
@Parameter(names = "--log", description = "Whether to log activities for debugging")
|
|
boolean log;
|
|
|
|
/**
|
|
* Configure logging parameters depending on the {@link Environment}.
|
|
*
|
|
* <p>If not running locally, set the logging formatter to {@link GcpJsonFormatter} that formats
|
|
* the log in a single-line json string printed to {@code STDOUT} or {@code STDERR}, will be
|
|
* correctly parsed by Stackdriver logging.
|
|
*
|
|
* @see <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/logging#best_practices">
|
|
* Logging Best Practices</a>
|
|
*/
|
|
private void configureLogging() {
|
|
// Remove all other handlers on the root logger to avoid double logging.
|
|
LoggerConfig rootLoggerConfig = LoggerConfig.getConfig("");
|
|
Arrays.asList(rootLoggerConfig.getHandlers()).forEach(rootLoggerConfig::removeHandler);
|
|
|
|
// If running on in a non-local environment, use GCP JSON formatter.
|
|
Handler rootHandler = new ConsoleHandler();
|
|
rootHandler.setLevel(Level.FINE);
|
|
if (env != Environment.LOCAL) {
|
|
rootHandler.setFormatter(new GcpJsonFormatter());
|
|
}
|
|
rootLoggerConfig.addHandler(rootHandler);
|
|
}
|
|
|
|
/**
|
|
* Parses command line arguments. Show usage if wrong arguments are given.
|
|
*
|
|
* @param args list of {@code String} arguments
|
|
* @return this {@code ProxyModule} object
|
|
*/
|
|
ProxyModule parse(String[] args) {
|
|
JCommander jCommander = new JCommander(this);
|
|
jCommander.setProgramName("proxy_server");
|
|
try {
|
|
jCommander.parse(args);
|
|
} catch (ParameterException e) {
|
|
jCommander.usage();
|
|
throw e;
|
|
}
|
|
configureLogging();
|
|
return this;
|
|
}
|
|
|
|
@Provides
|
|
@WhoisProtocol
|
|
int provideWhoisPort(ProxyConfig config) {
|
|
return Optional.ofNullable(whoisPort).orElse(config.whois.port);
|
|
}
|
|
|
|
@Provides
|
|
@EppProtocol
|
|
int provideEppPort(ProxyConfig config) {
|
|
return Optional.ofNullable(eppPort).orElse(config.epp.port);
|
|
}
|
|
|
|
@Provides
|
|
@HealthCheckProtocol
|
|
int provideHealthCheckPort(ProxyConfig config) {
|
|
return Optional.ofNullable(healthCheckPort).orElse(config.healthCheck.port);
|
|
}
|
|
|
|
@Provides
|
|
ImmutableMap<Integer, FrontendProtocol> providePortToProtocolMap(
|
|
Set<FrontendProtocol> protocolSet) {
|
|
return Maps.uniqueIndex(protocolSet, Protocol::port);
|
|
}
|
|
|
|
@Provides
|
|
Environment provideEnvironment() {
|
|
return env;
|
|
}
|
|
|
|
/**
|
|
* Provides shared logging handler.
|
|
*
|
|
* <p>The {@link LoggingHandler} records logs at {@code LogLevel.DEBUG} (internal Netty log
|
|
* level), which corresponds to {@code Level.FINE} (JUL log level). It uses a JUL logger called
|
|
* {@code io.netty.handler.logging.LoggingHandler} to actually process the logs. This logger is
|
|
* set to {@code Level.FINE} if {@code --log} parameter is passed, so that it does not filter out
|
|
* logs that the {@link LoggingHandler} captures. Otherwise the logs are silently ignored because
|
|
* the default logger level is {@code Level.INFO}.
|
|
*/
|
|
@Singleton
|
|
@Provides
|
|
LoggingHandler provideLoggingHandler() {
|
|
if (log) {
|
|
LoggerConfig.getConfig(io.netty.handler.logging.LoggingHandler.class).setLevel(Level.FINE);
|
|
}
|
|
return new LoggingHandler(LogLevel.DEBUG);
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
static GoogleCredential provideCredential(ProxyConfig config) {
|
|
try {
|
|
GoogleCredential credential = GoogleCredential.getApplicationDefault();
|
|
if (credential.createScopedRequired()) {
|
|
credential = credential.createScoped(config.gcpScopes);
|
|
}
|
|
return credential;
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("Unable to obtain OAuth2 credential.", e);
|
|
}
|
|
}
|
|
|
|
/** Access token supplier that auto refreshes 1 minute before expiry. */
|
|
@Singleton
|
|
@Provides
|
|
@Named("accessToken")
|
|
static Supplier<String> provideAccessTokenSupplier(
|
|
GoogleCredential credential, ProxyConfig config) {
|
|
return memoizeWithExpiration(
|
|
() -> {
|
|
try {
|
|
credential.refreshToken();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException("Cannot refresh access token.", e);
|
|
}
|
|
return credential.getAccessToken();
|
|
},
|
|
config.accessTokenValidPeriodSeconds - config.accessTokenRefreshBeforeExpirySeconds,
|
|
SECONDS);
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
static CloudKMS provideCloudKms(GoogleCredential credential, ProxyConfig config) {
|
|
return new CloudKMS.Builder(
|
|
Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), credential)
|
|
.setApplicationName(config.projectId)
|
|
.build();
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
static Storage provideStorage(GoogleCredential credential, ProxyConfig config) {
|
|
return new Storage.Builder(
|
|
Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), credential)
|
|
.setApplicationName(config.projectId)
|
|
.build();
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
@Named("encryptedPemBytes")
|
|
static byte[] provideEncryptedPemBytes(Storage storage, ProxyConfig config) {
|
|
try {
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
storage
|
|
.objects()
|
|
.get(config.gcs.bucket, config.gcs.sslPemFilename)
|
|
.executeMediaAndDownloadTo(outputStream);
|
|
return outputStream.toByteArray();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(
|
|
String.format(
|
|
"Error reading encrypted PEM file %s from GCS bucket %s",
|
|
config.gcs.sslPemFilename, config.gcs.bucket),
|
|
e);
|
|
}
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
@Named("pemBytes")
|
|
static byte[] providePemBytes(
|
|
CloudKMS cloudKms, @Named("encryptedPemBytes") byte[] encryptedPemBytes, ProxyConfig config) {
|
|
String cryptoKeyUrl =
|
|
String.format(
|
|
"projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s",
|
|
config.projectId, config.kms.location, config.kms.keyRing, config.kms.cryptoKey);
|
|
try {
|
|
DecryptRequest decryptRequest = new DecryptRequest().encodeCiphertext(encryptedPemBytes);
|
|
return cloudKms
|
|
.projects()
|
|
.locations()
|
|
.keyRings()
|
|
.cryptoKeys()
|
|
.decrypt(cryptoKeyUrl, decryptRequest)
|
|
.execute()
|
|
.decodePlaintext();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(
|
|
String.format("PEM file decryption failed using CryptoKey: %s", cryptoKeyUrl), e);
|
|
}
|
|
}
|
|
|
|
@Provides
|
|
static SslProvider provideSslProvider() {
|
|
// Prefer OpenSSL.
|
|
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
|
}
|
|
|
|
@Provides
|
|
@Singleton
|
|
static Clock provideClock() {
|
|
return new SystemClock();
|
|
}
|
|
|
|
@Provides
|
|
static ExecutorService provideExecutorService() {
|
|
return Executors.newWorkStealingPool();
|
|
}
|
|
|
|
@Provides
|
|
static ScheduledExecutorService provideScheduledExecutorService() {
|
|
return Executors.newSingleThreadScheduledExecutor();
|
|
}
|
|
|
|
@Singleton
|
|
@Provides
|
|
ProxyConfig provideProxyConfig(Environment env) {
|
|
return getProxyConfig(env);
|
|
}
|
|
|
|
/** Root level component that exposes the port-to-protocol map. */
|
|
@Singleton
|
|
@Component(
|
|
modules = {
|
|
ProxyModule.class,
|
|
CertificateModule.class,
|
|
HttpsRelayProtocolModule.class,
|
|
WhoisProtocolModule.class,
|
|
EppProtocolModule.class,
|
|
HealthCheckProtocolModule.class,
|
|
MetricsModule.class
|
|
}
|
|
)
|
|
interface ProxyComponent {
|
|
|
|
ImmutableMap<Integer, FrontendProtocol> portToProtocolMap();
|
|
|
|
MetricReporter metricReporter();
|
|
}
|
|
}
|