full-stack-fastapi-template icon indicating copy to clipboard operation
full-stack-fastapi-template copied to clipboard

[Question] Unable to get client IP

Open duz-sg opened this issue 5 years ago • 6 comments

I was trying to get client IP with request.client.host, and followed the documentation on https://dockerswarm.rocks/traefik-v1/traefik/#getting-the-client-ip, but it does not seem to work.

From the log, I'm still getting

INFO:uvicorn.access:10.0.5.18:40570 - "GET /api/v1/users/33 HTTP/1.1" 200

The output from logger.info(request.client.host) has the same result 10.0.5.18, this should be the IP traefik assigned internally, not the expected client IP.

Is there anything I'm missing to configure?

duz-sg avatar Jun 29 '20 05:06 duz-sg

Do you have --proxy-headers param for uvicorn?

bukowa avatar Jul 28 '20 18:07 bukowa

Do you have --proxy-headers param for uvicorn?

Thank you for the suggestion, just got some time to revisit this issue, I have made these changes, but the issue still remains.

This is my new start.sh:

exec gunicorn -k "$WORKER_CLASS" -c "$GUNICORN_CONF" --proxy-protocol --proxy-allow-from '*' --forwarded-allow-ips '*' "$APP_MODULE"

In the backend, the request headers show they are from the internal proxy: {'host': 'example.com', 'content-length': '95', 'accept': '*/*', 'accept-encoding': 'gzip', 'authorization': 'Bearer xxxxxxxxxxxx', 'content-type': 'application/json', 'x-forwarded-for': '10.0.38.40', 'x-forwarded-host': 'example.com', 'x-forwarded-port': '80', 'x-forwarded-proto': 'http', 'x-forwarded-server': '67b6a6b946de', 'x-real-ip': '10.0.38.40'}

Additionally, I tested some options in traefik-host.yml, but does not work:

--entryPoints.http.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.1.7 
--entryPoints.https.forwardedHeaders.trustedIPs=127.0.0.1/32,192.168.1.7
--providers.docker.useBindPortIP=true

duz-sg avatar Aug 12 '20 19:08 duz-sg

@duz-sg if you are using docker swarm see https://github.com/moby/moby/issues/25526

bukowa avatar Aug 12 '20 23:08 bukowa

Perhaps this helps: https://github.com/tomwojcik/starlette-context

See docs for more plugins: https://starlette-context.readthedocs.io/en/latest/plugins.html#forwarded-for

Example with FastAPI:

from starlette_context import middleware, plugins
app = FastAPI()

app.add_middleware(
    middleware.ContextMiddleware,
    plugins=(
        plugins.ForwardedForPlugin(),
    ),
)

Access it like this:

from starlette_context import context

@app.get("/")
def hello():
    forwarded_for = context.data["X-Forwarded-For"]
    return {"hello": "world", "forwarded_for": forwarded_for}

visini avatar Dec 02 '20 18:12 visini

I can't believe it took me almost a month to stumble upon this GEM! I have my application running on kubernetes and always thought that something was wrong with the ingress controller! Thanks @visini.

pushp1997 avatar Apr 16 '21 10:04 pushp1997

This Stack Overflow issue, "FastAPI (starlette) get client real IP", is highly relevant here.

In my case, for example, Gunicorn is running behind an Nginx reverse proxy as described on Deploying Gunicorn - Nginx Configuration

you need to tell Gunicorn to trust the X-Forwarded-* headers sent by Nginx. By default, Gunicorn will only trust these headers if the connection comes from localhost - Deploying Gunicorn Nginx Configuration Documentation

gunicorn --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --chdir srv main:app --forwarded-allow-ips '*' --access_log_format = '%({x-forwarded-for}i)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' --accesslog ="-"

A quick way to test if you get the x-forwarded-for from the request header is to enter the gunicorn command above and examine the access log that is redirected to stdout

Bottom line is that for most cases you should not mess with Starlette's Request class unless there is a good reason. After all that is why FastAPI has been built on top of this library.

In my case the following FastAPI using-request-directly code example from the documentation page works fine without any modifications and you do get the real IP of the client:

from fastapi import FastAPI, Request

app = FastAPI()


@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
    client_host = request.client.host
    return {"client_host": client_host, "item_id": item_id}

PS: If you deploy FastAPI as an Azure WebApp then the case I described perfectly fits because Azure is using gunicorn behind Nginx reverse proxy

athanhat avatar Feb 14 '23 11:02 athanhat