Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConnectionInfo::realip_remote_addr() may or may not contain a port number which makes it difficult to handle robustly #2528

Closed
abonander opened this issue Dec 18, 2021 · 1 comment · Fixed by #2554
Labels
A-web project: actix-web C-bug Category: bug
Milestone

Comments

@abonander
Copy link

Expected Behavior

The expected behavior is for ConnectionInfo::realip_remote_addr() to return a consistently formatted address, either with or without a port number. Preferably without as appending one when the X-Forwarded-For header is supplied would be incorrect (as the port number of the peer will not be the same port as the true peer if the request is being forwarded via a proxy, so copying the port number over is misleading).

Current Behavior

If there is no Forwarded or X-Forwarded-For header, .realip_remote_addr() returns the same value as .remote_addr() which is a socket address containing both an IP address and a port number.

However, if one of those headers is present then realip_remote_addr() will return just a bare IP address.

This makes it very difficult to parse and consume this value because IpAddr::from_str() will reject it in the former case and SocketAddr::from_str() will reject it in the latter case, so one is forced to attempt both.

Of course, since this value is set by the client and potentially untrustworthy (as the docs make very clear), then it may not be any kind of address at all. However, the issue here is that even when it's valid, it's still unnecessarily difficult to work with.

Possible Solution

Instead of directly forwarding to .remote_addr(), since the latter is set using SocketAddr::to_string() anyway, realip_remote_addr should default to socket_addr.ip().to_string().

This shouldn't break any existing usages that are correctly implemented as any attempt to parse this string as either a socket address or IP address without attempting both would have been error-prone to begin with.

This does have the downside of an extra, semi-redundant string allocation. As an optimization, the value stored internally could be an enum that's either a separately allocated String or a Range that can be used to slice the remote_addr() string to just the IP address segment. I'm not certain this would be necessary, though.

Steps to Reproduce (for bugs)

  1. Write a request handler that attempts to parse the value of ConnectionInfo::realip_remote_addr() as a SocketAddr:
#[get("/")]
async fn log_realip_remote_addr(req: HttpRequest) -> HttpResponse {
    let realip_remote_addr: SocketAddr = req.connection_info().realip_remote_addr().unwrap().parse().unwrap();

    log::info!("Received connection from {}", realip_remote_addr);

    HttpResponse::Ok().finish()
}
  1. Make a request to that handler and see that it completes normally.
  2. Manually add X-Forwarded-For: <any IP address> to the request.
  3. Observe the handler panic.

Context

When using the API of Circle, a payment provider, mutating requests that touch real money require passing a metadata object with information that can be used to find the user that originated the request in case of an audit: https://developers.circle.com/reference#payments-payments-create

This includes the user's IP address.

When the Actix-web application is deployed as part of a Kubernetes cluster in Google Cloud, and configured with an external load balancer, the .peer_addr() of the request will be that of the load balancer itself in Gcloud, so this cannot be used. Gcloud will append the IP address that originated to the request to the X-Forwarded-For header, creating it if it does not exist: https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header

Thus, the .realip_remote_addr() will be a socket address when testing the application locally, but a bare IP address when deployed. Ideally it would just always be an IP address for consistency and ease of handling.

Your Environment

  • Rust Version (I.e, output of rustc -V): 1.56.0
  • Actix Web Version: 4.0.0-beta.8 (looking at the code, the behavior is identical on -beta.15)
@robjtede robjtede added this to the actix-web v4 milestone Dec 18, 2021
@robjtede robjtede added A-web project: actix-web needs-investigation labels Dec 18, 2021
@robjtede robjtede added C-bug Category: bug and removed needs-investigation labels Dec 28, 2021
@robjtede
Copy link
Member

First of all, thanks for the excellent write up 👍🏻

Feels to me that having the port is never useful anyway. The PR to fix this just removes it in the peer address case.

mpalmer added a commit to mpalmer/actix-web that referenced this issue Apr 26, 2024
This is something of a followup to actix#2528, which asked for port information to not be included in  when it was taken from the local socket.

The  header's  element may optionally contain port information (https://datatracker.ietf.org/doc/html/rfc7239#section-6).
However, as I understand it,  is *supposed* to only contain an IP address, without port (per actix#2528).

This PR corrects that discrepancy, making it easier to parse the result of this method in application code.

There should not be any compatibility concerns, as anyone parsing the output of  would already need to handle both port and portless cases anyway.
mpalmer added a commit to mpalmer/actix-web that referenced this issue Apr 26, 2024
This is something of a followup to actix#2528, which asked for port information to not be included in  when it was taken from the local socket.

The  header's  element may optionally contain port information (https://datatracker.ietf.org/doc/html/rfc7239#section-6).
However, as I understand it,  is *supposed* to only contain an IP address, without port (per actix#2528).

This PR corrects that discrepancy, making it easier to parse the result of this method in application code.

There should not be any compatibility concerns, as anyone parsing the output of  would already need to handle both port and portless cases anyway.
mpalmer added a commit to mpalmer/actix-web that referenced this issue Apr 26, 2024
This is something of a followup to actix#2528, which asked for port information to not be included in  when it was taken from the local socket.

The  header's  element may optionally contain port information (https://datatracker.ietf.org/doc/html/rfc7239#section-6).
However, as I understand it,  is *supposed* to only contain an IP address, without port (per actix#2528).

This PR corrects that discrepancy, making it easier to parse the result of this method in application code.

There should not be any compatibility concerns, as anyone parsing the output of  would already need to handle both port and portless cases anyway.
mpalmer added a commit to mpalmer/actix-web that referenced this issue Apr 26, 2024
This is something of a followup to actix#2528, which asked for port information to not be included in  when it was taken from the local socket.

The  header's  element may optionally contain port information (https://datatracker.ietf.org/doc/html/rfc7239#section-6).
However, as I understand it,  is *supposed* to only contain an IP address, without port (per actix#2528).

This PR corrects that discrepancy, making it easier to parse the result of this method in application code.

There should not be any compatibility concerns, as anyone parsing the output of  would already need to handle both port and portless cases anyway.
github-merge-queue bot pushed a commit that referenced this issue Jun 9, 2024
* Strip non-address characters from Forwarded for=

This is something of a followup to #2528, which asked for port information to not be included in  when it was taken from the local socket.

The  header's  element may optionally contain port information (https://datatracker.ietf.org/doc/html/rfc7239#section-6).
However, as I understand it,  is *supposed* to only contain an IP address, without port (per #2528).

This PR corrects that discrepancy, making it easier to parse the result of this method in application code.

There should not be any compatibility concerns, as anyone parsing the output of  would already need to handle both port and portless cases anyway.

* Update CHANGES.md

---------

Co-authored-by: Rob Ede <robjtede@icloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-web project: actix-web C-bug Category: bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants