咱們在開發Flask應用程序時,一般經過運行Flask自帶的Web服務器來開發測試,這個服務器提供了基本的但功能完備的WSGI服務器。但開發結束之後,在應用程序上線到生成環境時,有不少不得不考慮的事情,其中之一是咱們是否應該要求客戶端使用加密鏈接以增長安全性。html
人們老是問我這個問題,特別是如何在HTTPS協議上部署Flask服務器。在本文中,我將介紹幾種爲Flask應用程序添加加密的方案,從一個很是簡單的能夠在五秒內實現,到一個強大的就像個人網站同樣能夠獲得一個A +評級解決方案(個人網站的SSL分析數據)。node
HTTP的加密和安全功能是經過傳輸層安全性(TLS)協議實現的。實際上,TLS定義了一種使任何網絡通訊信道安全的標準方法。由於我不是一個安全專家,若是我試着給你一個TLS協議的詳細描述,我認爲我沒法作到很好,因此我只想告訴你一些咱們比較感興趣的內容,要知道咱們給Flask服務器設置安全和加密目的。python
通常的思路是,當客戶端與服務器創建鏈接並請求加密鏈接時,服務器將使用其SSL證書進行響應。證書此時做爲服務器的標識,由於它包括服務器名和域名信息。爲確保服務器提供的信息正確無誤,證書是由證書頒發機構或CA進行加密簽名的。若是客戶端知道並信任CA,則能夠確認證書籤名確實來自該實體,而且客戶端能夠肯定其鏈接的服務器是合法的。nginx
客戶端成功驗證證書後,會建立用於與服務器通訊的加密密鑰。爲確保將此密鑰安全地發送到服務器,它使用服務器證書附帶的公鑰對其進行加密。服務器擁有與證書中的公鑰一塊兒使用的私鑰,所以它是惟一可以解密包的一方。從服務器收到加密密鑰時起,全部流量都使用只有客戶端和服務器知道的密鑰加密。git
從上述總結中你可能會猜到,要實現TLS加密,咱們須要兩個項目:服務器證書,包括公鑰並由CA簽名,以及與證書中包含的公鑰一塊兒使用的私鑰。github
Flask(更具體地說實際上是Werkzeug),支持使用即時證書,這對於經過HTTPS快速提供應用程序很是有用,並且不會搞亂系統的證書。你只有須要作的就是將 ssl_context ='adhoc'
添加到程序的 app.run()
調用中。遺憾的是,Flask CLI沒法使用此選項。舉個例子,下面是官方文檔中的「Hello,World」 Flask應用程序,並添加了TLS加密:web
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(ssl_context='adhoc')
複製代碼
要在Flask中使用臨時證書,你須要在虛擬環境中安裝一個相關依賴項:flask
$ pip install pyopenssl
複製代碼
當你運行腳本時,就會注意到Flask指示它正在運行 https://
服務器:vim
$ 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 []:contact@vimiix.com
複製代碼
咱們如今能夠在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'))
複製代碼
瀏覽器將繼續給出警告這個證書,但若是你檢查它,你就將看到你在建立時輸入的信息:
固然咱們都知道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服務器。
若是你使用 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 ---