如何使用 SSL HTTPS 保护 Amazon EC2 AWS 中的 Nginx 前端和 Puma 后端
How to secure with SSL HTTPS a Nginx front-end and Puma back-end in Amazon EC2 AWS
我正在尝试将我的网站从 http 迁移到 https。该体系结构是:Nginx 服务器上的 angularJS webapp 前端 运行(侦听端口 80)将请求发送到 Rails API 应用程序 运行 Puma 服务器(侦听端口 8080)。两台服务器都在一个 Amazon EC2 实例中。
这是我第一次迁移到 HTTPS,我一点都不熟练。多读书,我有一些进步,但现在我卡住了。
我想确认我的方法是正确的还是错误的。
目前:
- 我获得了 Let´s encrypt 证书,安装并更新了我的 Nginx 服务器。现在,服务器在前端监听 443 和 SSL。
- 我认为这足以保护站点,但我意识到我在从前端发送到后端的请求中遇到了 blocked:mixed 内容错误。
- 因此,我假设我还需要保护后端。 (请确认)
现在(这仅适用于开发,不适用于 AWS 实例)尝试保护后端 Puma 服务器,我采用了一种非常简单的方法:作为 Rails API是私有的,我创建了一个自签名的 SSL 证书,并像这样使用它来启动 Puma 服务器:
bundle exec puma -b 'ssl://127.0.0.1:3000?key=puma.server.key&cert=puma.server.crt' -e development -S ~/puma -C config/puma/development.rb
如前所述,这正在开发中。我的 Nginx 服务器正在侦听 443 并向侦听端口 3000 的 Puma 发送请求。
- 此时,我尝试在我的 EC2 生产实例中执行相同的操作。但是在这里,我遇到了一堵墙,我被卡住了。我刚刚创建了另一个自签名证书并以相同的方式启动了 puma 服务器。但连接被拒绝。我了解问题是 EC2 安全规则中的端口 3000 未打开。我尝试在两个 HTTPS 中打开端口,EC2 不允许我打开端口,因为它只适用于端口 443 和 HTTP,但是当服务器启动时,SSL 也不起作用。
所以,在这一点上,我想知道我的方法是否正确,但我只是遗漏了一些东西,否则,这对 EC2 实例来说是错误的方法,我需要做一些真正的事情不同的。注意:我已经阅读了一些关于如何配置 Nginx 服务器来代理 https 的内容,但目前还不太了解。我应该走这条路吗?
@ffeast 回答后的评论和问题:
我理解你的做法。我建议,它妨碍了第二种方法。但是,我有一些问题:
我能否解决使用此方法遇到的 blocked:mixed-content 错误?为什么?我的意思是,Rails API 请求是否应该更改?注意:目前,在 Angular 中,我有发送请求的资源,例如://domain-name:8080/action 并且 Puma 服务器正在侦听该端口中的请求。我目前没有任何代理通行证。
我想你包括了连接到 Puma 套接字的 de Nginx 配置,我想我需要创建这个 Puma 套接字,我需要检查如何。如果您知道包含一个示例会有什么帮助。如果我用这个套接字配置 Puma,我需要在特定端口启动 Puma 吗?
我试图理解整个画面,但我仍然感到困惑:
3.1 对 API(来自 Angular Nginx)的请求应该是什么样的?
3.2 如果我应该在特定端口启动 Puma?我不知道套接字方法是否需要...
3.3 proxy-pass 配置应该如何匹配这些请求。
能否请您澄清一下或更新您的示例?让我们假设现在的请求是 //domain-name:8080/action
更新
我正在尝试配置 Ningx 以代理将请求传递给 PUMA 套接字(目前正在开发中)。我收到错误,请查看新的 post 以保持此清洁:Nginx proxy pass to Rails API
更新 2
它的工作!我的网站是安全的,没有错误!这是我的配置。
upstream api.development {
# Path to Puma SOCK file, as defined previously
server unix:/tmp/puma.sock fail_timeout=0;
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /keys/ssl/development.server.crt;
ssl_certificate_key /keys/ssl/development.server.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
location / {
root /path-to-app;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
# Proxy pass requests to Yanpy API
location /api {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
#proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
rewrite ^/api(.*) / break;
proxy_pass http://api.development;
}
}
我唯一的问题是:如果我像这样评论所有 proxy_set_header 指令:
location /api {
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_set_header Host $host;
#proxy_set_header X-Forwarded-Proto https;
#proxy_redirect off;
rewrite ^/api(.*) / break;
proxy_pass http://api.development;
}
它也有效。我知道他们不是必须工作的,应该包括在内,因为他们有一些好处?
同上:
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
你走错路了。
在大多数情况下,您不需要保护后端应用程序,原因如下:
- 流量将通过本地连接或内部网络传输,因此通过安全连接路由它没有意义
- 你会妨碍 nginx <-> 后端通信性能(可以忽略不计但仍然如此)
- 你会遇到额外内部证书的各种问题
- 甚至更多 - 并非所有后端服务器都支持 https 处理 - 因为这根本不是他们的工作
- 从开发的角度来看,您的后端 Web 应用程序不应该关心它是 运行 通过 http 还是 httpS - 这些环境问题应该从您的应用程序的逻辑中完全分离出来
因此,任务归结为在 nginx 中配置 https 并通过与 HTTP-aware 后端服务器的不安全连接执行 proxy_pass (https://nginx.ru/en/docs/http/ngx_http_proxy_module.html#proxy_pass)。
问题是如何处理以下内容:
- 您的后端服务器不知道的网络主机名
- 是否生成
http
或https
urls
- 真正客户端的 ip 是什么,因为你的后端应用程序会看到 nginx 的 ip 地址
通常是这样解决的:
Host
header通过proxy_set_header传递,被后端服务器拾取
X-Forwarded-Proto
header 通过并通常受到后端服务器的尊重
X-Forwarded-For
header包含原用户的ip
我用谷歌搜索了与 puma
相关的设置,这与它最终的样子非常接近(从这里借用 https://gist.github.com/rkjha/d898e225266f6bbe75d8),@myapp_puma
部分特别令人感兴趣你的情况:
upstream myapp_puma {
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 443 default ssl;
server_name example.com;
root /home/username/example.com/current/public;
ssl on;
ssl_certificate /home/username/.comodo_certs/example.com.crt;
ssl_certificate_key /home/username/.comodo_certs/example.com.key;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
try_files $uri/index.html $uri @myapp_puma;
location @myapp_puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
proxy_pass http://myapp_puma;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
现在介绍您的 angular 应用程序。
它需要知道 url 它应该用于 API 请求。
它可以通过多种方式解决,但要点是代码中不应包含 hard-coded URL,您可以改用环境变量。这是其中一种方法 described - 它使用 env.js 文件包含在 angular 应用程序之前,然后依赖于整个代码中定义的常量。在您的情况下,apiUrl 应指向所需的 https 端点
(function (window) {
window.__env = window.__env || {};
// API url
window.__env.apiUrl = 'http://dev.your-api.com';
// Base url
window.__env.baseUrl = '/';
// Whether or not to enable debug mode
// Setting this to false will disable console output
window.__env.enableDebug = true;
}(this));
补充:
因为您的后端似乎正在处理没有 API 前缀的 URL,
你可以对 nginx 使用以下技巧:
location /api/ {
...
proxy_pass http://api.development/;
...
}
请注意 proxy_pass 后的尾部斜杠 - 在这种情况下,位置前缀将被删除。来自 here:
If the proxy_pass directive is specified with a URI, then when a
request is passed to the server, the part of a normalized request URI
matching the location is replaced by a URI specified in the directive
听起来整个考验都是关于您在某处收到的 blocked:mixed-content
错误:
它在浏览器中吗? 无论好坏,浏览器实际上永远不会知道您的 front-end 服务器与 back-end 个已超过 http
或 https
。换句话说,错误更有可能是由于您页面上的某处仍在通过 http
获取内容,无论是从您自己的域还是从 third-party 域获取内容,与通过 http
上的 front-end 服务器代理后端。例如,您可能希望将 front-end 代码中的 src="http://cdn.example.org/….js"
更改为 src="//cdn.example.org/….js"
以使所有 JavaScript 脚本都满意。
是在后端吗?一般为了"tell"后端连接安全,front-end代理必须添加适当的 X-Forwarded-Proto
header.
否则,http://nginx.org/r/proxy_pass在最近版本的 nginx 中支持验证 https,您可以使用 proxy_ssl_…
指令的组合来配置安全策略,例如,proxy_ssl_trusted_certificate
与public 版本的 self-signed 证书 and/or CA,以及 proxy_ssl_verify on;
.
P.S。实际上,我不同意关于后端和 front-end 服务器之间加密对性能影响的优点的其他答案。如果您有理由相信连接可能被窃听(甚至 Google 发现他们在数据中心之间的专用光纤线路已被政府窃听),那么,当然,请使用 self-signed https
在后端和 front-end 服务器之间。
在性能方面,https
的主要开销来自建立连接;但在服务器场景中,该连接可以重新用于为多个客户端提供服务(例如,可能通过 proxy_ssl_session_reuse
进行控制);即使无法重用连接,后端服务器和前端服务器之间的延迟通常也足够小,能够在不影响性能的情况下维持一些额外的往返(特别是考虑到 end-user 的延迟通常是比那个大一个数量级)。
话虽如此,https
address scheme nowadays is misused highly — most public sites have very little benefit of being put over mandatory https
, as it'll simply reduce the number of folks that can access such sites (see HTTP/2.0: The IETF Is Phoning It In 只是一个 policy-based 问题列表,还有很多 TLS 不兼容的技术问题),加上 client-facing https
确实降低了性能,因为需要额外的往返来建立连接,因此,如果您没有从 https
中获得任何额外好处,它通常只是一种负担。总而言之,你的偶尔有猫照片的博客没有它可能会更好。
我正在尝试将我的网站从 http 迁移到 https。该体系结构是:Nginx 服务器上的 angularJS webapp 前端 运行(侦听端口 80)将请求发送到 Rails API 应用程序 运行 Puma 服务器(侦听端口 8080)。两台服务器都在一个 Amazon EC2 实例中。
这是我第一次迁移到 HTTPS,我一点都不熟练。多读书,我有一些进步,但现在我卡住了。
我想确认我的方法是正确的还是错误的。
目前:
- 我获得了 Let´s encrypt 证书,安装并更新了我的 Nginx 服务器。现在,服务器在前端监听 443 和 SSL。
- 我认为这足以保护站点,但我意识到我在从前端发送到后端的请求中遇到了 blocked:mixed 内容错误。
- 因此,我假设我还需要保护后端。 (请确认)
现在(这仅适用于开发,不适用于 AWS 实例)尝试保护后端 Puma 服务器,我采用了一种非常简单的方法:作为 Rails API是私有的,我创建了一个自签名的 SSL 证书,并像这样使用它来启动 Puma 服务器:
bundle exec puma -b 'ssl://127.0.0.1:3000?key=puma.server.key&cert=puma.server.crt' -e development -S ~/puma -C config/puma/development.rb
如前所述,这正在开发中。我的 Nginx 服务器正在侦听 443 并向侦听端口 3000 的 Puma 发送请求。
- 此时,我尝试在我的 EC2 生产实例中执行相同的操作。但是在这里,我遇到了一堵墙,我被卡住了。我刚刚创建了另一个自签名证书并以相同的方式启动了 puma 服务器。但连接被拒绝。我了解问题是 EC2 安全规则中的端口 3000 未打开。我尝试在两个 HTTPS 中打开端口,EC2 不允许我打开端口,因为它只适用于端口 443 和 HTTP,但是当服务器启动时,SSL 也不起作用。
所以,在这一点上,我想知道我的方法是否正确,但我只是遗漏了一些东西,否则,这对 EC2 实例来说是错误的方法,我需要做一些真正的事情不同的。注意:我已经阅读了一些关于如何配置 Nginx 服务器来代理 https 的内容,但目前还不太了解。我应该走这条路吗?
@ffeast 回答后的评论和问题:
我理解你的做法。我建议,它妨碍了第二种方法。但是,我有一些问题:
我能否解决使用此方法遇到的 blocked:mixed-content 错误?为什么?我的意思是,Rails API 请求是否应该更改?注意:目前,在 Angular 中,我有发送请求的资源,例如://domain-name:8080/action 并且 Puma 服务器正在侦听该端口中的请求。我目前没有任何代理通行证。
我想你包括了连接到 Puma 套接字的 de Nginx 配置,我想我需要创建这个 Puma 套接字,我需要检查如何。如果您知道包含一个示例会有什么帮助。如果我用这个套接字配置 Puma,我需要在特定端口启动 Puma 吗?
我试图理解整个画面,但我仍然感到困惑: 3.1 对 API(来自 Angular Nginx)的请求应该是什么样的? 3.2 如果我应该在特定端口启动 Puma?我不知道套接字方法是否需要... 3.3 proxy-pass 配置应该如何匹配这些请求。
能否请您澄清一下或更新您的示例?让我们假设现在的请求是 //domain-name:8080/action
更新 我正在尝试配置 Ningx 以代理将请求传递给 PUMA 套接字(目前正在开发中)。我收到错误,请查看新的 post 以保持此清洁:Nginx proxy pass to Rails API
更新 2 它的工作!我的网站是安全的,没有错误!这是我的配置。
upstream api.development {
# Path to Puma SOCK file, as defined previously
server unix:/tmp/puma.sock fail_timeout=0;
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /keys/ssl/development.server.crt;
ssl_certificate_key /keys/ssl/development.server.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
location / {
root /path-to-app;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
# Proxy pass requests to Yanpy API
location /api {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
#proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
rewrite ^/api(.*) / break;
proxy_pass http://api.development;
}
}
我唯一的问题是:如果我像这样评论所有 proxy_set_header 指令:
location /api {
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_set_header Host $host;
#proxy_set_header X-Forwarded-Proto https;
#proxy_redirect off;
rewrite ^/api(.*) / break;
proxy_pass http://api.development;
}
它也有效。我知道他们不是必须工作的,应该包括在内,因为他们有一些好处?
同上:
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
你走错路了。
在大多数情况下,您不需要保护后端应用程序,原因如下:
- 流量将通过本地连接或内部网络传输,因此通过安全连接路由它没有意义
- 你会妨碍 nginx <-> 后端通信性能(可以忽略不计但仍然如此)
- 你会遇到额外内部证书的各种问题
- 甚至更多 - 并非所有后端服务器都支持 https 处理 - 因为这根本不是他们的工作
- 从开发的角度来看,您的后端 Web 应用程序不应该关心它是 运行 通过 http 还是 httpS - 这些环境问题应该从您的应用程序的逻辑中完全分离出来
因此,任务归结为在 nginx 中配置 https 并通过与 HTTP-aware 后端服务器的不安全连接执行 proxy_pass (https://nginx.ru/en/docs/http/ngx_http_proxy_module.html#proxy_pass)。
问题是如何处理以下内容:
- 您的后端服务器不知道的网络主机名
- 是否生成
http
或https
urls - 真正客户端的 ip 是什么,因为你的后端应用程序会看到 nginx 的 ip 地址
通常是这样解决的:
Host
header通过proxy_set_header传递,被后端服务器拾取X-Forwarded-Proto
header 通过并通常受到后端服务器的尊重X-Forwarded-For
header包含原用户的ip
我用谷歌搜索了与 puma
相关的设置,这与它最终的样子非常接近(从这里借用 https://gist.github.com/rkjha/d898e225266f6bbe75d8),@myapp_puma
部分特别令人感兴趣你的情况:
upstream myapp_puma {
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 443 default ssl;
server_name example.com;
root /home/username/example.com/current/public;
ssl on;
ssl_certificate /home/username/.comodo_certs/example.com.crt;
ssl_certificate_key /home/username/.comodo_certs/example.com.key;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
try_files $uri/index.html $uri @myapp_puma;
location @myapp_puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
proxy_pass http://myapp_puma;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
现在介绍您的 angular 应用程序。 它需要知道 url 它应该用于 API 请求。 它可以通过多种方式解决,但要点是代码中不应包含 hard-coded URL,您可以改用环境变量。这是其中一种方法 described - 它使用 env.js 文件包含在 angular 应用程序之前,然后依赖于整个代码中定义的常量。在您的情况下,apiUrl 应指向所需的 https 端点
(function (window) {
window.__env = window.__env || {};
// API url
window.__env.apiUrl = 'http://dev.your-api.com';
// Base url
window.__env.baseUrl = '/';
// Whether or not to enable debug mode
// Setting this to false will disable console output
window.__env.enableDebug = true;
}(this));
补充: 因为您的后端似乎正在处理没有 API 前缀的 URL, 你可以对 nginx 使用以下技巧:
location /api/ {
...
proxy_pass http://api.development/;
...
}
请注意 proxy_pass 后的尾部斜杠 - 在这种情况下,位置前缀将被删除。来自 here:
If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive
听起来整个考验都是关于您在某处收到的 blocked:mixed-content
错误:
它在浏览器中吗? 无论好坏,浏览器实际上永远不会知道您的 front-end 服务器与 back-end 个已超过
http
或https
。换句话说,错误更有可能是由于您页面上的某处仍在通过http
获取内容,无论是从您自己的域还是从 third-party 域获取内容,与通过http
上的 front-end 服务器代理后端。例如,您可能希望将 front-end 代码中的src="http://cdn.example.org/….js"
更改为src="//cdn.example.org/….js"
以使所有 JavaScript 脚本都满意。是在后端吗?一般为了"tell"后端连接安全,front-end代理必须添加适当的
X-Forwarded-Proto
header.
否则,http://nginx.org/r/proxy_pass在最近版本的 nginx 中支持验证 https,您可以使用 proxy_ssl_…
指令的组合来配置安全策略,例如,proxy_ssl_trusted_certificate
与public 版本的 self-signed 证书 and/or CA,以及 proxy_ssl_verify on;
.
P.S。实际上,我不同意关于后端和 front-end 服务器之间加密对性能影响的优点的其他答案。如果您有理由相信连接可能被窃听(甚至 Google 发现他们在数据中心之间的专用光纤线路已被政府窃听),那么,当然,请使用 self-signed https
在后端和 front-end 服务器之间。
在性能方面,https
的主要开销来自建立连接;但在服务器场景中,该连接可以重新用于为多个客户端提供服务(例如,可能通过 proxy_ssl_session_reuse
进行控制);即使无法重用连接,后端服务器和前端服务器之间的延迟通常也足够小,能够在不影响性能的情况下维持一些额外的往返(特别是考虑到 end-user 的延迟通常是比那个大一个数量级)。
话虽如此,https
address scheme nowadays is misused highly — most public sites have very little benefit of being put over mandatory https
, as it'll simply reduce the number of folks that can access such sites (see HTTP/2.0: The IETF Is Phoning It In 只是一个 policy-based 问题列表,还有很多 TLS 不兼容的技术问题),加上 client-facing https
确实降低了性能,因为需要额外的往返来建立连接,因此,如果您没有从 https
中获得任何额外好处,它通常只是一种负担。总而言之,你的偶尔有猫照片的博客没有它可能会更好。