使用NGINX和NGINX Plus中的Secure Link模块保护URL

NGINX | 2016年7月29日

开源NGINX软件和NGINX Plus用作Web服务器、反向代理和内容缓存时,都具有很高的安全性和可靠性。为进一步防止未经授权的客户端访问,可以使用Secure Link模块中的指令,要求客户端在所请求资产的URL中包含特定的哈希字符串。

本文将介绍如何配置Secure Link模块实现的两种方法。示例配置片段用于保护HTML文件和媒体播放列表文件,但也适用于任何类型的HTTP URL。这两种方法均适用于NGINX和NGINX Plus,但为简洁起见,本文后续仅使用NGINX Plus这一名称。

Secure Link 模块中的方法概述

The Secure Link模块通过比较HTTP请求URL中的编码字符串与该模块为该请求计算出的字符串,验证所请求资源的有效性。如果链接的有效期有限且已过期,则该链接会被视为已失效。这些检查的状态会记录在$secure_link变量中,并用于控制处理流程。

如前所述,该模块提供两种方法。在指定的http、server或location上下文中,只能配置其中一种。

  • 第一种模式更为简单,可通过secure_link_secret指令启用。编码字符串是将两个文本字符串拼接后计算得出的MD5哈希值:URL的最后一部分,以及在NGINX Plus配置中定义的密钥。(有关第一个文本字符串的详细信息,请参阅使用基本安全URL。)要访问受保护的资源,客户端必须将该哈希值紧跟在URL前缀之后。该前缀可以是任意不含斜杠的字符串。在以下示例URL中,前缀为videos,受保护的资源为文件bunny.m3u8:/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8。此方法的一种使用场景是:用户将图像或文档上传到服务器以供共享,但希望在正式链接发布之前,阻止任何知道文件名的人访问该文件。
  • 第二种方法更灵活,可通过secure_link和secure_link_md5指令启用。在此方法中,编码字符串是根据NGINX Plus配置文件中定义的变量计算得出的MD5哈希。最常见的做法是包含$remote_addr变量,以限制特定客户端IP地址的访问,但也可以使用其他值,例如$http_user_agent。该变量会捕获User-Agent标头,从而将访问权限限制为特定浏览器。还可以选择指定到期日期;到期后,即使哈希正确,该URL也不再有效。客户端必须将md5参数附加到请求URL,以指定哈希。如果用于计算哈希的字符串中包含到期日期,客户端还必须附加expires参数来指定该日期。以下是请求受保护文件pricelist.html的示例URL:/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Secure Link模块包含在预构建的开源NGINX binaries from nginx.org、操作系统供应商提供的NGINX软件包以及NGINX Plus中。从源代码构建NGINX时,默认不包含此模块;要启用此模块,请在--with-http_secure_link_module命令中添加configure参数。

使用基本安全URL

保护URL的一种更基本的方法是使用secure_link_secret指令。在以下示例代码片段中,我们将保护一个名为/bunny.m3u8的HTTP实时流(HLS)媒体播放列表文件。该文件存储在/opt/secure/hls目录中,但通过以videos为前缀的URL提供给客户端。

server {
    listen 80;
    server_name secure-link-demo;

    location /videos {
        secure_link_secret enigma;
        if ($secure_link = "") { return 403; }

        rewrite ^ /secure/$secure_link;
    }

    location /secure {
        internal;
        root /opt;
    }
}

采用此配置时,要访问文件“/opt/secure/hls/bunny.m3u8”,客户端必须提供以下URL:

/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

哈希字符串紧跟在前缀之后。前缀可以是任意不含斜杠的字符串(此处为“videos”)。

哈希值基于由两个元素拼接而成的文本字符串计算:

  • URL 中哈希值之后的部分,此处为“hls/bunny.m3u8”。
  • The parameter to the secure_link_secret指令的参数,此处为enigma。

如果客户端的请求URL未包含正确的哈希值,NGINX Plus会将$secure_link变量设置为空字符串。if测试失败,NGINX Plus会在HTTP响应中返回403 Forbidden状态码。

否则(即哈希值正确),rewrite指令会重写URL,在本例中将其重写为/secure/hls/bunny.m3u8($secure_link变量会捕获URL中哈希值之后的部分)。以/secure开头的URL由第二个location块处理。该块中的root指令将/opt设置为所请求文件的根目录,而internal指令则指定该块仅用于内部生成的请求。

在客户端为基本安全URL生成哈希

要获取客户端必须包含在URL中的十六进制格式MD5哈希值,请运行openssl md5命令并使用-hex选项:

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

有关以编程方式生成哈希值的讨论,请参阅《以编程方式生成哈希值》。

服务器对基本安全URL的响应

以下示例curl命令展示了服务器如何响应不同的受保护URL。

如果URL包含正确的MD5哈希值,响应为200 OK:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1HTTP/1.1 200 OK

如果MD5哈希值不正确,响应为403 Forbidden:

# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1HTTP/1.1 403 Forbidden

如果将bunny.m3u8的哈希值用于其他文件,响应也会是403 Forbidden:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1HTTP/1.1 403 Forbidden

使用会过期的安全 URL

一种更灵活的URL保护方法是使用secure_link和secure_link_md5指令。在本例中,我们使用这些指令,将对/var/www/files/pricelist.html文件的访问权限限定为仅允许IP地址为192.168.33.14的客户端访问,且有效期截至2016年12月31日。

虚拟服务器侦听端口 80,并处理以下代码块中的所有安全HTTP请求:location /files。在该代码块中,root指令将/var/www设置为所请求文件的根目录。

The secure_link指令定义了两个变量,用于获取请求URL中的参数:$arg_md5设置为md5参数的值,$arg_expires设置为expires参数的值。

The secure_link_md5 指令定义了用于通过哈希运算生成请求MD5值的表达式;处理URL时,会将该哈希值与 $arg_md5 的值进行比较。此处的示例表达式包括请求中传递的过期时间(由变量 $secure_link_expires 捕获)、URL($uri)、客户端IP地址($remote_addr)以及单词 enigma。

server {
    listen 80;
        server_name secure-link-demo;

        location /files {
            root /var/www;
            secure_link $arg_md5,$arg_expires;
            secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";

            if ($secure_link = "") { return 403; }
            if ($secure_link = "0") { return 410; }
        }
}

采用此配置时,要访问/var/www/files/pricelist.html,IP 地址为 192.168.33.14 的客户端必须在 2016 年 12 月 31 日(星期六)23:59:00 UTC 之前发送此请求 URL:

/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

如果客户端发送的URL中的哈希值(由变量$arg_md5捕获)与根据secure_link_md5指令计算出的哈希值不匹配,NGINX Plus会将变量$secure_link设置为空字符串。此时,if测试失败,NGINX Plus会在HTTP响应中返回403 Forbidden状态码。

如果哈希值匹配,但链接已过期,NGINX Plus会将$secure_link变量设置为0;此时,if测试仍会失败,但NGINX Plus会在HTTP响应中返回410 Gone状态码。

在客户端生成哈希值和过期时间

下面来看看客户端如何计算要包含在URL中的“md5”和“expires”参数。

第一步是确定到期日期对应的Unix时间,因为该值会以$secure_link_expires变量的形式包含在哈希表达式中。要获取Unix时间(即从纪元时间1970-01-01 00:00:00 UTC起经过的秒数),可使用带-d选项和+%s格式说明符的date命令。

在本例中,我们将过期时间设置为2016年12月31日(星期六)23:59:00 UTC,因此命令如下:

# date -d "2016-12-31 23:59" +%s1483228740

客户端将此值作为请求URL中的expires=1483228740参数。

现在,我们对由secure_link_md5指令定义的字符串($secure_link_expires$uri$remote_addr enigma)依次运行以下3个命令:

  • 使用“openssl md5”命令并指定“-binary”选项,可生成二进制格式的MD5哈希值。
  • 使用openssl base64命令对哈希值进行Base64编码。
  • The tr命令将编码值中的加号(+)替换为连字符(-),将斜杠(/)替换为下划线(_),并删除等号(=)。

在本例中,完整命令如下:

# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =AUEnXC7T-Tfv9WLsWbf-mw

客户端将此值作为请求URL中的“md5=AUEnXC7T-Tfv9WLsWbf-mw”参数。

以编程方式生成哈希值

如果NGINX Plus Web服务器通过应用服务器提供动态内容,则NGINX Plus和应用服务器需要使用同一个安全URL。您可以通过编程方式为URL中的md5参数生成哈希值。以下Node.js函数生成的哈希值与上方NGINX Plus配置片段中定义的哈希值一致。该函数以过期时间、URL、客户端IP地址和密钥词作为参数,并返回采用Base64编码的二进制格式MD5哈希值。

var crypto = require("crypto");

function generateSecurePathHash(expires, url, client_ip, secret) {
    if (!expires || !url || !client_ip || !secret) {
        return undefined;
    }
    
    var input = expires + url + client_ip + " " + secret;
    var binaryHash = crypto.createHash("md5").update(input).digest();
    var base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}

要计算当前示例的哈希值,我们需要传入以下参数:

generateSecurePathHash(new Date('12/31/2016 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");

服务器对设有过期时间的安全URL的响应

以下示例curl命令展示了服务器如何响应受保护的URL。

如果IP地址为192.168.33.14的客户端包含正确的MD5哈希值和过期时间,则响应为200 OK:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1
HTTP/1.1 200 OK

如果使用其他IP地址的客户端发送相同的URL,响应为403 Forbidden:

# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

如果md5参数的哈希值不正确,则响应为403 Forbidden:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

如果URL已过期(expires参数表示的日期已过去),则响应为410 Gone:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1HTTP/1.1 410 Gone

示例:使用到期日期保护分段文件

下面是另一个设有到期日期的安全URL示例,用于保护媒体资产的播放列表和分段文件。

与前一个示例不同的是,此处添加了一个map配置块,以便在将文件名捕获到$file_name变量中时,移除播放列表(.m3u8文件)和HLS分段(.ts文件)的扩展名。该变量随后会传递给secure_link_md5指令。这样既可以保护对各个.ts分段的请求,也可以保护对播放列表的请求。

与第一个示例相比,另一个不同之处是,我们在$http_user_agent变量(用于捕获User-Agent标头)中加入secure_link_md5指令,以限制使用特定Web浏览器的客户端访问(例如,使该URL可在Safari中使用,但无法在Chrome或Firefox中使用)。

map $uri $file_name {
    default none;
    "~*/s/(?<name>.*).m3u8" $name;
    "~*/s/(?<name>.*).ts" $name;
}

server {
    listen 80;
    server_name secure-link-demo;

    location /s {
        root /opt;
        secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$file_name$http_user_agent enigma";

        if ($secure_link = "") { return 403; }
        if ($secure_link = "0") { return 410; }
    }
}

总结

NGINX中的Secure Link模块支持添加编码数据(例如URL特定部分的哈希值),以保护文件免遭未经授权的访问。还可通过添加过期时间来限制链接的有效期,进一步增强安全性。

如需试用NGINX Plus,立即开始30天免费试用,或联系我们,讨论您的使用场景。


分享
标签: Security, F5 NGINX, Tech, 2016

关于作者

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。