mirror of
https://github.com/google/nomulus.git
synced 2025-06-27 14:54:51 +02:00
Add web WHOIS redirect support
Opened two ports (30010 and 30011 by default) that handles HTTP(S) GET requests. the HTTP request is redirected to the corresponding HTTPS site, whereas the HTTPS request is redirected to a site that supports web WHOIS. The GCLB currently exposes port 80, but not port 443 on its TCP proxy load balancer (see https://cloud.google.com/load-balancing/docs/choosing-load-balancer). As a result, the HTTP traffic has to be routed by the HTTP load balancer, which requires a separate HTTP health check (as opposed to the TCP health check that the TCP proxy LB uses). This CL also added support for HTTP health check. There is not a strong case for adding an end-to-end test for WebWhoisProtocolsModule (like those for EppProtocolModule, etc) as it just assembles standard HTTP codecs used for an HTTP server, plus the WebWhoisRedirectHandler, which is tested. The end-to-end test would just be testing if the Netty provided HTTP handlers correctly parse raw HTTP messages. Sever other small improvement is also included: [1] Use setInt other than set when setting content length in HTTP headers. I don't think it is necessary, but it is nevertheless a better practice to use a more specialized setter. [2] Do not write metrics when running locally. [3] Rename the qualifier @EppCertificates to @ServerSertificate as it now provides the certificate used in HTTPS traffic as well. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=206944843
This commit is contained in:
parent
f614044681
commit
4a5b317016
18 changed files with 686 additions and 97 deletions
|
@ -70,6 +70,8 @@ container_image(
|
|||
"30000",
|
||||
"30001",
|
||||
"30002",
|
||||
"30010",
|
||||
"30011",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ 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.
|
||||
* Dagger module that provides bindings needed to inject server certificate chain and private key.
|
||||
*
|
||||
* <p>The production 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
|
||||
|
@ -60,17 +60,22 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
|||
@Module
|
||||
public class CertificateModule {
|
||||
|
||||
/** Dagger qualifier to provide bindings related to EPP certificates */
|
||||
/** Dagger qualifier to provide bindings related to the certificates that the server provides. */
|
||||
@Qualifier
|
||||
public @interface EppCertificates {}
|
||||
@interface ServerCertificates {}
|
||||
|
||||
/** Dagger qualifier to provide bindings when running locally. */
|
||||
@Qualifier
|
||||
public @interface Local {}
|
||||
@interface Local {}
|
||||
|
||||
/** Dagger qualifier to provide bindings when running in production. */
|
||||
/**
|
||||
* Dagger qualifier to provide bindings when running in production.
|
||||
*
|
||||
* <p>The "production" here means that the proxy runs on GKE, as apposed to on a local machine. It
|
||||
* does not necessary mean the production environment.
|
||||
*/
|
||||
@Qualifier
|
||||
public @interface Prod {}
|
||||
@interface Prod {}
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
@ -95,7 +100,7 @@ public class CertificateModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
@EppCertificates
|
||||
@ServerCertificates
|
||||
static X509Certificate[] provideCertificates(
|
||||
Environment env,
|
||||
@Local Lazy<X509Certificate[]> localCertificates,
|
||||
|
@ -105,7 +110,7 @@ public class CertificateModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
@EppCertificates
|
||||
@ServerCertificates
|
||||
static PrivateKey providePrivateKey(
|
||||
Environment env,
|
||||
@Local Lazy<PrivateKey> localPrivateKey,
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList;
|
|||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import google.registry.proxy.CertificateModule.ServerCertificates;
|
||||
import google.registry.proxy.HttpsRelayProtocolModule.HttpsRelayProtocol;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
|
@ -37,8 +38,11 @@ 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.ssl.SslProvider;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -76,8 +80,8 @@ public class EppProtocolModule {
|
|||
@Provides
|
||||
@EppProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> provideHandlerProviders(
|
||||
Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
Provider<ProxyProtocolHandler> proxyProtocolHandlerProvider,
|
||||
@EppProtocol Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
@EppProtocol Provider<ReadTimeoutHandler> readTimeoutHandlerProvider,
|
||||
Provider<LengthFieldBasedFrameDecoder> lengthFieldBasedFrameDecoderProvider,
|
||||
Provider<LengthFieldPrepender> lengthFieldPrependerProvider,
|
||||
|
@ -152,6 +156,16 @@ public class EppProtocolModule {
|
|||
metrics);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
|
||||
SslProvider sslProvider,
|
||||
@ServerCertificates PrivateKey privateKey,
|
||||
@ServerCertificates X509Certificate... certificates) {
|
||||
return new SslServerInitializer<>(true, sslProvider, privateKey, certificates);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@EppProtocol
|
||||
static TokenStore provideTokenStore(
|
||||
|
|
|
@ -50,7 +50,7 @@ public class HealthCheckProtocolModule {
|
|||
return Protocol.frontendBuilder()
|
||||
.name(PROTOCOL_NAME)
|
||||
.port(healthCheckPort)
|
||||
.isHealthCheck(true)
|
||||
.hasBackend(false)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
|
|
@ -42,13 +42,9 @@ public interface Protocol {
|
|||
/** 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
|
||||
*/
|
||||
/** A builder for {@link FrontendProtocol}, by default there is a backend associated with it. */
|
||||
static FrontendProtocol.Builder frontendBuilder() {
|
||||
return new AutoValue_Protocol_FrontendProtocol.Builder().isHealthCheck(false);
|
||||
return new AutoValue_Protocol_FrontendProtocol.Builder().hasBackend(true);
|
||||
}
|
||||
|
||||
static BackendProtocol.Builder backendBuilder() {
|
||||
|
@ -83,18 +79,22 @@ public interface Protocol {
|
|||
|
||||
/**
|
||||
* The {@link BackendProtocol} used to establish a relay channel and relay the traffic to. Not
|
||||
* required for health check protocol.
|
||||
* required for health check protocol or HTTP(S) redirect.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract BackendProtocol relayProtocol();
|
||||
|
||||
public abstract boolean isHealthCheck();
|
||||
/**
|
||||
* Whether this {@code FrontendProtocol} relays to a {@code BackendProtocol}. All proxied
|
||||
* traffic must be represented by a protocol that has a backend.
|
||||
*/
|
||||
public abstract boolean hasBackend();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder extends Protocol.Builder<Builder, FrontendProtocol> {
|
||||
public abstract Builder relayProtocol(BackendProtocol value);
|
||||
|
||||
public abstract Builder isHealthCheck(boolean value);
|
||||
public abstract Builder hasBackend(boolean value);
|
||||
|
||||
abstract FrontendProtocol autoBuild();
|
||||
|
||||
|
@ -102,7 +102,7 @@ public interface Protocol {
|
|||
public FrontendProtocol build() {
|
||||
FrontendProtocol frontendProtocol = autoBuild();
|
||||
Preconditions.checkState(
|
||||
frontendProtocol.isHealthCheck() || frontendProtocol.relayProtocol() != null,
|
||||
!frontendProtocol.hasBackend() || frontendProtocol.relayProtocol() != null,
|
||||
"Frontend protocol %s must define a relay protocol.",
|
||||
frontendProtocol.name());
|
||||
return frontendProtocol;
|
||||
|
|
|
@ -44,6 +44,7 @@ public class ProxyConfig {
|
|||
public Epp epp;
|
||||
public Whois whois;
|
||||
public HealthCheck healthCheck;
|
||||
public WebWhois webWhois;
|
||||
public HttpsRelay httpsRelay;
|
||||
public Metrics metrics;
|
||||
|
||||
|
@ -89,6 +90,13 @@ public class ProxyConfig {
|
|||
public String checkResponse;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to web WHOIS redirects. */
|
||||
public static class WebWhois {
|
||||
public int httpPort;
|
||||
public int httpsPort;
|
||||
public String redirectHost;
|
||||
}
|
||||
|
||||
/** Configuration options that apply to HTTPS relay protocol. */
|
||||
public static class HttpsRelay {
|
||||
public int port;
|
||||
|
|
|
@ -38,6 +38,8 @@ 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.WebWhoisProtocolsModule.HttpWhoisProtocol;
|
||||
import google.registry.proxy.WebWhoisProtocolsModule.HttpsWhoisProtocol;
|
||||
import google.registry.proxy.WhoisProtocolModule.WhoisProtocol;
|
||||
import google.registry.proxy.handler.ProxyProtocolHandler;
|
||||
import google.registry.util.Clock;
|
||||
|
@ -75,9 +77,15 @@ public class ProxyModule {
|
|||
@Parameter(names = "--epp", description = "Port for EPP")
|
||||
private Integer eppPort;
|
||||
|
||||
@Parameter(names = "--health_check", description = "Port for health check protocol")
|
||||
@Parameter(names = "--health_check", description = "Port for health check")
|
||||
private Integer healthCheckPort;
|
||||
|
||||
@Parameter(names = "--http_whois", description = "Port for HTTP WHOIS")
|
||||
private Integer httpWhoisPort;
|
||||
|
||||
@Parameter(names = "--https_whois", description = "Port for HTTPS WHOIS")
|
||||
private Integer httpsWhoisPort;
|
||||
|
||||
@Parameter(names = "--env", description = "Environment to run the proxy in")
|
||||
private Environment env = Environment.LOCAL;
|
||||
|
||||
|
@ -165,6 +173,18 @@ public class ProxyModule {
|
|||
return Optional.ofNullable(healthCheckPort).orElse(config.healthCheck.port);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
int provideHttpWhoisProtocol(ProxyConfig config) {
|
||||
return Optional.ofNullable(httpWhoisPort).orElse(config.webWhois.httpPort);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
int provideHttpsWhoisProtocol(ProxyConfig config) {
|
||||
return Optional.ofNullable(httpsWhoisPort).orElse(config.webWhois.httpsPort);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ImmutableMap<Integer, FrontendProtocol> providePortToProtocolMap(
|
||||
Set<FrontendProtocol> protocolSet) {
|
||||
|
@ -321,11 +341,11 @@ public class ProxyModule {
|
|||
CertificateModule.class,
|
||||
HttpsRelayProtocolModule.class,
|
||||
WhoisProtocolModule.class,
|
||||
WebWhoisProtocolsModule.class,
|
||||
EppProtocolModule.class,
|
||||
HealthCheckProtocolModule.class,
|
||||
MetricsModule.class
|
||||
}
|
||||
)
|
||||
})
|
||||
interface ProxyComponent {
|
||||
|
||||
ImmutableMap<Integer, FrontendProtocol> portToProtocolMap();
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.google.common.flogger.FluentLogger;
|
|||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import google.registry.proxy.Protocol.BackendProtocol;
|
||||
import google.registry.proxy.Protocol.FrontendProtocol;
|
||||
import google.registry.proxy.ProxyConfig.Environment;
|
||||
import google.registry.proxy.ProxyModule.ProxyComponent;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
|
@ -87,9 +88,9 @@ public class ProxyServer implements Runnable {
|
|||
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.
|
||||
if (!inboundProtocol.hasBackend()) {
|
||||
// If the frontend has no backend to relay to (health check, web WHOIS redirect, etc), start
|
||||
// reading immediately.
|
||||
inboundChannel.config().setAutoRead(true);
|
||||
} else {
|
||||
// Connect to the relay (outbound) channel specified by the BackendProtocol.
|
||||
|
@ -208,11 +209,12 @@ public class ProxyServer implements Runnable {
|
|||
|
||||
// Configure the components, this needs to run first so that the logging format is properly
|
||||
// configured for each environment.
|
||||
ProxyModule proxyModule = new ProxyModule().parse(args);
|
||||
ProxyComponent proxyComponent =
|
||||
DaggerProxyModule_ProxyComponent.builder()
|
||||
.proxyModule(new ProxyModule().parse(args))
|
||||
.build();
|
||||
DaggerProxyModule_ProxyComponent.builder().proxyModule(proxyModule).build();
|
||||
|
||||
// Do not write metrics when running locally.
|
||||
if (proxyModule.provideEnvironment() != Environment.LOCAL) {
|
||||
MetricReporter metricReporter = proxyComponent.metricReporter();
|
||||
try {
|
||||
metricReporter.startAsync().awaitRunning(10, TimeUnit.SECONDS);
|
||||
|
@ -221,7 +223,6 @@ public class ProxyServer implements Runnable {
|
|||
logger.atSevere().withCause(timeoutException).log(
|
||||
"Failed to initialize MetricReporter: %s", timeoutException);
|
||||
}
|
||||
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(
|
||||
new Thread(
|
||||
|
@ -234,7 +235,9 @@ public class ProxyServer implements Runnable {
|
|||
"Failed to stop MetricReporter: %s", timeoutException);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Start the proxy.
|
||||
new ProxyServer(proxyComponent).run();
|
||||
}
|
||||
}
|
||||
|
|
139
java/google/registry/proxy/WebWhoisProtocolsModule.java
Normal file
139
java/google/registry/proxy/WebWhoisProtocolsModule.java
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2018 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.CertificateModule.ServerCertificates;
|
||||
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;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Qualifier;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** A module that provides the {@link FrontendProtocol}s to redirect HTTP(S) web WHOIS requests. */
|
||||
@Module
|
||||
public class WebWhoisProtocolsModule {
|
||||
|
||||
/** Dagger qualifier to provide HTTP whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HttpWhoisProtocol {}
|
||||
|
||||
/** Dagger qualifier to provide HTTPS whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
@interface HttpsWhoisProtocol {}
|
||||
|
||||
private static final String HTTP_PROTOCOL_NAME = "whois_http";
|
||||
private static final String HTTPS_PROTOCOL_NAME = "whois_https";
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideHttpWhoisProtocol(
|
||||
@HttpWhoisProtocol int httpWhoisPort,
|
||||
@HttpWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return google.registry.proxy.Protocol.frontendBuilder()
|
||||
.name(HTTP_PROTOCOL_NAME)
|
||||
.port(httpWhoisPort)
|
||||
.hasBackend(false)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@IntoSet
|
||||
static FrontendProtocol provideHttpsWhoisProtocol(
|
||||
@HttpsWhoisProtocol int httpsWhoisPort,
|
||||
@HttpsWhoisProtocol ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return google.registry.proxy.Protocol.frontendBuilder()
|
||||
.name(HTTPS_PROTOCOL_NAME)
|
||||
.port(httpsWhoisPort)
|
||||
.hasBackend(false)
|
||||
.handlerProviders(handlerProviders)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpWhoisHandlerProviders(
|
||||
Provider<HttpServerCodec> httpServerCodecProvider,
|
||||
Provider<HttpServerExpectContinueHandler> httpServerExpectContinueHandlerProvider,
|
||||
@HttpWhoisProtocol Provider<WebWhoisRedirectHandler> webWhoisRedirectHandlerProvides) {
|
||||
return ImmutableList.of(
|
||||
httpServerCodecProvider,
|
||||
httpServerExpectContinueHandlerProvider,
|
||||
webWhoisRedirectHandlerProvides);
|
||||
};
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static ImmutableList<Provider<? extends ChannelHandler>> providerHttpsWhoisHandlerProviders(
|
||||
@HttpsWhoisProtocol
|
||||
Provider<SslServerInitializer<NioSocketChannel>> sslServerInitializerProvider,
|
||||
Provider<HttpServerCodec> httpServerCodecProvider,
|
||||
Provider<HttpServerExpectContinueHandler> httpServerExpectContinueHandlerProvider,
|
||||
@HttpsWhoisProtocol Provider<WebWhoisRedirectHandler> webWhoisRedirectHandlerProvides) {
|
||||
return ImmutableList.of(
|
||||
sslServerInitializerProvider,
|
||||
httpServerCodecProvider,
|
||||
httpServerExpectContinueHandlerProvider,
|
||||
webWhoisRedirectHandlerProvides);
|
||||
};
|
||||
|
||||
@Provides
|
||||
static HttpServerCodec provideHttpServerCodec() {
|
||||
return new HttpServerCodec();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpWhoisProtocol
|
||||
static WebWhoisRedirectHandler provideHttpRedirectHandler(
|
||||
google.registry.proxy.ProxyConfig config) {
|
||||
return new WebWhoisRedirectHandler(false, config.webWhois.redirectHost);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static WebWhoisRedirectHandler provideHttpsRedirectHandler(
|
||||
google.registry.proxy.ProxyConfig config) {
|
||||
return new WebWhoisRedirectHandler(true, config.webWhois.redirectHost);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static HttpServerExpectContinueHandler provideHttpServerExpectContinueHandler() {
|
||||
return new HttpServerExpectContinueHandler();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@HttpsWhoisProtocol
|
||||
static SslServerInitializer<NioSocketChannel> provideSslServerInitializer(
|
||||
SslProvider sslProvider,
|
||||
@ServerCertificates PrivateKey privateKey,
|
||||
@ServerCertificates X509Certificate... certificates) {
|
||||
return new SslServerInitializer<>(false, sslProvider, privateKey, certificates);
|
||||
}
|
||||
}
|
|
@ -187,6 +187,14 @@ httpsRelay:
|
|||
# Maximum size of an HTTP message in bytes.
|
||||
maxMessageLengthBytes: 524288
|
||||
|
||||
webWhois:
|
||||
httpPort: 30010
|
||||
httpsPort: 30011
|
||||
|
||||
# The 302 redirect destination of HTTPS web WHOIS GET requests.
|
||||
# HTTP web WHOIS GET requests will be 301 redirected to HTTPS first.
|
||||
redirectHost: whois.yourdomain.tld
|
||||
|
||||
metrics:
|
||||
# Max queries per second for the Google Cloud Monitoring V3 (aka Stackdriver)
|
||||
# API. The limit can be adjusted by contacting Cloud Support.
|
||||
|
|
|
@ -93,7 +93,7 @@ abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpRespo
|
|||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||
.set(HttpHeaderNames.HOST, relayHost)
|
||||
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + accessTokenSupplier.get())
|
||||
.set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
request.content().writeBytes(byteBuf);
|
||||
return request;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package google.registry.proxy.handler;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.proxy.CertificateModule.EppCertificates;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
|
@ -30,8 +29,6 @@ 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.Singleton;
|
||||
|
||||
/**
|
||||
* Adds a server side SSL handler to the channel pipeline.
|
||||
|
@ -47,7 +44,6 @@ import javax.inject.Singleton;
|
|||
* 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> {
|
||||
|
||||
|
@ -59,16 +55,18 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
|
|||
AttributeKey.valueOf("CLIENT_CERTIFICATE_PROMISE_KEY");
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private final boolean requireClientCert;
|
||||
private final SslProvider sslProvider;
|
||||
private final PrivateKey privateKey;
|
||||
private final X509Certificate[] certificates;
|
||||
|
||||
@Inject
|
||||
SslServerInitializer(
|
||||
public SslServerInitializer(
|
||||
boolean requireClientCert,
|
||||
SslProvider sslProvider,
|
||||
@EppCertificates PrivateKey privateKey,
|
||||
@EppCertificates X509Certificate... certificates) {
|
||||
PrivateKey privateKey,
|
||||
X509Certificate... certificates) {
|
||||
logger.atInfo().log("Server SSL Provider: %s", sslProvider);
|
||||
this.requireClientCert = requireClientCert;
|
||||
this.sslProvider = sslProvider;
|
||||
this.privateKey = privateKey;
|
||||
this.certificates = certificates;
|
||||
|
@ -80,9 +78,10 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
|
|||
SslContextBuilder.forServer(privateKey, certificates)
|
||||
.sslProvider(sslProvider)
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.clientAuth(ClientAuth.REQUIRE)
|
||||
.clientAuth(requireClientCert ? ClientAuth.REQUIRE : ClientAuth.NONE)
|
||||
.build()
|
||||
.newHandler(channel.alloc());
|
||||
if (requireClientCert) {
|
||||
Promise<X509Certificate> clientCertificatePromise = channel.eventLoop().newPromise();
|
||||
Future<Channel> unusedFuture =
|
||||
sslHandler
|
||||
|
@ -100,6 +99,7 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
|
|||
}
|
||||
});
|
||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).set(clientCertificatePromise);
|
||||
}
|
||||
channel.pipeline().addLast(sslHandler);
|
||||
}
|
||||
}
|
||||
|
|
137
java/google/registry/proxy/handler/WebWhoisRedirectHandler.java
Normal file
137
java/google/registry/proxy/handler/WebWhoisRedirectHandler.java
Normal file
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2018 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 io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
|
||||
import static io.netty.handler.codec.http.HttpMethod.GET;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.MOVED_PERMANENTLY;
|
||||
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Handler that redirects web WHOIS requests to a canonical website.
|
||||
*
|
||||
* <p>ICANN requires that port 43 and web-based WHOIS are both available on whois.nic.TLD. Since we
|
||||
* expose a single IPv4/IPv6 anycast external IP address for the proxy, we need the load balancer to
|
||||
* router port 80/443 traffic to the proxy to support web WHOIS.
|
||||
*
|
||||
* <p>HTTP (port 80) traffic is simply upgraded to HTTPS (port 443) on the same host, while HTTPS
|
||||
* requests are redirected to the {@code redirectHost}, which is the canonical website that provide
|
||||
* the web WHOIS service.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-31jul17-en.html">
|
||||
* REGISTRY AGREEMENT</a>
|
||||
*/
|
||||
public class WebWhoisRedirectHandler extends SimpleChannelInboundHandler<HttpRequest> {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
/**
|
||||
* HTTP health check sent by GCP HTTP load balancer is set to use this host name.
|
||||
*
|
||||
* <p>Status 200 must be returned in order for a health check to be considered successful.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://cloud.google.com/load-balancing/docs/health-check-concepts#http_https_and_http2_health_checks">
|
||||
* HTTP, HTTPS, and HTTP/2 health checks</a>
|
||||
*/
|
||||
private static final String HEALTH_CHECK_HOST = "health-check.invalid";
|
||||
|
||||
private static final String HSTS_HEADER_NAME = "Strict-Transport-Security";
|
||||
private static final Duration HSTS_MAX_AGE = Duration.ofDays(365);
|
||||
|
||||
private final boolean isHttps;
|
||||
private final String redirectHost;
|
||||
|
||||
public WebWhoisRedirectHandler(boolean isHttps, String redirectHost) {
|
||||
this.isHttps = isHttps;
|
||||
this.redirectHost = redirectHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {
|
||||
FullHttpResponse response;
|
||||
// We only support GET, any other HTTP method should result in 405 error.
|
||||
if (!msg.method().equals(GET)) {
|
||||
response = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED);
|
||||
} else {
|
||||
// All HTTP/1.1 request must contain a Host header with the format "host:[port]".
|
||||
// See https://tools.ietf.org/html/rfc2616#section-14.23
|
||||
String host = Splitter.on(':').split(msg.headers().get(HOST)).iterator().next();
|
||||
if (host.equals(HEALTH_CHECK_HOST)) {
|
||||
// The health check request should always be sent to the HTTP port.
|
||||
response =
|
||||
isHttps
|
||||
? new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)
|
||||
: new DefaultFullHttpResponse(HTTP_1_1, OK);
|
||||
;
|
||||
} else {
|
||||
// HTTP -> HTTPS is a 301 redirect, whereas HTTPS -> web WHOIS site is 302 redirect.
|
||||
response = new DefaultFullHttpResponse(HTTP_1_1, isHttps ? FOUND : MOVED_PERMANENTLY);
|
||||
String redirectUrl = String.format("https://%s/", isHttps ? redirectHost : host);
|
||||
response.headers().set(LOCATION, redirectUrl);
|
||||
}
|
||||
}
|
||||
// Add HSTS header to HTTPS response.
|
||||
if (isHttps) {
|
||||
response
|
||||
.headers()
|
||||
.set(HSTS_HEADER_NAME, String.format("max-age=%d", HSTS_MAX_AGE.getSeconds()));
|
||||
}
|
||||
response
|
||||
.headers()
|
||||
.set(CONTENT_TYPE, TEXT_PLAIN)
|
||||
.setInt(CONTENT_LENGTH, response.content().readableBytes());
|
||||
|
||||
// Close the connection if keep-alive is not set in the request.
|
||||
if (!HttpUtil.isKeepAlive(msg)) {
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
response.headers().set(CONNECTION, KEEP_ALIVE);
|
||||
ChannelFuture unusedFuture = ctx.writeAndFlush(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
logger.atSevere().withCause(cause).log(
|
||||
(isHttps ? "HTTPS" : "HTTP") + " WHOIS inbound exception caught for channel %s",
|
||||
ctx.channel());
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
}
|
|
@ -182,8 +182,7 @@ public abstract class ProtocolModuleTest {
|
|||
EppProtocolModule.class,
|
||||
HealthCheckProtocolModule.class,
|
||||
HttpsRelayProtocolModule.class
|
||||
}
|
||||
)
|
||||
})
|
||||
interface TestComponent {
|
||||
@WhoisProtocol
|
||||
ImmutableList<Provider<? extends ChannelHandler>> whoisHandlers();
|
||||
|
|
|
@ -41,6 +41,10 @@ public class ProxyModuleTest {
|
|||
assertThat(proxyModule.provideEppPort(PROXY_CONFIG)).isEqualTo(PROXY_CONFIG.epp.port);
|
||||
assertThat(proxyModule.provideHealthCheckPort(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.healthCheck.port);
|
||||
assertThat(proxyModule.provideHttpWhoisProtocol(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.webWhois.httpPort);
|
||||
assertThat(proxyModule.provideHttpsWhoisProtocol(PROXY_CONFIG))
|
||||
.isEqualTo(PROXY_CONFIG.webWhois.httpsPort);
|
||||
assertThat(proxyModule.provideEnvironment()).isEqualTo(LOCAL);
|
||||
assertThat(proxyModule.log).isFalse();
|
||||
}
|
||||
|
@ -98,6 +102,20 @@ public class ProxyModuleTest {
|
|||
assertThat(proxyModule.provideHealthCheckPort(PROXY_CONFIG)).isEqualTo(23456);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_parseArgs_customhttpWhoisPort() {
|
||||
String[] args = {"--http_whois", "12121"};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideHttpWhoisProtocol(PROXY_CONFIG)).isEqualTo(12121);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_parseArgs_customhttpsWhoisPort() {
|
||||
String[] args = {"--https_whois", "21212"};
|
||||
proxyModule.parse(args);
|
||||
assertThat(proxyModule.provideHttpsWhoisProtocol(PROXY_CONFIG)).isEqualTo(21212);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_parseArgs_customEnvironment() {
|
||||
String[] args = {"--env", "ALpHa"};
|
||||
|
|
|
@ -48,14 +48,21 @@ public class TestUtils {
|
|||
.headers()
|
||||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||
.set(HttpHeaderNames.HOST, host)
|
||||
.set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
|
||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||
request.headers().set(HttpHeaderNames.HOST, host).setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeHttpResponse(String content, HttpResponseStatus status) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ public class SslServerInitializerTest {
|
|||
.build();
|
||||
|
||||
private ChannelInitializer<LocalChannel> getServerInitializer(
|
||||
boolean requireClientCert,
|
||||
Lock serverLock,
|
||||
Exception serverException,
|
||||
PrivateKey privateKey,
|
||||
|
@ -97,12 +98,22 @@ public class SslServerInitializerTest {
|
|||
protected void initChannel(LocalChannel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(
|
||||
new SslServerInitializer<LocalChannel>(SslProvider.JDK, privateKey, certificates),
|
||||
new SslServerInitializer<LocalChannel>(
|
||||
requireClientCert, SslProvider.JDK, privateKey, certificates),
|
||||
new EchoHandler(serverLock, serverException));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ChannelInitializer<LocalChannel> getServerInitializer(
|
||||
Lock serverLock,
|
||||
Exception serverException,
|
||||
PrivateKey privateKey,
|
||||
X509Certificate... certificates)
|
||||
throws Exception {
|
||||
return getServerInitializer(true, serverLock, serverException, privateKey, certificates);
|
||||
}
|
||||
|
||||
private ChannelInitializer<LocalChannel> getClientInitializer(
|
||||
X509Certificate trustedCertificate,
|
||||
PrivateKey privateKey,
|
||||
|
@ -137,7 +148,7 @@ public class SslServerInitializerTest {
|
|||
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
|
||||
SslServerInitializer<EmbeddedChannel> sslServerInitializer =
|
||||
new SslServerInitializer<>(SslProvider.JDK, ssc.key(), ssc.cert());
|
||||
new SslServerInitializer<>(true, SslProvider.JDK, ssc.key(), ssc.cert());
|
||||
EmbeddedChannel channel = new EmbeddedChannel();
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(sslServerInitializer);
|
||||
|
@ -187,6 +198,39 @@ public class SslServerInitializerTest {
|
|||
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_doesNotRequireClientCert() throws Exception {
|
||||
SelfSignedCertificate serverSsc = new SelfSignedCertificate(SSL_HOST);
|
||||
LocalAddress localAddress = new LocalAddress("DOES_NOT_REQUIRE_CLIENT_CERT");
|
||||
Lock clientLock = new ReentrantLock();
|
||||
Lock serverLock = new ReentrantLock();
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
Exception clientException = new Exception();
|
||||
Exception serverException = new Exception();
|
||||
EventLoopGroup eventLoopGroup =
|
||||
setUpServer(
|
||||
getServerInitializer(
|
||||
false, serverLock, serverException, serverSsc.key(), serverSsc.cert()),
|
||||
localAddress);
|
||||
Channel channel =
|
||||
setUpClient(
|
||||
eventLoopGroup,
|
||||
getClientInitializer(serverSsc.cert(), null, null, clientLock, buffer, clientException),
|
||||
localAddress,
|
||||
PROTOCOL);
|
||||
|
||||
SSLSession sslSession =
|
||||
verifySslChannel(
|
||||
channel, ImmutableList.of(serverSsc.cert()), clientLock, serverLock, buffer, SSL_HOST);
|
||||
// Verify that the SSL session does not contain any client cert. Note that this SslSession is
|
||||
// for the client channel, therefore its local certificates are the remote certificates of the
|
||||
// SslSession for the server channel, and vice versa.
|
||||
assertThat(sslSession.getLocalCertificates()).isNull();
|
||||
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
|
||||
|
||||
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_CertSignedByOtherCA() throws Exception {
|
||||
// The self-signed cert of the CA.
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2018 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.truth.Truth.assertThat;
|
||||
import static google.registry.proxy.TestUtils.assertHttpResponseEquivalent;
|
||||
import static google.registry.proxy.TestUtils.makeHttpGetRequest;
|
||||
import static google.registry.proxy.TestUtils.makeHttpPostRequest;
|
||||
import static google.registry.proxy.TestUtils.makeHttpResponse;
|
||||
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link WebWhoisRedirectHandler}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class WebWhoisRedirectHandlerTest {
|
||||
|
||||
private static final String REDIRECT_HOST = "www.example.com";
|
||||
private static final String TARGET_HOST = "whois.nic.tld";
|
||||
|
||||
private WebWhoisRedirectHandler redirectHandler;
|
||||
private EmbeddedChannel channel;
|
||||
private FullHttpRequest request;
|
||||
private FullHttpResponse response;
|
||||
|
||||
private static FullHttpResponse makeRedirectResponse(
|
||||
HttpResponseStatus status, String location, boolean keepAlive, boolean isHttps) {
|
||||
FullHttpResponse response = makeHttpResponse("", status);
|
||||
response.headers().set("content-type", "text/plain").set("content-length", "0");
|
||||
if (location != null) {
|
||||
response.headers().set("location", location);
|
||||
}
|
||||
if (keepAlive) {
|
||||
response.headers().set("connection", "keep-alive");
|
||||
}
|
||||
if (isHttps) {
|
||||
response.headers().set("Strict-Transport-Security", "max-age=31536000");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_http_redirectToHttps() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(false, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest(TARGET_HOST, "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response,
|
||||
makeRedirectResponse(
|
||||
HttpResponseStatus.MOVED_PERMANENTLY, "https://whois.nic.tld/", true, false));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_http_redirectToHttps_hostAndPort() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(false, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest(TARGET_HOST + ":80", "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response,
|
||||
makeRedirectResponse(
|
||||
HttpResponseStatus.MOVED_PERMANENTLY, "https://whois.nic.tld/", true, false));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_http_redirectToHttps_noKeepAlive() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(false, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest(TARGET_HOST, "/");
|
||||
request.headers().set("connection", "close");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response,
|
||||
makeRedirectResponse(
|
||||
HttpResponseStatus.MOVED_PERMANENTLY, "https://whois.nic.tld/", false, false));
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_http_notGet() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(false, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpPostRequest("", TARGET_HOST, "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response, makeRedirectResponse(HttpResponseStatus.METHOD_NOT_ALLOWED, null, true, false));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_http_healthCheck() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(false, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpPostRequest("", TARGET_HOST, "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response, makeRedirectResponse(HttpResponseStatus.METHOD_NOT_ALLOWED, null, true, false));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_https_redirectToDestination() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(true, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest(TARGET_HOST, "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response,
|
||||
makeRedirectResponse(HttpResponseStatus.FOUND, "https://www.example.com/", true, true));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_https_redirectToDestination_noKeepAlive() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(true, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest(TARGET_HOST, "/");
|
||||
request.headers().set("connection", "close");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response,
|
||||
makeRedirectResponse(HttpResponseStatus.FOUND, "https://www.example.com/", false, true));
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_https_notGet() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(true, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpPostRequest("", TARGET_HOST, "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response, makeRedirectResponse(HttpResponseStatus.METHOD_NOT_ALLOWED, null, true, true));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_https_healthCheck() {
|
||||
redirectHandler = new WebWhoisRedirectHandler(true, REDIRECT_HOST);
|
||||
channel = new EmbeddedChannel(redirectHandler);
|
||||
request = makeHttpGetRequest("health-check.invalid", "/");
|
||||
// No inbound message passed to the next handler.
|
||||
assertThat(channel.writeInbound(request)).isFalse();
|
||||
response = channel.readOutbound();
|
||||
assertHttpResponseEquivalent(
|
||||
response, makeRedirectResponse(HttpResponseStatus.FORBIDDEN, null, true, true));
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue