如何让 Google Cloud Load Balancer 尊重收到的 X-Forwarded-Proto?

如何让 Google Cloud Load Balancer 尊重收到的 X-Forwarded-Proto?

我的应用程序结构使用 GKE 和 CloudFlare。它看起来像这样:

CloudFlare -> GKE -> Ingress -> My app running nginx

我在 CloudFlare 中使用灵活的 SSL,因此只有用户和 CloudFlare 之间的连接使用 HTTPS,其余所有连接都使用 HTTP。我知道 CloudFlare 在这种情况下将设置为X-Forwarded-Protohttps但是当我看到我的 nginx 应用程序正在接收的标头时,它会得到X-Forwarded-Proto: http

我很确定这发生在 GKE 的负载均衡器和 Ingress 之间的某个地方,因为我可以看到CF-Visitor: {"scheme": "https"}CloudFlare 配置的标头设置为 HTTPS。我的理解是,这意味着 CloudFlare 确实设置X-Forwarded-Protohttps,但它在此过程中被覆盖了。

不幸的是,我无法从 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"
}

相关内容