Refactor common code used by the proxy and the prober (#375)

This commit is contained in:
Lai Jiang
2019-11-20 12:42:44 -05:00
committed by GitHub
parent 68887d427f
commit 6af1896362
49 changed files with 618 additions and 129 deletions
@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import google.registry.networking.handler.SslServerInitializer;
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
import google.registry.proxy.Protocol.BackendProtocol;
import google.registry.proxy.Protocol.FrontendProtocol;
@@ -28,7 +29,6 @@ import google.registry.proxy.handler.FrontendMetricsHandler;
import google.registry.proxy.handler.ProxyProtocolHandler;
import google.registry.proxy.handler.QuotaHandler.EppQuotaHandler;
import google.registry.proxy.handler.RelayHandler.FullHttpRequestRelayHandler;
import google.registry.proxy.handler.SslServerInitializer;
import google.registry.proxy.metric.FrontendMetrics;
import google.registry.proxy.quota.QuotaConfig;
import google.registry.proxy.quota.QuotaManager;
@@ -150,11 +150,7 @@ public class EppProtocolModule {
FrontendMetrics metrics,
ProxyConfig config) {
return new EppServiceHandler(
config.epp.relayHost,
config.epp.relayPath,
accessTokenSupplier,
helloBytes,
metrics);
config.epp.relayHost, config.epp.relayPath, accessTokenSupplier, helloBytes, metrics);
}
@Singleton
@@ -17,15 +17,16 @@ package google.registry.proxy;
import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import google.registry.networking.handler.SslClientInitializer;
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 io.netty.handler.ssl.SslProvider;
import java.security.cert.X509Certificate;
import javax.annotation.Nullable;
import javax.inject.Provider;
@@ -58,10 +59,21 @@ public class HttpsRelayProtocolModule {
.handlerProviders(handlerProviders);
}
@Provides
@HttpsRelayProtocol
static SslClientInitializer<NioSocketChannel> provideSslClientInitializer(
SslProvider sslProvider) {
return new SslClientInitializer<>(
sslProvider,
channel -> ((BackendProtocol) channel.attr(Protocol.PROTOCOL_KEY).get()).host(),
channel -> channel.attr(Protocol.PROTOCOL_KEY).get().port());
}
@Provides
@HttpsRelayProtocol
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
@HttpsRelayProtocol
Provider<SslClientInitializer<NioSocketChannel>> sslClientInitializerProvider,
Provider<HttpClientCodec> httpClientCodecProvider,
Provider<HttpObjectAggregator> httpObjectAggregatorProvider,
Provider<BackendMetricsHandler> backendMetricsHandlerProvider,
@@ -18,8 +18,8 @@ import com.google.common.collect.ImmutableList;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import google.registry.networking.handler.SslServerInitializer;
import google.registry.proxy.Protocol.FrontendProtocol;
import google.registry.proxy.handler.SslServerInitializer;
import google.registry.proxy.handler.WebWhoisRedirectHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
@@ -16,8 +16,8 @@ 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.networking.handler.SslServerInitializer.CLIENT_CERTIFICATE_PROMISE_KEY;
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.flogger.FluentLogger;
@@ -1,84 +0,0 @@
// 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.FluentLogger;
import google.registry.proxy.Protocol.BackendProtocol;
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.inject.Inject;
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 FluentLogger logger = FluentLogger.forEnclosingClass();
private final SslProvider sslProvider;
private final X509Certificate[] trustedCertificates;
@Inject
public SslClientInitializer(SslProvider sslProvider) {
// null uses the system default trust store.
this(sslProvider, null);
}
@VisibleForTesting
SslClientInitializer(SslProvider sslProvider, X509Certificate[] trustCertificates) {
logger.atInfo().log("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);
}
}
@@ -1,106 +0,0 @@
// 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.flogger.FluentLogger;
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 java.util.function.Supplier;
/**
* 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).
*/
@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 FluentLogger logger = FluentLogger.forEnclosingClass();
private final boolean requireClientCert;
private final SslProvider sslProvider;
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<X509Certificate[]> certificatesSupplier;
public SslServerInitializer(
boolean requireClientCert,
SslProvider sslProvider,
Supplier<PrivateKey> privateKeySupplier,
Supplier<X509Certificate[]> certificatesSupplier) {
logger.atInfo().log("Server SSL Provider: %s", sslProvider);
this.requireClientCert = requireClientCert;
this.sslProvider = sslProvider;
this.privateKeySupplier = privateKeySupplier;
this.certificatesSupplier = certificatesSupplier;
}
@Override
protected void initChannel(C channel) throws Exception {
SslHandler sslHandler =
SslContextBuilder.forServer(privateKeySupplier.get(), certificatesSupplier.get())
.sslProvider(sslProvider)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.clientAuth(requireClientCert ? ClientAuth.REQUIRE : ClientAuth.NONE)
.build()
.newHandler(channel.alloc());
if (requireClientCert) {
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);
}
}