From 6a3a8c92484812515d23a6b7f043197014e60801 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:09:50 -0500 Subject: [PATCH] =?UTF-8?q?fix(api-keys):=20detr=C3=A1s=20de=20Cloudflare?= =?UTF-8?q?=20la=20IP=20que=20se=20comparaba=20era=20la=20del=20edge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El 403 reportaba ip_vista=104.22.14.222, que es un edge de Cloudflare: el tráfico entra por CF y recién ahí por el proxy del hosting, así que la última entrada de X-Forwarded-For es el edge y no quien llama. Se usa CF-Connecting-IP cuando está: la escribe Cloudflare con la IP real y sobrescribe la que mande el cliente. Sin Cloudflare sigue valiendo la última entrada del XFF, como estaba. Queda anotado que todo esto vale mientras el tráfico entre por el proxy: quien pueda pegarle al origen directo puede falsear los dos encabezados, y eso se cierra en la red, no en esta función. Co-Authored-By: Claude Opus 5 --- rest/middlewares/ip_cliente.go | 21 +++++++++++++++++---- rest/middlewares/ip_cliente_test.go | 15 +++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/rest/middlewares/ip_cliente.go b/rest/middlewares/ip_cliente.go index 82907bf..22163bd 100644 --- a/rest/middlewares/ip_cliente.go +++ b/rest/middlewares/ip_cliente.go @@ -12,11 +12,24 @@ import ( // devuelve la IP interna del proxy (10.x.x.x), así que ninguna IP pública // autorizada coincidía nunca y toda llave con restricción de IP daba 403. // -// Se toma la ÚLTIMA entrada de X-Forwarded-For, no la primera: con un solo -// proxy adelante, esa es la que escribió el proxy y no la puede falsear quien -// llama. Si el cliente manda su propio X-Forwarded-For, el proxy le agrega la -// IP real al final — quedarse con la primera sería creerle al que llama. +// Sin Cloudflare se toma la ÚLTIMA entrada de X-Forwarded-For, no la primera: +// con un solo proxy adelante, esa es la que escribió el proxy y no la puede +// falsear quien llama. Si el cliente manda su propio X-Forwarded-For, el proxy +// le agrega la IP real al final — quedarse con la primera sería creerle al que +// llama. +// +// ponytail: esto vale mientras todo el tráfico entre por el proxy (y por +// Cloudflare, si está). Quien pueda pegarle al origen directo puede falsear +// ambos encabezados; si eso pasa a importar, hay que cerrar el origen a las +// redes del proxy en vez de complicar esta función. func IPDelCliente(c *fiber.Ctx) string { + // Cloudflare adelante: el proxy del hosting ve la IP del edge de CF, así + // que la última entrada de X-Forwarded-For es 104.x.x.x y no el que llama. + // CF-Connecting-IP la escribe Cloudflare con la IP real y sobrescribe la + // que mande el cliente, así que cuando está es la buena. + if cf := strings.TrimSpace(c.Get("CF-Connecting-IP")); cf != "" { + return cf + } if xff := c.Get("X-Forwarded-For"); xff != "" { partes := strings.Split(xff, ",") if ip := strings.TrimSpace(partes[len(partes)-1]); ip != "" { diff --git a/rest/middlewares/ip_cliente_test.go b/rest/middlewares/ip_cliente_test.go index baa44c4..3d27d50 100644 --- a/rest/middlewares/ip_cliente_test.go +++ b/rest/middlewares/ip_cliente_test.go @@ -12,14 +12,18 @@ func TestIPDelCliente(t *testing.T) { nombre string xff string real string + cf string want string }{ - {"un solo proxy", "46.202.93.92", "", "46.202.93.92"}, + // Con Cloudflare adelante, la última entrada del XFF es el edge de CF + // (104.x.x.x): la IP real solo está en CF-Connecting-IP. + {"detrás de cloudflare", "46.202.93.92, 104.22.14.222", "", "46.202.93.92", "46.202.93.92"}, + {"un solo proxy", "46.202.93.92", "", "", "46.202.93.92"}, // Si quien llama manda su propio X-Forwarded-For, el proxy le agrega la // IP real al final. Quedarse con la primera sería dejar que elija su IP. - {"cliente intentando falsear", "1.2.3.4, 46.202.93.92", "", "46.202.93.92"}, - {"con espacios", " 10.0.0.1 , 46.202.93.92 ", "", "46.202.93.92"}, - {"sin xff, con x-real-ip", "", "46.202.93.92", "46.202.93.92"}, + {"cliente intentando falsear", "1.2.3.4, 46.202.93.92", "", "", "46.202.93.92"}, + {"con espacios", " 10.0.0.1 , 46.202.93.92 ", "", "", "46.202.93.92"}, + {"sin xff, con x-real-ip", "", "46.202.93.92", "", "46.202.93.92"}, } app := fiber.New() @@ -35,6 +39,9 @@ func TestIPDelCliente(t *testing.T) { if cs.real != "" { req.Header.Set("X-Real-Ip", cs.real) } + if cs.cf != "" { + req.Header.Set("CF-Connecting-IP", cs.cf) + } resp, err := app.Test(req) if err != nil { t.Fatalf("%s: %v", cs.nombre, err)