![如何让 Google Cloud Load Balancer 尊重收到的 X-Forwarded-Proto?](https://linux22.com/image/721864/%E5%A6%82%E4%BD%95%E8%AE%A9%20Google%20Cloud%20Load%20Balancer%20%E5%B0%8A%E9%87%8D%E6%94%B6%E5%88%B0%E7%9A%84%20X-Forwarded-Proto%EF%BC%9F.png)
我的应用程序结构使用 GKE 和 CloudFlare。它看起来像这样:
CloudFlare -> GKE -> Ingress -> My app running nginx
我在 CloudFlare 中使用灵活的 SSL,因此只有用户和 CloudFlare 之间的连接使用 HTTPS,其余所有连接都使用 HTTP。我知道 CloudFlare 在这种情况下将设置为X-Forwarded-Proto
,https
但是当我看到我的 nginx 应用程序正在接收的标头时,它会得到X-Forwarded-Proto: http
。
我很确定这发生在 GKE 的负载均衡器和 Ingress 之间的某个地方,因为我可以看到CF-Visitor: {"scheme": "https"}
CloudFlare 配置的标头设置为 HTTPS。我的理解是,这意味着 CloudFlare 确实设置X-Forwarded-Proto
为https
,但它在此过程中被覆盖了。
不幸的是,我无法从 GKE 负载均衡器获取标头日志(似乎他们X-Forwarded-*
根本没有记录标头),所以我无法 100% 确认 CloudFlare 是否确实设置了标头,但如果不是,我会感到非常惊讶。
如果确实如此,Google Cloud 会X-Forwarded-Proto
用 覆盖标头http
。我该如何避免这样做?
编辑:我已经配置了 nginx ingress 而不是 gcehttps://cloud.google.com/community/tutorials/nginx-ingress-gke,并且X-Forwarded-Proto
设置https
为预期值。这是另一个信号,表明gce
入口控制器正在覆盖X-Forwarded-Proto
标头。
答案1
正如所述本文Cloudflare 附加了一个 X-Forwarded-Proto 标头,该标头可以是 HTTP 或 HTTPS,具体取决于用户访问网站时使用的协议。如果您认为 X-Forwarded-Proto 的值应该保留但已被 GCLB 更改,我建议您在Google 问题追踪器。
答案2
您可以创建一个中间件:
# frozen_string_literal: true
require 'json'
class CloudflareProxy
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless env['HTTP_CF_VISITOR']
env['HTTP_X_FORWARDED_PROTO'] = JSON.parse(env['HTTP_CF_VISITOR'])['scheme']
@app.call(env)
end
end
用于config/application.rb
:
config.middleware.use CloudflareProxy
标头参考CF-Visitor
:
https://support.cloudflare.com/hc/en-us/articles/200170986-Cloudflare 如何处理 HTTP 请求头-
CF-访客
仅包含一个名为“scheme”的键的 JSON 对象。该值与 X-Forwarded-Proto(HTTP 或 HTTPS)的值相同。CF-Visitor 仅在使用 Flexible SSL 时才相关。
答案3
我也为此苦苦挣扎ingress-nginx
。经过几天阅读 GitHub 问题和 GCP 文档,找出了 helm chart 的正确值:
controller:
ingressClassResource:
default: "true"
service:
loadBalancerIP: ${var.lb_ip}
externalTrafficPolicy: Local
config:
enable-real-ip: "true"
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
hsts: "false" # handled by cloudflare, we use flexible encryption mode
proxy-real-ip-cidr: "130.211.0.0/22,35.191.0.0/16,173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
在应用程序级别,您应该使用x-forwarded-for
标头中倒数第二个 IP。 ExpressJS 示例:
const express = require('express');
const app = express();
// more on this here: https://expressjs.com/en/guide/behind-proxies.html
app.set('trust proxy', 2);
app.get('/', (req, res) => res.end(`
ip: ${req.ip}
headers: ${JSON.stringify(req.headers, null, 2)}
`));
app.listen(80, () => { console.log(`Server running at http://localhost:80`); });
在这种情况下,即使有人尝试通过标头传递虚假 IP(或使用 VPN),你的应用也会使用正确的 IP:
curl \
--header "X-Forwarded-For: <FAKE_IP>" \
--header "X-Forwarded-Proto: https" \
--header "X-Forwarded-Host: example.com" https://example.com
ip: <REAL_IP>
headers: {
"host": "example.com",
"x-request-id": "...",
"x-real-ip": "<REAL_IP>",
"x-forwarded-for": "<FAKE_IP>,<REAL_IP>, <CLOUDFLARE_IP>",
"x-forwarded-host": "example.com",
"x-forwarded-port": "80",
"x-forwarded-proto": "https",
"x-forwarded-scheme": "https",
"x-scheme": "https",
"x-original-forwarded-for": "<FAKE_IP>,<REAL_IP>",
"accept-encoding": "gzip",
"cf-ray": "...",
"cf-visitor": "{\"scheme\":\"https\"}",
"user-agent": "curl/8.0.1",
"accept": "*/*",
"cf-connecting-ip": "<REAL_IP>",
"cf-ipcountry": "...",
"cdn-loop": "cloudflare"
}