nginx add_header的一个陷阱

Syntax: add_header name value [always];
Default:
Context: httpserverlocationif in location

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). The value can contain variables.

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

add_header指令可以从上一级继承  当且仅当 当前级别没有add_header指令

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

 

在全局配置加了个需要使用的header, 现在假如在某个location 或者 server 也需要add_header,则会覆盖掉全局配置的

http {… add_header http-global ‘123’;…}

server {… add_header server-conf ‘321’;…}

则 这个server 是看不到 http-global: 123  这个header的,需要注意

 

其实,proxy_set_header 也是完全相同的情况

Syntax: proxy_set_header field value;
Default:
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
Context: httpserverlocation

Allows redefining or appending fields to the request header passed to the proxied server. The value can contain text, variables, and their combinations. These directives are inherited from the previous level if and only if there are no proxy_set_header directives defined on the current level. By default, only two fields are redefined:

 

ssh 的一些小技巧

1. 通常批量处理的时候会遇到ip down的问题,会等待非常久,可以加个超时
ssh -o ConnectTimeout=3

2. 如果要在远端机器使用变量怎么办? 因为变量会在本地机器被默认解析, 比如awk的$1, 所以可以加个反斜杠\
ssh -o ConnectTimeout=3 $IP " cat filelist |awk '{print \$1}' "

3. 如果要把远端运行结果赋值给某个变量怎么办?,因为执行的命令也会在本地机器被默认解析,所以也需要加个反斜杠\
ssh $IP “df_data= \`df\`; echo \$df_data ”

4. 另外,如果是逐个IP批处理,很容易遇到执行一个就退出的情况,需要加个n
ssh -n $IP

ciphers suite的选择

HTTPS ciphers suite 的选择要奉行一个原则: 安全 兼容 性能

可以参考https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations 这个链接

Configuration Oldest compatible client
Modern Firefox 27, Chrome 30, IE 11 on Windows 7, Edge, Opera 17, Safari 9, Android 5.0, Java 8
Intermediate Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7
Old Windows XP IE6, Java 6

如果是个人网站,不需要考虑支持较老和古老的浏览器,直接选择Modern支持即可

对于大多数的网站来说,还是需要综合考量的,先假设只需要支持modern和Intermediate

Intermediate compatibility (default)

For services that don’t need compatibility with legacy clients (mostly WinXP), but still need to support a wide range of clients, this configuration is recommended. It is is compatible with Firefox 1, Chrome 1, IE 7, Opera 5 and Safari 1.

  • Ciphersuites: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
  • Versions: TLSv1.2, TLSv1.1, TLSv1
  • TLS curves: prime256v1, secp384r1, secp521r1
  • Certificate type: RSA
  • Certificate curve: ‘None
  • Certificate signature: sha256WithRSAEncryption
  • RSA key size: 2048
  • DH Parameter size: 2048
  • ECDH Parameter size: 256
  • HSTS: max-age=15768000
  • Certificate switching: None

几个关键点:

  1.  RSA key 用2048的签名就可以了
  2.  DH 用2048
  3.  使用TLS1.0-1.2, 不要使用不安全的SSLv2-3
  4.  加密的cipher-suite 可以参考以上,也可以自己调整顺序,比如可以根据自己客户端是PC浏览器多还是移动端多,把CHACHA挪后…

 

参考资料:
https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations

spdy 和 http2.0 支持

虽然spdy 协议早已被 HTTP2 取代,但是chrome 旧浏览器和 移动端基于chrome内核的应用还是挺多的,所以spdy 的兼容还是必要的

cloudflare 出了个spdy的补丁,可以兼容HTTP2和SPDY

https://blog.cloudflare.com/open-sourcing-our-nginx-http-2-spdy-code/

靠谱补丁 for nginx 1.10.3:
https://github.com/cujanovic/nginx-http2-spdy-patch/blob/master/nginx-spdy.patch
一般靠谱补丁:
https://github.com/cloudflare/sslconfig/tree/master/patches

打完补丁后, build的时候增加编译参数即可

–with-http_spdy_module #开启spdy 协议, 需要打上cloudflare的patch
–with-http_v2_module #开启HTTP2 协议,不需要任何补丁

nginx 相关配置可以参考nginx 配置文档
server {
listen 443 spdy http2;

}

nginx https 入门配置

nginx 版本选择,一般1.10 或者当前最新stable的1.12都没什么问题

编译参数:

--prefix=/opt/itc/nginx --with-http_ssl_module --with-http_v2_module --with-openssl=../openssl-1.1.0f --with-pcre=../pcre-8.38 --with-zlib=../zlib-1.2.11

其中:
openssl 可以选用1.0版本也可以选用1.1版本,性能会更强,可以在openssl.org下载
pcre zlib都需要自行在对方官网下载
另外, 这两个参数可以按需使用:


--with-openssl-opt=enable-weak-ssl-ciphers #openssl1.1版本开启weak_cipers
--with-http_spdy_module #开启spdy 协议,需要打上cloudflare的patch

配置:

server {
listen 443 spdy http2;
server_name www.4os.org;
root /opt/www/www.4os.org;
access_log logs/www.log main;

ssl_certificate ssl/your_site_key.crt; #公钥
ssl_certificate_key ssl/your_site_key.key; #私钥

ssl_dhparam ssl/dhparams.pem;
ssl_ciphers “ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!RC4:!MD5:!PSK:!aECDH:!DHE”;
ssl_prefer_server_ciphers on;
#ssl_ecdh_curve secp384r1;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

location / {

}
}

cipher_suite 的选择可以参考: https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations

https 支持 IE6和 winXP

winXP + IE6 是非常非常古老的组合, 如果确实需要支持会稍微麻烦些
1. winXP 必须是更新到SP3的,之前的版本无法支持sha2 证书
2. 需要支持TLS_RSA_WITH_3DES_EDE_CBC_SHA 这个属于weak的cipher suite,如果编译的是openssl1.1 版本,则需要编译的时候加入 –with-openssl-opt=enable-weak-ssl-ciphers 参数以开启
3. SNI 不支持,因此IE6只会接收默认的证书,需要把需要支持的域名打到默认证书里边,并且首先加载这个默认虚机 (从测试看,谁先加载谁就是默认证书)
4. 需要开启SSLv3,这是非常不建议的操作
默认的IE6用户设置是没有勾选IE设置里边的TLS1.0的,只支持SSLv2 和 SSLv3

sed 高级模式

n 模式: 读取下一行到pattern space,并只处理下一行
N 模式: 将下一行添加到pattern space中,把当前行和下一行一起处理

举个例子
默认情况,读入一行显示/处理一行


$ cat test 
a
b
c
d
$ sed '=;p' test 
1
a
a
2
b
b
3
c
c
4
d
d

n模式,读入2行,显示/处理第二行


$ sed '=;n;p' test 
1
a
b
b
3
c
d
d

N模式,读入2行,显示/处理2行


$ sed '=;N;p' test 
1
a
b
a
b
3
c
d
c
d

那么,N模式有什么用? 这就有了著名的sed删除换行符的梗

sed ':a;N;$!ba;s/\n/ /g' file

:a 是定义了个label
N 则是上文的N模式了,一次读入2行,处理2行

Explanation:

1.Create a label via :a.
2.Append the current and next line to the pattern space via N.
3.If we are before the last line, branch to the created label $!ba ($! means not to do it on the last line as there should be one final newline).
4.Finally the substitution replaces every newline with a space on the pattern space (which is the whole file).

rp_filter 在rhel6 和 rhel7 之后的一些改变

rp_filter, reverse-path filtering,反向过滤技术,系统在接收到一个IP包后,检查该IP是不是合乎要求,不合要求的IP包会被系统丢弃。该技术就称为rp filter。怎么样的包才算不合要求呢?例如,用户在A网口上收到一个IP包,检查其IP为B。然后考查:对于B这个IP,在发送时应该用哪个网口,“如果在不应该接收到该包的网口上接收到该IP包,则认为该IP包是hacker行为”。

例如:

A: 192.168.8.100

B: (IGMP Query) 10.0.0.1 来自路由器

查找路由表

网卡1为默认路由: 172.17.5.100 172.17.5.1

网卡2 192.168.8.100 192.168.8.1

系统根据路由表,认为10.0.0.1这个IP应该在第一个网卡172.17.5.100上收到,现实的情况是在第二张网卡192.168.8.100上收到了。认为这是不合理的,丢弃该包。致命的问题的,该包是来自路由器的IGMP Query包。

The rp_filter can reject incoming packets if their source address doesn’t match the network interface that they’re arriving on, which helps to prevent IP spoofing. Turning this on, however, has its consequences: If your host has several IP addresses on different interfaces, or if your single interface has multiple IP addresses on it, you’ll find that your kernel may end up rejecting valid traffic. It’s also important to note that even if you do not enable the rp_filter, protection against broadcast spoofing is always on. Also, the protection it provides is only against spoofed internal addresses; external addresses can still be spoofed.. By default, it is disabled.

rp_filter参数在升级到RHEL6之后的版本后出现了比较大的改变,我们看下内核参数解析

在RHEL5的时候这个内核参数的含义:


/usr/share/doc/kernel-doc-2.6.18/Documentation/networking/ip-sysctl.txt

rp_filter - BOOLEAN
        1 - do source validation by reversed path, as specified in RFC1812
            Recommended option for single homed hosts and stub network
            routers. Could cause troubles for complicated (not loop free)
            networks running a slow unreliable protocol (sort of RIP),
            or using static routes.

        0 - No source validation.

        conf/all/rp_filter must also be set to TRUE to do source validation
        on the interface

        Default value is 0. Note that some distributions enable it
        in startup scripts.

在RHEL6 RHEL7之后这个内核参数的含义:


/usr/share/doc/kernel-doc-2.6.32/Documentation/networking/ip-sysctl.txt

rp_filter - INTEGER
        0 - No source validation.
        1 - Strict mode as defined in RFC3704 Strict Reverse Path 
            Each incoming packet is tested against the FIB and if the interface
            is not the best reverse path the packet check will fail.
            By default failed packets are discarded.
        2 - Loose mode as defined in RFC3704 Loose Reverse Path 
            Each incoming packet's source address is also tested against the FIB
            and if the source address is not reachable via any interface
            the packet check will fail.

        Current recommended practice in RFC3704 is to enable strict mode 
        to prevent IP spoofing from DDos attacks. If using asymmetric routing
        or other complicated routing, then loose mode is recommended.

        The max value from conf/{all,interface}/rp_filter is used 
        when doing source validation on the {interface}.

        Default value is 0. Note that some distributions enable it
        in startup scripts.

所以如果需要和RHEL5的行为保持一致,应该设置


net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2

否则在多IP/多网卡/多网段的时候,会出现非预期的丢弃数据包的问题
具体可以参考:
redhat 官方文档: https://access.redhat.com/solutions/53031
案例分析: http://www.cnblogs.com/huazi/archive/2013/02/25/2932021.html

linux lvs 配置

lvs 需要配置下内核参数,否则会发生抢VIP断流之类的异常情况,本文档主要介绍DR模式下
首先是LVS 前端,需要设置两个参数arp_ignore和arp_announce


       echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore

       echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce

       echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore

       echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce

而后端的节点,也需要设置对应的参数


       echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore

       echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce

       echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore

       echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce

这里解释下这两个参数的含义:

arp_ignore - INTEGER
	Define different modes for sending replies in response to
	received ARP requests that resolve local target IP addresses:
	0 - (default): reply for any local target IP address, configured
	on any interface
	1 - reply only if the target IP address is local address
	configured on the incoming interface
	2 - reply only if the target IP address is local address
	configured on the incoming interface and both with the
	sender's IP address are part from same subnet on this interface
	3 - do not reply for local addresses configured with scope host,
	only resolutions for global and link addresses are replied
	4-7 - reserved
	8 - do not reply for all local addresses

	The max value from conf/{all,interface}/arp_ignore is used
	when ARP request is received on the {interface}

简单翻译下:
arp_ignore:定义对目标地址为本地IP的ARP询问不同的应答模式0

0 – (默认值): 回应任何网络接口上对任何本地IP地址的arp查询请求

1 – 只回答目标IP地址是来访网络接口本地地址的ARP查询请求

2 -只回答目标IP地址是来访网络接口本地地址的ARP查询请求,且来访IP必须在该网络接口的子网段内

3 – 不回应该网络界面的arp请求,而只对设置的唯一和连接地址做出回应

4-7 – 保留未使用

8 -不回应所有(本地地址)的arp查询


arp_announce - INTEGER
	Define different restriction levels for announcing the local
	source IP address from IP packets in ARP requests sent on
	interface:
	0 - (default) Use any local address, configured on any interface
	1 - Try to avoid local addresses that are not in the target's
	subnet for this interface. This mode is useful when target
	hosts reachable via this interface require the source IP
	address in ARP requests to be part of their logical network
	configured on the receiving interface. When we generate the
	request we will check all our subnets that include the
	target IP and will preserve the source address if it is from
	such subnet. If there is no such subnet we select source
	address according to the rules for level 2.
	2 - Always use the best local address for this target.
	In this mode we ignore the source address in the IP packet
	and try to select local address that we prefer for talks with
	the target host. Such local address is selected by looking
	for primary IP addresses on all our subnets on the outgoing
	interface that include the target IP address. If no suitable
	local address is found we select the first local address
	we have on the outgoing interface or on all other interfaces,
	with the hope we will receive reply for our request and
	even sometimes no matter the source IP address we announce.

	The max value from conf/{all,interface}/arp_announce is used.

	Increasing the restriction level gives more chance for
	receiving answer from the resolved target while decreasing
	the level announces more valid sender's information.

arp_announce:对网络接口上,本地IP地址的发出的,ARP回应,作出相应级别的限制: 确定不同程度的限制,宣布对来自本地源IP地址发出Arp请求的接口

0 – (默认) 在任意网络接口(eth0,eth1,lo)上的任何本地地址

1 -尽量避免不在该网络接口子网段的本地地址做出arp回应. 当发起ARP请求的源IP地址是被设置应该经由路由达到此网络接口的时候很有用.此时会检查来访IP是否为所有接口上的子网段内ip之一.如果改来访IP不属于各个网络接口上的子网段内,那么将采用级别2的方式来进行处理.

2 – 对查询目标使用最适当的本地地址.在此模式下将忽略这个IP数据包的源地址并尝试选择与能与该地址通信的本地地址.首要是选择所有的网络接口的子网中外出访问子网中包含该目标IP地址的本地地址. 如果没有合适的地址被发现,将选择当前的发送网络接口或其他的有可能接受到该ARP回应的网络接口来进行发送.

这里有个附加的例子解释

Assume that a linux box X has three interfaces - eth0, eth1 and eth2. Each interface has an IP address IP0,

IP1 and IP2. When a local application tries to send an IP packet with IP0 through the eth2. Unfortunately,

the target node’s mac address is not resolved. Thelinux box X will send the ARP request to know

the mac address of the target(or the gateway). In this case what is the IP source address of the

“ARP request message”? The IP0- the IP source address of the transmitting IP or IP2 - the outgoing

interface? Until now(actually just 3 hours before) ARP request uses the IP address assigned to

the outgoing interface(IP2 in the above example) However the linux’s behavior is a little bit

different. Actually the selection of source address in ARP request is totally configurable

bythe proc variable “arp_announce”

If we want to use the IP2 not the IP0 in the ARP request, we should change the value to 1 or 2.

The default value is 0 - allow IP0 is used for ARP request.

其实就是路由器的问题,因为路由器一般是动态学习ARP包的(一般动态配置DHCP的话),当内网的机器要发送一个到外部的ip包,那么它就会请求 路由器的Mac地址,发送一个arp请求,这个arp请求里面包括了自己的ip地址和Mac地址,而linux默认是使用ip的源ip地址作为arp里面 的源ip地址,而不是使用发送设备上面的 ,这样在lvs这样的架构下,所有发送包都是同一个VIP地址,那么arp请求就会包括VIP地址和设备 Mac,而路由器收到这个arp请求就会更新自己的arp缓存,这样就会造成ip欺骗了,VIP被抢夺,所以就会有问题。

arp缓存为什么会更新了,什么时候会更新呢,为了减少arp请求的次数,当主机接收到询问自己的arp请求的时候,就会把源ip和源Mac放入自 己的arp表里面,方便接下来的通讯。如果收到不是询问自己的包(arp是广播的,所有人都收到),就会丢掉,这样不会造成arp表里面无用数据太多导致 有用的记录被删除。

在设置参数的时候将arp_ignore 设置为1,意味着当别人的arp请求过来的时候,如果接收的设备上面没有这个ip,就不做出响应,默认是0,只要这台机器上面任何一个设备上面有这个ip,就响应arp请求,并发送mac地址

参考文档: http://www.cnblogs.com/lgfeng/archive/2012/10/16/2726308.html

免费https证书

免费https证书,早期有startssl,现在有 let’s Encrypt

Let’s Encrypt 是 由非盈利组织 ISRG(Internet Security Research Group) 带来的免费 自动颁发 开放认证的HTTPS证书服务

Let’s Encrypt is a free, automated, and open certificate authority brought to you by the non-profit Internet Security Research Group (ISRG).

Let’s Encrypt发展到现在,兼容性已经得到非常长足的发展,足以媲美geotrust globalsign verisign等商业证书,由于该证书还是天然三级证书链,性能方面也有明显的优势,更难能可贵的是: 它免费提供RSA/ECC两个版本的证书.

缺点? 基于安全性,只有90天的有效期,需要定期续签,另外它不类似于startssl提供WEB界面给使用者,需要使用命令行工具自行签发,有一定的门槛

本文的目的就是推广Let’s Encrypt 并降低入门门槛

1. 创建身份认证私钥

这个证书就是Let’s Encrypt 辨认识别你的私钥

openssl genrsa 4096 > account.key

2. 创建证书签名请求文件

CSR(Certificate Signing Request,证书签名请求)文件

创建RSA域名私钥

openssl genrsa 4096 > domain.key

创建ECC域名私钥

openssl ecparam -genkey -name secp384r1 | openssl ec -out ecc.key

生成CSR文件

openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:4os.org,DNS:www.4os.org")) > domain.csr
openssl req -new -sha256 -key ecc.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:4os.org,DNS:www.4os.org")) > ecc.csr

 

3. 配置nginx 支持let’s Encrypt 自动校验

let’s Encrypt 是通过自签名程序生成一个随机文件,并访问你的域名的URL来校验你是否拥有这个域名的所有权,所以需要配置一个特定的目录和nginx location 路径,如果你有多台分布,请确认都能正确的访问到该目录文件.

创建用于存放验证文件的目录,例如:

mkdir ~/www/challenges/

然后配置一个 HTTP 服务,以 Nginx 为例:

server {
    server_name www.4os.org 4os.org;

    location ^~ /.well-known/acme-challenge/ {
        alias /home/xxx/www/challenges/;
        try_files $uri =404;
    }

...
}

4.从Let’s Encrypt获取证书

下载证书自动校验签发工具

wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py

校验并获取证书


python acme2_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir ~/www/challenges/  > ./signed.crt
python acme2_tiny.py --account-key ./account.key --csr ./ecc.csr --acme-dir ~/www/challenges/  > ./ecc.crt

现在获取下来的叫做站点证书, 为了浏览器能正确的识别证书的有效性,需要把中间证书一并下载下来并和站点证书合并


wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > chained.pem
cat ecc.crt intermediate.pem > ecc_chained.pem

5.nginx双证书 配置


#RSA
ssl_certificate ssl/chained.pem;
ssl_certificate_key ssl/domain.key;
#ECC
ssl_certificate ssl/ecc_chained.pem;
ssl_certificate_key ssl/ecc.key;

需要特别留意下: nginx 1.11.1版本以后才支持双证书,如果低于此版本,建议只使用RSA证书即可

本文主要参考了jerryqu的个人网站,关于https jerryqu做了大量的研究,Let’s Encrypt,免费好用的 HTTPS 证书