NGINX大全 第七章 安全控制

NGINX大全 第七章 安全控制

第七章 安全控制

7.0 介绍

安全性是分层完成的,你的安全模型必须要有多个层才能算真正加强。在本章中,我们将通过许多不同的方式使用NGINX和NGINX Plus来保护您的Web应用程序。您可以将这些安全方法中的许多方法相互结合使用,以帮助加强安全性。以下是一些探索NGINX和NGINX Plus功能的安全性部分,可以帮助您加强应用程序。您可能会注意到本章未涉及NGINX的最大安全功能之一,即ModSecurity 3.0 NGINX模块,它将NGINX转变为Web应用程序防火墙(WAF)。要了解有关WAF功能的更多信息,请下载ModSecurity 3.0和NGINX:快速入门指南

7.1 基于IP地址的访问

问题

您需要根据客户端的IP地址控制访问。

解决方案

使用HTTP访问模块来控制对受保护资源的访问:

location /admin/ {
    deny 10.0.0.1;
    allow 10.0.0.0/20;
    allow 2001:0db8::/32;
    deny all;
}

给定的location块允许从10.0.0.0/20中除10.0.0.1以外的任何IPv4地址进行访问,允许从2001:0db8::/32子网中的IPv6地址进行访问,并对从任何其他地址发出的请求返回状态码403。allowdeny指令在HTTP,server和location上下文中有效。规则按顺序被检查,直到有一项匹配到远程地址。

讨论

保护互联网上宝贵的资源和服务必须分层进行。NGINX有能力成为这些层之一。deny指令阻止访问给定的上下文,而allow指令可用于允许阻塞访问的子集。您可以使用IP地址,IPv4或IPv6,CIDR块范围,关键字all和Unix套接字。通常,在保护资源时,可能允许一块内部IP地址的访问并拒绝其他所有人访问。

7.2 允许跨域资源共享(CORS)

问题

您正在从另一个域提供资源,并且需要允许跨域资源共享(CORS)以使浏览器能够利用这些资源。

解决方案

根据request方法更改header以启用CORS:

map $request_method $cors_method {
    OPTIONS 11;
    GET 1;
    POST 1;
    default 0;
}

server {
    ...
    location / {
        if ($cors_method ~ '1') {
            add_header 'Access-Control-Allow-Methods'
            'GET,POST,OPTIONS';
            add_header 'Access-Control-Allow-Origin'
            '*.example.com';
            add_header 'Access-Control-Allow-Headers'
            'DNT,
            Keep-Alive,
            User-Agent,
            X-Requested-With,
            If-Modified-Since,
            Cache-Control,
            Content-Type';
        }
        if ($cors_method = '11') {
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

在这个例子中做了很多事情,这些被合在一起,通过用map将GET和POST方法组合。OPTIONS请求方法向客户端返回有关此服务器的CORS规则的预检请求。CORS允许使用OPTIONS,GET和POST方法。设置Access-Control-Allow-Origin header允许从该服务器提供的内容可被用于与该header匹配的源页面。预检请求可以缓存在客户端上1,728,000秒,即20天。

讨论

当他们请求的资源不属于自己的域时,JavaScript等资源会生成CORS。当请求被视为跨域时,浏览器必须遵守CORS规则。如果资源没有专门允许其使用的header,则浏览器不会使用该资源。为了允许我们的资源被其他子域使用,我们必须设置CORS头,这可以使用add_header指令来完成。如果请求是具有标准content type的GET,HEAD或POST,并且请求没有特殊header,则浏览器将发出请求并仅检查域。其他请求方法将使浏览器发出预检请求,以检查请求该资源将遵循的服务器的条款。如果您没有正确设置这些header,浏览器在尝试使用该资源时将会出错。

7.3 客户端加密

问题

您需要在NGINX服务器和客户端之间加密流量。

解决方案

利用SSL模块中的一种,例如ngx_http_ssl_modulengx_stream_ssl_module来加密流量:

http { # All directives used below are also valid in stream
    server {
        listen 8433 ssl;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_certificate /etc/nginx/ssl/example.pem;
        ssl_certificate_key /etc/nginx/ssl/example.key;
        ssl_certificate /etc/nginx/ssl/example.ecdsa.crt;
        ssl_certificate_key /etc/nginx/ssl/example.ecdsa.key;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
    }
}

此配置设置一个server以侦听使用SSL加密的端口8443。这server接受SSL协议版本TLSv1.2和TLSv1.3。两组证书和密钥对的存储位置向服务器公开以供使用。这server被指示使用客户端提供的最高强度,同时限制一些不安全的东西。由于我们提供了ECC证书密钥对,因此优先考虑椭圆曲线加密算法(ECC)密码。SSL会话缓存和超时机制允许工作程序在给定时间内缓存和存储会话参数。还有许多其他会话缓存选项可以帮助提高所有类型用例的性能或安全性。您可以将会话缓存选项互相结合使用。但是,指定一个没有默认值的选项将关闭该默认的内置会话缓存。

讨论

安全传输层是加密传输中的信息的最常用方式。在撰写本文时,TLS协议优先于SSL协议。那是因为SSL的版本1到3现在被认为是不安全的。虽然协议名称可能不同,但TLS仍然建立了一个安全的套接字层。NGINX使您的服务能够保护您和客户端之间的信息,从而保护客户和您的业务。使用签名证书时,需要将证书与证书颁发机构链连接起来。连接证书和链时,证书应位于文件中的链上方。如果您的证书颁发机构在链中提供了许多文件,它还可以提供它们分层的顺序。SSL会话缓存通过不必协商SSL/TLS版本和密码来增强性能。

在测试中,ECC证书被发现比同等强度的RSA证书更快。密钥大小较小,这使得能够提供更多的SSL/TLS连接,并具有更快的握手。NGINX允许您配置多个证书和密钥,然后为客户端浏览器提供最佳证书。这使您可以利用更新的技术,但仍然为老客户服务。

7.4 Upstream加密

问题

您需要加密NGINX和upstream服务之间的流量,并为合规性规则设置特定的协商规则,或者upstream是否在您的安全网络之外。

解决方案

使用HTTP代理模块的SSL指令指定SSL规则:

location / {
    proxy_pass https://upstream.example.com;
    proxy_ssl_verify on;
    proxy_ssl_verify_depth 2;
    proxy_ssl_protocols TLSv1.2;
}

这些代理指令设置了特定的SSL规则让NGINX服从。配置的指令确保NGINX会验证upstream服务上的最多两个证书深度的证书和链。proxy_ssl_protocols指令指定NGINX仅使用TLS 1.2版。默认情况下,NGINX不验证upstream的证书并接受所有TLS版本。

讨论

HTTP代理模块的配置指令非常庞大,如果您需要加密upstream流量,至少应该启用验证。只需通过更改传递给proxy_pass指令的值的协议,就可以通过HTTPS进行代理。但是,这不会验证upstream证书。其他指令(例如proxy_ssl_certificateproxy_ssl_certificate_key)允许您锁定upstream的加密以增强安全性。您还可以指定proxy_ssl_crl或证书吊销列表,其中列出了不再被视为有效的证书。这些SSL代理指令有助于加强您自己网络内的或公共互联网上的系统通信渠道。

7.5 保护location

问题

您需要使用密钥保护location块。

解决方案

使用安全链接模块和secure_link_secret指令对具有安全链接的用户限制资源访问:

location /resources {
    secure_link_secret mySecret;
    if ($secure_link = "") { return 403; }
    rewrite ^ /secured/$secure_link;
}

location /secured/ {
    internal;
    root /var/www;
}

此配置创建一个内部和面向公众的位置块。面向公众的location块/resources将返回403 Forbidden,除非请求URI包含md5哈希字符串,该字符串可以使用提供给secure_link_secret指令的秘钥进行验证。除非URI中的哈希被验证,否则$ secure_link变量是一个空字符串。

讨论

使用秘钥保护资源是确保文件受到保护的好方法。该秘钥与URI结合使用。然后该字符串被md5哈希,并在URI中使用该md5哈希的十六进制摘要。哈希被放入链接并由NGINX评估。NGINX知道所请求文件的路径,因为它位于URI中在哈希后面。NGINX也知道你的秘钥,因为它是通过secure_link_secret指令提供的。NGINX能够快速验证md5哈希,并将URI存储在$secure_link变量中。如果无法验证哈希值,则将该变量设置为空字符串。值得注意的是,传递给secure_link_secret的参数必须是一个静态字符串; 它不能是一个变量。

7.6 用秘钥生成安全链接

问题

您需要使用密钥从应用程序生中成安全链接。

解决方案

NGINX中的安全链接模块接受md5哈希字符串的十六进制摘要,其中字符串是URI路径和秘钥的拼接。在上一节,章节7.5的基础上,我们将创建安全链接,该链接将与先前的配置示例一起使用,假设存在一个文件/var/www/secured/index.html。要生成md5哈希的十六进制摘要,我们可以使用Unixopenssl命令:

$ echo -n 'index.htmlmySecret' | openssl md5 -hex
(stdin)= a53bee08a4bf0bbea978ddf736363a12

在这里,我们展示了我们正在保护的URI,index.html,与我们的秘钥,mySecret拼接在一起。该字符串被传递给openssl命令以输出md5十六进制摘要。

以下的示例是使用Python标准库中包含的hashlib库在Python中构建相同的哈希摘要:

import hashlib
hashlib.md5.(b'index.htmlmySecret').hexdigest()
'a53bee08a4bf0bbea978ddf736363a12'

现在我们有了这个哈希摘要,我们可以在URL中使用它。我们的示例将是www.example.com通过我们的/resources location向文件/var/www/secured/index.html发送请求。我们的完整网址如下:

www.example.com/resources/a53bee08a4bf0bbea978ddf736363a12/index.html

讨论

生成摘要可以通过多种语言以多种方式完成。要记住的事项:URI路径在秘钥之前,字符串中没有回车符,并使用md5哈希的十六进制摘要。

7.7 使用过期日期保护location

问题

您需要使用在将来某个时间到期且特定于某个客户端的链接来保护location。

解决方案

利用安全链接模块中包含的其他指令来设置过期时间并在安全链接中使用变量:

location /resources {
    root /var/www;
    secure_link $arg_md5,$arg_expires;
    secure_link_md5 "$secure_link_expires$uri$remote_addr mySecret";
    if ($secure_link = "") { return 403; }
    if ($secure_link = "0") { return 410; }
}

secure_link指令使用逗号分隔两个参数。第一个参数是保存md5哈希的变量。此示例使用md5的HTTP参数。第二个参数是一个变量,它保存链接到期的时间(在Unix纪元时间格式中)。secure_link_md5指令接受一个参数,该参数声明用于构造md5哈希的字符串格式。与其他配置一样,如果哈希不验证,则$secure_link变量被设置为空字符串。但是,使用此方法,如果哈希匹配但时间已过,则$secure_link变量将设置为0。

讨论

这种保护链接的用法比章节7.5中显示的secure_link_secret更灵活,看起来更干净。使用这些指令,您可以在哈希字符串中使用NGINX可用的任意数量的变量。在哈希字符串中使用用户的特别变量将增强您的安全性,因为用户将无法将链接交换给受保护的资源。建议使用像$remote_addr$http_x_forwarded_for这样的变量,或者应用程序生成的session cookie header。secure_link的参数可以来自任何你喜欢的变量,它们可以被命名为最合适的名字。围绕$secure_link变量设置的条件为Forbidden和Gone返回已知的HTTP代码。HTTP 410,Gone适用于过期链接,因为条件被认为是永久性的。

7.8 生成会过期的链接

问题

你需要生成一个会过期的链接

解决方案

以Unix纪元格式生成过期时间的时间戳。在Unix系统上,您可以使用日期进行测试,如下所示:

$ date -d "2020-12-31 00:00" +%s --utc
1609372800

接下来,您需要拼接哈希字符串以匹配使用secure_link_md5指令配置的字符串。在这种情况下,我们将使用的字符串是1293771600/resources/index.html127.0.0.1 mySecret。md5哈希与十六进制摘要略有不同。它是二进制格式的md5哈希,base64编码,加号(+)转换为连字符(-),斜杠(/)转换为下划线(_),等号(=)被删除。以下是Unix系统上的示例:

$ echo -n '1609372800/resources/index.html127.0.0.1 mySecret' \
| openssl md5 -binary \
| openssl base64 \
| tr +/ -_ \
| tr -d =
TG6ck3OpAttQ1d7jW3JOcw

现在我们有了哈希,我们可以将它作为参数和过期日期一起使用:

/resources/index.html?md5=TG6ck3OpAttQ1d7jW3JOcw&expires=1609372800

以下是Python中使用相对过期期时间的一个更实际的示例,将链接设置为生成后一小时过期。在撰写本文时,此示例使用Python标准库与Python 2.7和3.x一起使用:

from datetime import datetime, timedelta
from base64 import b64encode
import hashlib

# Set environment vars
resource = b'/resources/index.html'
remote_addr = b'127.0.0.1'
host = b'www.example.com'
mysecret = b'mySecret'

# Generate expire timestamp
now = datetime.utcnow()
expire_dt = now + timedelta(hours=1)
expire_epoch = str.encode(expire_dt.strftime('%s'))

# md5 hash the string
uncoded = expire_epoch + resource + remote_addr + mysecret
md5hashed = hashlib.md5(uncoded).digest()

# Base64 encode and transform the string
b64 = b64encode(md5hashed)
unpadded_b64url = b64.replace(b'+', b'-').replace(b'/', b'_').replace(b'=', b'')

# Format and generate the link
linkformat = "{}{}?md5={}?expires={}"
securelink = linkformat.format(
    host.decode(),
    resource.decode(),
    unpadded_b64url.decode(),
    expire_epoch.decode()
)
print(securelink)

讨论

使用此模式,我们能够以特殊格式生成URL中可用的安全链接。通过使用从未发送到客户端的变量做秘钥来提供安全性。您可以根据需要使用尽可能多的其他变量来保护location。md5散列和base64编码是常见的,轻量级的,几乎可用于所有语言。

7.9 HTTPS重定向

问题

您需要将未加密的请求重定向到HTTPS。

解决方案

使用重写将所有HTTP流量发送到HTTPS:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

此配置为任何主机名,IPv4和IPv6,侦听端口80作为的默认server。return语句返回301永久重定向到同一主机名和请求URI的HTTPS服务器。

讨论

在适当的地方始终重定向到HTTPS非常重要。您可能会发现您不需要重定向所有请求,除了在客户端和服务器之间传递敏感信息的请求以外。在这种情况下,您可能只想将return语句放在特定的location里,例如/login

7.10 重定向到HTTPS,在此SSL/TLS已在NGINX之前终止

问题

您需要重定向到HTTPS,但是,您已经在NGINX之前的某个层终止了SSL/TLS。

解决方案

使用标准的X-Forwarded-Proto header来确定是否需要重定向:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    if ($http_x_forwarded_proto = 'http') {
        return 301 https://$host$request_uri;
    }
}

此配置非常类似于HTTPS重定向。但是,在这个配置中,只有在header X-Forwarded-Proto等于HTTP时我们才重定向。

讨论

这是一个常见的用例,您可以在NGINX前面的层中终止SSL/TLS。您可以做这样的事情的一个原因是节省计算成本。但是,您需要确保每个请求都是HTTPS,但终止SSL/TLS的层无法重定向。但是,它可以设置代理header。此配置适用于Amazon Web Services Elastic Load Balancer等层,它将不用额外开销地卸载SSL/TLS。这是一个方便的技巧,以确保您的HTTP流量是安全的。

7.11 HTTP严格传输安全

问题

您需要指示浏览器永远不会通过HTTP发送请求。

解决方案

通过设置Strict-Transport-Security header来使用HTTP严格传输安全(HSTS)增强功能:

add_header Strict-Transport-Security max-age=31536000;

此配置将Strict-Transport-Security header设置为一年的最大期限。这将指示浏览器在尝试向此域发出HTTP请求时始终执行内部重定向,以便所有请求都将通过HTTPS进行。

讨论

对于某些应用程序,仅仅一个HTTP请求被中间攻击的人员捕获就可能导致公司完蛋。如果通过HTTP发送包含敏感信息的表单帖子,则NGINX的HTTPS重定向将无法保护您; 破坏已经造成。这种选择加入安全增强会通知浏览器永远不会发出HTTP请求,因此请求永远会被加密发送。

7.12 满足任何数量的安全方法

问题

您需要提供多种方法将安全性传递给封闭站点。

解决方案

使用satisfy指令指示NGINX您想要满足任一或所有可用的安全方法:

location / {
    satisfy any;

    allow 192.168.1.0/24;
    deny all;

    auth_basic "closed site";
    auth_basic_user_file conf/htpasswd;
}

这个配置告诉NGINX用户请求location /需要满足以下安全方法之一:请求要么需要来自192.168.1.0/24 CIDR块,要么能够提供可以在conf/htpasswd文件中找到的用户名和密码。satisfy指令采用以下两个选项之一:any或all。

讨论

satisfy指令是提供多种方式对Web应用程序进行身份验证的好方法。通过将satisfy指令指定any,用户必须满足其中一个安全挑战。通过将satisfy指令指定all,用户必须满足所有的安全挑战。该指令可以与章节7.1中详述的http_access_module,章节6.1中详述的http_auth_basic_module,章节6.2中详述的http_auth_request_module和章节6.3中详述的http_auth_jwt_module一起使用。安全性只有在多个层中完成才算真正安全。satisfy指令将帮助您实现此目的,为了需要深层安全规则的location和server。

7.13 动态DDoS缓解

只能在NGINX Plus中使用。下略。

猜你喜欢
NGINX大全 第十六章 实用操作提示和结论
阅读 2179

在本章中,我将介绍如何确保配置文件简洁明了以及调试配置文件。

NGINX大全 第一章 基础
阅读 1612

在本章中,您将学习如何安装主要配置文件所在的NGINX以及管理命令。 您还将学习如何验证安装并向默认服务器发出请求。

NGINX大全 第十五章 性能调优
阅读 1957

本章还介绍了连接调优,以保持连接对客户端和上游服务器的开放性,并通过调整操作系统来提供更多连接。

NGINX大全 第四章 可大规模扩展的内容缓存
阅读 2765

使用NGINX,您可以在任何可以放置NGINX服务器的地方缓存您的内容,从而有效地创建您自己的CDN。

NGINX大全 第十四章 使用访问日志,错误日志和请求跟踪进行调试和故障排除
阅读 2784

在本章中,我们将讨论访问和错误日志,通过Syslog协议进行流传输以及使用NGINX生成的请求标识符来端到端地跟踪请求。

NGINX大全 第十三章 高级活动监控
阅读 1504

本章详细介绍了NGINX Plus仪表板,NGINX Plus API和开源存根状态模块的功能。

NGINX大全 第十一章 容器/微服务
阅读 1259

本章重点介绍如何构建NGINX和NGINX Plus容器镜像,使容器化环境更容易工作的特性,以及在Kubernetes和OpenShift上部署镜像。

NGINX大全 第十二章 高可用性部署模式
阅读 1464

本章详细介绍了如何运行多个NGINX服务器以确保负载均衡层中的高可用性的技术。