mirror of
https://github.com/google/nomulus
synced 2025-12-23 06:15:42 +00:00
Add canary service to GKE (#2594)
This commit is contained in:
@@ -151,12 +151,14 @@ public final class EppProtocolModule {
|
||||
static EppServiceHandler provideEppServiceHandler(
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
@Named("hello") byte[] helloBytes,
|
||||
@Named("canary") boolean canary,
|
||||
FrontendMetrics metrics,
|
||||
ProxyConfig config,
|
||||
@HttpsRelayProtocol boolean localRelay) {
|
||||
return new EppServiceHandler(
|
||||
localRelay ? "localhost" : config.epp.relayHost,
|
||||
config.epp.relayPath,
|
||||
canary,
|
||||
idTokenSupplier,
|
||||
helloBytes,
|
||||
metrics);
|
||||
|
||||
@@ -41,6 +41,7 @@ public class ProxyConfig {
|
||||
|
||||
public String projectId;
|
||||
public String oauthClientId;
|
||||
public boolean canary;
|
||||
public List<String> gcpScopes;
|
||||
public int serverCertificateCacheSeconds;
|
||||
public Gcs gcs;
|
||||
|
||||
@@ -278,6 +278,13 @@ public class ProxyModule {
|
||||
() -> OidcTokenUtils.createOidcToken(credentialsBundle, clientId), 1, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("canary")
|
||||
static boolean provideIsCanary(ProxyConfig config) {
|
||||
return config.canary;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static CloudKMS provideCloudKms(GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
|
||||
|
||||
@@ -96,11 +96,13 @@ public final class WhoisProtocolModule {
|
||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||
ProxyConfig config,
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
@Named("canary") boolean canary,
|
||||
FrontendMetrics metrics,
|
||||
@HttpsRelayProtocol boolean localRelay) {
|
||||
return new WhoisServiceHandler(
|
||||
localRelay ? "localhost" : config.whois.relayHost,
|
||||
config.whois.relayPath,
|
||||
canary,
|
||||
idTokenSupplier,
|
||||
metrics);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
# GCP project ID
|
||||
projectId: your-gcp-project-id
|
||||
|
||||
# Whether to connect to the canary (instead of regular) service.
|
||||
canary: false
|
||||
|
||||
# OAuth client ID set as the audience of the OIDC token. This value must be the
|
||||
# same as the auth.oauthClientId value in Nomulus config file, which usually is
|
||||
# the IAP client ID, to allow the request to access IAP protected endpoints.
|
||||
|
||||
@@ -60,10 +60,11 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
||||
public EppServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
boolean canary,
|
||||
Supplier<String> idTokenSupplier,
|
||||
byte[] helloBytes,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||
super(relayHost, relayPath, canary, idTokenSupplier, metrics);
|
||||
this.helloBytes = helloBytes.clone();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import javax.net.ssl.SSLHandshakeException;
|
||||
public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
private static final String CANARY_HEADER = "canary";
|
||||
|
||||
protected static final ImmutableSet<Class<? extends Exception>> NON_FATAL_INBOUND_EXCEPTIONS =
|
||||
ImmutableSet.of(ReadTimeoutException.class, SSLHandshakeException.class);
|
||||
@@ -72,6 +73,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
private final Map<String, Cookie> cookieStore = new LinkedHashMap<>();
|
||||
private final String relayHost;
|
||||
private final String relayPath;
|
||||
private final boolean canary;
|
||||
private final Supplier<String> idTokenSupplier;
|
||||
|
||||
protected final FrontendMetrics metrics;
|
||||
@@ -79,10 +81,12 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
HttpsRelayServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
boolean canary,
|
||||
Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
this.relayHost = relayHost;
|
||||
this.relayPath = relayPath;
|
||||
this.canary = canary;
|
||||
this.idTokenSupplier = idTokenSupplier;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
@@ -104,6 +108,9 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
||||
.set(HttpHeaderNames.HOST, relayHost)
|
||||
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + idTokenSupplier.get())
|
||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
if (canary) {
|
||||
request.headers().set(CANARY_HEADER, "true");
|
||||
}
|
||||
request.content().writeBytes(byteBuf);
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
||||
public WhoisServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
boolean canary,
|
||||
Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||
super(relayHost, relayPath, canary, idTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -261,6 +261,13 @@ public abstract class ProtocolModuleTest {
|
||||
return Suppliers.ofInstance("fake.test.id.token");
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("canary")
|
||||
static boolean provideIsCanary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static LoggingHandler provideLoggingHandler() {
|
||||
|
||||
@@ -25,7 +25,6 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpMessage;
|
||||
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.HttpMessage;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
@@ -53,6 +52,22 @@ public final class TestUtils {
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeHttpPostRequest(
|
||||
String content, String host, String path, boolean canary) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, path, buf);
|
||||
request
|
||||
.headers()
|
||||
.set("user-agent", "Proxy")
|
||||
.set("host", host)
|
||||
.setInt("content-length", buf.readableBytes());
|
||||
if (canary) {
|
||||
request.headers().set("canary", "true");
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeHttpGetRequest(String host, String path) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||
@@ -74,16 +89,46 @@ public final class TestUtils {
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content, String host, String path, String idToken) throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
String content, String host, String path, boolean canary, String idToken) throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path, canary);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + idToken)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
|
||||
.set("content-type", "text/plain")
|
||||
.set("accept", "text/plain");
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content, String host, String path, String idToken) throws IOException {
|
||||
return makeWhoisHttpRequest(content, host, path, false, idToken);
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeEppHttpRequest(
|
||||
String content,
|
||||
String host,
|
||||
String path,
|
||||
boolean canary,
|
||||
String idToken,
|
||||
String sslClientCertificateHash,
|
||||
String clientAddress,
|
||||
Cookie... cookies)
|
||||
throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path, canary);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + idToken)
|
||||
.set("content-type", "application/epp+xml")
|
||||
.set("accept", "application/epp+xml")
|
||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
|
||||
if (cookies.length != 0) {
|
||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public static FullHttpRequest makeEppHttpRequest(
|
||||
String content,
|
||||
String host,
|
||||
@@ -93,31 +138,20 @@ public final class TestUtils {
|
||||
String clientAddress,
|
||||
Cookie... cookies)
|
||||
throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + idToken)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml")
|
||||
.set("accept", "application/epp+xml")
|
||||
.set(ProxyHttpHeaders.CERTIFICATE_HASH, sslClientCertificateHash)
|
||||
.set(ProxyHttpHeaders.IP_ADDRESS, clientAddress)
|
||||
.set(ProxyHttpHeaders.FALLBACK_IP_ADDRESS, clientAddress);
|
||||
if (cookies.length != 0) {
|
||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
return request;
|
||||
return makeEppHttpRequest(
|
||||
content, host, path, false, idToken, sslClientCertificateHash, clientAddress, cookies);
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeWhoisHttpResponse(String content, HttpResponseStatus status) {
|
||||
FullHttpResponse response = makeHttpResponse(content, status);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
|
||||
response.headers().set("content-type", "text/plain");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static FullHttpResponse makeEppHttpResponse(
|
||||
String content, HttpResponseStatus status, Cookie... cookies) {
|
||||
FullHttpResponse response = makeHttpResponse(content, status);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/epp+xml");
|
||||
response.headers().set("content-type", "application/epp+xml");
|
||||
for (Cookie cookie : cookies) {
|
||||
response.headers().add("set-cookie", ServerCookieEncoder.STRICT.encode(cookie));
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ class EppServiceHandlerTest {
|
||||
|
||||
private static final String HELLO =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<hello/>
|
||||
</epp>
|
||||
""";
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
|
||||
<hello/>
|
||||
</epp>
|
||||
""";
|
||||
|
||||
private static final String RELAY_HOST = "registry.example.tld";
|
||||
private static final String RELAY_PATH = "/epp";
|
||||
@@ -71,7 +71,8 @@ class EppServiceHandlerTest {
|
||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||
|
||||
private final EppServiceHandler eppServiceHandler =
|
||||
new EppServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
@@ -146,7 +147,7 @@ class EppServiceHandlerTest {
|
||||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
setHandshakeSuccess(channel2, clientCertificate);
|
||||
|
||||
@@ -166,7 +167,7 @@ class EppServiceHandlerTest {
|
||||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
|
||||
setHandshakeSuccess(channel2, clientCertificate2);
|
||||
@@ -214,6 +215,33 @@ class EppServiceHandlerTest {
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sendRequestToNextHandler_canary() throws Exception {
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, true, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
channel = setUpNewChannel(eppServiceHandler2);
|
||||
setHandshakeSuccess();
|
||||
// First inbound message is hello.
|
||||
channel.readInbound();
|
||||
String content = "<epp>stuff</epp>";
|
||||
channel.writeInbound(Unpooled.wrappedBuffer(content.getBytes(UTF_8)));
|
||||
FullHttpRequest request = channel.readInbound();
|
||||
assertThat(request)
|
||||
.isEqualTo(
|
||||
TestUtils.makeEppHttpRequest(
|
||||
content,
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
true,
|
||||
ID_TOKEN,
|
||||
getCertificateHash(clientCertificate),
|
||||
CLIENT_ADDRESS));
|
||||
// Nothing further to pass to the next handler.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_sendResponseToNextHandler() throws Exception {
|
||||
setHandshakeSuccess();
|
||||
|
||||
@@ -50,7 +50,7 @@ class WhoisServiceHandlerTest {
|
||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||
|
||||
private final WhoisServiceHandler whoisServiceHandler =
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, metrics);
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
@BeforeEach
|
||||
@@ -74,7 +74,7 @@ class WhoisServiceHandlerTest {
|
||||
|
||||
// Setup second channel.
|
||||
WhoisServiceHandler whoisServiceHandler2 =
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, false, () -> ID_TOKEN, metrics);
|
||||
EmbeddedChannel channel2 =
|
||||
// We need a new channel id so that it has a different hash code.
|
||||
// This only is needed for EmbeddedChannel because it has a dummy hash code implementation.
|
||||
@@ -98,6 +98,23 @@ class WhoisServiceHandlerTest {
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_fireInboundHttpRequest_canary() throws Exception {
|
||||
WhoisServiceHandler whoisServiceHandler2 =
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, true, () -> ID_TOKEN, metrics);
|
||||
channel = new EmbeddedChannel(whoisServiceHandler2);
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||
FullHttpRequest expectedRequest =
|
||||
makeWhoisHttpRequest(QUERY_CONTENT, RELAY_HOST, RELAY_PATH, true, ID_TOKEN);
|
||||
// Input data passed to next handler
|
||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
||||
FullHttpRequest inputRequest = channel.readInbound();
|
||||
assertThat(inputRequest).isEqualTo(expectedRequest);
|
||||
// The channel is still open, and nothing else is to be read from it.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_parseOutboundHttpResponse() {
|
||||
String outputString = "line1\r\nline2\r\n";
|
||||
|
||||
Reference in New Issue
Block a user