// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.proxy.handler;
import static com.google.common.base.Preconditions.checkState;
import static java.nio.charset.StandardCharsets.US_ASCII;
import google.registry.util.FormattingLogger;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import javax.inject.Inject;
/**
* Handler that processes possible existence of a PROXY protocol v1 header.
*
*
When an EPP client connects to the registry (through the proxy), the registry performs two
* validations to ensure that only known registrars are allowed. First it checks the sha265 hash of
* the client SSL certificate and match it to the hash stored in datastore for the registrar. It
* then checks if the connection is from an whitelisted IP address that belongs to that registrar.
*
*
The proxy receives client connects via the GCP load balancer, which results in the loss of
* original client IP from the channel. Luckily, the load balancer supports the PROXY protocol v1,
* which adds a header with source IP information, among other things, to the TCP request at the
* start of the connection.
*
*
This handler determines if a connection is proxied (PROXY protocol v1 header present) and
* correctly sets the source IP address to the channel's attribute regardless of whether it is
* proxied. After that it removes itself from the channel pipeline because the proxy header is only
* present at the beginning of the connection.
*
*
This handler must be the very first handler in a protocol, even before SSL handlers, because
* PROXY protocol header comes as the very first thing, even before SSL handshake request.
*
* @see The PROXY protocol
*/
public class ProxyProtocolHandler extends ByteToMessageDecoder {
/** Key used to retrieve origin IP address from a channel's attribute. */
public static final AttributeKey REMOTE_ADDRESS_KEY =
AttributeKey.valueOf("REMOTE_ADDRESS_KEY");
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
// The proxy header must start with this prefix.
// Sample header: "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n".
private static final byte[] HEADER_PREFIX = "PROXY".getBytes(US_ASCII);
private boolean finished = false;
private String proxyHeader = null;
@Inject
ProxyProtocolHandler() {}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
if (finished) {
if (proxyHeader != null) {
logger.finefmt("PROXIED CONNECTION: %s", ctx.channel());
logger.finefmt("PROXY HEADER: %s", proxyHeader);
ctx.channel().attr(REMOTE_ADDRESS_KEY).set(proxyHeader.split(" ")[2]);
} else {
SocketAddress remoteAddress = ctx.channel().remoteAddress();
if (remoteAddress instanceof InetSocketAddress) {
ctx.channel()
.attr(REMOTE_ADDRESS_KEY)
.set(((InetSocketAddress) remoteAddress).getAddress().getHostAddress());
logger.finefmt("REMOTE IP ADDRESS: %s", ctx.channel().attr(REMOTE_ADDRESS_KEY).get());
}
}
ctx.pipeline().remove(this);
}
}
/**
* Attempts to decode an internally accumulated buffer and find the proxy protocol header.
*
*
When the connection is not proxied (i. e. the initial bytes are not "PROXY"), simply set
* {@link #finished} to true and allow the handler to be removed. Otherwise the handler waits
* until there's enough bytes to parse the header, save the parsed header to {@link #proxyHeader},
* and then mark {@link #finished}.
*
* @param in internally accumulated buffer, newly arrived bytes are appended to it.
* @param out objects passed to the next handler, in this case nothing is ever passed because the
* header itself is processed and written to the attribute of the proxy, and the handler is
* then removed from the pipeline.
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List