我们在开发Flask应用程序时,通常通过运行Flask自带的Web服务器来开发测试,这个服务器提供了基本的但功能完备的WSGI服务器。但开发结束以后,在应用程序上线到生成环境时,有很多不得不考虑的事情,其中之一是我们是否应该要求客户端使用加密连接以增加安全性。
人们总是问我这个问题,特别是如何在HTTPS协议上部署Flask服务器。在本文中,我将介绍几种为Flask应用程序添加加密的方案,从一个非常简单的可以在五秒内实现,到一个强大的就像我的网站一样可以得到一个A +评级解决方案(我的网站的SSL分析数据)。
HTTPS是如何工作的?
HTTP的加密和安全功能是通过传输层安全性(TLS)协议实现的。实际上,TLS定义了一种使任何网络通信信道安全的标准方法。因为我不是一个安全专家,如果我试着给你一个TLS协议的详细描述,我认为我无法做到很好,所以我只想告诉你一些我们比较感兴趣的内容,要知道我们给Flask服务器设置安全和加密目的。
一般的思路是,当客户端与服务器建立连接并请求加密连接时,服务器将使用其SSL证书进行响应。证书此时作为服务器的标识,因为它包括服务器名和域名信息。为确保服务器提供的信息正确无误,证书是由证书颁发机构或CA进行加密签名的。如果客户端知道并信任CA,则可以确认证书签名确实来自该实体,并且客户端可以确定其连接的服务器是合法的。
客户端成功验证证书后,会创建用于与服务器通信的加密密钥。为确保将此密钥安全地发送到服务器,它使用服务器证书附带的公钥对其进行加密。服务器拥有与证书中的公钥一起使用的私钥,因此它是唯一能够解密包的一方。从服务器收到加密密钥时起,所有流量都使用只有客户端和服务器知道的密钥加密。
从上述总结中你可能会猜到,要实现TLS加密,我们需要两个项目:服务器证书,包括公钥并由CA签名,以及与证书中包含的公钥一起使用的私钥。
最简单的方法
Flask(更具体地说其实是Werkzeug),支持使用即时证书,这对于通过HTTPS快速提供应用程序非常有用,而且不会搞乱系统的证书。你只有需要做的就是将 ssl_context ='adhoc'
添加到程序的 app.run()
调用中。遗憾的是,Flask CLI无法使用此选项。举个例子,下面是官方文档中的“Hello,World” Flask应用程序,并添加了TLS加密:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(ssl_context='adhoc')
复制代码
要在Flask中使用临时证书,你需要在虚拟环境中安装一个相关依赖项:
$ pip install pyopenssl
复制代码
当你运行脚本时,就会注意到Flask指示它正在运行 https://
服务器:
$ python hello.py
* Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
复制代码
很简单,对吧!这样有一个问题就是浏览器不喜欢这种类型的证书,因此浏览器会显示一个大而可怕的警告,你需要在访问应用程序之前先手动允许才可以访问。一旦允许浏览器连接,你就拥有了一个加密连接,就像从具有有效证书的服务器获得的连接一样,这使得这些临时证书便于开发中进行快速和验证测试,但不能用于任何实际生产环境中。
自签名证书
所谓的自签名证书是使用与同一证书关联的私钥生成签名的证书。我在上面提到客户端需要“知道并信任”签署证书的CA,因为这个信任关系允许客户端验证服务器证书。Web浏览器和其他HTTP客户端一般都预先配置了一个已知和可信CA的列表,但显然如果你使用自签名证书,CA将不会被知道,验证也必将失败。这正是我们在上一节中使用的临时证书所发生的情况。如果Web浏览器无法验证服务器证书,它将允许你手动来继续并访问相关网站,但这将确保你自己必须了解你所承担的风险。
但是,到底风险是什么呢?使用上一节中的Flask服务器来说,显然我们是相信自己的,所以对我们没有任何风险。主要问题是当用户在连接到他们不了解或不是他们控制的站点时出现此警告。在这些情况下,用户无法知道服务器是否可信,因为任何人都可以为任何域生成证书,正如接下来你要看到的。
虽然自签名证书有时很有用,但Flask的临时证书并不是那么好,因为每次服务器运行时,都会通过pyOpenSSL动态生成不同的证书。使用自签名证书时,最好每次启动服务器时都使用相同的证书,因为这样可以将浏览器配置为信任它,从而消除了安全警告。
我们可以从命令行很轻松生成自签名证书。只需要安装了openssl:
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365
复制代码
这个命令在cert.pem
中写入新证书,其中相应的私钥位于key.pem
中,有效期为365天。运行此命令时,系统会询问你几个问题(译者注:用于生成证书的所有者信息)。下面你可以看到我如何回答它们为我自己的私有服务器 生成证书:
Generating a 4096 bit RSA private key
......................++
.............++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Vimiix
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:vimiix
Email Address []:[email protected]
复制代码
我们现在可以在Flask应用程序中使用这个新的自签名证书,方法是将 app.run()
中的 ssl_context
参数设置为具有证书和私钥文件的文件名的元组:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(ssl_context=('cert.pem', 'key.pem'))
复制代码
浏览器将继续给出警告这个证书,但如果你检查它,你就将看到你在创建时输入的信息:
用于生产的Web服务器
当然我们都知道Flask开发服务器只适合开发和测试。那么我们如何在生产服务器上安装SSL证书呢?
如果您使用的是gunicorn,可以使用命令行参数执行此操作:
$ gunicorn --certfile cert.pem --keyfile key.pem -b 0.0.0.0:8000 hello:app
复制代码
如果你使用 nginx 作为反向代理,那么你可以使用 nginx 配置证书,然后 nginx 可以 “全权代理” 加密连接,这意味着它将接受来自外部的加密连接,然后使用常规的未加密连接与我们的后端服务交互。nginx的配置项如下:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# ...
}
复制代码
还需要考虑的另一个重要问题是如何让 nginx 处理通过普通的 HTTP连接的客户端。在我看来,最好的解决方案是通过重定向到相同的URL,使用HTTPS来响应未加密的请求。对于Flask应用程序,您可以使用Flask-SSLify扩展来实现。对于使用nginx,我们可以在配置中包含另一个服务器模块:
server {
listen 80;
server_name example.com;
location / { # 全部转发到https的相同url下
return 301 https://$host$request_uri;
}
}
复制代码
如果你使用的是其他Web服务器,请查看其文档,应该会找到类似的方法来创建上面显示的配置。
使用“真实”证书
我们现在已经了解了自签名证书的所有可选方案,但在所有这些情况下,有共同的限制就是Web浏览器仍然不会信任这些证书,除非你手动接受他们,因此生产服务器上的证书的最佳选择站点应该是从一个众所周知并且由所有Web浏览器自动信任的CA中获取一个证书。当你从某个CA请求证书时,这个机构将会验证你是否可以控制你的服务器和域,但验证的执行方式取决于CA。如果你的服务器通过验证,则CA将为你的服务器颁发具有该CA签名的证书,并将其提供给你进行安装。证书一般在一段时间内有效,通常不会超过一年。大多数CA都会为这些证书收取费用,但有一些CA会免费提供这些证书。最受欢迎的免费CA就是 :Let's Encrypt。
从 Let's Encrypt 获取证书相当容易,因为整个过程是自动化的。假设你使用的是基于Ubuntu的服务器,则必须首先在服务器上安装其开源 certbot 工具:
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install certbot
复制代码
现在你已准备好请求证书的前置工作了。certbot 有几种方法可用于验证你的网站。通常,“webroot” 方法最容易实现。使用此方法,certbot 会在Web服务器公开的目录中添加一些文件作为静态文件,然后尝试使用你希望为其生成证书的域名,通过HTTP访问这些文件。如果此测试成功,则 certbot 知道运行它的服务器与正确的域名相关联,并且满足并发布证书。使用此方法请求证书的命令如下
$ sudo certbot certonly --webroot -w /var/www/example -d example.com
复制代码
在这个例子中,我们正在尝试为 example.com
域名生成证书,该域名使用 /var/www/example
中的目录作为静态文件根目录。如果 certbot 能够验证域名,它将把证书文件写为 /etc/letsencrypt/live/example.com/fullchain.pem
,将私钥写为 /etc/letsencrypt/live/example.com/privkey.pem
,这些证书在90天内有效。
要使用这个新获取的证书,你可以输入上面提到的两个文件名来代替我们之前使用的自签名文件,这应该适用于上述任何配置。当然,你还需要保证验证的域名能正常访问到你的应用程序,因为这是浏览器接受证书有效的唯一方式。
如果你使用 nginx 作为反向代理,则可以在配置中创建映射,为certbot提供一个私有目录,以便在其中编写其验证文件。在下面的示例中,我扩展了上一节中显示的HTTP服务器块,以将所有 Let's Encrypt 的相关请求发送到你选择的特定目录:
server {
listen 80;
server_name example.com;
location ~ /.well-known {
root /path/to/letsencrypt/verification/directory;
}
location / {
return 301 https://$host$request_uri;
}
}
复制代码
当需要续订证书时,也会使用Certbot。为此,你只需执行以下命令:
$ sudo certbot renew
复制代码
如果系统中有任何证书即将过期,则执行上述命令会更新它们,并确保新证书保留在相同位置。如果希望获取续订的证书,则可能需要重新启动Web服务器。
获得SSL A+认证
如果你使用 Let's Encrypt 的证书或其他已知CA的证书,并且你在此服务器上运行最近维护的操作系统,那么就SSL安全性而言,你可能已经拥有了很接近高评分的服务器。您可以前往Qualys SSL Labs网站并获取报告,了解你服务器的排名。
接下来还有一点小事需要做,该报告将指出你需要改进的地方,但总的来说,我希望你会被告知服务器为加密通信公开的选项太宽泛或太弱,让你对已知漏洞持开放态度。
相对容易进行改进的一个地方是如何生成在加密密钥交换期间使用的系数,其通常具有相当弱的默认值。特别是,Diffie-Hellman系数需要相当长的时间才能生成,因此默认情况下服务器使用较小的数字来节省时间。但是我们可以预先生成强系数并将它们存储在一个文件中,然后 nginx 就可以使用它。使用openssl工具,你可以运行以下命令:
openssl dhparam -out /path/to/dhparam.pem 2048
复制代码
如果你想要更强的系数,你可以改变2048或更大,如4096。此命令需要一段时间才能运行,特别是如果你的服务器CPU数量不多的话,但是当它完成后,你就会拥有一个具有强系数的 dhparam.pem
文件,你可以将其插入到 nginx 的 ssl服务器模块中:
ssl_dhparam /path/to/dhparam.pem;
复制代码
接下来,你可能需要配置服务器允许加密通信的加密方式。这是我服务器上的加密列表:
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-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:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
复制代码
在这个列表中,禁用的密码以 !
为前缀。SSL报告将告诉你是否存在不推荐的密码。你必须不时地关注是否发现了需要修改这个加密列表的新漏洞。
你可以在下面找到我当前的 nginx SSL 配置,其中包括上述设置,以及我为解决SSL报告中的警告而添加的一些设置:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_dhparam /path/to/dhparam.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-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:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security max-age=15768000;
# ...
}
复制代码
在文章开头,你可以看到我为我的网站获得的评分结果。如果你在所有类别中都达到100%标记,则必须为配置添加更多的限制,但这也将限制可以连接到你的站点的客户端数量。通常,较旧的浏览器和HTTP客户端使用的是不是很强的加密方式,但如果禁用这些加密方式,则这些客户端将无法连接。(译者注:安全性与兼容性的选择,作为服务提供方应该考虑这一点,适当的降低服务的限制,来兼容较旧的请求方式,也是一种妥协。)因此,你基本上需要妥协,并且还需要定期查看安全报告,并随着时间的推移进行更新修正。
遗憾的是,对于这些最后的SSL改进的复杂程度,你必须使用专业级Web服务器,那么如果你不想使用nginx,要找到一个支持这些设置的web服务,可选择的数量目前是很少的。我知道 Apache 支持,但除此之外,我不知道谁家还支持。
原文:Running Your Flask Application Over HTTPS --- miguelgrinberg
--- EOF ---