翻譯 - ASP.NET Core 託管和部署 - 在 Linux 上使用 Nginx 託管 ASP.NET Core 網站

sims發表於2021-01-18

翻譯自 https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-5.0

本文介紹了在 Ubuntu 16.04 伺服器上設定生產環境可用的 ASP.NET Core 環境。這裡的介紹對於更新版本的 Ubuntu 可能也會工作,但是並沒有在更新版本的伺服器上測試。

更過關於 ASP.NET Core 只是的 Linux 發行版,請檢視 Prerequisites for .NET Core on Linux

注意:

對於 Ubuntu 14.04,建議使用 supervisord 監控 Kestrel 程式作為解決方案。對於 Ubuntu 14.04 的介紹,可以該話題的之前版本。

本指南包含以下內容:

  • 使用反向代理伺服器放置一個現存的 ASP.NET Core 應用程式
  • 設定反向代理伺服器將請求轉發到 Kestrel web 伺服器
  • 保證 web 應用程式在啟動時作為守護程式(daemon)執行
  • 配置一個程式管理工具幫助 web 應用程式重新啟動

先決條件

1. 使用帶有 sudo 許可權的標準使用者賬號訪問 Ubuntu 16.04 伺服器

2. 在服務上安裝 .NET Core 執行時。

    a. 訪問 Download .NET Core page

    b. 選擇一個最新非預覽版的 .NET Core 版本

    c. 下載表格中 Run apps - Runtime 最新非預覽版本

    d.  選擇 Linux Package manager instructions 連結,按照你的版本的 Ubuntu 的說明進行操作

3. 一個現存的 ASP.NET Core 應用程式

之後的任何時候,在升級完 shared framework 後,需要重新啟動伺服器託管的 ASP.NET Core 應用程式。

釋出和複製應用程式

配置應用程式為框架獨立的部署。

如果應用程式在本地執行,並且沒有配置安全連線(HTTPS),可以安裝下面任意一種途徑解決:

  • 配置應用程式處理安全的本地連線。更多資訊請檢視 HTTPS configuration
  • 從檔案 Properties/launchSettings.json 中的屬性 applicationUrl 中移除 https://localhost:5001(如果存在的話)

在開發環境中執行 dotnet publish 打包應用程式到一個可以執行在伺服器上的目錄 (例如,bin/Release/{TARGET FRAMEWORK MONIKER}/publish,佔位符 {TARGET FRAMEWORK MONIKER} 是目標框架名稱) 中:

dotnet publish --configuration Release

如果你不想在伺服器上維護 .NET Core 執行時,應用程式也可以被髮布為自包含部署(self-contained deployment)。

使用組織工作流中的工具(例如,SCP,SFTP)複製 ASP.NET Core 應用程式到伺服器。通常把 web 應用程式放到 var 目錄(例如:var/www/helloapp)。

注意

在生產部署環境中,一個持續整合的工作流完成釋出和複製資源到伺服器上。

測試伺服器:

1. 從命令列執行應用程式:dotnet <app_assembly>.dll

2. 在瀏覽器中,導航到 http://<serveraddress>:<port> 驗證應用程式正常執行

 配置反向代理伺服器

 反向代理通常用來設定動態 web 應用程式服務。一個反向代理終結 HTTP 請求並轉發給 ASP.NET Core 應用程式。

使用反向代理服務

Kestrel 從 ASP.NET Core 服務動態內容是強大的,然而,web 服務能力並沒有像 IIS,Apache,或者 Nginx 有很多特性。一個反向代理伺服器可以從 HTTP 伺服器分擔一些工作,例如服務靜態內容,快取請求,壓縮請求和 HTTPS 終結。反向代理伺服器可能部署在專用機器上,也可能和 HTTP 伺服器部署在同一臺機器上。

出於本指南的目的,一個單獨的 Nginx 例項被使用。它和 HTTP 服務執行在同一臺伺服器上。根據需求,不同的設定會被選擇。

因為請求都被反向代理轉發,使用 Microsoft.AspNetCore.HttpOverrides 包中的中介軟體 Forwarded Headers Middleware。這個中介軟體使用 X-Forwarded-Proto header 更新了 Request.Scheme,所以重定向 URIs和其它安全策略工作正確。

Forwarded Headers Middleware 應該在其它中介軟體之前執行。這個順序保證了依賴 forwarded headers 資訊的中介軟體可以在處理過程中使用 header 的值。在 diagnostics 和 錯誤處理中介軟體之後執行 Forwarded Headers Middleware,檢視 Forwarded Headers Middleware order

在呼叫其它中介軟體之前,在 Startup.Configure 的頂部呼叫 UseForwardedHeaders。配置中介軟體轉發 X-Forwarded-For 和 X-Forwarded-Proto headers:

using Microsoft.AspNetCore.HttpOverrides;

...

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

如果沒有中介軟體沒有指定 ForwardedHeadersOptions,預設轉發的 headers 是 None。

代理執行在迴路地址 (127.0.0.0/8, [::]),包含標準本地地址 (127.0.01),預設是被信任的。如果其它的代理或者組織內的網路處理網路和 web 伺服器之間的請求,可以使用 ForwardedHeadersOptions 把它們新增到 KnownProxies 或者 KnownNetworks 列表中。下面的例項在 Startup.ConfigureServices 中新增了一個 IP 地址為 10.0.0.100 可信任的代理到 Forwarded Header Middleware KnownProxies 中。

using System.Net;

...

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

更多資訊檢視 Configure ASP.NET Core to work with proxy servers and load balancers

安裝 Nginx

使用 apt-get 安裝 Nginx。安裝器建立一個 systemd 初始化指令碼啟動 Nginx 作為守護程式。按照下面 Ubuntu 安裝 Nginx 說明操作:Official Debian/Ubuntu packages

注意:

如果要求可選的 Nginx 模組,可能需要從原始碼編譯 Nginx。

由於 Nginx 是第一次安裝,執行下面的命令顯式啟動:

sudo service nginx start

通過瀏覽器顯示 Nginx 預設載入頁驗證 Nginx 是否正常。載入的頁面 http://<server_IP_address>/index.nginx-debian.html 是可以訪問的。

配置 Nginx

為了配置 Nginx 作為一個反向代理轉發 HTTP 請求到你的 ASP.NET Core 應用程式,需要修改 /etc/nginx/sites-available/default。使用文字編輯器開啟它,使用下面的內容替換:

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

如果應用程式是 SingalR 或者 Blazor Server app,檢視 ASP.NET Core SignalR production hosting and scaling 和 Host and deploy ASP.NET Core Blazor Serve

如果沒有 server_name 匹配,Nginx使用預設服務。如果沒有定義預設的服務,配置檔案中的第一個服務作為預設服務。作為最佳實踐,在配置檔案中新增一個返回 444 狀態碼的預設服務。一個預設的配置示例如下:

server {
    listen   80 default_server;
    # listen [::]:80 default_server deferred;
    return   444;
}

前面的配置檔案和預設服務,Nginx 接受埠 80 上主機頭為 example.com 或者 *.example.com 的流量。不匹配這些主機的請求將不會被轉發到 Kestrel。Nginx 轉發匹配的請求到 Kestrel 的 http://localhost:5000。更多資訊檢視 How nginx processes a request。更改 Kestrel 的 IP/埠。檢視 Kestrel: Endpoint configuration

注意:

沒有合適的指定 server_name 指令會暴露你的應用程式的安全弱點。子域萬用字元繫結(例如,*.example.com)並不會造成安全問題,如果你控制了全部的父域(而不是 *.com,這個存在隱患)。更多資訊檢視rfc7230 section-5.4.。

Nginx 配置建立後,執行 sudo nginx -t 驗證配置檔案是否有語法錯誤。如果配置檔案測試成功,可以執行 sudo nginx -s reload 強制 Nginx 使用修改後的配置。

 直接在伺服器上執行應用程式:

1. 導航到應用程式目錄

2. 執行應用程式:dotnet <app_assembly.dll>,app_assembly.dll 是應用程式程式集的檔名稱。

如果應用程式在伺服器上執行成功,但是通過網路訪問失敗,可以檢查伺服器的防火牆確認80埠已經開啟。如果使用的是 Azure Ubuntu VM,新增一個網路安全組(NSG)規則確保80埠入站流量。不需要使能80埠出站規則,因為出站流量在入站規則使能的視情況會自動保證使能。

完成應用程式的測試後,Ctrl + C 關閉應用程式。

監視應用程式

伺服器被設定為轉發指向 http://<serveraddress>:80 的請求到執行在 Kestrel 地址為 http://127.0.0.1:5000 的 ASP.NET Core 應用程式上。然而,Nginx 沒有被設定為管理 Kestrel 程式。systemd 可以被用來建立一個服務檔案去啟動和監視背後的 web 應用程式。systemd 是一個初始化系統,提供了很多強大的特性去啟動,停止和管理程式。

建立一個服務檔案

建立一個服務定義檔案:

sudo nano /etc/systemd/system/kestrel-helloapp.service

下面是一個應用程式服務檔案的示例:

[Unit]
Description=Example .NET Web API App running on Ubuntu

[Service]
WorkingDirectory=/var/www/helloapp
ExecStart=/usr/bin/dotnet /var/www/helloapp/helloapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

前面的這個例子中,管理服務的使用者通過 User 選項指定。使用者 (www-data)必須存在並且擁有應用程式檔案的合適的許可權。

使用 TimeoutStopSec 配置在應用程式關閉後收到初始中斷訊號等待的時長。如果應用程式這時沒有關閉,SIGKILL 可以用來結束應用程式。可以提供不帶單位的秒(例如,150),時間範圍(例如,2min 30s),或者 infinity 禁用超時。TimeoutStopSed 預設值是 DefaultTimeoutStopSec 的值,存在於配置檔案 (systemd-system.conf,system.conf.d,systemd-user.conf,user.conf.d)。大部分發行版的預設超時時間是 90 秒。

# The default value is 90 seconds for most distributions.
TimeoutStopSec=90

Linux 檔案系統區分大小寫。Production 被設定為 ASPNETCORE_ENVIRONMENT 會使得搜尋配置檔案 appsetting.Production.json,而不是appsetting.production.json。

某些值(例如, SQL 連線字串)必須轉義才能被配置提供器去讀取環境變數。使用下面的命令生成一個在配置檔案中使用的合適的轉義值:

systemd-escape "<value-to-escape>"

環境變數名稱不支援冒號(:)分隔符。使用雙下劃線(__)代替冒號。Environment Variables configuration provider 在環境變數被讀入配置的時候會轉換雙下劃線為冒號。在下面的示例中,連線字串鍵值 ConnectionStrings:DefaultConnection 在服務定義檔案中被設定為: ConnectionStrings__DefaultConnection:

Environment=ConnectionStrings__DefaultConnection={Connection String}

儲存檔案並且使能服務:

sudo systemctl enable kestrel-helloapp.service

啟動服務,驗證執行:

sudo systemctl start kestrel-helloapp.service
sudo systemctl status kestrel-helloapp.service

◝ kestrel-helloapp.service - Example .NET Web API App running on Ubuntu
    Loaded: loaded (/etc/systemd/system/kestrel-helloapp.service; enabled)
    Active: active (running) since Thu 2016-10-18 04:09:35 NZDT; 35s ago
Main PID: 9021 (dotnet)
    CGroup: /system.slice/kestrel-helloapp.service
            └─9021 /usr/local/bin/dotnet /var/www/helloapp/helloapp.dll

使用反向代理配置,Kestrel 通過 systemd 管理,web 應用程式已經完全配置好,可以在本機的瀏覽器中訪問 http://localhost。也可以通過遠端主機訪問,除非是有防火牆的阻塞。檢查返回頭部,Server 頭部顯示的是 ASP.NET Core 應用程式託管在 Kestrel 上。

HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked

瀏覽日誌

由於 web 應用程式使用的 Kestrel 通過 systemd 管理,所有的事件和處理過程都被記錄到中心日誌中。然而,這個日誌包含所有 systemd 管理的服務和程式的所有條目的日誌。要檢視 kestrel-ledinpro.service 的條目,使用下面的命令:

sudo journalctl -fu kestrel-helloapp.service

更進一步的篩選,時間選項,例如 --since today,until 1 hour ago,或者這些的結合可以減少返回條目的數量:

sudo journalctl -fu kestrel-helloapp.service --since "2016-10-18" --until "2016-10-18 04:00"

 資料保護

 ASP.NET Core Data Protection stack 被多個 ASP.NET Core 中介軟體使用,包含認證中介軟體(例如,cookie 中介軟體)和跨站請求偽造(CSRF)保護。即使資料保護 APIs 不被使用者程式碼呼叫,資料保護也應該建立一個持久加密的鍵值儲存配置。如果資料保護沒有配置,在記憶體中的鍵值在應用程式重啟的時候就會被丟棄。

如果 key ring 儲存在記憶體中,當應用程式重啟的時候就會:

  • 所有基於 cooked 的認證 tokens 都會失效
  • 使用者在他們下一次請求的時候會被要求再次登入
  • 任何使用 key ring 的資料保護不再能被解密。這可能包含 CSRF tokens 和 ASP.NET Core MVC TempData cookies

為了配置資料保護持久化和加密 key ring,請檢視:

長請求頭部區域

代理伺服器預設設定根據平臺請求頭部區域限制通常是 4K 或者 8K 大小。應用程式可能要求比預設大小更長的請求頭部(例如,使用 Azure Active Directory 的應用程式)。如果更長的請求頭部要求,代理伺服器的預設設定就需要調整。應用的數值根據情況而定。更多資訊,請檢視伺服器文件:

注意:

除非有必要,否則不要增加代理 buffers 的大小。增加這些值增大了 buffer 溢位的風險和 被惡意使用者的拒絕服務 Denial of Service(Dos) 攻擊。

保護應用程式

使能 AppArmor

Linux Security Modules(LSM) 是一個框架,自 Linux 2.6 版本依賴就是 Linux 核心的一部分。LSM 支援安全模組的不同實現。AppArmor 實現了 Mandatory Access Control 系統的一種 LSM,它允許限制程式訪問有限的資源集合。確保 AppArmor 使能是合適的配置。

配置防火牆

關閉所有用不到的埠。Uncomplicated firewall (ufw) 通過提供了 CLI 配置防火牆為 iptable 提供了一個前端。

警告:

如果配置不正確,防火牆將會阻止訪問整個系統。錯誤的指定 SSH 埠將會將你鎖定在系統外,如果你使用 SSH 去連線它。預設埠是 22。更多資訊請檢視 introduction to ufw 和 manual

安裝 ufw,在需要的埠上配置允許流量:

sudo apt-get install ufw

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

sudo ufw enable

保護 Nginx

修改 Nginx 返回名稱

編輯 src/http/ngx_http_header_filter_module.c:

static char ngx_http_server_string[] = "Server: Web Server" CRLF;
static char ngx_http_server_full_string[] = "Server: Web Server" CRLF;

配置選項

使用更多要求的模組配置服務。考慮使用 web 應用程式防火牆加固應用程式,例如 ModSecurity

HTTPS 配置

配置應用程式安全 (HTTPS) 的本地連線

dotnet run 命令使用應用程式的 Properties/launchSettings.json 檔案,這個檔案配置應用程式在由 applicationUrl 屬性提供的 URLs 上面監聽。例如,https://localhost;http://localhost:5000。

配置應用程式在開發中 dotnet run 命令或者在開發環境中(F5 or Ctrl + F5 在 Visual Studio Code)  中使用一個證書,可以使用以下途徑之一:

配置反向代理安全(HTTPS)的客戶端連線

  • 通過指定一個受信任的證書頒發機構的有效證書,配置服務在 443 埠上監聽 HTTPS 流量
  • 利用 /etc/nginx/nginx.conf 檔案中的一些實踐描述加強安全。例子包含了選擇一個強加密和重定向HTTP上所有的流量到 HTTPS。

注意

對於開發環境,我們建議使用臨時重定向(302)而不是永久重定向(301)。連結快取可能在開發環境中導致不穩定的行為。

  • 新增一個 HTTP Strict-Transport-Security(HSTS)頭部保證客戶端隨後的所有請求都使用 HTTPS。
    更多關於 HSTS 的指南,請檢視 Enforce HTTPS in ASP.NET Core
  • 如果在以後不使用 HTTPS 了,可以使用下面其中之一的方法:
    1. 不要新增 HSTS 頭部
    2. 選擇一個更短的 max-age 值

新增 /etc/nginx/proxy.conf 配置檔案:

proxy_redirect          off;
proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header        X-Forwarded-Proto $scheme;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;

使用下面的內容替換 /etc/nginx/nginx.conf 配置檔案的內容。下面的示例在一個配置檔案中包含 http 和 server 部分:

http {
    include        /etc/nginx/proxy.conf;
    limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
    server_tokens  off;

    sendfile on;
    keepalive_timeout   29; # Adjust to the lowest possible value that makes sense for your use case.
    client_body_timeout 10; client_header_timeout 10; send_timeout 10;

    upstream helloapp{
        server localhost:5000;
    }

    server {
        listen     80;
        return     301 https://$host$request_uri;
    }

    server {
        listen                    443 ssl;
        server_name               example.com *.example.com;
        ssl_certificate           /etc/ssl/certs/testCert.crt;
        ssl_certificate_key       /etc/ssl/certs/testCert.key;
        ssl_protocols             TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers               "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve            secp384r1;
        ssl_session_cache         shared:SSL:10m;
        ssl_session_tickets       off;
        ssl_stapling              on; #ensure your cert is capable
        ssl_stapling_verify       on; #ensure your cert is capable

        add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

        #Redirects all traffic
        location / {
            proxy_pass http://helloapp;
            limit_req  zone=one burst=10 nodelay;
        }
    }
}

注意:

Blazor WebAssembly 應用程式要求更大的 burst 引數以適應應用程式更大數量的請求。更多資訊,檢視 Host and deploy ASP.NET Core Blazor WebAssembly

Secure Nginx from clickjacking

Clickjacking,也被稱為介面不久攻擊,是一種惡意攻擊,訪客被欺騙在一個不同的頁面上點選一個連結或者按鈕,而不是在當前正在訪問的頁面。使用 X-FRAME-OPTIONS 保護站點。

為了減輕點選劫持攻擊:

1. 編輯 nginx.conf 檔案:

sudo nano /etc/nginx/nginx.conf

新增行:add_header X-Frame-Options "SAMEORIGIN";

2. 儲存檔案

3. 重啟 Nginx

MIME-type 嗅探

這個頭部阻止大多數的瀏覽器嗅探一個離開宣告內容型別的返回,由於頭部指示瀏覽器不要覆蓋返回內容的型別。使用 nosniff 選項,如果服務說內容是 text/html,那麼瀏覽器就渲染為 text/html。

1. 變價 nginx.conf 檔案:

sudo nano /etc/nginx/nginx.conf

新增行: add_header X-Content-Type-Options "nosniff";

2. 儲存檔案

3. 重啟 Nginx

額外的 Nginx 建議

在升級完伺服器上共享的框架,重新啟動伺服器託管的 ASP.NET Core 應用程式。

 

相關文章