All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.vertx.http.runtime.ForwardedProxyHandler Maven / Gradle / Ivy

The newest version!
package io.quarkus.vertx.http.runtime;

import static io.quarkus.vertx.http.runtime.TrustedProxyCheck.denyAll;

import java.net.InetAddress;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import org.jboss.logging.Logger;
import org.wildfly.common.net.Inet;

import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.dns.DnsClient;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.net.impl.SocketAddressImpl;

/**
 * Restricts who can send `Forwarded`, `X-Forwarded` or `X-Forwarded-*` headers to trusted proxies
 * configured through {@link ProxyConfig#trustedProxies}.
 */
public class ForwardedProxyHandler implements Handler {

    private static final Logger LOGGER = Logger.getLogger(ForwardedProxyHandler.class.getName());

    private final TrustedProxyCheck.TrustedProxyCheckBuilder proxyCheckBuilder;

    private final Supplier vertx;

    private final Handler delegate;

    private final ForwardingProxyOptions forwardingProxyOptions;

    public ForwardedProxyHandler(TrustedProxyCheck.TrustedProxyCheckBuilder proxyCheckBuilder,
            Supplier vertx, Handler delegate,
            ForwardingProxyOptions forwardingProxyOptions) {
        this.proxyCheckBuilder = proxyCheckBuilder;
        this.vertx = vertx;
        this.delegate = delegate;
        this.forwardingProxyOptions = forwardingProxyOptions;
    }

    @Override
    public void handle(HttpServerRequest event) {
        if (event.remoteAddress() == null) {
            // client address may not be available with virtual http channel
            LOGGER.debug("Client address is not available, 'Forwarded' and 'X-Forwarded' headers are going to be ignored");
            handleForwardedServerRequest(event, denyAll());
        } else if (event.remoteAddress().isDomainSocket()) {
            // we do not support domain socket proxy checks, ignore the headers
            LOGGER.debug("Domain socket are not supported, 'Forwarded' and 'X-Forwarded' headers are going to be ignored");
            handleForwardedServerRequest(event, denyAll());
        } else {
            // create proxy check, then handle request
            if (proxyCheckBuilder.hasHostNames()) {
                // we need to perform DNS lookup for trusted proxy hostnames
                lookupHostNamesAndHandleRequest(event,
                        proxyCheckBuilder.getHostNameToPort().entrySet().iterator(), proxyCheckBuilder,
                        vertx.get().createDnsClient());
            } else {
                resolveProxyIpAndHandleRequest(event, proxyCheckBuilder);
            }
        }
    }

    private void lookupHostNamesAndHandleRequest(HttpServerRequest event,
            Iterator> iterator,
            TrustedProxyCheck.TrustedProxyCheckBuilder builder,
            DnsClient dnsClient) {
        if (iterator.hasNext()) {
            // perform recursive DNS lookup for all hostnames
            // we do not cache result as IP address may change, and we advise users to use IP or CIDR
            final var entry = iterator.next();
            final String hostName = entry.getKey();
            dnsClient.lookup(hostName,
                    new Handler>() {
                        @Override
                        public void handle(AsyncResult stringAsyncResult) {
                            if (stringAsyncResult.succeeded() && stringAsyncResult.result() != null) {
                                var trustedIP = Inet.parseInetAddress(stringAsyncResult.result());
                                if (trustedIP != null) {
                                    // create proxy check for resolved IP and proceed with the lookup
                                    lookupHostNamesAndHandleRequest(event, iterator,
                                            builder.withTrustedIP(trustedIP, entry.getValue()), dnsClient);
                                } else {
                                    logInvalidIpAddress(hostName);
                                    // ignore this hostname proxy check and proceed with the lookup
                                    lookupHostNamesAndHandleRequest(event, iterator, builder, dnsClient);
                                }
                            } else {
                                // inform we can't cope without IP
                                logDnsLookupFailure(hostName);
                                // ignore this hostname proxy check and proceed with the lookup
                                lookupHostNamesAndHandleRequest(event, iterator, builder, dnsClient);
                            }
                        }

                    });
        } else {
            // DNS lookup is done
            if (builder.hasProxyChecks()) {
                resolveProxyIpAndHandleRequest(event, builder);
            } else {
                // ignore headers as there are no proxy checks
                handleForwardedServerRequest(event, denyAll());
            }
        }
    }

    private void resolveProxyIpAndHandleRequest(HttpServerRequest event,
            TrustedProxyCheck.TrustedProxyCheckBuilder builder) {
        InetAddress proxyIP = ((SocketAddressImpl) event.remoteAddress()).ipAddress();
        if (proxyIP == null) {
            // if host is an IP address, proxyIP won't be null
            proxyIP = Inet.parseInetAddress(event.remoteAddress().host());
        }

        if (proxyIP == null) {
            // perform DNS lookup, then create proxy check and handle request
            final String hostName = Objects.requireNonNull(event.remoteAddress().hostName());
            vertx.get().createDnsClient().lookup(hostName,
                    new Handler>() {
                        @Override
                        public void handle(AsyncResult stringAsyncResult) {
                            TrustedProxyCheck proxyCheck;
                            if (stringAsyncResult.succeeded()) {
                                // use resolved IP to build proxy check
                                final var proxyIP = Inet.parseInetAddress(stringAsyncResult.result());
                                if (proxyIP != null) {
                                    proxyCheck = builder.build(proxyIP, event.remoteAddress().port());
                                } else {
                                    logInvalidIpAddress(hostName);
                                    proxyCheck = denyAll();
                                }
                            } else {
                                // we can't cope without IP => ignore headers
                                logDnsLookupFailure(hostName);
                                proxyCheck = denyAll();
                            }

                            handleForwardedServerRequest(event, proxyCheck);
                        }
                    });
        } else {
            // we have proxy IP => create proxy check and handle request
            var proxyCheck = builder.build(proxyIP, event.remoteAddress().port());
            handleForwardedServerRequest(event, proxyCheck);
        }
    }

    private void handleForwardedServerRequest(HttpServerRequest event, TrustedProxyCheck proxyCheck) {
        delegate.handle(new ForwardedServerRequestWrapper(event, forwardingProxyOptions, proxyCheck));
    }

    private static void logInvalidIpAddress(String hostName) {
        LOGGER.debugf("Illegal state - DNS server returned invalid IP address for hostname '%s'", hostName);
    }

    private static void logDnsLookupFailure(String hostName) {
        LOGGER.debugf("Can't resolve proxy IP address from '%s'", hostName);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy