You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by André Warnier <aw...@ice-sa.com> on 2015/05/01 02:28:42 UTC

Re: Finding the Apache httpd IP address when AJP is used

Paul Klinkenberg wrote:
> Hi André,
> 
>> Paul Klinkenberg wrote:
>>> Hi André,
>>>> Paul Klinkenberg wrote:
>>>>> Hi Christopher,
>>>>> Thanks for taking the time to respond; again much appreciated.
>>>>> Your point, and André's, is understood. Security should not be done based on incoming IP address.
>>>>> With this current project, we off course want to deliver software which is secure by default. Now, if someone would install Tomcat, then add the mod_cfml valve, and then doesn't lock port 8080 or 8009, the server would become vulnerable in the same way as if the /host-manager would not have password-protection.
>>>>> Currently, I am discussing with the main mod_cfml developers Jordan Michaels and Bilal Soylu how to implement security, since I now won't be implementing IP restriction. We'll probably go with using the "secret" configuration parameter for ajp like you suggested. Or maybe using a shared "secret" key between the frontend server and the Tomcat valve. In this last case, we would also have tackled security when remote attackers try to contact Tomcat on http-8080 directly, instead of using the ajp connector.
>>>>> I never knew the remote_addr could not be trusted, but I believe you at once when you say so.
>>>>> I thought it was taken from the actual socket connection. With the exception of ajp by the way, where it is programmatically changed to reflect the remote client while handling the http call. Out of curiosity, could you shed some light as to why the remote_addr is not to be trusted in a regular http request?
>>>>> Thanks again for your time and effort!
>>>>> Kind regards,
>>>>> Paul Klinkenberg
>>>> On Tomcat, you can set the AJP Connector to only listen on the local IP address of the Tomcat server host.  That means that only "local LAN" clients (including the httpd front-end, presumably) can connect to that <Connector>.
>>>> So this already stops any external client (be it workstation or server) from even connecting to Tomcat using AJP.
>>>> It also, presumably, insures that only your internal httpd front-ends can potentially connect to Tomcat via AJP.
>>>>
>>>> Now if you do not even trust your internal servers/clients, /then/ you need additional measures. But in such a case, whether you use a "secret" which the front-end must provide, or whether you use an additional header or Jk variable, is only a choice; but any of those requires some setup on the front-ends.
>>>>
>>>> The same is for the other Connectors, like HTTP/HTTPS.  If you do not want people to connect through these, disable them or have them also only listen on a local IP address.
>>> Thanks for these tips. I see there are quite a few options to secure the AJP connector, which is great.
>>> For the project I am currently working on, I have to take into consideration that the user might already have Tomcat installed, and then probably with the default configuration. That would mean the AJP connector is available, and http connector as well. When someone now wants to add the mod_cfml valve to their setup, I will warn them in the install/config notes to lock down their tomcat server, if they haven't done so already. Next to this, I would like to be able to make the valve "secure by default", without having to rely on external settings.
>>> For this "secure by default", a required shared secret key seems like a solution to me.
>> Note : to check. I am not sure if the HTTP/HTTPS Connectors provide this "shared secret" thing. This may well be an AJP Connector feature only.
>>
>> Remote users accessing either the http connector or ajp connector (only possible if the server is not firewalled), would need to have that key in order to get the valve to create a new context.
>>> I _do_ trust the internal servers/clients, I just want to make sure that if a mod_cfml user was too lame to secure it's server, then mod_cfml isn't the weakest link to be able to hack the server. I hope that makes sense?
>> Ok, so at this point, you only want to know, by intellectual curiosity, *how you could* theoretically, in your Valve, obtain the IP address and port of the front-end proxy server who is forwarding the original client request to your Tomcat.
>> Oof, that was hard to write, and I hope it is correct.
>>
>> Actually, Christopher already provided the answer to that, in a previous post :
>>
>>>>> The only way to check the caller would be to get ahold of the Socket
>>>>> that Tomcat is using to communicate. That's not easily done, since
>>>>> Tomcat wants to protect its sockets from code messing-around with the
>>>>> state of those Sockets."
>> That's a clue, but not a very helpful one for you, is it ?
>>
>> I believe that the main issue here is that there is no such standard functionality dictated by the Servlet Specification, so there is no obligation for any Servlet Engine to provide this, and apparently thus Tomcat does not provide a way to obtain this information easily, because it doesn't have to.
>> And according to Christopher, there may even be a deliberate attempt from the Tomcat code to prevent one being able to do such things easily, because it could potentially mess up things pretty badly if one went about playing with the underlying socket objects which Tomcat's Connectors use behind the scenes. (And it would in any case  not be portable to another Servlet Engine; but you already have that issue with your Valve anyway, so you probably don't care.)
>>
>> Another way of saying the above, is that Tomcat works very hard at "abstracting" all this underlying connection stuff, because it isn't really any of a Valve's or a Webapp's business to know any of this stuff and potentially create code which is not portable between specification-observing Servlet Engines.
>>
>>
>> But then, after having said all that, and consulted the useful on-line documentation for HttpServletRequest -> ServletRequest
>> (at https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html)
>> I see :
>> getRemoteAddr()
>> Returns the Internet Protocol (IP) address of the client or last proxy that sent the request.
>>
>> So it would seem that, in the case where the request was proxied (such as via httpd+mod_jk), getRemoteAddr() should return the IP address of the proxy. Isn't that what you wanted in the first place ?
>>
>> And as far as I can see from other documentation (such as http://tomcat.apache.org/connectors-doc/generic_howto/proxy.html), it is if you want this call to return the actual original client's IP (the one which connects to the front-end httpd), that you have to do something special at the httpd proxy level.
>>
>> So now I am a bit confused, because as I recall (maybe wrongly), you wanted the IP of the httpd+mod_jk front-end, didn't you ?
>>
>> And in the opposite case, there is anyway this immanent truth : if you consider a schema like this :
>>
>> browser <-- HTTP --> httpd front-end <-- AJP --> Tomcat
>>             (1)                          (2)
>>
>> Connection (1) is basically a matter only between the client and the front-end, and Tomcat has nothing to do with it.  Tomcat only knows about connection (2), because for that one, it is one of the connected parties.
>> Tomcat (and whatever code in it) would basically never know *anything* about the connection (1) above, unless the httpd front-end decides to pass information about it to Tomcat, over connection (2).  And the only way to have this happen, is to configure the front-end specifically to do so.
>>
>> Another way to say this : no matter how deep you would dig into the objects and methods available in Tomcat to your Valve, there is no way you could ever find any information about connection (1), because there isn't any, other than what you can get from the HTTP headers or the Jk variables which the front-end adds to the HTTP request that it forwards to Tomcat.
> 
> You were totally on your way to come to the point where my original question was aimed at, and then suddenly, bam, a right turn ;-)
> That happened when you write "getRemoteAddr() Returns the Internet Protocol (IP) address of the client or last proxy that sent the request."
> Yes, that is normally the case, but not when using AJP.
> The missing link in the story is the "translation" that AJP does:
> --------------------------------------------------------
> 1) browser --- HTTP --->  httpd front-end
> 2) httpd front-end --- AJP --->    Tomcat-AJP
> 3) Tomcat-AJP   --- HTTP --->  Tomcat-HTTP

That is kind of wrong.
A more accurate schema would be :
3) Tomcat-AJP (Connector) --> HttpServletRequest --> Valve --> webapps
and similarly
    Tomcat-HTTPS (Connector) --> HttpServletRequest --> Valve --> webapps
    Tomcat-HTTP (Connector) --> HttpServletRequest --> Valve --> webapps


> (and back off course:  ----> AJP ----> httpd ----> browser )
> --------------------------------------------------------
> I doubt whether the AJP connector really sets up an http connection, which the arrow "---HTTP--->"  implies at position 3).

It doesn't. But who ever said that ? I did not imply that in my simplistic schema.

  I do know that both the servletrequest and the valve present inside the http connector,

The Valve is not really "inside the http connector".  The Valve happens "after" any 
Connector has done its job.  The Connector (whichever one it is through which the request 
came in), translates the request from whatever format it was on the wire, into a standard 
HttpServletRequest representation.  That is what the Valve sees, and whatever webapp comes 
after the Valve.

  think it is a genuine http request coming from the browser-client, not an ajp one.

I believe that this is a misunderstanding on your part, of what AJP is.  There is no such 
thing, properly speaking, as an "AJP request".  See below.

  For example, debug results show:
> request.getProtocol() : HTTP/1.1

That is expected. AJP really "transports" normal HTTP requests. It just encodes them 
differently "on the wire", than what standard HTTP does.  But by the time your Valve gets 
the request, it will look just like a normal HTTP request.
(It is much the same with HTTPS : on the wire, a request will look very different from a 
pure HTTP request; but what is transported is a normal HTTP request.)
(If you are (intellectually) curious about the way in which AJP transports a HTTP request, 
see this : http://tomcat.apache.org/connectors-doc-archive/jk2/common/AJPv13.html)

> request.getRemoteAddr() : [ip of the browser-client]

That is not the default, and it /is/ due to a specific configuration of your Apache httpd.
Wanna bet ?

> request.getLocalPort() : 80   <<< not 8080! 

See http://tomcat.apache.org/connectors-doc/generic_howto/proxy.html :

"local port: getLocalPort() This is also equal to getServerPort(), unless a Host header is 
contained in the request. In this case the server port is taken from that header if it 
contains an explicit port, or is equal to the default port of the scheme used."

This being a HTTP/1.1 request, it contains a Host header (without a port).
The scheme used being HTTP, getLocalPort() thus returns 80 (the default port for HTTP).

If you want something which returns the real Connector port on which the request came in, 
use request.getServerPort().

> request.getCoyoteRequest().getWorkerThreadName() : ajp-nio-8009-exec-1   <<< The _only_ reference I could find to anything non-http, but this is a string... And I needed to use reflection to get to the coyote request.
> 

That is just basically a "coincidence".  Connectors start Threads to process incoming 
requests. When they do so, they also have to provide a unique name for the Thread.  The 
AJP NIO Connector happens to build such names as per the scheme shown above.
It is not something which you should rely on.
In fact, if the goal is protecting the Valve against unwanted access, the Valve should not 
care through which Connector the request came in.  No matter through which Connector it 
came in, it can potentially be from an unwanted client.

> What I wanted to know, indeed out of intellectual interest, is what you described perfectly (even though it was hard to write): the ip address of the httpd front-end server. 
> 
> I really don't want to exhaust your time on a hunt for something that is just nice to know, but won't be used afterwards. But I (again!) much appreciate the time you took to dive into this quest I gotten myself into ;)
> 

This being a list manned by volunteers, and being one of them, I only answer when I feel 
like it, so no trouble at all. But for the sake of the list archives which people may 
consult later, I felt it was useful to provide the above notes.



---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Finding the Apache httpd IP address when AJP is used

Posted by Paul Klinkenberg <pa...@ongevraagdadvies.nl>.
Hi André,
> Paul Klinkenberg wrote:
>> Hi André,
>>> Paul Klinkenberg wrote:
>>>> Hi André,
>>>>> Paul Klinkenberg wrote:
>>>>>> Hi Christopher,
>>>>>> Thanks for taking the time to respond; again much appreciated.
>>>>>> Your point, and André's, is understood. Security should not be done based on incoming IP address.
>>>>>> With this current project, we off course want to deliver software which is secure by default. Now, if someone would install Tomcat, then add the mod_cfml valve, and then doesn't lock port 8080 or 8009, the server would become vulnerable in the same way as if the /host-manager would not have password-protection.
>>>>>> Currently, I am discussing with the main mod_cfml developers Jordan Michaels and Bilal Soylu how to implement security, since I now won't be implementing IP restriction. We'll probably go with using the "secret" configuration parameter for ajp like you suggested. Or maybe using a shared "secret" key between the frontend server and the Tomcat valve. In this last case, we would also have tackled security when remote attackers try to contact Tomcat on http-8080 directly, instead of using the ajp connector.
>>>>>> I never knew the remote_addr could not be trusted, but I believe you at once when you say so.
>>>>>> I thought it was taken from the actual socket connection. With the exception of ajp by the way, where it is programmatically changed to reflect the remote client while handling the http call. Out of curiosity, could you shed some light as to why the remote_addr is not to be trusted in a regular http request?
>>>>>> Thanks again for your time and effort!
>>>>>> Kind regards,
>>>>>> Paul Klinkenberg
>>>>> On Tomcat, you can set the AJP Connector to only listen on the local IP address of the Tomcat server host.  That means that only "local LAN" clients (including the httpd front-end, presumably) can connect to that <Connector>.
>>>>> So this already stops any external client (be it workstation or server) from even connecting to Tomcat using AJP.
>>>>> It also, presumably, insures that only your internal httpd front-ends can potentially connect to Tomcat via AJP.
>>>>> 
>>>>> Now if you do not even trust your internal servers/clients, /then/ you need additional measures. But in such a case, whether you use a "secret" which the front-end must provide, or whether you use an additional header or Jk variable, is only a choice; but any of those requires some setup on the front-ends.
>>>>> 
>>>>> The same is for the other Connectors, like HTTP/HTTPS.  If you do not want people to connect through these, disable them or have them also only listen on a local IP address.
>>>> Thanks for these tips. I see there are quite a few options to secure the AJP connector, which is great.
>>>> For the project I am currently working on, I have to take into consideration that the user might already have Tomcat installed, and then probably with the default configuration. That would mean the AJP connector is available, and http connector as well. When someone now wants to add the mod_cfml valve to their setup, I will warn them in the install/config notes to lock down their tomcat server, if they haven't done so already. Next to this, I would like to be able to make the valve "secure by default", without having to rely on external settings.
>>>> For this "secure by default", a required shared secret key seems like a solution to me.
>>> Note : to check. I am not sure if the HTTP/HTTPS Connectors provide this "shared secret" thing. This may well be an AJP Connector feature only.
>>> 
>>> Remote users accessing either the http connector or ajp connector (only possible if the server is not firewalled), would need to have that key in order to get the valve to create a new context.
>>>> I _do_ trust the internal servers/clients, I just want to make sure that if a mod_cfml user was too lame to secure it's server, then mod_cfml isn't the weakest link to be able to hack the server. I hope that makes sense?
>>> Ok, so at this point, you only want to know, by intellectual curiosity, *how you could* theoretically, in your Valve, obtain the IP address and port of the front-end proxy server who is forwarding the original client request to your Tomcat.
>>> Oof, that was hard to write, and I hope it is correct.
>>> 
>>> Actually, Christopher already provided the answer to that, in a previous post :
>>> 
>>>>>> The only way to check the caller would be to get ahold of the Socket
>>>>>> that Tomcat is using to communicate. That's not easily done, since
>>>>>> Tomcat wants to protect its sockets from code messing-around with the
>>>>>> state of those Sockets."
>>> That's a clue, but not a very helpful one for you, is it ?
>>> 
>>> I believe that the main issue here is that there is no such standard functionality dictated by the Servlet Specification, so there is no obligation for any Servlet Engine to provide this, and apparently thus Tomcat does not provide a way to obtain this information easily, because it doesn't have to.
>>> And according to Christopher, there may even be a deliberate attempt from the Tomcat code to prevent one being able to do such things easily, because it could potentially mess up things pretty badly if one went about playing with the underlying socket objects which Tomcat's Connectors use behind the scenes. (And it would in any case  not be portable to another Servlet Engine; but you already have that issue with your Valve anyway, so you probably don't care.)
>>> 
>>> Another way of saying the above, is that Tomcat works very hard at "abstracting" all this underlying connection stuff, because it isn't really any of a Valve's or a Webapp's business to know any of this stuff and potentially create code which is not portable between specification-observing Servlet Engines.
>>> 
>>> 
>>> But then, after having said all that, and consulted the useful on-line documentation for HttpServletRequest -> ServletRequest
>>> (at https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html)
>>> I see :
>>> getRemoteAddr()
>>> Returns the Internet Protocol (IP) address of the client or last proxy that sent the request.
>>> 
>>> So it would seem that, in the case where the request was proxied (such as via httpd+mod_jk), getRemoteAddr() should return the IP address of the proxy. Isn't that what you wanted in the first place ?
>>> 
>>> And as far as I can see from other documentation (such as http://tomcat.apache.org/connectors-doc/generic_howto/proxy.html), it is if you want this call to return the actual original client's IP (the one which connects to the front-end httpd), that you have to do something special at the httpd proxy level.
>>> 
>>> So now I am a bit confused, because as I recall (maybe wrongly), you wanted the IP of the httpd+mod_jk front-end, didn't you ?
>>> 
>>> And in the opposite case, there is anyway this immanent truth : if you consider a schema like this :
>>> 
>>> browser <-- HTTP --> httpd front-end <-- AJP --> Tomcat
>>>            (1)                          (2)
>>> 
>>> Connection (1) is basically a matter only between the client and the front-end, and Tomcat has nothing to do with it.  Tomcat only knows about connection (2), because for that one, it is one of the connected parties.
>>> Tomcat (and whatever code in it) would basically never know *anything* about the connection (1) above, unless the httpd front-end decides to pass information about it to Tomcat, over connection (2).  And the only way to have this happen, is to configure the front-end specifically to do so.
>>> 
>>> Another way to say this : no matter how deep you would dig into the objects and methods available in Tomcat to your Valve, there is no way you could ever find any information about connection (1), because there isn't any, other than what you can get from the HTTP headers or the Jk variables which the front-end adds to the HTTP request that it forwards to Tomcat.
>> You were totally on your way to come to the point where my original question was aimed at, and then suddenly, bam, a right turn ;-)
>> That happened when you write "getRemoteAddr() Returns the Internet Protocol (IP) address of the client or last proxy that sent the request."
>> Yes, that is normally the case, but not when using AJP.
>> The missing link in the story is the "translation" that AJP does:
>> --------------------------------------------------------
>> 1) browser --- HTTP --->  httpd front-end
>> 2) httpd front-end --- AJP --->    Tomcat-AJP
>> 3) Tomcat-AJP   --- HTTP --->  Tomcat-HTTP
> 
> That is kind of wrong.
> A more accurate schema would be :
> 3) Tomcat-AJP (Connector) --> HttpServletRequest --> Valve --> webapps
> and similarly
>   Tomcat-HTTPS (Connector) --> HttpServletRequest --> Valve --> webapps
>   Tomcat-HTTP (Connector) --> HttpServletRequest --> Valve --> webapps
> 
> 
>> (and back off course:  ----> AJP ----> httpd ----> browser )
>> --------------------------------------------------------
>> I doubt whether the AJP connector really sets up an http connection, which the arrow "---HTTP--->"  implies at position 3).
> 
> It doesn't. But who ever said that ? I did not imply that in my simplistic schema.
> 
> I do know that both the servletrequest and the valve present inside the http connector,
> 
> The Valve is not really "inside the http connector".  The Valve happens "after" any Connector has done its job.  The Connector (whichever one it is through which the request came in), translates the request from whatever format it was on the wire, into a standard HttpServletRequest representation.  That is what the Valve sees, and whatever webapp comes after the Valve.
> 
> think it is a genuine http request coming from the browser-client, not an ajp one.
> 
> I believe that this is a misunderstanding on your part, of what AJP is.  There is no such thing, properly speaking, as an "AJP request".  See below.
> 
> For example, debug results show:
>> request.getProtocol() : HTTP/1.1
> 
> That is expected. AJP really "transports" normal HTTP requests. It just encodes them differently "on the wire", than what standard HTTP does.  But by the time your Valve gets the request, it will look just like a normal HTTP request.
> (It is much the same with HTTPS : on the wire, a request will look very different from a pure HTTP request; but what is transported is a normal HTTP request.)
> (If you are (intellectually) curious about the way in which AJP transports a HTTP request, see this : http://tomcat.apache.org/connectors-doc-archive/jk2/common/AJPv13.html)
> 
>> request.getRemoteAddr() : [ip of the browser-client]
> 
> That is not the default, and it /is/ due to a specific configuration of your Apache httpd.
> Wanna bet ?
> 
>> request.getLocalPort() : 80   <<< not 8080! 
> 
> See http://tomcat.apache.org/connectors-doc/generic_howto/proxy.html :
> 
> "local port: getLocalPort() This is also equal to getServerPort(), unless a Host header is contained in the request. In this case the server port is taken from that header if it contains an explicit port, or is equal to the default port of the scheme used."
> 
> This being a HTTP/1.1 request, it contains a Host header (without a port).
> The scheme used being HTTP, getLocalPort() thus returns 80 (the default port for HTTP).
> 
> If you want something which returns the real Connector port on which the request came in, use request.getServerPort().
> 
>> request.getCoyoteRequest().getWorkerThreadName() : ajp-nio-8009-exec-1   <<< The _only_ reference I could find to anything non-http, but this is a string... And I needed to use reflection to get to the coyote request.
> 
> That is just basically a "coincidence".  Connectors start Threads to process incoming requests. When they do so, they also have to provide a unique name for the Thread.  The AJP NIO Connector happens to build such names as per the scheme shown above.
> It is not something which you should rely on.
> In fact, if the goal is protecting the Valve against unwanted access, the Valve should not care through which Connector the request came in.  No matter through which Connector it came in, it can potentially be from an unwanted client.
> 
>> What I wanted to know, indeed out of intellectual interest, is what you described perfectly (even though it was hard to write): the ip address of the httpd front-end server. I really don't want to exhaust your time on a hunt for something that is just nice to know, but won't be used afterwards. But I (again!) much appreciate the time you took to dive into this quest I gotten myself into ;)
> 
> This being a list manned by volunteers, and being one of them, I only answer when I feel like it, so no trouble at all. But for the sake of the list archives which people may consult later, I felt it was useful to provide the above notes.

Well, I guess I have shown by now that I do not have real knowledge about the inner working of the Tomcat Connectors :-/  Thanks for the explanation!

Regarding my understanding that the AJP connector manipulates the remote_addr / getRemoteAddr(), see the https://tomcat.apache.org/connectors-doc/generic_howto/proxy.html <https://tomcat.apache.org/connectors-doc/generic_howto/proxy.html> :
-------------------------------------
Typical Problems
[...]
    Another example is the client IP address, which for the web server is the source IP of the incoming connection, whereas for the backend the connection always comes from the web server. This can be a problem, when the client IP is used by the backend application e.g. for security reasons.

AJP as a Solution
    Most of these problems are automatically handled by the AJP protocol and the AJP connectors of the backend. The AJP protocol transports this communication metadata and the backend connector presents this metadata whenever the application asks for it using Servlet API methods. 
-------------------------------------

Meanwhile, I have already updated the project I was working on, and am ready to move on to new adventures ;)

Thanks a lot, kind regards,

Paul Klinkenberg


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org