fastapi / full-stack-fastapi-template

Full stack, modern web application template. Using FastAPI, React, SQLModel, PostgreSQL, Docker, GitHub Actions, automatic HTTPS and more.
MIT License
27.56k stars 4.91k forks source link

[Question] Unable to get client IP #224

Open duz-sg opened 4 years ago

duz-sg commented 4 years ago

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?

bukowa commented 4 years ago

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

duz-sg commented 4 years ago

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
bukowa commented 4 years ago

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

visini commented 3 years ago

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}
pushp1997 commented 3 years ago

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.

athanhat commented 1 year ago

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