如何使用 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,我一点都不熟练。多读书,我有一些进步,但现在我卡住了。

我想确认我的方法是正确的还是错误的。

目前:

  1. 我获得了 Let´s encrypt 证书,安装并更新了我的 Nginx 服务器。现在,服务器在前端监听 443 和 SSL。
  2. 我认为这足以保护站点,但我意识到我在从前端发送到后端的请求中遇到了 blocked:mixed 内容错误。
  3. 因此,我假设我还需要保护后端。 (请确认)
  4. 现在(这仅适用于开发,不适用于 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 发送请求。

  1. 此时,我尝试在我的 EC2 生产实例中执行相同的操作。但是在这里,我遇到了一堵墙,我被卡住了。我刚刚创建了另一个自签名证书并以相同的方式启动了 puma 服务器。但连接被拒绝。我了解问题是 EC2 安全规则中的端口 3000 未打开。我尝试在两个 HTTPS 中打开端口,EC2 不允许我打开端口,因为它只适用于端口 443 和 HTTP,但是当服务器启动时,SSL 也不起作用。

所以,在这一点上,我想知道我的方法是否正确,但我只是遗漏了一些东西,否则,这对 EC2 实例来说是错误的方法,我需要做一些真正的事情不同的。注意:我已经阅读了一些关于如何配置 Nginx 服务器来代理 https 的内容,但目前还不太了解。我应该走这条路吗?

@ffeast 回答后的评论和问题:

我理解你的做法。我建议,它妨碍了第二种方法。但是,我有一些问题:

  1. 我能否解决使用此方法遇到的 blocked:mixed-content 错误?为什么?我的意思是,Rails API 请求是否应该更改?注意:目前,在 Angular 中,我有发送请求的资源,例如://domain-name:8080/action 并且 Puma 服务器正在侦听该端口中的请求。我目前没有任何代理通行证。

  2. 我想你包括了连接到 Puma 套接字的 de Nginx 配置,我想我需要创建这个 Puma 套接字,我需要检查如何。如果您知道包含一个示例会有什么帮助。如果我用这个套接字配置 Puma,我需要在特定端口启动 Puma 吗?

  3. 我试图理解整个画面,但我仍然感到困惑: 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;

你走错路了。

在大多数情况下,您不需要保护后端应用程序,原因如下:

  1. 流量将通过本地连接或内部网络传输,因此通过安全连接路由它没有意义
  2. 你会妨碍 nginx <-> 后端通信性能(可以忽略不计但仍然如此)
  3. 你会遇到额外内部证书的各种问题
  4. 甚至更多 - 并非所有后端服务器都支持 https 处理 - 因为这根本不是他们的工作
  5. 从开发的角度来看,您的后端 Web 应用程序不应该关心它是 运行 通过 http 还是 httpS - 这些环境问题应该从您的应用程序的逻辑中完全分离出来

因此,任务归结为在 nginx 中配置 https 并通过与 HTTP-aware 后端服务器的不安全连接执行 proxy_pass (https://nginx.ru/en/docs/http/ngx_http_proxy_module.html#proxy_pass)。

问题是如何处理以下内容:

  1. 您的后端服务器不知道的网络主机名
  2. 是否生成httphttpsurls
  3. 真正客户端的 ip 是什么,因为你的后端应用程序会看到 nginx 的 ip 地址

通常是这样解决的:

  1. Host header通过proxy_set_header传递,被后端服务器拾取
  2. X-Forwarded-Proto header 通过并通常受到后端服务器的尊重
  3. 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 个已超过 httphttps。换句话说,错误更有可能是由于您页面上的某处仍在通过 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 中获得任何额外好处,它通常只是一种负担。总而言之,你的偶尔有猫照片的博客没有它可能会更好。