Access remote IP address in resource server proxied through Zuul and Apache - 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?

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

Normally, you can use servletRequest.getRemoteAddr() to get the client’s IP address that’s accessing your Java web application.
String ipAddress = request.getRemoteAddr();
But, if user is behind a proxy server or access your web server through a load balancer (for example, in cloud hosting), the above code will get the IP address of the proxy server or load balancer server, not the original IP address of a client.
To solve it, you should get the IP address of the request’s HTTP header “X-Forwarded-For (XFF)“.
String ipAddress = request.getHeader("X-FORWARDED-FOR");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}

Related

How to get base url without accessing a request

How to get the base URL in AspNet core application without having a request?
I know from the Request you can get the scheme and host (ie $"{Request.Scheme}://{Request.Host}" would give something like https://localhost:5000), but is it possible to get this information from anywhere else?
In other words, if I have a service class that needs to build absolute URLs, how can I get the current URL when there is not an http request available?
UPDATE: Maybe that scenario does not even make sense since the hosting URL is totally external to the application and that's why it only makes sense to extract it from the Request host..
i needed for some reason to get the base URL in Start.cs Configure, so i come up with this
var URLS = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
You are right, hosting URL is an external information, and you can simply pass it as configuration parameter to your application.
Maybe this will help you somehow: without request, you can get a configured listening address (like http://+:5000) using the IWebHostBuilder interface. It provides access to host settings via the GetSetting method:
/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
There is a WebHostDefaults.ServerUrlsKey setting name, that allows to configure listening address. We override it when add .UseUrls extension method:
public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
or define urls configuration parameter as described in the documentation (you know, by default listening is configured to localhost:5000).
So, having instance of IWebHostBuilder, you can call .GetSetting(WebHostDefaults.ServerUrlsKey) and get the current value.
,The ASP.NET Core Module generates a dynamic port to assign to the backend process. CreateDefaultBuilder calls the UseIISIntegration method. UseIISIntegration configures Kestrel to listen on the dynamic port at the localhost IP address (127.0.0.1). If the dynamic port is 1234, Kestrel listens at 127.0.0.1:1234. This configuration replaces other URL configurations provided by.
For IIS Integration, it works if you get the address after the WebHostBuilder.Build() have run.
var builder = CreateWebHostBuilder(args);
var webHost = builder.Build();
var addresses = webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
var address = addresses.FirstOrDefault();
AppDomain.CurrentDomain.SetData("BaseUrl", address ?? "");
webHost.Run();
and got the local Kestrel address in the HostedService like this:
string baseUrl = AppDomain.CurrentDomain.GetData("BaseUrl").ToString();
But there's a catch - this address is useless, because you can not make a request directly on this address. The IIS Integration middleware checks that only the IIS handler can make a request on this address. It produces a similar error:
<category>Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware</category>
<state>'MS-ASPNETCORE-TOKEN' does not match the expected pairing token 'ed5bc610-b7b9-4c1c-9941-954d0579edfc', request rejected.</state>
And in general case (no IIS Integration) this method of getting the address does not work if you use Kestrel configured to run with a custom port (not 5000), or a dynamic port 0. In this case the address needs to be obtained in a delayed manner, only after the application started.
For this case i tried this way: In Configure method in the StartUp class, i saved in ServerAddressFeature in the private member.
private IServerAddressesFeature _serverAddressesFeature;
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
... not related code here ...
And in the ConfigureServices method i added a dependency
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServerAddressesFeature>((sp) => _serverAddressesFeature);
... not related code here ...
Then in a hosted service i obtain this saved feature using dependency injection, and use it to get the address.
It works, only get the address in the StartAsync method, not in the service constructor!
public class WarmUpService : IHostedService
{
private readonly ILogger _logger;
private readonly IServerAddressesFeature _saf;
public WarmUpService(ILogger<WarmUpService> logger, IServerAddressesFeature serverAddressesFeature)
{
_logger = logger;
_saf = serverAddressesFeature;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
try
{
// the URL can be Got here
string baseUrl = _saf?.Addresses?.FirstOrDefault();
// await _WarmUp(baseUrl);
}
catch(Exception ex)
{
_logger.LogCritical(ex, "WarmUp Failed");
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

https certificate issue with subdomain

I have my application running on EC2 behind the load balancer.
Have got https certificate for www.example.com and *.example.com.
Application is running on http but https is been setup in load balancer.
I have added sub-domain support in my application based on the company.
Like, https://XYZ.example.com for company XYZ.
If i access using, https://XYZ.example.com, its working fine.
If I access using, https://www.XYZ.example.com, browser warns like,
"The owner of www.arun.contactcentral.io has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website."
But, If i access https://www.example.com, it works fine.
Though, I have got certification for *.example.com, it doesnt work even i access www.XYZ.example.com.
I have a filter to handle http to https direction, but still it is not filtering WWW from the url.
public class HttpsFilter implements Filter {
private static final String HTTP = "http";
private static final String HTTPS = "https";
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
#Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse httpResponse = (HttpServletResponse) res;
String xfp = request.getHeader(X_FORWARDED_PROTO);
if (HTTPS.equals(xfp)) {
//httpResponse.setHeader("Strict-Transport-Security", "max-age=60");
chain.doFilter(req, res);
return;
}
else if (HTTP.equals(xfp)) {
String serverUrl = HTTPS+"://"+req.getServerName()+((HttpServletRequest)req).getServletPath();
httpResponse.sendRedirect(serverUrl);
return;
}
}
}
Thanks,
Baskar.S
Wildcard SSL certificates will match only ONE level of subdomains (except in very rare and not well supported cases). The wildcard asterisk will not match . (dot).
So, a certificate for *.example.com WILL match
www.example.com
xyz.example.com
some-really-long-name.example.com
but it will NOT match
example.com
www.xyz.example.com
abc.def.ghi.example.com
If you want to match www.xyz.example.com and xyz.example.com, you will need two different certificates.
https://en.wikipedia.org/wiki/Wildcard_certificate#Limitation

Apache Axis2 Webservice- getting port and ip address of client

I have a Apache Axis2 web service and Im trying to log client ip address and port number. Im able to get the ip address using:
MessageContext inMessageContext = MessageContext.getCurrentMessageContext();
String ip = (String)inMessageContext.getProperty("REMOTE_ADDR");
How can i obtain the port number it came from?
I am newbie for axis2, I cant understand your question. Are you trying to access requester port number or requesting URL port number...?
May be below link useful for you to getting requesting URL port number. Please check
public class MyServlet extends AxisServlet
{
protected MessageContext createMessageContext( HttpServletRequest request, HttpServletResponse response, boolean invocationType ) throws IOException
{
MessageContext mc = super.createMessageContext( request, response, invocationType );
URL url = new URL( request.getRequestURL().toString() );
mc.setProperty( "myPort", url.getPort() );
return mc;
}
}
Of course you must put your class name in axis2/.../web.xml and restart tomcat. Then you can check port number inside any axis2 invocation:
MessageContext mc = MessageContext.getCurrentMessageContext();
int port = ( Integer ) mc.getProperty( "myPort" );
source : How to detect which transportReceiver is used in Axis2

Test For Localhost On ASP.NET Web API ActionFilterAttribute

How can I test for localhost on an ActionFilterAttribute with ASP.NET Web API? I want to skip the SSL check.
public class RequireHttpsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
throw new ValidationException(new SecureConnection());
}
base.OnActionExecuting(actionContext);
}
}
If you just want to test whether the request URI is localhost, then IsLoopback on the URI should work fine.
But here's the thing... request URIs can easily be spoofed by computers that aren't the local computer. So any remote computer can actually send you a request with a localhost Host header.
A better way is to use Filip's suggestion in his blog post:
http://www.strathweb.com/2013/01/adding-request-islocal-to-asp-net-web-api/
That should work for both selfhost and webhost and whether the IP address of the client is a loopback.
Looks like this works. Let me know if I am incorrect.
request.RequestUri.IsLoopback

Getting IP Address of the remote peer in Websocket API for Java EE 7

How can I get the IP address of the remote peer in Websocket API for Java Glassfish ?
Another method, based on this answer to another question, is to get the headers of the HandshakeRequest. The headers you are looking for are either of the following.
origin: [IP Address]
x-forwarded-for: [Possibly a separate IP]
Just for clarity, here's my setup, and how I discovered this:
Wamp 2.5 on MyMachine:6060. This hosts a client HTML page.
Wamp 2.5 on LabMachine:6060 (normal connections) and LabMachine:6443 (secure connections). This acts as a proxy.
GlassFish 4.0 on MyMachine:8080 (normal) and MyMachine:8181 (SSL). This is the endpoint.
I connected to the client page via my own machine, the lab machine, and a colleague's machine. In every case, the origin header of the WebSocket request was
http://MyMachine:6060
However, in each case the x-forwarded-host header was different, matching the IP addresses of the actual client.
See getRemoteAddr()
You will need to cast your socket Session instance to a TyrusSession, which implements the standard JSR-356 Session interface.
You might need to upgrade Tyrus, as I am not sure if the version you have supports this method. I am using Tyrus 1.7 and it works fine for me. Here is how to upgrade.
WebSockets are based on HTTP requests. Therefore you are probably extending an HttpServlet or a WebSocketServlet somewhere, so the usual way of getting the IP from the HttpServletRequest should work:
Example:
public class WebSocketsServlet extends HttpServlet {
private final WebSocketApplication app = new WebSocketApplication();
#Override
public void init(ServletConfig config) throws ServletException {
WebSocketEngine.getEngine().register(
config.getServletContext().getContextPath() + "/context", app);
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("IP: " + req.getRemoteAddr());
super.doGet(req, resp);
}
}