Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access remote IP address in resource server proxied through Zuul and Apache

For a security check I need access to the user's remote IP address in my resource service. This resource service is a simple recent Spring Boot app, that registers itself with my Eureka server:

@SpringBootApplication
@EnableEurekaClient
public class ServletInitializer extends SpringBootServletInitializer {
    public static void main(final String[] args) {
        SpringApplication.run(ServletInitializer.class, args);
    }
}

All services registered with my Eureka server are dynamically routed through my Zuul routing proxy server based on Angel.SR3 starter-zuul and starter-eureka:

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class RoutingProxyServer {
    public static void main(final String[] args) {
        SpringApplication.run(RoutingProxyServer.class, args);
    }
}

The Zuul routing proxy server also configures an AJP connector for the next step:

@Configuration
@ConditionalOnProperty("ajp.port")
public class TomcatAjpConfig extends TomcatWebSocketContainerCustomizer {
    @Value("${ajp.port}")
    private int port;

    @Override
    public void doCustomize(final TomcatEmbeddedServletContainerFactory tomcat) {
        super.doCustomize(tomcat);

        // Listen for AJP requests
        Connector ajp = new Connector("AJP/1.3");
        ajp.setPort(port);
        tomcat.addAdditionalTomcatConnectors(ajp);
    }
}

All requests to the dynamic routing zuul proxy are proxied themselves through Apache to provide HTTPS on the standard 443 port:

# Preserve Host when proxying so jar apps return working URLs in JSON responses
RequestHeader           set X-Forwarded-Proto "https"
ProxyPreserveHost   On
# Redirect remaining traffic to routing proxy server
ProxyPass       /       ajp://192.168.x.x:8009/
# Also update Location, Content-Location and URI headers on HTTP redirect responses
ProxyPassReverse    /       ajp://192.168.x.x:8009/

With all this in place the resource service is made available, but unfortunately the remoteAddress that I get from Spring Security is the address of the Zuul proxy/Apache server, not the remote client IP address.

In the past I had used a org.springframework.security.authentication.AuthenticationDetailsSource that preferred the X-Forwarded-For header value over the normal remoteAddress to get the correct IP address, but I can not work out how to pass the proper remote IP address to my resource service when passing through two proxies (Apache + Zuul).

Can anyone help me access the correct remote IP address behind these two proxies, or suggest an alternative approach to get this to work?

like image 543
Tim Avatar asked Aug 27 '15 19:08

Tim


People also ask

How does Zuul routing work?

Zuul is an edge service that proxies requests to multiple backing services. It provides a unified “front door” to your system, which allows a browser, mobile app, or other user interface to consume services from multiple hosts without managing cross-origin resource sharing (CORS) and authentication for each one.

What is the difference between Zuul and spring cloud gateway?

Zuul is built on servlet 2.5 (works with 3. x), using blocking APIs. It doesn't support any long lived connections, like websockets. Gateway is built on Spring Framework 5, Project Reactor and Spring Boot 2 using non-blocking APIs.

How does Zuul API gateway work?

It handles all the requests and performs the dynamic routing of microservice applications. It is also known as Edge Server. Zuul is built to enable dynamic routing, monitoring, resiliency, and security. It can also route the requests to multiple Amazon Auto Scaling Groups.


1 Answers

Turns out the X-Forwarded-For header was taken from the original request feeding into Zuul to populate HttpServletRequest#getRemoteAddr(). This would then have to be passed on to the proxied backend services through RequestContext#getZuulRequestHeaders().put("X-Forwarded-For", remoteAddr). The following ZuulFilter accomplishes this, even if it isn't appending it's own value to the X-Forwarded-For filter just yet.

@Component
@Slf4j
public class XForwardedForFilter extends ZuulFilter {
private static final String X_FORWARDED_FOR = "X-Forwarded-For";

@Override
public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();

    // Rely on HttpServletRequest to retrieve the correct remote address from upstream X-Forwarded-For header
    HttpServletRequest request = ctx.getRequest();
    String remoteAddr = request.getRemoteAddr();

    // Pass remote address downstream by setting X-Forwarded for header again on Zuul request
    log.debug("Settings X-Forwarded-For to: {}", remoteAddr);
    ctx.getZuulRequestHeaders().put(X_FORWARDED_FOR, remoteAddr);

    return null;
}

@Override
public boolean shouldFilter() {
    return true;
}

@Override
public String filterType() {
    return "pre";
}

@Override
public int filterOrder() {
    return 10000;
}
}

One might want to clear the header value in Apache before proxying to Zuul to prevent accepting just any value provided by the user: RequestHeader unset X-Forwarded-For

like image 89
Tim Avatar answered Sep 28 '22 05:09

Tim