Cloudflare 是如何处理HTTP Request Headers的(How Does Cloudflare handle HTTP Request headers)?

我们都知道,cloudflare其实本质上就是一个反向代理(reverse proxy), 因此cloudflare 会把收到的所有的HTTP headers request原原本本的转发给你的源服务器,在此基础上还会添加一些CF自己的header.

这些header 都有一个共同的特性,就是以CF开头

CF-Connecting-IP: 

Provides the client (visitor) IP address (connecting to Cloudflare) to the origin web server. 
This header will only be sent on the traffic from Cloudflare's edge to your origin webserver.

True-Client-IP (Enterprise plan only):

企业用户专享

X-Forwarded-For:

Maintains proxy server and original visitor IP addresses. If there was no existing X-Forwarded-For header in the request sent to Cloudflare, 
X-Forwarded-For has an identical value to the CF-Connecting-IP header

CF-RAY:

The cf-ray header is a hashed value encoding information about the data center and the visitor’s request

CF-IPCountry:

Contains a two character country code of the originating visitor’s country. XX is used for unknown country information

CF-Vistor:

Currently, this header is a JSON object, containing only one key called “scheme”. The meaning is identical to that of X-Forwarded-Proto above - e.g. it will be either HTTP or HTTPS, 
and it is only really relevant if you need to enable Flexible SSL in your Cloudflare settings.

CDN-Loop:

Allows Cloudflare to specify how many times a request can enter Cloudflare's network before it is blocked as a looping request.

CF-Worker:

The CF-Worker request header is added to an edge Worker subrequest that identifies the host that spawned the subrequest. 
This is useful when you want to protect against cross-zone worker subrequests.
You can add CF-Worker header on server logs similar to the way you add the CF-RAY header. Just add "$http_cf_worker" in the log format file: log_format cf_custom "CF-Worker:$http_cf_worker"'

用phpinfo()函数测试了一下,发现多了以下几个fields:

$_SERVER['HTTP_CDN_LOOP']: cloudflare
$_SERVER['HTTP_CF_CONNECTING_IP']: 99.99.99.99
$_SERVER['HTTP_CF_VISITOR']: {"scheme":"https"}
$_SERVER['HTTP_CF_RAY']: 6a4247906e83762b-EWR
$_SERVER['HTTP_X_FORWARDED_FOR']: 99.99.99.99
$_SERVER['HTTP_CF_IPCOUNTRY']: US

同时,REMOTE_ADDR这个ip是CF的节点IP

$_SERVER['REMOTE_ADDR']: 174.245.52.215 (CF新泽西IP)

 

Nginx 中获取cloudflare保护的网站的访问者的真正IP

一般来说,有三个参数可以使用:

  1. CF-Connecting-IP, 在nginx log中为 cf-connecting-ip.
  2. True-Client-IP, 仅给Cloudflare 企业用户使用,在nginx log中为true-client-ip
  3. X-Forwarded-For, 在nginx log 中比较常见,为x-forwarded-for. X-Forwarded-For 其实是一个数组,按顺序记录了用户的真正的IP和用户使用的Proxy.

举例说明:

X-Forwarded-For: 203.0.113.1 

这个表示用户的真实IP是203.0.113.1

X-Forwarded-For: 198.51.100.101,198.51.100.102,203.0.113.1

这个表示用户的真实IP是203.0.113.1,然后依次经过了198.51.100.102和198.51.100.101代理,然后才访问到了CF的edge 节点

综上所述, CF 推荐使用CF-COnnecting-IP 和 True-Client-IP 两种header,因为他们能够保证他们的value只有一个IP

Cloudflare 配合nginx 来禁止某些国家, user agent 的访问

其实目前cloudflare 的免费版已经在control panel 里面可以完美的实现这些功能,但是cloudflare 能做到的只是禁止某些国家或者user agent 的访问,如果我们想更好的优化这些流量,比如说对不同的geo跳转到不同的网站,或者对于某些蜘蛛来说显示不同的网站,那么就需要nginx 的配合了, 在这里我们主要利用的是nginx 的map 这个功能.

对于geo 的控制

如果你的网站在cloudflare 的保护下,那么cloudflare 默认会在header里面加上’HTTP_CF_IPCOUNTRY’, 这就相当于cloudflare 提供了免费的IP数据库,利用nginx 的map 功能,比如说禁止来自US, CA, UK, AU的流量, 那就就可以按照如下配置:

在nginx 的全局中

map $http_cf_ipcountry $allow {
default yes;
US no;
CA no;
UK no;
AU no;
}

在server中

if ($allow = no) {
return 403;
}

在这里需要注意的是,按照nginx 的官方文档,map 只能位于http block里面,return 就比较自由了,可以在server,location block里面

深入一点,return的可以不仅仅是403,还可以是301, 302等等,比如说把US,CA,UK和 AU 的流量跳转到google,可以这么配置:

if ($allow = no) {
return 301 https://www.google.com;
}

在更加深入一点,活用map功能,我们可以不仅仅是map GEO,还可以map user agent等等,这里就需要正则的配合了.

Nginx 前挂Cloudflare 后的日志分析

将nginx服务器隐藏在cloudflare 服务后端,在nginx 的默认access log里面显示的IP 都是cloudflare,因此我们需要把日志中的访问IP改成真正的用户IP. 有很多种办法可以实现,但是下面的这种办法应该是最简单的.

Cloudflare 用X-Forwarded-For这个header 来传递用户的真实IP,因此我们只需要在nginx 的conf中,设置一个新的nginx log format就可以.

默认的nginx access log format 是:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"'; 

我们可以添加一个新的log format:

log_format csf '$http_x_forwarded_for - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

access log 可以设置成类似于这样的:

access_log /your/access/log/path csf;

这样,用户的真实IP就会展现在access log里面了