mirror of
https://github.com/google/nomulus.git
synced 2025-07-29 14:06:28 +02:00
Refactor OIDC-based auth mechanism (#2049)
This PR changes the two flavors of OIDC authentication mechanisms to verify the same audience. This allows the same token to pass both mechanisms. Previously the regular OIDC flavor uses the project id as its required audience, which does not work for local user credentials (such as ones used by the nomulus tool), which requires a valid OAuth client ID as audience when minting the token (project id is NOT a valid OAuth client ID). I considered allowing multiple audiences, but the result is not as clean as just using the same everywhere, because the fall-through logic would have generated a lot of noises for failed attempts. This PR also changes the client side to solely use OIDC token whenever possible, including the proxy, cloud scheduler and cloud tasks. The nomulus tool still uses OAuth access token by default because it requires USER level authentication, which in turn requires us to fill the User table with objects corresponding to the email address of everyone needing access to the tool. TESTED=verified each client is able to make authenticated calls on QA with or without IAP.
This commit is contained in:
parent
cf1a148208
commit
fdfbb9572d
56 changed files with 565 additions and 891 deletions
|
@ -16,7 +16,6 @@ package google.registry.proxy;
|
|||
|
||||
import static google.registry.util.ResourceUtils.readResourceBytes;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
@ -44,7 +43,6 @@ import io.netty.handler.timeout.ReadTimeoutHandler;
|
|||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -116,7 +114,7 @@ public final class EppProtocolModule {
|
|||
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).
|
||||
// Initial bytes to strip (i.e. strip the length header).
|
||||
config.epp.headerLengthBytes);
|
||||
}
|
||||
|
||||
|
@ -149,18 +147,12 @@ public final class EppProtocolModule {
|
|||
|
||||
@Provides
|
||||
static EppServiceHandler provideEppServiceHandler(
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
@Named("iapClientId") Optional<String> iapClientId,
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
@Named("hello") byte[] helloBytes,
|
||||
FrontendMetrics metrics,
|
||||
ProxyConfig config) {
|
||||
return new EppServiceHandler(
|
||||
config.epp.relayHost,
|
||||
config.epp.relayPath,
|
||||
refreshedCredentialsSupplier,
|
||||
iapClientId,
|
||||
helloBytes,
|
||||
metrics);
|
||||
config.epp.relayHost, config.epp.relayPath, idTokenSupplier, helloBytes, metrics);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
|
|
@ -40,7 +40,7 @@ public class ProxyConfig {
|
|||
private static final String CUSTOM_CONFIG_FORMATTER = "config/proxy-config-%s.yaml";
|
||||
|
||||
public String projectId;
|
||||
public String iapClientId;
|
||||
public String oauthClientId;
|
||||
public List<String> gcpScopes;
|
||||
public int serverCertificateCacheSeconds;
|
||||
public Gcs gcs;
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.cloud.storage.BlobId;
|
|||
import com.google.cloud.storage.Storage;
|
||||
import com.google.cloud.storage.StorageException;
|
||||
import com.google.cloud.storage.StorageOptions;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.monitoring.metrics.MetricReporter;
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
|
@ -45,6 +46,7 @@ import google.registry.proxy.handler.ProxyProtocolHandler;
|
|||
import google.registry.util.Clock;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import google.registry.util.JdkLoggerConfig;
|
||||
import google.registry.util.OidcTokenUtils;
|
||||
import google.registry.util.SystemClock;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
|
@ -59,6 +61,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.Handler;
|
||||
|
@ -126,7 +129,8 @@ public class ProxyModule {
|
|||
// corresponds to Level.FINE (JUL log level). It uses a JUL logger with the name
|
||||
// "io.netty.handler.logging.LoggingHandler" to actually process the logs. This JUL logger is
|
||||
// set to Level.FINE if the --log parameter is passed, so that it does not filter out logs
|
||||
// that the LoggingHandler writes. Otherwise the logs are silently ignored because the default
|
||||
// that the LoggingHandler writes. Otherwise, the logs are silently ignored because the
|
||||
// default
|
||||
// JUL logger level is Level.INFO.
|
||||
JdkLoggerConfig.getConfig(LoggingHandler.class).setLevel(Level.FINE);
|
||||
// Log source IP information if --log parameter is passed. This is considered PII and should
|
||||
|
@ -158,10 +162,10 @@ public class ProxyModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@Named("iapClientId")
|
||||
@Named("oauthClientId")
|
||||
@Singleton
|
||||
Optional<String> provideIapClientId(ProxyConfig config) {
|
||||
return Optional.ofNullable(config.iapClientId);
|
||||
String provideClientId(ProxyConfig config) {
|
||||
return config.oauthClientId;
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -242,6 +246,23 @@ public class ProxyModule {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an OIDC token with the given OAuth client ID as audience used for authentication.
|
||||
*
|
||||
* <p>The token is cached for 1 hour to reduce repeated calls to the metadata server.
|
||||
*
|
||||
* @see <a href="https://cloud.google.com/docs/authentication/token-types#id-lifetime">ID token
|
||||
* lifetime</a>
|
||||
*/
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("idToken")
|
||||
static Supplier<String> provideOidcToken(
|
||||
GoogleCredentialsBundle credentialsBundle, @Named("oauthClientId") String clientId) {
|
||||
return Suppliers.memoizeWithExpiration(
|
||||
() -> OidcTokenUtils.createOidcToken(credentialsBundle, clientId), 1, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
static CloudKMS provideCloudKms(GoogleCredentialsBundle credentialsBundle, ProxyConfig config) {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
package google.registry.proxy;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
@ -35,7 +34,6 @@ import google.registry.util.Clock;
|
|||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -46,7 +44,9 @@ import javax.inject.Singleton;
|
|||
|
||||
/** A module that provides the {@link FrontendProtocol} used for whois protocol. */
|
||||
@Module
|
||||
public class WhoisProtocolModule {
|
||||
public final class WhoisProtocolModule {
|
||||
|
||||
private WhoisProtocolModule() {}
|
||||
|
||||
/** Dagger qualifier to provide whois protocol related handlers and other bindings. */
|
||||
@Qualifier
|
||||
|
@ -93,15 +93,10 @@ public class WhoisProtocolModule {
|
|||
@Provides
|
||||
static WhoisServiceHandler provideWhoisServiceHandler(
|
||||
ProxyConfig config,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
@Named("iapClientId") Optional<String> iapClientId,
|
||||
@Named("idToken") Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
return new WhoisServiceHandler(
|
||||
config.whois.relayHost,
|
||||
config.whois.relayPath,
|
||||
refreshedCredentialsSupplier,
|
||||
iapClientId,
|
||||
metrics);
|
||||
config.whois.relayHost, config.whois.relayPath, idTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -8,14 +8,17 @@
|
|||
# GCP project ID
|
||||
projectId: your-gcp-project-id
|
||||
|
||||
# IAP client ID, if IAP is enabled for this project
|
||||
iapClientId: null
|
||||
# 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.
|
||||
# Regular OIDC authentication mechanism also checks for this audience.
|
||||
oauthClientId: iap-client-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
|
||||
# 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
|
||||
|
||||
|
@ -25,8 +28,8 @@ gcpScopes:
|
|||
|
||||
# Server certificate is cached for 30 minutes.
|
||||
#
|
||||
# Encrypted server server certificate and private keys are stored on GCS. They
|
||||
# are cached and shared for all connections for 30 minutes. We not not cache
|
||||
# Encrypted server certificate and private keys are stored on GCS. They
|
||||
# are cached and shared for all connections for 30 minutes. We do not cache
|
||||
# the certificate indefinitely because if we upload a new one to GCS, all
|
||||
# existing instances need to be killed if they cache the old one indefinitely.
|
||||
serverCertificateCacheSeconds: 1800
|
||||
|
@ -59,8 +62,8 @@ epp:
|
|||
# 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.
|
||||
# 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).
|
||||
|
@ -76,7 +79,7 @@ epp:
|
|||
# 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
|
||||
# should be reasonable enough for any registrar to log in and submit their
|
||||
# request.
|
||||
readTimeoutSeconds: 3600
|
||||
|
||||
|
@ -91,7 +94,7 @@ epp:
|
|||
# Default quota for any userId not matched in customQuota.
|
||||
defaultQuota:
|
||||
|
||||
# List of identifiers, e. g. IP address, certificate hash.
|
||||
# List of identifiers, e.g. IP address, certificate hash.
|
||||
#
|
||||
# userId for defaultQuota should always be an empty list. Any value
|
||||
# in the list will be discarded.
|
||||
|
@ -129,7 +132,7 @@ whois:
|
|||
# (http://www.freesoft.org/CIE/RFC/1035/9.htm).
|
||||
maxMessageLengthBytes: 512
|
||||
|
||||
# Whois protocol is transient, the client should not establish a long lasting
|
||||
# Whois protocol is transient, the client should not establish a long-lasting
|
||||
# idle connection.
|
||||
readTimeoutSeconds: 60
|
||||
|
||||
|
@ -144,7 +147,7 @@ whois:
|
|||
# Default quota for any userId not matched in customQuota.
|
||||
defaultQuota:
|
||||
|
||||
# List of identifiers, e. g. IP address, certificate hash.
|
||||
# List of identifiers, e.g. IP address, certificate hash.
|
||||
#
|
||||
# userId for defaultQuota should always be an empty list.
|
||||
userId: []
|
||||
|
|
|
@ -20,7 +20,6 @@ import static google.registry.networking.handler.SslServerInitializer.CLIENT_CER
|
|||
import static google.registry.proxy.handler.ProxyProtocolHandler.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
|
@ -37,7 +36,6 @@ import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
|||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Handler that processes EPP protocol logic. */
|
||||
|
@ -62,12 +60,11 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
|||
public EppServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
Supplier<String> idTokenSupplier,
|
||||
byte[] helloBytes,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
||||
this.helloBytes = helloBytes;
|
||||
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||
this.helloBytes = helloBytes.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,6 +88,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
|||
*/
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
ctx.channel()
|
||||
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
||||
|
@ -110,6 +108,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
|||
logger.atWarning().withCause(promise.cause()).log(
|
||||
"Cannot finish handshake for channel %s, remote IP %s",
|
||||
ctx.channel(), ctx.channel().attr(REMOTE_ADDRESS_KEY).get());
|
||||
@SuppressWarnings("unused")
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
});
|
||||
|
@ -136,7 +135,7 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
|
|||
checkArgument(msg instanceof HttpResponse);
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
String sessionAliveValue = response.headers().get(ProxyHttpHeaders.EPP_SESSION);
|
||||
if (sessionAliveValue != null && sessionAliveValue.equals("close")) {
|
||||
if ("close".equals(sessionAliveValue)) {
|
||||
promise.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
|
|
|
@ -16,12 +16,7 @@ package google.registry.proxy.handler;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
|
@ -42,11 +37,9 @@ 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 io.netty.handler.timeout.ReadTimeoutException;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
|
@ -63,8 +56,8 @@ import javax.net.ssl.SSLHandshakeException;
|
|||
* 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.
|
||||
* <p>This handler is session-aware and will store all the session cookies that are contained in the
|
||||
* HTTP response headers, which are added back to headers of subsequent HTTP requests.
|
||||
*/
|
||||
public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHttpResponse> {
|
||||
|
||||
|
@ -79,21 +72,18 @@ 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 Supplier<GoogleCredentials> refreshedCredentialsSupplier;
|
||||
private final Optional<String> iapClientId;
|
||||
private final Supplier<String> idTokenSupplier;
|
||||
|
||||
protected final FrontendMetrics metrics;
|
||||
|
||||
HttpsRelayServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
this.relayHost = relayHost;
|
||||
this.relayPath = relayPath;
|
||||
this.refreshedCredentialsSupplier = refreshedCredentialsSupplier;
|
||||
this.iapClientId = iapClientId;
|
||||
this.idTokenSupplier = idTokenSupplier;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
|
@ -108,30 +98,12 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
|||
protected FullHttpRequest decodeFullHttpRequest(ByteBuf byteBuf) {
|
||||
FullHttpRequest request =
|
||||
new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, relayPath);
|
||||
GoogleCredentials credentials = refreshedCredentialsSupplier.get();
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.USER_AGENT, "Proxy")
|
||||
.set(HttpHeaderNames.HOST, relayHost)
|
||||
.set(
|
||||
HttpHeaderNames.AUTHORIZATION, "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.set(HttpHeaderNames.AUTHORIZATION, "Bearer " + idTokenSupplier.get())
|
||||
.setInt(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
|
||||
// Set the Proxy-Authorization header if using IAP
|
||||
if (iapClientId.isPresent()) {
|
||||
IdTokenProvider idTokenProvider = (IdTokenProvider) credentials;
|
||||
try {
|
||||
// Note: we use Option.FORMAT_FULL to make sure the JWT we receive contains the email
|
||||
// address (as is required by IAP)
|
||||
IdToken idToken =
|
||||
idTokenProvider.idTokenWithAudience(
|
||||
iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL));
|
||||
request
|
||||
.headers()
|
||||
.set(HttpHeaderNames.PROXY_AUTHORIZATION, "Bearer " + idToken.getTokenValue());
|
||||
} catch (IOException e) {
|
||||
logger.atSevere().withCause(e).log("Error when attempting to retrieve IAP ID token");
|
||||
}
|
||||
}
|
||||
request.content().writeBytes(byteBuf);
|
||||
return request;
|
||||
}
|
||||
|
@ -204,6 +176,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
|||
logger.atSevere().withCause(cause).log(
|
||||
"Inbound exception caught for channel %s", ctx.channel());
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
ChannelFuture unusedFuture = ctx.close();
|
||||
}
|
||||
|
||||
|
@ -222,6 +195,7 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
|||
logger.atSevere().withCause(channelFuture.cause()).log(
|
||||
"Outbound exception caught for channel %s", channelFuture.channel());
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
ChannelFuture unusedFuture = channelFuture.channel().close();
|
||||
}
|
||||
});
|
||||
|
@ -230,6 +204,9 @@ public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec<FullHt
|
|||
|
||||
/** Exception thrown when the response status from GAE is not 200. */
|
||||
public static class NonOkHttpResponseException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 5340993059579288708L;
|
||||
|
||||
NonOkHttpResponseException(FullHttpResponse response, Channel channel) {
|
||||
super(
|
||||
String.format(
|
||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.proxy.handler;
|
|||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
|
@ -26,7 +25,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
|||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Handler that processes WHOIS protocol logic. */
|
||||
|
@ -35,10 +33,9 @@ public final class WhoisServiceHandler extends HttpsRelayServiceHandler {
|
|||
public WhoisServiceHandler(
|
||||
String relayHost,
|
||||
String relayPath,
|
||||
Supplier<GoogleCredentials> refreshedCredentialsSupplier,
|
||||
Optional<String> iapClientId,
|
||||
Supplier<String> idTokenSupplier,
|
||||
FrontendMetrics metrics) {
|
||||
super(relayHost, relayPath, refreshedCredentialsSupplier, iapClientId, metrics);
|
||||
super(relayHost, relayPath, idTokenSupplier, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
|
||||
import com.google.common.base.Throwables;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.util.SelfSignedCaCertificate;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
@ -90,7 +89,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
|||
}
|
||||
|
||||
/** Get a {@link ByteBuf} that represents the raw epp request with the given content. */
|
||||
private ByteBuf getByteBufFromContent(byte[] content) {
|
||||
private static ByteBuf getByteBufFromContent(byte[] content) {
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
buffer.writeInt(content.length + HEADER_LENGTH);
|
||||
buffer.writeBytes(content);
|
||||
|
@ -102,18 +101,17 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
|||
new String(content, UTF_8),
|
||||
PROXY_CONFIG.epp.relayHost,
|
||||
PROXY_CONFIG.epp.relayPath,
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideFakeIdToken().get(),
|
||||
getCertificateHash(certificate),
|
||||
CLIENT_ADDRESS,
|
||||
TestModule.provideIapClientId(),
|
||||
cookies);
|
||||
}
|
||||
|
||||
private FullHttpResponse makeEppHttpResponse(byte[] content, Cookie... cookies) {
|
||||
private static FullHttpResponse makeEppHttpResponse(byte[] content, Cookie... cookies) {
|
||||
return makeEppHttpResponse(content, HttpResponseStatus.OK, cookies);
|
||||
}
|
||||
|
||||
private FullHttpResponse makeEppHttpResponse(
|
||||
private static FullHttpResponse makeEppHttpResponse(
|
||||
byte[] content, HttpResponseStatus status, Cookie... cookies) {
|
||||
return TestUtils.makeEppHttpResponse(new String(content, UTF_8), status, cookies);
|
||||
}
|
||||
|
@ -121,7 +119,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
|||
@BeforeEach
|
||||
@Override
|
||||
void beforeEach() throws Exception {
|
||||
testComponent = makeTestComponent(new FakeClock());
|
||||
testComponent = makeTestComponent();
|
||||
certificate = SelfSignedCaCertificate.create().cert();
|
||||
initializeChannel(
|
||||
ch -> {
|
||||
|
@ -129,6 +127,7 @@ class EppProtocolModuleTest extends ProtocolModuleTest {
|
|||
ch.attr(CLIENT_CERTIFICATE_PROMISE_KEY).set(ch.eventLoop().newPromise());
|
||||
addAllTestableHandlers(ch);
|
||||
});
|
||||
@SuppressWarnings("unused")
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
||||
}
|
||||
|
|
|
@ -17,14 +17,7 @@ package google.registry.proxy;
|
|||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.proxy.ProxyConfig.Environment.LOCAL;
|
||||
import static google.registry.proxy.ProxyConfig.getProxyConfig;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -59,9 +52,7 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
|||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
@ -80,14 +71,14 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
* correctly performed by various handlers attached to its pipeline. Non-business essential handlers
|
||||
* should be excluded.
|
||||
*
|
||||
* <p>Subclass should implement an no-arg constructor that calls constructors of this class,
|
||||
* <p>Subclass should implement a no-arg constructor that calls constructors of this class,
|
||||
* providing the method reference of the {@link TestComponent} method to call to obtain the list of
|
||||
* {@link ChannelHandler} providers for the {@link Protocol} to test, and optionally a set of {@link
|
||||
* ChannelHandler} classes to exclude from testing.
|
||||
*/
|
||||
public abstract class ProtocolModuleTest {
|
||||
|
||||
static final ProxyConfig PROXY_CONFIG = getProxyConfig(Environment.LOCAL);
|
||||
static final ProxyConfig PROXY_CONFIG = getProxyConfig(LOCAL);
|
||||
|
||||
TestComponent testComponent;
|
||||
|
||||
|
@ -112,7 +103,7 @@ public abstract class ProtocolModuleTest {
|
|||
FullHttpResponseRelayHandler.class,
|
||||
// This handler is tested in its own unit tests. It is installed in web whois redirect
|
||||
// protocols. The end-to-end tests for the rest of the handlers in its pipeline need to
|
||||
// be able to emit incoming requests out of the channel for assertions. Therefore this
|
||||
// be able to emit incoming requests out of the channel for assertions. Therefore, this
|
||||
// handler is removed from the pipeline.
|
||||
WebWhoisRedirectHandler.class,
|
||||
// The rest are not part of business logic and do not need to be tested, obviously.
|
||||
|
@ -150,7 +141,7 @@ public abstract class ProtocolModuleTest {
|
|||
this(handlerProvidersMethod, DEFAULT_EXCLUDED_HANDLERS);
|
||||
}
|
||||
|
||||
/** Excludes handler providers that are not of interested for testing. */
|
||||
/** Excludes handler providers that are not of interest for testing. */
|
||||
private ImmutableList<Provider<? extends ChannelHandler>> excludeHandlerProvidersForTesting(
|
||||
ImmutableList<Provider<? extends ChannelHandler>> handlerProviders) {
|
||||
return handlerProviders.stream()
|
||||
|
@ -177,7 +168,7 @@ public abstract class ProtocolModuleTest {
|
|||
}
|
||||
}
|
||||
|
||||
static TestComponent makeTestComponent(FakeClock fakeClock) {
|
||||
static TestComponent makeTestComponent() {
|
||||
return DaggerProtocolModuleTest_TestComponent.builder()
|
||||
.testModule(new TestModule(new FakeClock()))
|
||||
.build();
|
||||
|
@ -185,7 +176,7 @@ public abstract class ProtocolModuleTest {
|
|||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
testComponent = makeTestComponent(new FakeClock());
|
||||
testComponent = makeTestComponent();
|
||||
initializeChannel(this::addAllTestableHandlers);
|
||||
}
|
||||
|
||||
|
@ -244,10 +235,11 @@ public abstract class ProtocolModuleTest {
|
|||
this.fakeClock = fakeClock;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("iapClientId")
|
||||
public static Optional<String> provideIapClientId() {
|
||||
return Optional.of("iapClientId");
|
||||
@Named("clientId")
|
||||
public static String provideClientId() {
|
||||
return "clientId";
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
@ -264,19 +256,9 @@ public abstract class ProtocolModuleTest {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
static Supplier<GoogleCredentials> provideFakeCredentials() {
|
||||
ComputeEngineCredentials mockCredentials = mock(ComputeEngineCredentials.class);
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("fake.test.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
try {
|
||||
when(mockCredentials.idTokenWithAudience(
|
||||
"iapClientId", ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Suppliers.ofInstance(mockCredentials);
|
||||
@Named("idToken")
|
||||
static Supplier<String> provideFakeIdToken() {
|
||||
return Suppliers.ofInstance("fake.test.id.token");
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
@ -306,7 +288,7 @@ public abstract class ProtocolModuleTest {
|
|||
@Singleton
|
||||
@Provides
|
||||
static Environment provideEnvironment() {
|
||||
return Environment.LOCAL;
|
||||
return LOCAL;
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
|
|
@ -17,10 +17,6 @@ package google.registry.proxy;
|
|||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.auth.oauth2.IdTokenProvider;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.util.ProxyHttpHeaders;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
@ -39,10 +35,11 @@ import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
|||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Utility class for various helper methods used in testing. */
|
||||
public class TestUtils {
|
||||
public final class TestUtils {
|
||||
|
||||
private TestUtils() {}
|
||||
|
||||
public static FullHttpRequest makeHttpPostRequest(String content, String host, String path) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(content.getBytes(US_ASCII));
|
||||
|
@ -77,19 +74,13 @@ public class TestUtils {
|
|||
}
|
||||
|
||||
public static FullHttpRequest makeWhoisHttpRequest(
|
||||
String content,
|
||||
String host,
|
||||
String path,
|
||||
GoogleCredentials credentials,
|
||||
Optional<String> iapClientId)
|
||||
throws IOException {
|
||||
String content, String host, String path, String idToken) throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.set("authorization", "Bearer " + idToken)
|
||||
.set(HttpHeaderNames.CONTENT_TYPE, "text/plain")
|
||||
.set("accept", "text/plain");
|
||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
||||
return request;
|
||||
}
|
||||
|
||||
|
@ -97,21 +88,19 @@ public class TestUtils {
|
|||
String content,
|
||||
String host,
|
||||
String path,
|
||||
GoogleCredentials credentials,
|
||||
String idToken,
|
||||
String sslClientCertificateHash,
|
||||
String clientAddress,
|
||||
Optional<String> iapClientId,
|
||||
Cookie... cookies)
|
||||
throws IOException {
|
||||
FullHttpRequest request = makeHttpPostRequest(content, host, path);
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", "Bearer " + credentials.getAccessToken().getTokenValue())
|
||||
.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);
|
||||
maybeSetProxyAuthForIap(request, credentials, iapClientId);
|
||||
if (cookies.length != 0) {
|
||||
request.headers().set("cookie", ClientCookieEncoder.STRICT.encode(cookies));
|
||||
}
|
||||
|
@ -140,7 +129,7 @@ public class TestUtils {
|
|||
* <p>This method is needed because an HTTP message decoded and aggregated from inbound {@link
|
||||
* ByteBuf} is of a different class than the one written to the outbound {@link ByteBuf}, and The
|
||||
* {@link ByteBuf} implementations that hold the content of the HTTP messages are different, even
|
||||
* though the actual content, headers, etc are the same.
|
||||
* though the actual content, headers, etc. are the same.
|
||||
*
|
||||
* <p>This method is not type-safe, msg1 & msg2 can be a request and a response, respectively. Do
|
||||
* not use this method directly.
|
||||
|
@ -161,16 +150,4 @@ public class TestUtils {
|
|||
public static void assertHttpRequestEquivalent(HttpRequest req1, HttpRequest req2) {
|
||||
assertHttpMessageEquivalent(req1, req2);
|
||||
}
|
||||
|
||||
private static void maybeSetProxyAuthForIap(
|
||||
FullHttpRequest request, GoogleCredentials credentials, Optional<String> iapClientId)
|
||||
throws IOException {
|
||||
if (iapClientId.isPresent()) {
|
||||
String idTokenValue =
|
||||
((IdTokenProvider) credentials)
|
||||
.idTokenWithAudience(iapClientId.get(), ImmutableList.of(Option.FORMAT_FULL))
|
||||
.getTokenValue();
|
||||
request.headers().set("proxy-authorization", "Bearer " + idTokenValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
|||
"test.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
TestModule.provideFakeIdToken().get());
|
||||
assertThat(actualRequest).isEqualTo(expectedRequest);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
// Nothing more to read.
|
||||
|
@ -89,8 +88,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
|||
"test1.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
TestModule.provideFakeIdToken().get());
|
||||
assertThat(actualRequest1).isEqualTo(expectedRequest1);
|
||||
// No more message at this point.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
|
@ -104,8 +102,7 @@ class WhoisProtocolModuleTest extends ProtocolModuleTest {
|
|||
"test2.tld",
|
||||
PROXY_CONFIG.whois.relayHost,
|
||||
PROXY_CONFIG.whois.relayPath,
|
||||
TestModule.provideFakeCredentials().get(),
|
||||
TestModule.provideIapClientId());
|
||||
TestModule.provideFakeIdToken().get());
|
||||
assertThat(actualRequest2).isEqualTo(expectedRequest2);
|
||||
// The third message is not complete yet.
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
|
|
@ -25,14 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.proxy.TestUtils;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
|
@ -52,7 +46,6 @@ import io.netty.handler.codec.http.cookie.DefaultCookie;
|
|||
import io.netty.util.concurrent.Promise;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -69,27 +62,18 @@ class EppServiceHandlerTest {
|
|||
private static final String RELAY_PATH = "/epp";
|
||||
private static final String CLIENT_ADDRESS = "epp.client.tld";
|
||||
private static final String PROTOCOL = "epp";
|
||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
||||
|
||||
private static final ComputeEngineCredentials mockCredentials =
|
||||
mock(ComputeEngineCredentials.class);
|
||||
private static final String ID_TOKEN = "fake.id.token";
|
||||
|
||||
private X509Certificate clientCertificate;
|
||||
|
||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||
|
||||
private final EppServiceHandler eppServiceHandler =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.of(IAP_CLIENT_ID),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
new EppServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
private void setHandshakeSuccess(EmbeddedChannel channel, X509Certificate certificate) {
|
||||
private static void setHandshakeSuccess(EmbeddedChannel channel, X509Certificate certificate) {
|
||||
@SuppressWarnings("unused")
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
channel.attr(CLIENT_CERTIFICATE_PROMISE_KEY).get().setSuccess(certificate);
|
||||
|
@ -99,7 +83,8 @@ class EppServiceHandlerTest {
|
|||
setHandshakeSuccess(channel, clientCertificate);
|
||||
}
|
||||
|
||||
private void setHandshakeFailure(EmbeddedChannel channel) {
|
||||
private static void setHandshakeFailure(EmbeddedChannel channel) {
|
||||
@SuppressWarnings("unused")
|
||||
Promise<X509Certificate> unusedPromise =
|
||||
channel
|
||||
.attr(CLIENT_CERTIFICATE_PROMISE_KEY)
|
||||
|
@ -116,25 +101,19 @@ class EppServiceHandlerTest {
|
|||
content,
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
mockCredentials,
|
||||
ID_TOKEN,
|
||||
getCertificateHash(clientCertificate),
|
||||
CLIENT_ADDRESS,
|
||||
Optional.of(IAP_CLIENT_ID),
|
||||
cookies);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
clientCertificate = SelfSignedCaCertificate.create().cert();
|
||||
channel = setUpNewChannel(eppServiceHandler);
|
||||
}
|
||||
|
||||
private EmbeddedChannel setUpNewChannel(EppServiceHandler handler) {
|
||||
private static EmbeddedChannel setUpNewChannel(EppServiceHandler handler) {
|
||||
return new EmbeddedChannel(
|
||||
DefaultChannelId.newInstance(),
|
||||
new ChannelInitializer<EmbeddedChannel>() {
|
||||
|
@ -165,12 +144,7 @@ class EppServiceHandlerTest {
|
|||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
setHandshakeSuccess(channel2, clientCertificate);
|
||||
|
||||
|
@ -190,12 +164,7 @@ class EppServiceHandlerTest {
|
|||
// Set up the second channel.
|
||||
EppServiceHandler eppServiceHandler2 =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, HELLO.getBytes(UTF_8), metrics);
|
||||
EmbeddedChannel channel2 = setUpNewChannel(eppServiceHandler2);
|
||||
X509Certificate clientCertificate2 = SelfSignedCaCertificate.create().cert();
|
||||
setHandshakeSuccess(channel2, clientCertificate2);
|
||||
|
@ -358,38 +327,4 @@ class EppServiceHandlerTest {
|
|||
assertThat((Object) channel.readOutbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withoutIapClientId() throws Exception {
|
||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
||||
EppServiceHandler nonIapServiceHandler =
|
||||
new EppServiceHandler(
|
||||
RELAY_HOST,
|
||||
RELAY_PATH,
|
||||
() -> mockCredentials,
|
||||
Optional.empty(),
|
||||
HELLO.getBytes(UTF_8),
|
||||
metrics);
|
||||
channel = setUpNewChannel(nonIapServiceHandler);
|
||||
|
||||
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,
|
||||
mockCredentials,
|
||||
getCertificateHash(clientCertificate),
|
||||
CLIENT_ADDRESS,
|
||||
Optional.empty()));
|
||||
// Nothing further to pass to the next handler.
|
||||
assertThat((Object) channel.readInbound()).isNull();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.ComputeEngineCredentials;
|
||||
import com.google.auth.oauth2.IdToken;
|
||||
import com.google.auth.oauth2.IdTokenProvider.Option;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.proxy.handler.HttpsRelayServiceHandler.NonOkHttpResponseException;
|
||||
import google.registry.proxy.metric.FrontendMetrics;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
@ -40,7 +34,6 @@ import io.netty.handler.codec.EncoderException;
|
|||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -52,24 +45,16 @@ class WhoisServiceHandlerTest {
|
|||
private static final String QUERY_CONTENT = "test.tld";
|
||||
private static final String PROTOCOL = "whois";
|
||||
private static final String CLIENT_HASH = "none";
|
||||
private static final String IAP_CLIENT_ID = "iapClientId";
|
||||
private static final String ID_TOKEN = "fake.id.token";
|
||||
|
||||
private static final ComputeEngineCredentials mockCredentials =
|
||||
mock(ComputeEngineCredentials.class);
|
||||
private final FrontendMetrics metrics = mock(FrontendMetrics.class);
|
||||
|
||||
private final WhoisServiceHandler whoisServiceHandler =
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.of(IAP_CLIENT_ID), metrics);
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> ID_TOKEN, metrics);
|
||||
private EmbeddedChannel channel;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() throws Exception {
|
||||
when(mockCredentials.getAccessToken()).thenReturn(new AccessToken("this.access.token", null));
|
||||
IdToken mockIdToken = mock(IdToken.class);
|
||||
when(mockIdToken.getTokenValue()).thenReturn("fake.test.id.token");
|
||||
when(mockCredentials.idTokenWithAudience(IAP_CLIENT_ID, ImmutableList.of(Option.FORMAT_FULL)))
|
||||
.thenReturn(mockIdToken);
|
||||
// Need to reset metrics for each test method, since they are static fields on the class and
|
||||
// shared between each run.
|
||||
channel = new EmbeddedChannel(whoisServiceHandler);
|
||||
|
@ -89,8 +74,7 @@ class WhoisServiceHandlerTest {
|
|||
|
||||
// Setup second channel.
|
||||
WhoisServiceHandler whoisServiceHandler2 =
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
||||
new WhoisServiceHandler(RELAY_HOST, RELAY_PATH, () -> 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.
|
||||
|
@ -104,8 +88,7 @@ class WhoisServiceHandlerTest {
|
|||
void testSuccess_fireInboundHttpRequest() throws Exception {
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||
FullHttpRequest expectedRequest =
|
||||
makeWhoisHttpRequest(
|
||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.of(IAP_CLIENT_ID));
|
||||
makeWhoisHttpRequest(QUERY_CONTENT, RELAY_HOST, RELAY_PATH, ID_TOKEN);
|
||||
// Input data passed to next handler
|
||||
assertThat(channel.writeInbound(inputBuffer)).isTrue();
|
||||
FullHttpRequest inputRequest = channel.readInbound();
|
||||
|
@ -128,27 +111,6 @@ class WhoisServiceHandlerTest {
|
|||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_withoutIapClientId() throws Exception {
|
||||
// Without an IAP client ID configured, we shouldn't include the proxy-authorization header
|
||||
WhoisServiceHandler nonIapHandler =
|
||||
new WhoisServiceHandler(
|
||||
RELAY_HOST, RELAY_PATH, () -> mockCredentials, Optional.empty(), metrics);
|
||||
channel = new EmbeddedChannel(nonIapHandler);
|
||||
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(QUERY_CONTENT.getBytes(US_ASCII));
|
||||
FullHttpRequest expectedRequest =
|
||||
makeWhoisHttpRequest(
|
||||
QUERY_CONTENT, RELAY_HOST, RELAY_PATH, mockCredentials, Optional.empty());
|
||||
// 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 testFailure_OutboundHttpResponseNotOK() {
|
||||
String outputString = "line1\r\nline2\r\n";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue