翻译 – ASP.NET Core 托管和部署 – 在 Linux 上使用 Nginx 托管 ASP.NET Core 网站

翻译自 //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 中移除 //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. 在浏览器中,导航到 //<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 是否正常。加载的页面 //<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 的 //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 关闭应用程序。

监视应用程序

服务器被设置为转发指向 //<serveraddress>:80 的请求到运行在 Kestrel 地址为 //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 应用程序已经完全配置好,可以在本机的浏览器中访问 //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 上面监听。例如,//localhost;//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 应用程序。