mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 16:37:13 +02:00
Open source GCP proxy
Dagger updated to 2.13, along with all its dependencies. Also allows us to have multiple config files for different environment (prod, sandbox, alpha, local, etc) and specify which one to use on the command line with a --env flag. Therefore the same binary can be used in all environments. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=176551289
This commit is contained in:
parent
c7484b25e0
commit
7e42ee48a4
54 changed files with 6648 additions and 15 deletions
50
java/google/registry/proxy/BUILD
Normal file
50
java/google/registry/proxy/BUILD
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Description:
|
||||
# This package contains the code for the binary that proxies TCP traffic from
|
||||
# the GCE/GKE to AppEngine.
|
||||
|
||||
package(
|
||||
default_visibility = ["//java/google/registry:registry_project"],
|
||||
)
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
java_library(
|
||||
name = "proxy",
|
||||
srcs = glob(["**/*.java"]),
|
||||
resources = glob([
|
||||
"resources/*",
|
||||
"config/*.yaml",
|
||||
]),
|
||||
deps = [
|
||||
"//java/google/registry/config",
|
||||
"//java/google/registry/monitoring/metrics",
|
||||
"//java/google/registry/monitoring/metrics/stackdriver",
|
||||
"//java/google/registry/util",
|
||||
"@com_beust_jcommander",
|
||||
"@com_google_api_client",
|
||||
"@com_google_apis_google_api_services_cloudkms",
|
||||
"@com_google_apis_google_api_services_monitoring",
|
||||
"@com_google_auto_value",
|
||||
"@com_google_code_findbugs_jsr305",
|
||||
"@com_google_dagger",
|
||||
"@com_google_guava",
|
||||
"@io_netty_buffer",
|
||||
"@io_netty_codec",
|
||||
"@io_netty_codec_http",
|
||||
"@io_netty_common",
|
||||
"@io_netty_handler",
|
||||
"@io_netty_transport",
|
||||
"@javax_inject",
|
||||
"@joda_time",
|
||||
"@org_bouncycastle_bcpkix_jdk15on",
|
||||
],
|
||||
)
|
||||
|
||||
java_binary(
|
||||
name = "proxy_server",
|
||||
main_class = "google.registry.proxy.ProxyServer",
|
||||
runtime_deps = [
|
||||
":proxy",
|
||||
"@io_netty_tcnative",
|
||||
],
|
||||
)
|
163
java/google/registry/proxy/CertificateModule.java
Normal file
163
java/google/registry/proxy/CertificateModule.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
// 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.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.proxy.ProxyModule.PemBytes;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.function.Function;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMException;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
|
||||
/**
|
||||
* Dagger module that provides bindings needed to inject EPP SSL certificate chain and private key.
|
||||
*
|
||||
* <p>The certificates and private key are stored in a .pem file that is encrypted by Cloud KMS. The
|
||||
* .pem file can be generated by concatenating the .crt certificate files on the chain and the .key
|
||||
* private file.
|
||||
*
|
||||
* <p>The certificates in the .pem file must be stored in order, where the next certificate's
|
||||
* subject is the previous certificate's issuer.
|
||||
*
|
||||
* @see <a href="https://cloud.google.com/kms/">Cloud Key Management Service</a>
|
||||
*/
|
||||
@Module
|
||||
public class CertificateModule {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Select specific type from a given {@link ImmutableList} and convert them using the converter.
|
||||
*
|
||||
* @param objects the {@link ImmutableList} to filter from.
|
||||
* @param clazz the class to filter.
|
||||
* @param converter the converter function to act on the items in the filtered list.
|
||||
*/
|
||||
private static <T, E> ImmutableList<E> filterAndConvert(
|
||||
ImmutableList<Object> objects, Class<T> clazz, Function<T, E> converter) {
|
||||
return objects
|
||||
.stream()
|
||||
.filter(obj -> clazz.isInstance(obj))
|
||||
.map(obj -> clazz.cast(obj))
|
||||
.map(converter)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("pemObjects")
|
||||
static ImmutableList<Object> providePemObjects(PemBytes pemBytes) {
|
||||
PEMParser pemParser =
|
||||
new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes.getBytes()), UTF_8));
|
||||
ImmutableList.Builder<Object> listBuilder = new ImmutableList.Builder<>();
|
||||
Object obj;
|
||||
// PEMParser returns an object (private key, certificate, etc) each time readObject() is called,
|
||||
// until no more object is to be read from the file.
|
||||
while (true) {
|
||||
try {
|
||||
obj = pemParser.readObject();
|
||||
if (obj == null) {
|
||||
break;
|
||||
} else {
|
||||
listBuilder.add(obj);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.severe(e, "Cannot parse PEM file correctly.");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return listBuilder.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static PrivateKey providePrivateKey(@Named("pemObjects") ImmutableList<Object> pemObjects) {
|
||||
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
|
||||
Function<PEMKeyPair, PrivateKey> privateKeyConverter =
|
||||
pemKeyPair -> {
|
||||
try {
|
||||
return converter.getKeyPair(pemKeyPair).getPrivate();
|
||||
} catch (PEMException e) {
|
||||
logger.severefmt(e, "Error converting private key: %s", pemKeyPair);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
ImmutableList<PrivateKey> privateKeys =
|
||||
filterAndConvert(pemObjects, PEMKeyPair.class, privateKeyConverter);
|
||||
checkState(
|
||||
privateKeys.size() == 1,
|
||||
"The pem file must contain exactly one private key, but %s keys are found",
|
||||
privateKeys.size());
|
||||
return privateKeys.get(0);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("eppServerCertificates")
|
||||
static X509Certificate[] provideCertificates(
|
||||
@Named("pemObjects") ImmutableList<Object> pemObject) {
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
|
||||
Function<X509CertificateHolder, X509Certificate> certificateConverter =
|
||||
certificateHolder -> {
|
||||
try {
|
||||
return converter.getCertificate(certificateHolder);
|
||||
} catch (CertificateException e) {
|
||||
logger.severefmt(e, "Error converting certificate: %s", certificateHolder);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
ImmutableList<X509Certificate> certificates =
|
||||
filterAndConvert(pemObject, X509CertificateHolder.class, certificateConverter);
|
||||
checkState(certificates.size() != 0, "No certificates found in the pem file");
|
||||
X509Certificate lastCert = null;
|
||||
for (X509Certificate cert : certificates) {
|
||||
if (lastCert != null) {
|
||||
checkState(
|
||||
lastCert.getIssuerX500Principal().equals(cert.getSubjectX500Principal()),
|
||||
"Certificate chain error:\n%s\nis not signed by\n%s",
|
||||
lastCert,
|
||||
cert);
|
||||
}
|
||||
lastCert = cert;
|
||||
}
|
||||
X509Certificate[] certificateArray = new X509Certificate[certificates.size()];
|
||||
certificates.toArray(certificateArray);
|
||||
return certificateArray;
|
||||
}
|
||||
}
|
152
java/google/registry/proxy/EppProtocolModule.java
Normal file
152
java/google/registry/proxy/EppProtocolModule.java
Normal file
|
@ -0,0 +1,152 @@
|
|||
// 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 google.registry.util.ResourceUtils.readResourceBytes;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.handler.EppServiceHandler;
|
||||
import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
|
||||
import google.registry.proxy.handler.SslServerInitializer;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.LengthFieldPrepender;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol} used for epp protocol. */
|
||||
@Module
|
||||
class EppProtocolModule {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/** Dagger qualifier to provide epp protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface EppProtocol {};
|
||||
|
||||
private static final String PROTOCOL_NAME = "epp";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
ProxyConfig config,
|
||||
@EppProtocol int eppPort,
|
||||
@EppProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders,
|
||||
@HttpsRelayProtocol BackendProtocol.Builder backendProtocolBuilder) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(eppPort)
|
||||
.handlerProviders(handlerProviders)
|
||||
.relayProtocol(backendProtocolBuilder.host(config.epp.relayHost).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
Provider<ProxyProtocolHandler> proxyProtocolHandlerProvider,
|
||||
@EppProtocol Provider<ReadTimeoutHandler> readTimeoutHandlerProvider,
|
||||
Provider<LengthFieldBasedFrameDecoder> lengthFieldBasedFrameDecoderProvider,
|
||||
Provider<LengthFieldPrepender> lengthFieldPrependerProvider,
|
||||
Provider<EppServiceHandler> eppServiceHandlerProvider,
|
||||
Provider<LoggingHandler> loggingHandlerProvider,
|
||||
Provider<FullHttpRequestRelayHandler> relayHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
proxyProtocolHandlerProvider,
|
||||
sslServerInitializerProvider,
|
||||
readTimeoutHandlerProvider,
|
||||
lengthFieldBasedFrameDecoderProvider,
|
||||
lengthFieldPrependerProvider,
|
||||
eppServiceHandlerProvider,
|
||||
loggingHandlerProvider,
|
||||
relayHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static LengthFieldBasedFrameDecoder provideLengthFieldBasedFrameDecoder(ProxyConfig config) {
|
||||
return new LengthFieldBasedFrameDecoder(
|
||||
// Max message length.
|
||||
config.epp.maxMessageLengthBytes,
|
||||
// Header field location offset.
|
||||
0,
|
||||
// Header field length.
|
||||
config.epp.headerLengthBytes,
|
||||
// Adjustment applied to the header field value in order to obtain message length.
|
||||
-config.epp.headerLengthBytes,
|
||||
// Initial bytes to strip (i. e. strip the length header).
|
||||
config.epp.headerLengthBytes);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static LengthFieldPrepender provideLengthFieldPrepender(ProxyConfig config) {
|
||||
return new LengthFieldPrepender(
|
||||
// Header field length.
|
||||
config.epp.headerLengthBytes,
|
||||
// Length includes header field length.
|
||||
true);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static ReadTimeoutHandler provideReadTimeoutHandler(ProxyConfig config) {
|
||||
return new ReadTimeoutHandler(config.epp.readTimeoutSeconds);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("hello")
|
||||
static byte[] provideHelloBytes() {
|
||||
try {
|
||||
return readResourceBytes(EppProtocolModule.class, "resources/hello.xml").read();
|
||||
} catch (IOException e) {
|
||||
logger.severe(e, "Cannot read EPP <hello> message file.");
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static EppServiceHandler provideEppServiceHandler(
|
||||
@Named("accessToken") Supplier<String> accessTokenSupplier,
|
||||
@Named("hello") byte[] helloBytes,
|
||||
FrontendMetrics metrics,
|
||||
ProxyConfig config) {
|
||||
return new EppServiceHandler(
|
||||
config.epp.relayHost,
|
||||
config.epp.relayPath,
|
||||
accessTokenSupplier,
|
||||
config.epp.serverHostname,
|
||||
helloBytes,
|
||||
metrics);
|
||||
}
|
||||
}
|
76
java/google/registry/proxy/HealthCheckProtocolModule.java
Normal file
76
java/google/registry/proxy/HealthCheckProtocolModule.java
Normal file
|
@ -0,0 +1,76 @@
|
|||
// 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 com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.handler.HealthCheckHandler;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.FixedLengthFrameDecoder;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Module that provides a {@link FrontendProtocol} used for GCP load balancer health checking.
|
||||
*
|
||||
* <p>The load balancer sends health checking messages to the GCE instances to assess whether they
|
||||
* are ready to receive traffic. No relay channel needs to be established for this protocol.
|
||||
*/
|
||||
@Module
|
||||
public class HealthCheckProtocolModule {
|
||||
|
||||
/** Dagger qualifier to provide health check protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HealthCheckProtocol {}
|
||||
|
||||
private static final String PROTOCOL_NAME = "health_check";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
@HealthCheckProtocol int healthCheckPort,
|
||||
@HealthCheckProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(healthCheckPort)
|
||||
.isHealthCheck(true)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<FixedLengthFrameDecoder> fixedLengthFrameDecoderProvider,
|
||||
Provider<HealthCheckHandler> healthCheckHandlerProvider) {
|
||||
return ImmutableList.of(fixedLengthFrameDecoderProvider, healthCheckHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static FixedLengthFrameDecoder provideFixedLengthFrameDecoder(ProxyConfig config) {
|
||||
return new FixedLengthFrameDecoder(config.healthCheck.checkRequest.length());
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HealthCheckHandler provideHealthCheckHandler(ProxyConfig config) {
|
||||
return new HealthCheckHandler(
|
||||
config.healthCheck.checkRequest, config.healthCheck.checkResponse);
|
||||
}
|
||||
}
|
97
java/google/registry/proxy/HttpsRelayProtocolModule.java
Normal file
97
java/google/registry/proxy/HttpsRelayProtocolModule.java
Normal file
|
@ -0,0 +1,97 @@
|
|||
// 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 com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.handler.BackendMetricsHandler;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpResponseRelayHandler;
|
||||
import google.registry.proxy.handler.SslClientInitializer;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Qualifier;
|
||||
|
||||
/**
|
||||
* Module that provides a {@link BackendProtocol.Builder} for HTTPS protocol.
|
||||
*
|
||||
* <p>Only a builder is provided because the client protocol itself depends on the remote host
|
||||
* address, which is provided in the server protocol module that relays to this client protocol
|
||||
* module, e. g. {@link WhoisProtocolModule}.
|
||||
*/
|
||||
@Module
|
||||
public class HttpsRelayProtocolModule {
|
||||
|
||||
/** Dagger qualifier to provide https relay protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HttpsRelayProtocol {}
|
||||
|
||||
private static final String PROTOCOL_NAME = "https_relay";
|
||||
|
||||
@Provides
|
||||
@HttpsRelayProtocol
|
||||
static BackendProtocol.Builder provideProtocolBuilder(
|
||||
ProxyConfig config,
|
||||
@HttpsRelayProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return Protocol.backendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(config.httpsRelay.port)
|
||||
.handlerProviders(handlerProviders);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsRelayProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
|
||||
Provider<HttpClientCodec> httpClientCodecProvider,
|
||||
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
|
||||
Provider<BackendMetricsHandler> backendMetricsHandlerProvider,
|
||||
Provider<LoggingHandler> loggingHandlerProvider,
|
||||
Provider<FullHttpResponseRelayHandler> relayHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
sslClientInitializerProvider,
|
||||
httpClientCodecProvider,
|
||||
httpObjectAggregatorProvider,
|
||||
backendMetricsHandlerProvider,
|
||||
loggingHandlerProvider,
|
||||
relayHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpClientCodec provideHttpClientCodec() {
|
||||
return new HttpClientCodec();
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpObjectAggregator provideHttpObjectAggregator(ProxyConfig config) {
|
||||
return new HttpObjectAggregator(config.httpsRelay.maxMessageLengthBytes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Provides
|
||||
@Named("relayTrustedCertificates")
|
||||
public static X509Certificate[] provideTrustedCertificates() {
|
||||
// null uses the system default trust store.
|
||||
return null;
|
||||
}
|
||||
}
|
78
java/google/registry/proxy/MetricsModule.java
Normal file
78
java/google/registry/proxy/MetricsModule.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
// 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 com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.client.googleapis.util.Utils;
|
||||
import com.google.api.services.monitoring.v3.Monitoring;
|
||||
import com.google.api.services.monitoring.v3.model.MonitoredResource;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.monitoring.metrics.MetricReporter;
|
||||
import google.registry.monitoring.metrics.MetricWriter;
|
||||
import google.registry.monitoring.metrics.stackdriver.StackdriverWriter;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Module that provides necessary bindings to instantiate a {@link MetricReporter} */
|
||||
@Module
|
||||
public class MetricsModule {
|
||||
|
||||
// TODO (b/64765479): change to GKE cluster and config in YAML file.
|
||||
private static final String MONITORED_RESOURCE_TYPE = "gce_instance";
|
||||
private static final String GCE_INSTANCE_ZONE = "us-east4-c";
|
||||
private static final String GCE_INSTANCE_ID = "5401454098973297721";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Monitoring provideMonitoring(GoogleCredential credential, ProxyConfig config) {
|
||||
return new Monitoring.Builder(
|
||||
Utils.getDefaultTransport(), Utils.getDefaultJsonFactory(), credential)
|
||||
.setApplicationName(config.projectId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static MetricWriter provideMetricWriter(Monitoring monitoringClient, ProxyConfig config) {
|
||||
// The MonitoredResource for GAE apps is not writable (and missing fields anyway) so we just
|
||||
// use the gce_instance resource type instead.
|
||||
return new StackdriverWriter(
|
||||
monitoringClient,
|
||||
config.projectId,
|
||||
new MonitoredResource()
|
||||
.setType(MONITORED_RESOURCE_TYPE)
|
||||
.setLabels(ImmutableMap.of("zone", GCE_INSTANCE_ZONE, "instance_id", GCE_INSTANCE_ID)),
|
||||
config.metrics.stackdriverMaxQps,
|
||||
config.metrics.stackdriverMaxPointsPerRequest);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static MetricReporter provideMetricReporter(MetricWriter metricWriter, ProxyConfig config) {
|
||||
return new MetricReporter(
|
||||
metricWriter,
|
||||
config.metrics.writeIntervalSeconds,
|
||||
new ThreadFactoryBuilder().setDaemon(true).build());
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {MetricsModule.class, ProxyModule.class})
|
||||
interface MetricsComponent {
|
||||
MetricReporter metricReporter();
|
||||
}
|
||||
}
|
130
java/google/registry/proxy/Protocol.java
Normal file
130
java/google/registry/proxy/Protocol.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
// 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 com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Provider;
|
||||
|
||||
/** Value class that encapsulates parameters of a specific connection. */
|
||||
public interface Protocol {
|
||||
|
||||
/** Key used to retrieve the {@link Protocol} from a {@link Channel}'s {@link Attribute}. */
|
||||
AttributeKey<Protocol> PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL_KEY");
|
||||
|
||||
/** Protocol name. */
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Port to bind to (for {@link FrontendProtocol}) or to connect to (for {@link BackendProtocol}).
|
||||
*/
|
||||
int port();
|
||||
|
||||
/** The {@link ChannelHandler} providers to use for the protocol, in order. */
|
||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders();
|
||||
|
||||
/**
|
||||
* A builder for {@link FrontendProtocol}, default is non-health-checking.
|
||||
*
|
||||
* @see HealthCheckProtocolModule
|
||||
*/
|
||||
static FrontendProtocol.Builder frontendBuilder() {
|
||||
return new AutoValue_Protocol_FrontendProtocol.Builder().isHealthCheck(false);
|
||||
}
|
||||
|
||||
static BackendProtocol.Builder backendBuilder() {
|
||||
return new AutoValue_Protocol_BackendProtocol.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic builder enabling chaining for concrete implementations.
|
||||
*
|
||||
* @param <B> builder of the concrete subtype of {@link Protocol}.
|
||||
* @param <P> type of the concrete subtype of {@link Protocol}.
|
||||
*/
|
||||
abstract class Builder<B extends Builder<B, P>, P extends Protocol> {
|
||||
|
||||
public abstract B name(String value);
|
||||
|
||||
public abstract B port(int port);
|
||||
|
||||
public abstract B handlerProviders(ImmutableList<Provider<? extends ChannelHandler>> value);
|
||||
|
||||
public abstract P build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection parameters for a connection from the client to the proxy.
|
||||
*
|
||||
* <p>This protocol is associated to a {@link NioSocketChannel} established by remote peer
|
||||
* connecting to the given {@code port} that the proxy is listening on.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class FrontendProtocol implements Protocol {
|
||||
|
||||
/**
|
||||
* The {@link BackendProtocol} used to establish a relay channel and relay the traffic to. Not
|
||||
* required for health check protocol.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract BackendProtocol relayProtocol();
|
||||
|
||||
public abstract boolean isHealthCheck();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder extends Protocol.Builder<Builder, FrontendProtocol> {
|
||||
public abstract Builder relayProtocol(BackendProtocol value);
|
||||
|
||||
public abstract Builder isHealthCheck(boolean value);
|
||||
|
||||
abstract FrontendProtocol autoBuild();
|
||||
|
||||
@Override
|
||||
public FrontendProtocol build() {
|
||||
FrontendProtocol frontendProtocol = autoBuild();
|
||||
Preconditions.checkState(
|
||||
frontendProtocol.isHealthCheck() || frontendProtocol.relayProtocol() != null,
|
||||
"Frontend protocol %s must define a relay protocol.",
|
||||
frontendProtocol.name());
|
||||
return frontendProtocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection parameters for a connection from the proxy to the GAE app.
|
||||
*
|
||||
* <p>This protocol is associated to a {@link NioSocketChannel} established by the proxy
|
||||
* connecting to a remote peer.
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class BackendProtocol implements Protocol {
|
||||
/** The hostname that the proxy connects to. */
|
||||
public abstract String host();
|
||||
|
||||
/** Builder of {@link BackendProtocol}. */
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder extends Protocol.Builder<Builder, BackendProtocol> {
|
||||
public abstract Builder host(String value);
|
||||
}
|
||||
}
|
||||
}
|
102
java/google/registry/proxy/ProxyConfig.java
Normal file
102
java/google/registry/proxy/ProxyConfig.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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 google.registry.config.YamlUtils.getConfigSettings;
|
||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** The POJO that YAML config files are deserialized into. */
|
||||
public class ProxyConfig {
|
||||
|
||||
enum Environment {
|
||||
PRODUCTION,
|
||||
SANDBOX,
|
||||
ALPHA,
|
||||
LOCAL,
|
||||
TEST,
|
||||
}
|
||||
|
||||
private static final String DEFAULT_CONFIG = "config/default-config.yaml";
|
||||
private static final String CUSTOM_CONFIG_FORMATTER = "config/proxy-config-%s.yaml";
|
||||
|
||||
public String projectId;
|
||||
public List<String> gcpScopes;
|
||||
public int accessTokenValidPeriodSeconds;
|
||||
public int accessTokenRefreshBeforeExpirySeconds;
|
||||
public String sslPemFilename;
|
||||
public Kms kms;
|
||||
public Epp epp;
|
||||
public Whois whois;
|
||||
public HealthCheck healthCheck;
|
||||
public HttpsRelay httpsRelay;
|
||||
public Metrics metrics;
|
||||
|
||||
/** Configuration options that apply to Cloud KMS. */
|
||||
public static class Kms {
|
||||
public String location;
|
||||
public String keyRing;
|
||||
public String cryptoKey;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to EPP protocol. */
|
||||
public static class Epp {
|
||||
public int port;
|
||||
public String relayHost;
|
||||
public String relayPath;
|
||||
public int maxMessageLengthBytes;
|
||||
public int headerLengthBytes;
|
||||
public int readTimeoutSeconds;
|
||||
public String serverHostname;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to WHOIS protocol. */
|
||||
public static class Whois {
|
||||
public int port;
|
||||
public String relayHost;
|
||||
public String relayPath;
|
||||
public int maxMessageLengthBytes;
|
||||
public int readTimeoutSeconds;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to GCP load balancer health check protocol. */
|
||||
public static class HealthCheck {
|
||||
public int port;
|
||||
public String checkRequest;
|
||||
public String checkResponse;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to HTTPS relay protocol. */
|
||||
public static class HttpsRelay {
|
||||
public int port;
|
||||
public int maxMessageLengthBytes;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to Stackdriver monitoring metrics. */
|
||||
public static class Metrics {
|
||||
public int stackdriverMaxQps;
|
||||
public int stackdriverMaxPointsPerRequest;
|
||||
public int writeIntervalSeconds;
|
||||
}
|
||||
|
||||
static ProxyConfig getProxyConfig(Environment env) {
|
||||
String defaultYaml = readResourceUtf8(ProxyConfig.class, DEFAULT_CONFIG);
|
||||
String customYaml =
|
||||
readResourceUtf8(
|
||||
ProxyConfig.class, String.format(CUSTOM_CONFIG_FORMATTER, env.name().toLowerCase()));
|
||||
return getConfigSettings(defaultYaml, customYaml, ProxyConfig.class);
|
||||
}
|
||||
}
|
304
java/google/registry/proxy/ProxyModule.java
Normal file
304
java/google/registry/proxy/ProxyModule.java
Normal file
|
@ -0,0 +1,304 @@
|
|||
// 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 google.registry.util.ResourceUtils.readResourceBytes;
|
||||
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.common.base.Optional;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
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.FormattingLogger;
|
||||
import google.registry.util.SystemClock;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
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 {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@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;
|
||||
|
||||
/**
|
||||
* Enable FINE level logging.
|
||||
*
|
||||
* <p>Set the loggers log level to {@code FINE}, and also add a console handler that actually
|
||||
* output {@code FINE} level messages to stdout. The handler filters out all non FINE level log
|
||||
* record to avoid double logging. Log records at level higher than FINE (e. g. INFO) will be
|
||||
* handled at parent loggers. Note that {@code FINE} level corresponds to {@code DEBUG} level in
|
||||
* Netty.
|
||||
*/
|
||||
private static void enableDebugLevelLogging() {
|
||||
ImmutableList<Logger> parentLoggers =
|
||||
ImmutableList.of(
|
||||
Logger.getLogger("io.netty.handler.logging.LoggingHandler"),
|
||||
// Parent of all FormattingLoggers, so that we do not have to configure each
|
||||
// FormattingLogger individually.
|
||||
Logger.getLogger("google.registry.proxy"));
|
||||
for (Logger logger : parentLoggers) {
|
||||
logger.setLevel(Level.FINE);
|
||||
Handler handler = new ConsoleHandler();
|
||||
handler.setFilter(record -> Objects.equals(record.getLevel(), Level.FINE));
|
||||
handler.setLevel(Level.FINE);
|
||||
logger.addHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if (log) {
|
||||
logger.info("DEBUG LOGGING: ENABLED");
|
||||
enableDebugLevelLogging();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
int provideWhoisPort(ProxyConfig config) {
|
||||
return Optional.fromNullable(whoisPort).or(config.whois.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
int provideEppPort(ProxyConfig config) {
|
||||
return Optional.fromNullable(eppPort).or(config.epp.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckProtocol
|
||||
int provideHealthCheckPort(ProxyConfig config) {
|
||||
return Optional.fromNullable(healthCheckPort).or(config.healthCheck.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ImmutableMap<Integer, FrontendProtocol> providePortToProtocolMap(
|
||||
Set<FrontendProtocol> protocolSet) {
|
||||
return Maps.uniqueIndex(protocolSet, Protocol::port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
Environment provideEnvironment() {
|
||||
return env;
|
||||
}
|
||||
|
||||
/** Shared logging handler, set to default DEBUG(FINE) level. */
|
||||
@Singleton
|
||||
@Provides
|
||||
static LoggingHandler provideLoggingHandler() {
|
||||
return new LoggingHandler();
|
||||
}
|
||||
|
||||
@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) {
|
||||
logger.severe(e, "Unable to obtain OAuth2 credential.");
|
||||
throw new RuntimeException(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) {
|
||||
logger.severe(e, "Cannot refresh access token.");
|
||||
throw new RuntimeException(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
|
||||
@Named("encryptedPemBytes")
|
||||
static byte[] provideEncryptedPemBytes(ProxyConfig config) {
|
||||
try {
|
||||
return readResourceBytes(ProxyModule.class, "resources/" + config.sslPemFilename).read();
|
||||
} catch (IOException e) {
|
||||
logger.severefmt(e, "Error reading encrypted PEM file: %s", config.sslPemFilename);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static PemBytes 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 PemBytes.create(
|
||||
cloudKms
|
||||
.projects()
|
||||
.locations()
|
||||
.keyRings()
|
||||
.cryptoKeys()
|
||||
.decrypt(cryptoKeyUrl, decryptRequest)
|
||||
.execute()
|
||||
.decodePlaintext());
|
||||
} catch (IOException e) {
|
||||
logger.severefmt(e, "PEM file decryption failed using CryptoKey: %s", cryptoKeyUrl);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
static SslProvider provideSslProvider() {
|
||||
// Prefer OpenSSL.
|
||||
return OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Clock provideClock() {
|
||||
return new SystemClock();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
ProxyConfig provideProxyConfig(Environment env) {
|
||||
return getProxyConfig(env);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class for decrypted bytes of the PEM file.
|
||||
*
|
||||
* <p>Note that this should not be an @AutoValue class because we need a clone of the bytes to be
|
||||
* returned, otherwise the wrapper class becomes mutable.
|
||||
*/
|
||||
// TODO: remove this class once FOSS build can use @BindsInstance to bind a byte[]
|
||||
// (https://github.com/bazelbuild/bazel/issues/4138)
|
||||
static class PemBytes {
|
||||
|
||||
private final byte[] bytes;
|
||||
|
||||
static final PemBytes create(byte[] bytes) {
|
||||
return new PemBytes(bytes);
|
||||
}
|
||||
|
||||
private PemBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
byte[] getBytes() {
|
||||
return bytes.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
)
|
||||
interface ProxyComponent {
|
||||
ImmutableMap<Integer, FrontendProtocol> portToProtocolMap();
|
||||
}
|
||||
}
|
239
java/google/registry/proxy/ProxyServer.java
Normal file
239
java/google/registry/proxy/ProxyServer.java
Normal file
|
@ -0,0 +1,239 @@
|
|||
// 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 google.registry.proxy.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.proxy.handler.RelayHandler.RELAY_CHANNEL_KEY;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.monitoring.metrics.MetricReporter;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.ProxyModule.ProxyComponent;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import io.netty.util.internal.logging.JdkLoggerFactory;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import javax.inject.Provider;
|
||||
|
||||
/**
|
||||
* A multi-protocol proxy server that listens on port(s) specified in {@link
|
||||
* ProxyModule.ProxyComponent#portToProtocolMap()} }.
|
||||
*/
|
||||
public class ProxyServer implements Runnable {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
private static final MetricReporter metricReporter =
|
||||
DaggerMetricsModule_MetricsComponent.create().metricReporter();
|
||||
/** Maximum length of the queue of incoming connections. */
|
||||
private static final int MAX_SOCKET_BACKLOG = 128;
|
||||
|
||||
private final ImmutableMap<Integer, FrontendProtocol> portToProtocolMap;
|
||||
private final HashMap<Integer, Channel> portToChannelMap = new HashMap<>();
|
||||
private final EventLoopGroup eventGroup = new NioEventLoopGroup();
|
||||
|
||||
ProxyServer(ProxyComponent proxyComponent) {
|
||||
this.portToProtocolMap = proxyComponent.portToProtocolMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ChannelInitializer} for connections from a client of a certain protocol.
|
||||
*
|
||||
* <p>The {@link #initChannel} method does the following:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Determine the {@link FrontendProtocol} of the inbound {@link Channel} from its parent
|
||||
* {@link Channel}, i. e. the {@link Channel} that binds to local port and listens.
|
||||
* <li>Add handlers for the {@link FrontendProtocol} to the inbound {@link Channel}.
|
||||
* <li>Establish an outbound {@link Channel} that serves as the relay channel of the inbound
|
||||
* {@link Channel}, as specified by {@link FrontendProtocol#relayProtocol}.
|
||||
* <li>After the outbound {@link Channel} connects successfully, enable {@link
|
||||
* ChannelOption#AUTO_READ} on the inbound {@link Channel} to start reading.
|
||||
* </ol>
|
||||
*/
|
||||
private static class ServerChannelInitializer extends ChannelInitializer<NioSocketChannel> {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel inboundChannel) throws Exception {
|
||||
// Add inbound channel handlers.
|
||||
FrontendProtocol inboundProtocol =
|
||||
(FrontendProtocol) inboundChannel.parent().attr(PROTOCOL_KEY).get();
|
||||
inboundChannel.attr(PROTOCOL_KEY).set(inboundProtocol);
|
||||
addHandlers(inboundChannel.pipeline(), inboundProtocol.handlerProviders());
|
||||
|
||||
if (inboundProtocol.isHealthCheck()) {
|
||||
// A health check server protocol has no relay channel. It simply replies to incoming
|
||||
// request with a preset response.
|
||||
inboundChannel.config().setAutoRead(true);
|
||||
} else {
|
||||
// Connect to the relay (outbound) channel specified by the BackendProtocol.
|
||||
BackendProtocol outboundProtocol = inboundProtocol.relayProtocol();
|
||||
Bootstrap bootstrap =
|
||||
new Bootstrap()
|
||||
// Use the same thread to connect to the relay channel, therefore avoiding
|
||||
// synchronization handling due to interactions between the two channels
|
||||
.group(inboundChannel.eventLoop())
|
||||
.channel(NioSocketChannel.class)
|
||||
.handler(
|
||||
new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel outboundChannel)
|
||||
throws Exception {
|
||||
addHandlers(
|
||||
outboundChannel.pipeline(), outboundProtocol.handlerProviders());
|
||||
}
|
||||
})
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
// Outbound channel relays to inbound channel.
|
||||
.attr(RELAY_CHANNEL_KEY, inboundChannel)
|
||||
.attr(PROTOCOL_KEY, outboundProtocol);
|
||||
ChannelFuture outboundChannelFuture =
|
||||
bootstrap.connect(outboundProtocol.host(), outboundProtocol.port());
|
||||
outboundChannelFuture.addListener(
|
||||
(ChannelFuture future) -> {
|
||||
if (future.isSuccess()) {
|
||||
Channel outboundChannel = future.channel();
|
||||
// Inbound channel relays to outbound channel.
|
||||
inboundChannel.attr(RELAY_CHANNEL_KEY).set(outboundChannel);
|
||||
// Outbound channel established successfully, inbound channel can start reading.
|
||||
// This setter also calls channel.read() to request read operation.
|
||||
inboundChannel.config().setAutoRead(true);
|
||||
logger.infofmt(
|
||||
"Relay established: %s <-> %s\nSERVER: %s\nCLIENT: %s",
|
||||
inboundProtocol.name(),
|
||||
outboundProtocol.name(),
|
||||
inboundChannel,
|
||||
outboundChannel);
|
||||
} else {
|
||||
logger.severefmt(
|
||||
future.cause(),
|
||||
"Cannot connect to relay channel for %s protocol connection from %s.",
|
||||
inboundProtocol.name(),
|
||||
inboundChannel.remoteAddress().getHostName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void addHandlers(
|
||||
ChannelPipeline channelPipeline,
|
||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
for (Provider<? extends ChannelHandler> handlerProvider : handlerProviders) {
|
||||
channelPipeline.addLast(handlerProvider.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ServerBootstrap serverBootstrap =
|
||||
new ServerBootstrap()
|
||||
.group(eventGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ServerChannelInitializer())
|
||||
.option(ChannelOption.SO_BACKLOG, MAX_SOCKET_BACKLOG)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
// Do not read before relay channel is established.
|
||||
.childOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
// Bind to each port specified in portToHandlersMap.
|
||||
portToProtocolMap.forEach(
|
||||
(port, protocol) -> {
|
||||
try {
|
||||
// Wait for binding to be established for each listening port.
|
||||
ChannelFuture serverChannelFuture = serverBootstrap.bind(port).sync();
|
||||
if (serverChannelFuture.isSuccess()) {
|
||||
logger.infofmt(
|
||||
"Start listening on port %s for %s protocol.", port, protocol.name());
|
||||
Channel serverChannel = serverChannelFuture.channel();
|
||||
serverChannel.attr(PROTOCOL_KEY).set(protocol);
|
||||
portToChannelMap.put(port, serverChannel);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.severefmt(
|
||||
e, "Cannot listen on port %s for %s protocol.", port, protocol.name());
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for all listening ports to close.
|
||||
portToChannelMap.forEach(
|
||||
(port, channel) -> {
|
||||
try {
|
||||
// Block until all server channels are closed.
|
||||
ChannelFuture unusedFuture = channel.closeFuture().sync();
|
||||
logger.infofmt(
|
||||
"Stop listening on port %s for %s protocol.",
|
||||
port, channel.attr(PROTOCOL_KEY).get().name());
|
||||
} catch (InterruptedException e) {
|
||||
logger.severefmt(
|
||||
e,
|
||||
"Listening on port %s for %s protocol interrupted.",
|
||||
port,
|
||||
channel.attr(PROTOCOL_KEY).get().name());
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
logger.info("Shutting down server...");
|
||||
Future<?> unusedFuture = eventGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Use JDK logger for Netty's LoggingHandler,
|
||||
// which is what google.registry.util.FormattingLog uses under the hood.
|
||||
InternalLoggerFactory.setDefaultFactory(JdkLoggerFactory.INSTANCE);
|
||||
|
||||
try {
|
||||
metricReporter.startAsync().awaitRunning(10, TimeUnit.SECONDS);
|
||||
logger.info("Started up MetricReporter");
|
||||
} catch (TimeoutException timeoutException) {
|
||||
logger.severefmt("Failed to initialize MetricReporter: %s", timeoutException);
|
||||
}
|
||||
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
metricReporter.stopAsync().awaitTerminated(10, TimeUnit.SECONDS);
|
||||
logger.info("Shut down MetricReporter");
|
||||
} catch (TimeoutException timeoutException) {
|
||||
logger.severefmt("Failed to stop MetricReporter: %s", timeoutException);
|
||||
}
|
||||
}));
|
||||
|
||||
ProxyComponent proxyComponent =
|
||||
DaggerProxyModule_ProxyComponent.builder()
|
||||
.proxyModule(new ProxyModule().parse(args))
|
||||
.build();
|
||||
new ProxyServer(proxyComponent).run();
|
||||
}
|
||||
}
|
98
java/google/registry/proxy/WhoisProtocolModule.java
Normal file
98
java/google/registry/proxy/WhoisProtocolModule.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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 com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
|
||||
import google.registry.proxy.handler.WhoisServiceHandler;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol} used for whois protocol. */
|
||||
@Module
|
||||
class WhoisProtocolModule {
|
||||
|
||||
/** Dagger qualifier to provide whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface WhoisProtocol {};
|
||||
|
||||
private static final String PROTOCOL_NAME = "whois";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideProtocol(
|
||||
ProxyConfig config,
|
||||
@WhoisProtocol int whoisPort,
|
||||
@WhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders,
|
||||
@HttpsRelayProtocol BackendProtocol.Builder backendProtocolBuilder) {
|
||||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(whoisPort)
|
||||
.handlerProviders(handlerProviders)
|
||||
.relayProtocol(backendProtocolBuilder.host(config.whois.relayHost).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
@WhoisProtocol Provider<ReadTimeoutHandler> readTimeoutHandlerProvider,
|
||||
Provider<LineBasedFrameDecoder> lineBasedFrameDecoderProvider,
|
||||
Provider<WhoisServiceHandler> whoisServiceHandlerProvider,
|
||||
Provider<LoggingHandler> loggingHandlerProvider,
|
||||
Provider<FullHttpRequestRelayHandler> relayHandlerProvider) {
|
||||
return ImmutableList.of(
|
||||
readTimeoutHandlerProvider,
|
||||
lineBasedFrameDecoderProvider,
|
||||
whoisServiceHandlerProvider,
|
||||
loggingHandlerProvider,
|
||||
relayHandlerProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||
ProxyConfig config,
|
||||
@Named("accessToken") Supplier<String> accessTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
return new WhoisServiceHandler(
|
||||
config.whois.relayHost, config.whois.relayPath, accessTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static LineBasedFrameDecoder provideLineBasedFrameDecoder(ProxyConfig config) {
|
||||
return new LineBasedFrameDecoder(config.whois.maxMessageLengthBytes);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@WhoisProtocol
|
||||
static ReadTimeoutHandler provideReadTimeoutHandler(ProxyConfig config) {
|
||||
return new ReadTimeoutHandler(config.whois.readTimeoutSeconds);
|
||||
}
|
||||
}
|
128
java/google/registry/proxy/config/default-config.yaml
Normal file
128
java/google/registry/proxy/config/default-config.yaml
Normal file
|
@ -0,0 +1,128 @@
|
|||
# This is the default configuration file for the proxy. Do not make changes to
|
||||
# it unless you are writing new features that requires you to. To customize an
|
||||
# individual deployment or environment, create a proxy-config.yaml file in the
|
||||
# same directory overriding only the values you wish to change. You may need
|
||||
# to override some of these values to configure and enable some services used in
|
||||
# production environments.
|
||||
|
||||
# GCP project ID
|
||||
projectId: your-gcp-project-id
|
||||
|
||||
# OAuth scope that the GoogleCredential will be constructed with. This list
|
||||
# should include all service scopes that the proxy depends on.
|
||||
gcpScopes:
|
||||
# The default OAuth scope granted to GCE instances. Local development instance
|
||||
# needs this scope to mimic running on GCE. Currently it is used to access
|
||||
# Cloud KMS and Stackdriver Monitoring APIs.
|
||||
- https://www.googleapis.com/auth/cloud-platform
|
||||
|
||||
# The OAuth scope required to be included in the access token for the GAE app
|
||||
# to authenticate.
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
|
||||
# Access token is valid for 60 minutes.
|
||||
#
|
||||
# See also: Data store
|
||||
# (https://developers.google.com/api-client-library/java/google-api-java-client/oauth2#data_store).
|
||||
accessTokenValidPeriodSeconds: 3600
|
||||
|
||||
# Access token is refreshed 1 minutes before expiry.
|
||||
#
|
||||
# This is the default refresh time used by
|
||||
# com.google.api.client.auth.oauth2.Credential#intercept.
|
||||
accessTokenRefreshBeforeExpirySeconds: 60
|
||||
|
||||
# Name of the encrypted PEM file.
|
||||
sslPemFilename: your-ssl.pem
|
||||
|
||||
# Strings used to construct the KMS crypto key URL.
|
||||
# See: https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyRings.cryptoKeys
|
||||
kms:
|
||||
# Location where your key ring is stored (global, us-east1, etc).
|
||||
location: your-kms-location
|
||||
|
||||
# Name of the KeyRing that contains the CryptoKey file.
|
||||
keyRing: your-kms-keyRing
|
||||
|
||||
# Name of the CryptoKey used to encrypt the PEM file.
|
||||
cryptoKey: your-kms-cryptoKey
|
||||
|
||||
epp:
|
||||
port: 700
|
||||
relayHost: registry-project-id.appspot.com
|
||||
relayPath: /_dr/epp
|
||||
|
||||
# Maximum input message length in bytes.
|
||||
#
|
||||
# The first 4 bytes in a message is the total length of message, in bytes.
|
||||
#
|
||||
# We accept a message up to 1 GB, which should be plentiful, if not over the
|
||||
# top. In fact we should probably limit this to a more reasonable number, as a
|
||||
# 1 GB message will likely cause the proxy to go out of memory.
|
||||
#
|
||||
# See also: RFC 5734 4 Data Unit Format
|
||||
# (https://tools.ietf.org/html/rfc5734#section-4).
|
||||
maxMessageLengthBytes: 1073741824
|
||||
|
||||
# Length of the header field in bytes.
|
||||
#
|
||||
# Note that value of the header field is the total length (in bytes) of the
|
||||
# message, including the header itself, the length of the epp xml instance is
|
||||
# therefore 4 bytes shorter than this value.
|
||||
headerLengthBytes: 4
|
||||
|
||||
# Time after which an idle connection will be closed.
|
||||
#
|
||||
# The RFC gives registry discretionary power to set a timeout period. 1 hr
|
||||
# should be reasonable enough for any registrar to login and submit their
|
||||
# request.
|
||||
readTimeoutSeconds: 3600
|
||||
|
||||
# Hostname of the EPP server.
|
||||
# TODO(b/64510444) Remove this after nomulus no longer check sni header.
|
||||
serverHostname: epp.yourdomain.tld
|
||||
|
||||
whois:
|
||||
port: 43
|
||||
relayHost: registry-project-id.appspot.com
|
||||
relayPath: /_dr/whois
|
||||
|
||||
# Maximum input message length in bytes.
|
||||
#
|
||||
# Domain name cannot be longer than 256 characters. 512-character message
|
||||
# length should be safe for most cases, including registrar queries.
|
||||
#
|
||||
# See also: RFC 1035 2.3.4 Size limits
|
||||
# (http://www.freesoft.org/CIE/RFC/1035/9.htm).
|
||||
maxMessageLengthBytes: 512
|
||||
|
||||
# Whois protocol is transient, the client should not establish a long lasting
|
||||
# idle connection.
|
||||
readTimeoutSeconds: 60
|
||||
|
||||
healthCheck:
|
||||
port: 11111
|
||||
|
||||
# Health checker request message, defined in GCP load balancer backend.
|
||||
checkRequest: HEALTH_CHECK_REQUEST
|
||||
|
||||
# Health checker response message, defined in GCP load balancer backend.
|
||||
checkResponse: HEALTH_CHECK_RESPONSE
|
||||
|
||||
httpsRelay:
|
||||
port: 443
|
||||
|
||||
# Maximum size of an HTTP message in bytes.
|
||||
maxMessageLengthBytes: 524288
|
||||
|
||||
metrics:
|
||||
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
|
||||
# API. The limit can be adjusted by contacting Cloud Support.
|
||||
stackdriverMaxQps: 30
|
||||
|
||||
# Max number of points that can be sent to Stackdriver in a single
|
||||
# TimeSeries.Create API call.
|
||||
stackdriverMaxPointsPerRequest: 200
|
||||
|
||||
# How often metrics are written.
|
||||
writeIntervalSeconds: 60
|
|
@ -0,0 +1 @@
|
|||
# Add environment-specific proxy configuration here.
|
|
@ -0,0 +1 @@
|
|||
# Add environment-specific proxy configuration here.
|
|
@ -0,0 +1 @@
|
|||
# Add environment-specific proxy configuration here.
|
|
@ -0,0 +1 @@
|
|||
# Add environment-specific proxy configuration here.
|
1
java/google/registry/proxy/config/proxy-config-test.yaml
Normal file
1
java/google/registry/proxy/config/proxy-config-test.yaml
Normal file
|
@ -0,0 +1 @@
|
|||
# This file is for test only. Leave it blank.
|
129
java/google/registry/proxy/handler/BackendMetricsHandler.java
Normal file
129
java/google/registry/proxy/handler/BackendMetricsHandler.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.proxy.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.proxy.handler.EppServiceHandler.CLIENT_CERTIFICATE_HASH_KEY;
|
||||
import static google.registry.proxy.handler.RelayHandler.RELAY_CHANNEL_KEY;
|
||||
|
||||
import google.registry.proxy.handler.RelayHandler.FullHttpResponseRelayHandler;
|
||||
import google.registry.proxy.metric.BackendMetrics;
|
||||
import google.registry.util.Clock;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Handler that records metrics a backend channel.
|
||||
*
|
||||
* <p>This handler is added right before {@link FullHttpResponseRelayHandler} in the backend
|
||||
* protocol handler provider method. {@link FullHttpRequest} outbound messages encounter this first
|
||||
* before being handed over to HTTP related handler. {@link FullHttpResponse} inbound messages are
|
||||
* first constructed (from plain bytes) by preceding handlers and then logged in this handler.
|
||||
*/
|
||||
public class BackendMetricsHandler extends ChannelDuplexHandler {
|
||||
|
||||
private final Clock clock;
|
||||
private final BackendMetrics metrics;
|
||||
|
||||
private String relayedProtocolName;
|
||||
private String clientCertHash;
|
||||
private Channel relayedChannel;
|
||||
|
||||
/**
|
||||
* A queue that saves the time at which a request is sent to the GAE app.
|
||||
*
|
||||
* <p>This queue is used to calculate HTTP request-response latency. HTTP 1.1 specification allows
|
||||
* for pipelining, in which a client can sent multiple requests without waiting for each
|
||||
* responses. Therefore a queue is needed to record all the requests that are sent but have not
|
||||
* yet received a response.
|
||||
*
|
||||
* <p>A server must send its response in the same order it receives requests. This invariance
|
||||
* guarantees that the request time at the head of the queue always corresponds to the response
|
||||
* received in {@link #channelRead}.
|
||||
*
|
||||
* @see <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html">RFC 2616 8.1.2.2
|
||||
* Pipelining</a>
|
||||
*/
|
||||
private final Queue<DateTime> requestSentTimeQueue = new ArrayDeque<>();
|
||||
|
||||
@Inject
|
||||
BackendMetricsHandler(Clock clock, BackendMetrics metrics) {
|
||||
this.clock = clock;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// Backend channel is always established after a frontend channel is connected, so this
|
||||
relayedChannel = ctx.channel().attr(RELAY_CHANNEL_KEY).get();
|
||||
checkNotNull(relayedChannel, "No frontend channel found.");
|
||||
relayedProtocolName = relayedChannel.attr(PROTOCOL_KEY).get().name();
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
checkArgument(msg instanceof FullHttpResponse, "Incoming response must be FullHttpResponse.");
|
||||
checkState(!requestSentTimeQueue.isEmpty(), "Response received before request is sent.");
|
||||
metrics.responseReceived(
|
||||
relayedProtocolName,
|
||||
clientCertHash,
|
||||
(FullHttpResponse) msg,
|
||||
clock.nowUtc().getMillis() - requestSentTimeQueue.remove().getMillis());
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
checkArgument(msg instanceof FullHttpRequest, "Outgoing request must be FullHttpRequest.");
|
||||
// For WHOIS, client certificate hash is always set to "none".
|
||||
// For EPP, the client hash attribute is set upon handshake completion, before the first HELLO
|
||||
// is sent to the server. Therefore the first call to write() with HELLO payload has access to
|
||||
// the hash in its channel attribute.
|
||||
if (clientCertHash == null) {
|
||||
clientCertHash =
|
||||
Optional.ofNullable(relayedChannel.attr(CLIENT_CERTIFICATE_HASH_KEY).get())
|
||||
.orElse("none");
|
||||
}
|
||||
FullHttpRequest request = (FullHttpRequest) msg;
|
||||
|
||||
// Record sent time before write finishes allows us to take network latency into account.
|
||||
DateTime sentTime = clock.nowUtc();
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.write(msg, promise)
|
||||
.addListener(
|
||||
future -> {
|
||||
if (future.isSuccess()) {
|
||||
// Only instrument request metrics when the request is actually sent to GAE.
|
||||
metrics.requestSent(relayedProtocolName, clientCertHash, request);
|
||||
requestSentTimeQueue.add(sentTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
154
java/google/registry/proxy/handler/EppServiceHandler.java
Normal file
154
java/google/registry/proxy/handler/EppServiceHandler.java
Normal file
|
@ -0,0 +1,154 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.proxy.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/** Handler that processes EPP protocol logic. */
|
||||
public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/**
|
||||
* Attribute key to the client certificate hash whose value is set when the certificate promise is
|
||||
* fulfilled.
|
||||
*/
|
||||
public static final AttributeKey<String> CLIENT_CERTIFICATE_HASH_KEY =
|
||||
AttributeKey.valueOf("CLIENT_CERTIFICATE_HASH_KEY");
|
||||
|
||||
/** Name of the HTTP header that stores the client certificate hash. */
|
||||
public static final String SSL_CLIENT_CERTIFICATE_HASH_FIELD = "X-SSL-Certificate";
|
||||
|
||||
/** Name of the HTTP header that stores the epp server name requested by the client using SNI. */
|
||||
// TODO(b/64510444): remove this header entirely when borg proxy is retired.
|
||||
public static final String REQUESTED_SERVERNAME_VIA_SNI_FIELD = "X-Requested-Servername-SNI";
|
||||
|
||||
/** Name of the HTTP header that stores the client IP address. */
|
||||
public static final String FORWARDED_FOR_FIELD = "X-Forwarded-For";
|
||||
|
||||
/** Name of the HTTP header that indicates if the EPP session should be closed. */
|
||||
public static final String EPP_SESSION_FIELD = "Epp-Session";
|
||||
|
||||
public static final String EPP_CONTENT_TYPE = "application/epp+xml";
|
||||
|
||||
private final String serverHostname;
|
||||
private final byte[] helloBytes;
|
||||
|
||||
private String sslClientCertificateHash;
|
||||
private String clientAddress;
|
||||
|
||||
public EppServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
String serverHostname,
|
||||
byte[] helloBytes,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, accessTokenSupplier, metrics);
|
||||
this.serverHostname = serverHostname;
|
||||
this.helloBytes = helloBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write <hello> to the server after SSL handshake completion to request <greeting>
|
||||
*
|
||||
* <p>When handling EPP over TCP, the server should issue a <greeting> to the client when a
|
||||
* connection is established. Nomulus app however does not automatically sends the <greeting> upon
|
||||
* connection. The proxy therefore first sends a <hello> to registry to request a <greeting>
|
||||
* response.
|
||||
*
|
||||
* <p>The <hello> request is only sent after SSL handshake is completed between the client and the
|
||||
* proxy so that the client certificate hash is available, which is needed to communicate with the
|
||||
* server. Because {@link SslHandshakeCompletionEvent} is triggered before any calls to {@link
|
||||
* #channelRead} are scheduled by the event loop executor, the <hello> request is guaranteed to be
|
||||
* the first message sent to the server.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5734">RFC 5732 EPP Transport over TCP</a>
|
||||
* @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">The Proxy
|
||||
* Protocol</a>
|
||||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
ctx.channel()
|
||||
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
||||
.get()
|
||||
.addListener(
|
||||
(Promise<X509Certificate> promise) -> {
|
||||
if (promise.isSuccess()) {
|
||||
sslClientCertificateHash = getCertificateHash(promise.get());
|
||||
// Set the client cert hash key attribute for both this channel,
|
||||
// used for collecting metrics on specific clients.
|
||||
ctx.channel().attr(CLIENT_CERTIFICATE_HASH_KEY).set(sslClientCertificateHash);
|
||||
clientAddress = ctx.channel().attr(REMOTE_ADDRESS_KEY).get();
|
||||
metrics.registerActiveConnection(
|
||||
"epp", sslClientCertificateHash, ctx.channel());
|
||||
channelRead(ctx, Unpooled.wrappedBuffer(helloBytes));
|
||||
} else {
|
||||
logger.severefmt(promise.cause(), "Cannot finish handshake.");
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
});
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
checkNotNull(clientAddress, "Cannot obtain client address.");
|
||||
checkNotNull(sslClientCertificateHash, "Cannot obtain client certificate hash.");
|
||||
FullHttpRequest request = super.decodeFullHttpRequest(byteBuf);
|
||||
request
|
||||
.headers()
|
||||
.set(SSL_CLIENT_CERTIFICATE_HASH_FIELD, sslClientCertificateHash)
|
||||
.set(REQUESTED_SERVERNAME_VIA_SNI_FIELD, serverHostname)
|
||||
.set(FORWARDED_FOR_FIELD, clientAddress)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, EPP_CONTENT_TYPE)
|
||||
.set(HttpHeaderNames.ACCEPT, EPP_CONTENT_TYPE);
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
checkArgument(msg instanceof HttpResponse);
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
String sessionAliveValue = response.headers().get(EPP_SESSION_FIELD);
|
||||
if (sessionAliveValue != null && sessionAliveValue.equals("close")) {
|
||||
promise.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
}
|
42
java/google/registry/proxy/handler/HealthCheckHandler.java
Normal file
42
java/google/registry/proxy/handler/HealthCheckHandler.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
// 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.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** A handler that responds to GCP load balancer health check message */
|
||||
public class HealthCheckHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final ByteBuf checkRequest;
|
||||
private final ByteBuf checkResponse;
|
||||
|
||||
public HealthCheckHandler(String checkRequest, String checkResponse) {
|
||||
this.checkRequest = Unpooled.wrappedBuffer(checkRequest.getBytes(StandardCharsets.US_ASCII));
|
||||
this.checkResponse = Unpooled.wrappedBuffer(checkResponse.getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
if (buf.equals(checkRequest)) {
|
||||
ctx.writeAndFlush(checkResponse);
|
||||
}
|
||||
buf.release();
|
||||
}
|
||||
}
|
185
java/google/registry/proxy/handler/HttpsRelayServiceHandler.java
Normal file
185
java/google/registry/proxy/handler/HttpsRelayServiceHandler.java
Normal file
|
@ -0,0 +1,185 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
|
||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handler that relays a single (framed) ByteBuf message to an HTTPS server.
|
||||
*
|
||||
* <p>This handler reads in a {@link ByteBuf}, converts it to an {@link FullHttpRequest}, and passes
|
||||
* it to the {@code channelRead} method of the next inbound handler the channel pipeline, which is
|
||||
* usually a {@link RelayHandler<FullHttpRequest>}. The relay handler writes the request to the
|
||||
* relay channel, which is connected to an HTTPS endpoint. After the relay channel receives a {@link
|
||||
* FullHttpResponse} back, its own relay handler writes the response back to this channel, which is
|
||||
* the relay channel of the relay channel. This handler then handles write request by encoding the
|
||||
* {@link FullHttpResponse} to a plain {@link ByteBuf}, and pass it down to the {@code write} method
|
||||
* of the next outbound handler in the channel pipeline, which eventually writes the response bytes
|
||||
* to the remote peer of this channel.
|
||||
*
|
||||
* <p>This handler is session aware and will store all the session cookies that the are contained in
|
||||
* the HTTP response headers, which are added back to headers of subsequent HTTP requests.
|
||||
*/
|
||||
abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||
private final String relayHost;
|
||||
private final String relayPath;
|
||||
private final Supplier<String> accessTokenSupplier;
|
||||
|
||||
protected final FrontendMetrics metrics;
|
||||
|
||||
HttpsRelayServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
this.relayHost = relayHost;
|
||||
this.relayPath = relayPath;
|
||||
this.accessTokenSupplier = accessTokenSupplier;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the {@link FullHttpRequest}.
|
||||
*
|
||||
* <p>This default method creates a bare-bone {@link FullHttpRequest} that may need to be
|
||||
* modified, e. g. adding headers specific for each protocol.
|
||||
*
|
||||
* @param byteBuf inbound message.
|
||||
*/
|
||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, relayPath);
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||
.set(HttpHeaderNames.HOST, relayHost)
|
||||
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + accessTokenSupplier.get())
|
||||
.set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
request.content().writeBytes(byteBuf);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load session cookies in the cookie store and write them in to the HTTP request.
|
||||
*
|
||||
* <p>Multiple cookies are folded into one {@code Cookie} header per RFC 6265.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6265#section-5.4">RFC 6265 5.4.The Cookie
|
||||
* Header</a>
|
||||
*/
|
||||
private void loadCookies(FullHttpRequest request) {
|
||||
if (!cookieStore.isEmpty()) {
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.COOKIE, ClientCookieEncoder.STRICT.encode(cookieStore.values()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out)
|
||||
throws Exception {
|
||||
FullHttpRequest request = decodeFullHttpRequest(byteBuf);
|
||||
loadCookies(request);
|
||||
out.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the {@link ByteBuf}
|
||||
*
|
||||
* <p>This default method puts all the response payload into the {@link ByteBuf}.
|
||||
*
|
||||
* @param fullHttpResponse outbound http response.
|
||||
*/
|
||||
ByteBuf encodeFullHttpResponse(FullHttpResponse fullHttpResponse) {
|
||||
return fullHttpResponse.content();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session cookies from the HTTP response header to the cookie store.
|
||||
*
|
||||
* <p>Multiple cookies are </b>not</b> folded in to one {@code Set-Cookie} header per RFC 6265.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc6265#section-3">RFC 6265 3.Overview</a>
|
||||
*/
|
||||
private void saveCookies(FullHttpResponse response) {
|
||||
for (String cookieString : response.headers().getAll(HttpHeaderNames.SET_COOKIE)) {
|
||||
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||
cookieStore.put(cookie.name(), cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FullHttpResponse response, ByteBuf byteBuf)
|
||||
throws Exception {
|
||||
checkArgument(
|
||||
response.status().equals(HttpResponseStatus.OK),
|
||||
"Cannot relay HTTP response status \"%s\"\n%s",
|
||||
response.status(),
|
||||
response.content().toString(UTF_8));
|
||||
saveCookies(response);
|
||||
byteBuf.writeBytes(encodeFullHttpResponse(response));
|
||||
}
|
||||
|
||||
/** Terminates connection upon inbound exception. */
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
logger.severefmt(cause, "Inbound exception caught for channel %s", ctx.channel());
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
|
||||
/** Terminates connection upon outbound exception. */
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
|
||||
throws Exception {
|
||||
promise.addListener(
|
||||
(ChannelFuture channelFuture) -> {
|
||||
if (!channelFuture.isSuccess()) {
|
||||
logger.severefmt(
|
||||
channelFuture.cause(),
|
||||
"Outbound exception caught for channel %s",
|
||||
channelFuture.channel());
|
||||
ChannelFuture unusedFuture = channelFuture.channel().close();
|
||||
}
|
||||
});
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
}
|
167
java/google/registry/proxy/handler/ProxyProtocolHandler.java
Normal file
167
java/google/registry/proxy/handler/ProxyProtocolHandler.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.util.AttributeKey;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Handler that processes possible existence of a PROXY protocol v1 header.
|
||||
*
|
||||
* <p>When an EPP client connects to the registry (through the proxy), the registry performs two
|
||||
* validations to ensure that only known registrars are allowed. First it checks the sha265 hash of
|
||||
* the client SSL certificate and match it to the hash stored in datastore for the registrar. It
|
||||
* then checks if the connection is from an whitelisted IP address that belongs to that registrar.
|
||||
*
|
||||
* <p>The proxy receives client connects via the GCP load balancer, which results in the loss of
|
||||
* original client IP from the channel. Luckily, the load balancer supports the PROXY protocol v1,
|
||||
* which adds a header with source IP information, among other things, to the TCP request at the
|
||||
* start of the connection.
|
||||
*
|
||||
* <p>This handler determines if a connection is proxied (PROXY protocol v1 header present) and
|
||||
* correctly sets the source IP address to the channel's attribute regardless of whether it is
|
||||
* proxied. After that it removes itself from the channel pipeline because the proxy header is only
|
||||
* present at the beginning of the connection.
|
||||
*
|
||||
* <p>This handler must be the very first handler in a protocol, even before SSL handlers, because
|
||||
* PROXY protocol header comes as the very first thing, even before SSL handshake request.
|
||||
*
|
||||
* @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">The PROXY protocol</a>
|
||||
*/
|
||||
public class ProxyProtocolHandler extends ByteToMessageDecoder {
|
||||
|
||||
/** Key used to retrieve origin IP address from a channel's attribute. */
|
||||
public static final AttributeKey<String> REMOTE_ADDRESS_KEY =
|
||||
AttributeKey.valueOf("REMOTE_ADDRESS_KEY");
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
// The proxy header must start with this prefix.
|
||||
// Sample header: "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n".
|
||||
private static final byte[] HEADER_PREFIX = "PROXY".getBytes(US_ASCII);
|
||||
|
||||
private boolean finished = false;
|
||||
private String proxyHeader = null;
|
||||
|
||||
@Inject
|
||||
ProxyProtocolHandler() {}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
super.channelRead(ctx, msg);
|
||||
if (finished) {
|
||||
if (proxyHeader != null) {
|
||||
logger.finefmt("PROXIED CONNECTION: %s", ctx.channel());
|
||||
logger.finefmt("PROXY HEADER: %s", proxyHeader);
|
||||
ctx.channel().attr(REMOTE_ADDRESS_KEY).set(proxyHeader.split(" ")[2]);
|
||||
} else {
|
||||
SocketAddress remoteAddress = ctx.channel().remoteAddress();
|
||||
if (remoteAddress instanceof InetSocketAddress) {
|
||||
ctx.channel()
|
||||
.attr(REMOTE_ADDRESS_KEY)
|
||||
.set(((InetSocketAddress) remoteAddress).getAddress().getHostAddress());
|
||||
logger.finefmt("REMOTE IP ADDRESS: %s", ctx.channel().attr(REMOTE_ADDRESS_KEY).get());
|
||||
}
|
||||
}
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to decode an internally accumulated buffer and find the proxy protocol header.
|
||||
*
|
||||
* <p>When the connection is not proxied (i. e. the initial bytes are not "PROXY"), simply set
|
||||
* {@link #finished} to true and allow the handler to be removed. Otherwise the handler waits
|
||||
* until there's enough bytes to parse the header, save the parsed header to {@link #proxyHeader},
|
||||
* and then mark {@link #finished}.
|
||||
*
|
||||
* @param in internally accumulated buffer, newly arrived bytes are appended to it.
|
||||
* @param out objects passed to the next handler, in this case nothing is ever passed because the
|
||||
* header itself is processed and written to the attribute of the proxy, and the handler is
|
||||
* then removed from the pipeline.
|
||||
*/
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
// Wait until there are more bytes available than the header's length before processing.
|
||||
if (in.readableBytes() >= HEADER_PREFIX.length) {
|
||||
if (containsHeader(in)) {
|
||||
// The inbound message contains the header, it must be a proxied connection. Note that
|
||||
// currently proxied connection is only used for EPP protocol, which requires the connection
|
||||
// to be SSL enabled. So the beginning of the inbound message upon connection can only be
|
||||
// either the proxy header (when proxied), or SSL handshake request (when not proxied),
|
||||
// which does not start with "PROXY". Therefore it is safe to assume that if the beginning
|
||||
// of the message contains "PROXY", it must be proxied, and must contain \r\n.
|
||||
int eol = findEndOfLine(in);
|
||||
// If eol is not found, that is because that we do not yet have enough inbound message, do
|
||||
// nothing and wait for more bytes to be readable. eol will eventually be positive because
|
||||
// of the reasoning above: The connection starts with "PROXY", so it must be a proxied
|
||||
// connection and contain \r\n.
|
||||
if (eol >= 0) {
|
||||
// ByteBuf.readBytes is called so that the header is processed and not passed to handlers
|
||||
// further in the pipeline.
|
||||
proxyHeader = in.readBytes(eol).toString(US_ASCII);
|
||||
// Skip \r\n.
|
||||
in.skipBytes(2);
|
||||
// Proxy header processed, mark finished so that this handler is removed.
|
||||
finished = true;
|
||||
}
|
||||
} else {
|
||||
// The inbound message does not contain a proxy header, mark finished so that this handler
|
||||
// is removed. Note that no inbound bytes are actually processed by this handler because we
|
||||
// did not call ByteBuf.readBytes(), but ByteBuf.getByte(), which does not change reader
|
||||
// index of the ByteBuf. So any inbound byte is then passed to the next handler to process.
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index in the buffer of the end of line found. Returns -1 if no end of line was
|
||||
* found in the buffer.
|
||||
*/
|
||||
private static int findEndOfLine(final ByteBuf buffer) {
|
||||
final int n = buffer.writerIndex();
|
||||
for (int i = buffer.readerIndex(); i < n; i++) {
|
||||
final byte b = buffer.getByte(i);
|
||||
if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
|
||||
return i; // \r\n
|
||||
}
|
||||
}
|
||||
return -1; // Not found.
|
||||
}
|
||||
|
||||
/** Checks if the given buffer contains the proxy header prefix. */
|
||||
private boolean containsHeader(ByteBuf buffer) {
|
||||
// The readable bytes is always more or equal to the size of the header prefix because this
|
||||
// method is only called when this condition is true.
|
||||
checkState(buffer.readableBytes() >= HEADER_PREFIX.length);
|
||||
for (int i = 0; i < HEADER_PREFIX.length; ++i) {
|
||||
if (buffer.getByte(buffer.readerIndex() + i) != HEADER_PREFIX[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
99
java/google/registry/proxy/handler/RelayHandler.java
Normal file
99
java/google/registry/proxy/handler/RelayHandler.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Receives inbound massage of type {@code I}, and writes it to the {@code relayChannel} stored in
|
||||
* the inbound channel's attribute.
|
||||
*/
|
||||
public class RelayHandler<I> extends SimpleChannelInboundHandler<I> {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/** Key used to retrieve the relay channel from a {@link Channel}'s {@link Attribute}. */
|
||||
public static final AttributeKey<Channel> RELAY_CHANNEL_KEY =
|
||||
AttributeKey.valueOf("RELAY_CHANNEL");
|
||||
|
||||
public RelayHandler(Class<? extends I> clazz) {
|
||||
super(clazz, false);
|
||||
}
|
||||
|
||||
/** Terminate connection when an exception is caught during inbound IO. */
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
logger.severefmt(cause, "Inbound exception caught for channel %s", ctx.channel());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
/** Close relay channel if this channel is closed. */
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel relayChannel = ctx.channel().attr(RELAY_CHANNEL_KEY).get();
|
||||
if (relayChannel != null) {
|
||||
relayChannel.close();
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
/** Read message of type {@code I}, write it as-is into the relay channel. */
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception {
|
||||
Channel relayChannel = ctx.channel().attr(RELAY_CHANNEL_KEY).get();
|
||||
checkNotNull(relayChannel, "Relay channel not specified for channel: %s", ctx.channel());
|
||||
if (relayChannel.isActive()) {
|
||||
// Relay channel is open, write to it.
|
||||
ChannelFuture channelFuture = relayChannel.writeAndFlush(msg);
|
||||
channelFuture.addListener(
|
||||
future -> {
|
||||
// Cannot write into relay channel, close this channel.
|
||||
if (!future.isSuccess()) {
|
||||
ctx.close();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// close this channel if the relay channel is closed.
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Specialized {@link RelayHandler} that takes a {@link FullHttpRequest} as inbound payload. */
|
||||
public static class FullHttpRequestRelayHandler extends RelayHandler<FullHttpRequest> {
|
||||
@Inject
|
||||
public FullHttpRequestRelayHandler() {
|
||||
super(FullHttpRequest.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Specialized {@link RelayHandler} that takes a {@link FullHttpResponse} as inbound payload. */
|
||||
public static class FullHttpResponseRelayHandler extends RelayHandler<FullHttpResponse> {
|
||||
@Inject
|
||||
public FullHttpResponseRelayHandler() {
|
||||
super(FullHttpResponse.class);
|
||||
}
|
||||
}
|
||||
}
|
80
java/google/registry/proxy/handler/SslClientInitializer.java
Normal file
80
java/google/registry/proxy/handler/SslClientInitializer.java
Normal file
|
@ -0,0 +1,80 @@
|
|||
// 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.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static google.registry.proxy.Protocol.PROTOCOL_KEY;
|
||||
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
|
||||
/**
|
||||
* Adds a client side SSL handler to the channel pipeline.
|
||||
*
|
||||
* <p>This <b>must</b> be the first handler provided for any handler provider list, if it is
|
||||
* provided. The type parameter {@code C} is needed so that unit tests can construct this handler
|
||||
* that works with {@link EmbeddedChannel};
|
||||
*/
|
||||
@Singleton
|
||||
@Sharable
|
||||
public class SslClientInitializer<C extends Channel> extends ChannelInitializer<C> {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
private final SslProvider sslProvider;
|
||||
private final X509Certificate[] trustedCertificates;
|
||||
|
||||
@Inject
|
||||
SslClientInitializer(
|
||||
SslProvider sslProvider,
|
||||
@Nullable @Named("relayTrustedCertificates") X509Certificate... trustCertificates) {
|
||||
logger.finefmt("Client SSL Provider: %s", sslProvider);
|
||||
this.sslProvider = sslProvider;
|
||||
this.trustedCertificates = trustCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(C channel) throws Exception {
|
||||
BackendProtocol protocol = (BackendProtocol) channel.attr(PROTOCOL_KEY).get();
|
||||
checkNotNull(protocol, "Protocol is not set for channel: %s", channel);
|
||||
SslHandler sslHandler =
|
||||
SslContextBuilder.forClient()
|
||||
.sslProvider(sslProvider)
|
||||
.trustManager(trustedCertificates)
|
||||
.build()
|
||||
.newHandler(channel.alloc(), protocol.host(), protocol.port());
|
||||
|
||||
// Enable hostname verification.
|
||||
SSLEngine sslEngine = sslHandler.engine();
|
||||
SSLParameters sslParameters = sslEngine.getSSLParameters();
|
||||
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
|
||||
sslEngine.setSSLParameters(sslParameters);
|
||||
|
||||
channel.pipeline().addLast(sslHandler);
|
||||
}
|
||||
}
|
105
java/google/registry/proxy/handler/SslServerInitializer.java
Normal file
105
java/google/registry/proxy/handler/SslServerInitializer.java
Normal file
|
@ -0,0 +1,105 @@
|
|||
// 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.handler;
|
||||
|
||||
import google.registry.util.FormattingLogger;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.ssl.ClientAuth;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Adds a server side SSL handler to the channel pipeline.
|
||||
*
|
||||
* <p>This <b>should</b> be the first handler provided for any handler provider list, if it is
|
||||
* provided. Unless you wish to first process the PROXY header with {@link ProxyProtocolHandler},
|
||||
* which should come before this handler. The type parameter {@code C} is needed so that unit tests
|
||||
* can construct this handler that works with {@link EmbeddedChannel};
|
||||
*
|
||||
* <p>The ssl handler added requires client authentication, but it uses an {@link
|
||||
* InsecureTrustManagerFactory}, which accepts any ssl certificate presented by the client, as long
|
||||
* as the client uses the corresponding private key to establish SSL handshake. The client
|
||||
* certificate hash will be passed along to GAE as an HTTP header for verification (not handled by
|
||||
* this handler).
|
||||
*/
|
||||
@Singleton
|
||||
@Sharable
|
||||
public class SslServerInitializer<C extends Channel> extends ChannelInitializer<C> {
|
||||
|
||||
/**
|
||||
* Attribute key to the client certificate promise whose value is set when SSL handshake completes
|
||||
* successfully.
|
||||
*/
|
||||
public static final AttributeKey<Promise<X509Certificate>> CLIENT_CERTIFICATE_PROMISE_KEY =
|
||||
AttributeKey.valueOf("CLIENT_CERTIFICATE_PROMISE_KEY");
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
private final SslProvider sslProvider;
|
||||
private final PrivateKey privateKey;
|
||||
private final X509Certificate[] certificates;
|
||||
|
||||
@Inject
|
||||
SslServerInitializer(
|
||||
SslProvider sslProvider,
|
||||
PrivateKey privateKey,
|
||||
@Named("eppServerCertificates") X509Certificate... certificates) {
|
||||
logger.finefmt("Server SSL Provider: %s", sslProvider);
|
||||
this.sslProvider = sslProvider;
|
||||
this.privateKey = privateKey;
|
||||
this.certificates = certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(C channel) throws Exception {
|
||||
SslHandler sslHandler =
|
||||
SslContextBuilder.forServer(privateKey, certificates)
|
||||
.sslProvider(sslProvider)
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.clientAuth(ClientAuth.REQUIRE)
|
||||
.build()
|
||||
.newHandler(channel.alloc());
|
||||
Promise<X509Certificate> clientCertificatePromise = channel.eventLoop().newPromise();
|
||||
Future<Channel> unusedFuture =
|
||||
sslHandler
|
||||
.handshakeFuture()
|
||||
.addListener(
|
||||
future -> {
|
||||
if (future.isSuccess()) {
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
clientCertificatePromise.setSuccess(
|
||||
(X509Certificate)
|
||||
sslHandler.engine().getSession().getPeerCertificates()[0]);
|
||||
} else {
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
clientCertificatePromise.setFailure(future.cause());
|
||||
}
|
||||
});
|
||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).set(clientCertificatePromise);
|
||||
channel.pipeline().addLast(sslHandler);
|
||||
}
|
||||
}
|
54
java/google/registry/proxy/handler/WhoisServiceHandler.java
Normal file
54
java/google/registry/proxy/handler/WhoisServiceHandler.java
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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.handler;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
|
||||
/** Handler that processes WHOIS protocol logic. */
|
||||
public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
||||
|
||||
public WhoisServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<String> accessTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, accessTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
metrics.registerActiveConnection("whois", "none", ctx.channel());
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
FullHttpRequest request = super.decodeFullHttpRequest(byteBuf);
|
||||
request
|
||||
.headers()
|
||||
// Close connection after a response is received, per RFC-3912
|
||||
// https://tools.ietf.org/html/rfc3912
|
||||
.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
|
||||
.set(HttpHeaderNames.ACCEPT, HttpHeaderValues.TEXT_PLAIN);
|
||||
return request;
|
||||
}
|
||||
}
|
125
java/google/registry/proxy/metric/BackendMetrics.java
Normal file
125
java/google/registry/proxy/metric/BackendMetrics.java
Normal file
|
@ -0,0 +1,125 @@
|
|||
// 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.metric;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.monitoring.metrics.CustomFitter;
|
||||
import google.registry.monitoring.metrics.EventMetric;
|
||||
import google.registry.monitoring.metrics.ExponentialFitter;
|
||||
import google.registry.monitoring.metrics.FibonacciFitter;
|
||||
import google.registry.monitoring.metrics.IncrementableMetric;
|
||||
import google.registry.monitoring.metrics.LabelDescriptor;
|
||||
import google.registry.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Backend metrics instrumentation. */
|
||||
public class BackendMetrics {
|
||||
|
||||
// Maximum request size is defined in the config file, this is not realistic and we'd be out of
|
||||
// memory when the size approach 1 GB.
|
||||
private static final CustomFitter DEFAULT_SIZE_FITTER = FibonacciFitter.create(1073741824);
|
||||
|
||||
// Maximum 1 hour latency, this is not specified by the spec, but given we have a one hour idle
|
||||
// timeout, it seems reasonable that maximum latency is set to 1 hour as well. If we are
|
||||
// approaching anywhere near 1 hour latency, we'd be way out of SLO anyway.
|
||||
private static final ExponentialFitter DEFAULT_LATENCY_FITTER =
|
||||
ExponentialFitter.create(22, 2, 1.0);
|
||||
|
||||
private static final ImmutableSet<LabelDescriptor> LABELS =
|
||||
ImmutableSet.of(
|
||||
LabelDescriptor.create("protocol", "Name of the protocol."),
|
||||
LabelDescriptor.create(
|
||||
"client_cert_hash", "SHA256 hash of the client certificate, if available."));
|
||||
|
||||
static final IncrementableMetric requestsCounter =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/proxy/backend/requests",
|
||||
"Total number of requests send to the backend.",
|
||||
"Requests",
|
||||
LABELS);
|
||||
|
||||
static final IncrementableMetric responsesCounter =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/proxy/backend/responses",
|
||||
"Total number of responses received by the backend.",
|
||||
"Responses",
|
||||
ImmutableSet.<LabelDescriptor>builder()
|
||||
.addAll(LABELS)
|
||||
.add(LabelDescriptor.create("status", "HTTP status code."))
|
||||
.build());
|
||||
|
||||
static final EventMetric requestBytes =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newEventMetric(
|
||||
"/proxy/backend/request_bytes",
|
||||
"Size of the backend requests sent.",
|
||||
"Bytes",
|
||||
LABELS,
|
||||
DEFAULT_SIZE_FITTER);
|
||||
|
||||
static final EventMetric responseBytes =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newEventMetric(
|
||||
"/proxy/backend/response_bytes",
|
||||
"Size of the backend responses received.",
|
||||
"Bytes",
|
||||
LABELS,
|
||||
DEFAULT_SIZE_FITTER);
|
||||
|
||||
static final EventMetric latencyMs =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newEventMetric(
|
||||
"/proxy/backend/latency_ms",
|
||||
"Round-trip time between a request sent and its corresponding response received.",
|
||||
"Milliseconds",
|
||||
LABELS,
|
||||
DEFAULT_LATENCY_FITTER);
|
||||
|
||||
@Inject
|
||||
BackendMetrics() {}
|
||||
|
||||
/**
|
||||
* Resets all backend metrics.
|
||||
*
|
||||
* <p>This should only used in tests to clear out states. No production code should call this
|
||||
* function.
|
||||
*/
|
||||
void resetMetric() {
|
||||
requestBytes.reset();
|
||||
requestsCounter.reset();
|
||||
responseBytes.reset();
|
||||
responsesCounter.reset();
|
||||
latencyMs.reset();
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void requestSent(String protocol, String certHash, FullHttpRequest request) {
|
||||
requestsCounter.increment(protocol, certHash);
|
||||
requestBytes.record(request.content().readableBytes(), protocol, certHash);
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void responseReceived(
|
||||
String protocol, String certHash, FullHttpResponse response, long latency) {
|
||||
latencyMs.record(latency, protocol, certHash);
|
||||
responseBytes.record(response.content().readableBytes(), protocol, certHash);
|
||||
responsesCounter.increment(protocol, certHash, response.status().toString());
|
||||
}
|
||||
}
|
99
java/google/registry/proxy/metric/FrontendMetrics.java
Normal file
99
java/google/registry/proxy/metric/FrontendMetrics.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
// 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.metric;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.monitoring.metrics.IncrementableMetric;
|
||||
import google.registry.monitoring.metrics.LabelDescriptor;
|
||||
import google.registry.monitoring.metrics.Metric;
|
||||
import google.registry.monitoring.metrics.MetricRegistryImpl;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Frontend metrics instrumentation. */
|
||||
public class FrontendMetrics {
|
||||
|
||||
private static final ImmutableSet<LabelDescriptor> LABELS =
|
||||
ImmutableSet.of(
|
||||
LabelDescriptor.create("protocol", "Name of the protocol."),
|
||||
LabelDescriptor.create(
|
||||
"client_cert_hash", "SHA256 hash of the client certificate, if available."));
|
||||
|
||||
private static final ConcurrentMap<ImmutableList<String>, ChannelGroup> activeConnections =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
static final Metric<Long> activeConnectionsGauge =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newGauge(
|
||||
"/proxy/frontend/active_connections",
|
||||
"Number of active connections from clients to the proxy.",
|
||||
"Connections",
|
||||
LABELS,
|
||||
() ->
|
||||
activeConnections
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(
|
||||
ImmutableMap.toImmutableMap(
|
||||
Map.Entry::getKey, entry -> (long) entry.getValue().size())),
|
||||
Long.class);
|
||||
|
||||
static final IncrementableMetric totalConnectionsCounter =
|
||||
MetricRegistryImpl.getDefault()
|
||||
.newIncrementableMetric(
|
||||
"/proxy/frontend/total_connections",
|
||||
"Total number connections ever made from clients to the proxy.",
|
||||
"Connections",
|
||||
LABELS);
|
||||
|
||||
@Inject
|
||||
public FrontendMetrics() {}
|
||||
|
||||
/**
|
||||
* Resets all frontend metrics.
|
||||
*
|
||||
* <p>This should only be used in tests to reset states. Production code should not call this
|
||||
* method.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void resetMetrics() {
|
||||
totalConnectionsCounter.reset();
|
||||
activeConnections.clear();
|
||||
}
|
||||
|
||||
@NonFinalForTesting
|
||||
public void registerActiveConnection(String protocol, String certHash, Channel channel) {
|
||||
totalConnectionsCounter.increment(protocol, certHash);
|
||||
ImmutableList<String> labels = ImmutableList.of(protocol, certHash);
|
||||
ChannelGroup channelGroup;
|
||||
if (activeConnections.containsKey(labels)) {
|
||||
channelGroup = activeConnections.get(labels);
|
||||
} else {
|
||||
channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
activeConnections.put(labels, channelGroup);
|
||||
}
|
||||
channelGroup.add(channel);
|
||||
}
|
||||
}
|
4
java/google/registry/proxy/resources/hello.xml
Normal file
4
java/google/registry/proxy/resources/hello.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<hello/>
|
||||
</epp>
|
|
@ -66,7 +66,9 @@ def domain_registry_repositories(
|
|||
omit_com_google_dagger_compiler=False,
|
||||
omit_com_google_dagger_producers=False,
|
||||
omit_com_google_errorprone_error_prone_annotations=False,
|
||||
omit_com_google_errorprone_javac_shaded=False,
|
||||
omit_com_google_gdata_core=False,
|
||||
omit_com_google_googlejavaformat_google_java_format=False,
|
||||
omit_com_google_guava=False,
|
||||
omit_com_google_guava_testlib=False,
|
||||
omit_com_google_http_client=False,
|
||||
|
@ -86,6 +88,7 @@ def domain_registry_repositories(
|
|||
omit_com_googlecode_json_simple=False,
|
||||
omit_com_ibm_icu_icu4j=False,
|
||||
omit_com_jcraft_jzlib=False,
|
||||
omit_com_squareup_javapoet=False,
|
||||
omit_com_squareup_javawriter=False,
|
||||
omit_com_sun_xml_bind_jaxb_core=False,
|
||||
omit_com_sun_xml_bind_jaxb_impl=False,
|
||||
|
@ -94,8 +97,17 @@ def domain_registry_repositories(
|
|||
omit_commons_codec=False,
|
||||
omit_commons_logging=False,
|
||||
omit_dnsjava=False,
|
||||
omit_io_netty_buffer=False,
|
||||
omit_io_netty_codec=False,
|
||||
omit_io_netty_codec_http=False,
|
||||
omit_io_netty_common=False,
|
||||
omit_io_netty_handler=False,
|
||||
omit_io_netty_resolver=False,
|
||||
omit_io_netty_tcnative=False,
|
||||
omit_io_netty_transport=False,
|
||||
omit_it_unimi_dsi_fastutil=False,
|
||||
omit_javax_activation=False,
|
||||
omit_javax_annotation_jsr250_api=False,
|
||||
omit_javax_inject=False,
|
||||
omit_javax_mail=False,
|
||||
omit_javax_servlet_api=False,
|
||||
|
@ -227,8 +239,12 @@ def domain_registry_repositories(
|
|||
com_google_dagger_producers()
|
||||
if not omit_com_google_errorprone_error_prone_annotations:
|
||||
com_google_errorprone_error_prone_annotations()
|
||||
if not omit_com_google_errorprone_javac_shaded:
|
||||
com_google_errorprone_javac_shaded()
|
||||
if not omit_com_google_gdata_core:
|
||||
com_google_gdata_core()
|
||||
if not omit_com_google_googlejavaformat_google_java_format:
|
||||
com_google_googlejavaformat_google_java_format()
|
||||
if not omit_com_google_guava:
|
||||
com_google_guava()
|
||||
if not omit_com_google_guava_testlib:
|
||||
|
@ -267,6 +283,8 @@ def domain_registry_repositories(
|
|||
com_ibm_icu_icu4j()
|
||||
if not omit_com_jcraft_jzlib:
|
||||
com_jcraft_jzlib()
|
||||
if not omit_com_squareup_javapoet:
|
||||
com_squareup_javapoet()
|
||||
if not omit_com_squareup_javawriter:
|
||||
com_squareup_javawriter()
|
||||
if not omit_com_sun_xml_bind_jaxb_core:
|
||||
|
@ -283,10 +301,28 @@ def domain_registry_repositories(
|
|||
commons_logging()
|
||||
if not omit_dnsjava:
|
||||
dnsjava()
|
||||
if not omit_io_netty_buffer:
|
||||
io_netty_buffer()
|
||||
if not omit_io_netty_codec:
|
||||
io_netty_codec()
|
||||
if not omit_io_netty_codec_http:
|
||||
io_netty_codec_http()
|
||||
if not omit_io_netty_common:
|
||||
io_netty_common()
|
||||
if not omit_io_netty_handler:
|
||||
io_netty_handler()
|
||||
if not omit_io_netty_resolver:
|
||||
io_netty_resolver()
|
||||
if not omit_io_netty_tcnative:
|
||||
io_netty_tcnative()
|
||||
if not omit_io_netty_transport:
|
||||
io_netty_transport()
|
||||
if not omit_it_unimi_dsi_fastutil:
|
||||
it_unimi_dsi_fastutil()
|
||||
if not omit_javax_activation:
|
||||
javax_activation()
|
||||
if not omit_javax_annotation_jsr250_api:
|
||||
javax_annotation_jsr250_api()
|
||||
if not omit_javax_inject:
|
||||
javax_inject()
|
||||
if not omit_javax_mail:
|
||||
|
@ -888,21 +924,21 @@ def com_google_auto_value():
|
|||
def com_google_code_findbugs_jsr305():
|
||||
java_import_external(
|
||||
name = "com_google_code_findbugs_jsr305",
|
||||
jar_sha256 = "905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed",
|
||||
jar_urls = [
|
||||
"http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar",
|
||||
],
|
||||
licenses = ["notice"], # The Apache Software License, Version 2.0
|
||||
jar_sha256 = "c885ce34249682bc0236b4a7d56efcc12048e6135a5baf7a9cde8ad8cda13fcd",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar",
|
||||
"http://maven.ibiblio.org/maven2/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def com_google_dagger():
|
||||
java_import_external(
|
||||
name = "com_google_dagger",
|
||||
jar_sha256 = "5070e1dff5c551a4908ba7b93125c0243de2a688aed3d2f475357d86d9d7c0ad",
|
||||
jar_sha256 = "b2142693bc7413f0b74330f0a92ca44ea95a12a22b659972ed6aa9832e8352e4",
|
||||
jar_urls = [
|
||||
"http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/dagger/dagger/2.8/dagger-2.8.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger/2.8/dagger-2.8.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger/2.13/dagger-2.13.jar",
|
||||
"http://maven.ibiblio.org/maven2/com/google/dagger/dagger/2.13/dagger-2.13.jar",
|
||||
],
|
||||
licenses = ["notice"], # Apache 2.0
|
||||
deps = ["@javax_inject"],
|
||||
|
@ -922,17 +958,21 @@ def com_google_dagger():
|
|||
def com_google_dagger_compiler():
|
||||
java_import_external(
|
||||
name = "com_google_dagger_compiler",
|
||||
jar_sha256 = "7b2686f94907868c5364e9965601ffe2f020ba4af1849ad9b57dad5fe3fa6242",
|
||||
jar_sha256 = "8b711253c9cbb58bd2c019cb38afb32ee79f283e1bb3030c8c85b645c7a6d25f",
|
||||
jar_urls = [
|
||||
"http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.8/dagger-compiler-2.8.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.8/dagger-compiler-2.8.jar",
|
||||
"http://maven.ibiblio.org/maven2/com/google/dagger/dagger-compiler/2.13/dagger-compiler-2.13.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.13/dagger-compiler-2.13.jar",
|
||||
],
|
||||
licenses = ["notice"], # Apache 2.0
|
||||
deps = [
|
||||
"@com_google_code_findbugs_jsr305",
|
||||
"@com_google_dagger//:runtime",
|
||||
"@com_google_dagger_producers//:runtime",
|
||||
"@com_google_code_findbugs_jsr305",
|
||||
"@com_google_googlejavaformat_google_java_format",
|
||||
"@com_google_guava",
|
||||
"@com_squareup_javapoet",
|
||||
"@javax_annotation_jsr250_api",
|
||||
"@javax_inject",
|
||||
],
|
||||
extra_build_file_content = "\n".join([
|
||||
"java_plugin(",
|
||||
|
@ -952,15 +992,17 @@ def com_google_dagger_compiler():
|
|||
def com_google_dagger_producers():
|
||||
java_import_external(
|
||||
name = "com_google_dagger_producers",
|
||||
jar_sha256 = "1e4043e85f67de381d19e22c7932aaf7ff1611091be7e1aaae93f2c37f331cf2",
|
||||
jar_sha256 = "cf35b21c634939917eee9ffcd72a9f5f6e261ad57a4c0f0d15cf6f1430262bb0",
|
||||
jar_urls = [
|
||||
"http://domain-registry-maven.storage.googleapis.com/repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.8/dagger-producers-2.8.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.8/dagger-producers-2.8.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.13/dagger-producers-2.13.jar",
|
||||
"http://maven.ibiblio.org/maven2/com/google/dagger/dagger-producers/2.13/dagger-producers-2.13.jar",
|
||||
],
|
||||
licenses = ["notice"], # Apache 2.0
|
||||
deps = [
|
||||
"@com_google_dagger//:runtime",
|
||||
"@com_google_code_findbugs_jsr305",
|
||||
"@com_google_guava",
|
||||
"@javax_inject",
|
||||
],
|
||||
generated_rule_name = "runtime",
|
||||
extra_build_file_content = "\n".join([
|
||||
|
@ -986,6 +1028,56 @@ def com_google_errorprone_error_prone_annotations():
|
|||
licenses = ["notice"], # Apache 2.0
|
||||
)
|
||||
|
||||
def com_google_errorprone_javac_shaded():
|
||||
java_import_external(
|
||||
name = "com_google_errorprone_javac_shaded",
|
||||
# GNU General Public License, version 2, with the Classpath Exception
|
||||
# http://openjdk.java.net/legal/gplv2+ce.html
|
||||
licenses = ["TODO"],
|
||||
jar_sha256 = "65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar",
|
||||
"http://maven.ibiblio.org/maven2/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def com_google_googlejavaformat_google_java_format():
|
||||
java_import_external(
|
||||
name = "com_google_googlejavaformat_google_java_format",
|
||||
licenses = ["notice"], # The Apache Software License, Version 2.0
|
||||
jar_sha256 = "39d18ec9ab610097074bf49e971285488eaf5d0bc2369df0a0d5a3f9f9de2faa",
|
||||
jar_urls = [
|
||||
"http://maven.ibiblio.org/maven2/com/google/googlejavaformat/google-java-format/1.4/google-java-format-1.4.jar",
|
||||
"http://repo1.maven.org/maven2/com/google/googlejavaformat/google-java-format/1.4/google-java-format-1.4.jar",
|
||||
],
|
||||
deps = [
|
||||
"@com_google_guava",
|
||||
"@com_google_errorprone_javac_shaded",
|
||||
],
|
||||
)
|
||||
|
||||
def com_squareup_javapoet():
|
||||
java_import_external(
|
||||
name = "com_squareup_javapoet",
|
||||
licenses = ["notice"], # Apache 2.0
|
||||
jar_sha256 = "8e108c92027bb428196f10fa11cffbe589f7648a6af2016d652279385fdfd789",
|
||||
jar_urls = [
|
||||
"http://maven.ibiblio.org/maven2/com/squareup/javapoet/1.8.0/javapoet-1.8.0.jar",
|
||||
"http://repo1.maven.org/maven2/com/squareup/javapoet/1.8.0/javapoet-1.8.0.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def javax_annotation_jsr250_api():
|
||||
java_import_external(
|
||||
name = "javax_annotation_jsr250_api",
|
||||
licenses = ["reciprocal"], # COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
|
||||
jar_sha256 = "a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar",
|
||||
"http://maven.ibiblio.org/maven2/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def com_google_gdata_core():
|
||||
java_import_external(
|
||||
name = "com_google_gdata_core",
|
||||
|
@ -2147,6 +2239,102 @@ def xpp3():
|
|||
],
|
||||
)
|
||||
|
||||
def io_netty_buffer():
|
||||
java_import_external(
|
||||
name = "io_netty_buffer",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "b24a28e2129fc11e1f6124ebf93725d1f9c0904ea679d261da7b2e21d4c8615e",
|
||||
jar_urls = [
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-buffer/4.1.17.Final/netty-buffer-4.1.17.Final.jar",
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-buffer/4.1.17.Final/netty-buffer-4.1.17.Final.jar",
|
||||
],
|
||||
deps = ["@io_netty_common"],
|
||||
)
|
||||
|
||||
def io_netty_codec():
|
||||
java_import_external(
|
||||
name = "io_netty_codec",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "790ce1b7694fc41663131579d776a370e332e3b3fe2fe6543662fd5a40a948e1",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-codec/4.1.17.Final/netty-codec-4.1.17.Final.jar",
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-codec/4.1.17.Final/netty-codec-4.1.17.Final.jar",
|
||||
],
|
||||
deps = ["@io_netty_transport"],
|
||||
)
|
||||
|
||||
def io_netty_codec_http():
|
||||
java_import_external(
|
||||
name = "io_netty_codec_http",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "fc05d02755c5d204ccc848be8399ef5d48d5a80da9b93f075287c57eb9381e5b",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-codec-http/4.1.17.Final/netty-codec-http-4.1.17.Final.jar",
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-codec-http/4.1.17.Final/netty-codec-http-4.1.17.Final.jar",
|
||||
],
|
||||
deps = ["@io_netty_codec"],
|
||||
)
|
||||
|
||||
def io_netty_common():
|
||||
java_import_external(
|
||||
name = "io_netty_common",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "dddabdec01959180da44129d130301b84c23b473411288f143d5e29e0b098d26",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-common/4.1.17.Final/netty-common-4.1.17.Final.jar",
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-common/4.1.17.Final/netty-common-4.1.17.Final.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def io_netty_handler():
|
||||
java_import_external(
|
||||
name = "io_netty_handler",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "85bada604fe14bc358da7b140583264a88d7a45ca12daba1216c4225aadb0c7b",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-handler/4.1.17.Final/netty-handler-4.1.17.Final.jar",
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-handler/4.1.17.Final/netty-handler-4.1.17.Final.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def io_netty_resolver():
|
||||
java_import_external(
|
||||
name = "io_netty_resolver",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "082ac49149cb72c675c7ed1615ba35923d3167e65bfb37c4a1422ec499137cb1",
|
||||
jar_urls = [
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-resolver/4.1.17.Final/netty-resolver-4.1.17.Final.jar",
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-resolver/4.1.17.Final/netty-resolver-4.1.17.Final.jar",
|
||||
],
|
||||
deps = ["@io_netty_common"],
|
||||
)
|
||||
|
||||
def io_netty_tcnative():
|
||||
java_import_external(
|
||||
name = "io_netty_tcnative",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "cd49317267a8f2fd617075d22e25ceb3aef98e6b64bd6f66cca95f8825cdc1f3",
|
||||
jar_urls = [
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-tcnative/2.0.7.Final/netty-tcnative-2.0.7.Final.jar",
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-tcnative/2.0.7.Final/netty-tcnative-2.0.7.Final.jar",
|
||||
],
|
||||
)
|
||||
|
||||
def io_netty_transport():
|
||||
java_import_external(
|
||||
name = "io_netty_transport",
|
||||
licenses = ["notice"], # Apache License, Version 2.0
|
||||
jar_sha256 = "60763426c79dd930c70d0da95e474f662bd17a58d3d57b332696d089cf208089",
|
||||
jar_urls = [
|
||||
"http://maven.ibiblio.org/maven2/io/netty/netty-transport/4.1.17.Final/netty-transport-4.1.17.Final.jar",
|
||||
"http://repo1.maven.org/maven2/io/netty/netty-transport/4.1.17.Final/netty-transport-4.1.17.Final.jar",
|
||||
],
|
||||
deps = [
|
||||
"@io_netty_buffer",
|
||||
"@io_netty_resolver",
|
||||
],
|
||||
)
|
||||
|
||||
def _check_bazel_version(project, bazel_version):
|
||||
if "bazel_version" not in dir(native):
|
||||
fail("%s requires Bazel >=%s but was <0.2.1" % (project, bazel_version))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue