ALBやCloudFrontなどを使わずできるだけ簡単にEC2にHTTPS接続をやってみた件

こんばんは。最近はヒートテック着てたら暑いですね

最近個人開発で、EC2に簡単にAPIサーバーを立てる機会がありました。いつもこういう時はSSL/TLS接続の設定は行わずHTTPのまま運用するんですが、ちょっと気合を入れてHTTPSで運用してみようと決意しました🚀

AWSで簡単にHTTPS接続を利用するのであれば、ACMでSSL/TLS証明書を取得しALBやCloudFrontにその証明書をアタッチしSSL終端させHTTPリクエストをEC2やECSに送る、という方法がよく取られると思います。ですがこの方法だとALBの維持費用に1000円以上はかかりますしCloudFrontは設定がめんどくさかったりとデメリットが多いです。

この記事では、ALBよりも費用をかけず、CloudFrontよりも簡単にEC2にHTTPS接続ができるようにしてみました。

具体的にはnginx-ssl-proxyというDockerイメージを使います。

nginx-ssl-proxyとは

nginx-ssl-proxyは、透過型SSLプロキシとして機能するDockerイメージです。Dockerイメージの内部的にはcertbotとnginxを利用していて、SSL証明書の取得およびSSL終端+プロキシをまとめて簡単に設定できるのでとても便利です。また、この方法ではSSL/TLS証明書の自動更新も行ってくれるため、証明書の更新し忘れも防ぐことができます。

具体的な設定の例を以下に記載します。

事前条件

  • EC2にポート80/443でアクセスできること
  • EC2にdocker/docker-composeをインストール済みであること
  • HTTPS接続を行いたいDNSレコードの設定が可能であること

設定例

実際にここではAmazon Linux 2 で上記nginx-ssl-proxyコンテナを利用し、https://hogehoge.morifuji-is.ninja/というURLにアクスするとnginx-ssl-proxyコンテナを介して同じdocker-composeに構成されているアプリケーションのコンテナにリクエストをプロキシさせ、HTTPS接続を成功させてみます。

この例ではアプリケーションのコンテナをnginxとして設定しますが、ここを自身のアプリケーションに挿げ替えて試してください

まずはEC2に対してHTTP接続およびHTTPS接続ができる状態にします。具体的には 1.DNSレコードのAレコードにEC2のElasticIPを設定する, 2.EC2のセキュリティグループでインバウンドのポート80とポート443を許可する などの作業です。

次にEC2の中でコンテナを立ち上げます。 docker-compose.ymlは以下の通りです。

# docker-compose.yml
version: '3'

services:
  nginx-ssl-proxy:
    image: danieldent/nginx-ssl-proxy
    restart: always
    environment:
      UPSTREAM: sample-nginx:81       # プロキシさせたいホストとポート
      SERVERNAME: hogehoge.morifuji-is.ninja # SSL/TLS証明書を発行したいドメイン
    ports:
      - "80:80" # 80も必要
      - "443:443" # 443も必要
    volumes:
      - "/etc/letsencrypt"
  sample-nginx:   # アプリケーションのコンテナ
    image: nginx
    ports:
      - "81:81"
    environment:
    - NGINX_PORT=81

実行すると以下のようなログが表示されます。

$ docker-compose up

ec2-user-nginx-ssl-proxy-1  | [s6-init] making user provided files available at /var/run/s6/etc...exited 0.
ec2-user-nginx-ssl-proxy-1  | [s6-init] ensuring user provided files have correct perms...exited 0.
ec2-user-nginx-ssl-proxy-1  | [fix-attrs.d] applying ownership & permissions fixes...
ec2-user-nginx-ssl-proxy-1  | [fix-attrs.d] done.
ec2-user-nginx-ssl-proxy-1  | [cont-init.d] executing container initialization scripts...
ec2-user-nginx-ssl-proxy-1  | [cont-init.d] done.
ec2-user-nginx-ssl-proxy-1  | [services.d] starting services
ec2-user-nginx-ssl-proxy-1  | [services.d] done.
ec2-user-nginx-ssl-proxy-1  | Waiting for Nginx to come up...
ec2-user-nginx-ssl-proxy-1  | 2022/03/27 11:33:02 [ DEBUG ] Parsing environment references in '/etc/nginx/conf.d/default.conf'
ec2-user-nginx-ssl-proxy-1  |   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
ec2-user-nginx-ssl-proxy-1  |                                  Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
ec2-user-nginx-ssl-proxy-1  | 2022/03/27 11:33:02 [warn] 130#130: "ssl_stapling" ignored, no OCSP responder URL in the certificate "/etc/letsencrypt/fullchain-copy.pem"
ec2-user-nginx-ssl-proxy-1  | nginx: [warn] "ssl_stapling" ignored, no OCSP responder URL in the certificate "/etc/letsencrypt/fullchain-copy.pem"
ec2-user-nginx-ssl-proxy-1  |   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
ec2-user-nginx-ssl-proxy-1  |                                  Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0127.0.0.1 - - [27/Mar/2022:11:33:03 +0000] "GET / HTTP/1.1" 301 162 "-" "curl/7.64.0" "-"
ec2-user-nginx-ssl-proxy-1  | <html>
ec2-user-nginx-ssl-proxy-1  | <head><title>301 Moved Permanently</title></head>
ec2-user-nginx-ssl-proxy-1  | <body>
ec2-user-nginx-ssl-proxy-1  | <center><h1>301 Moved Permanently</h1></center>
ec2-user-nginx-ssl-proxy-1  | <hr><center>nginx</center>
ec2-user-nginx-ssl-proxy-1  | </body>
ec2-user-nginx-ssl-proxy-1  | </html>
100   162  100   162    0     0   158k      0 --:--:-- --:--:-- --:--:--  158k
ec2-user-nginx-ssl-proxy-1  | Nginx has arrived.
ec2-user-nginx-ssl-proxy-1  | Waiting for a response from http://hogehoge.morifuji-is.ninja/
ec2-user-nginx-ssl-proxy-1  |   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
ec2-user-nginx-ssl-proxy-1  |                                  Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     035.77.117.143 - - [27/Mar/2022:11:33:03 +0000] "GET / HTTP/1.1" 301 162 "-" "curl/7.64.0" "-"
100   162  100   162    0     0    778      0 --:--:-- --:--:-- --:--:--   775
ec2-user-nginx-ssl-proxy-1  | <html>
ec2-user-nginx-ssl-proxy-1  | <head><title>301 Moved Permanently</title></head>
ec2-user-nginx-ssl-proxy-1  | <body>
ec2-user-nginx-ssl-proxy-1  | <center><h1>301 Moved Permanently</h1></center>
ec2-user-nginx-ssl-proxy-1  | <hr><center>nginx</center>
ec2-user-nginx-ssl-proxy-1  | </body>
ec2-user-nginx-ssl-proxy-1  | </html>
ec2-user-nginx-ssl-proxy-1  | Something is responding at http://hogehoge.morifuji-is.ninja/
ec2-user-nginx-ssl-proxy-1  | Saving debug log to /var/log/letsencrypt/letsencrypt.log
ec2-user-nginx-ssl-proxy-1  | Plugins selected: Authenticator webroot, Installer None
ec2-user-nginx-ssl-proxy-1  | Registering without email!
ec2-user-nginx-ssl-proxy-1  | Obtaining a new certificate
ec2-user-nginx-ssl-proxy-1  | Performing the following challenges:
ec2-user-nginx-ssl-proxy-1  | http-01 challenge for hogehoge.morifuji-is.ninja
ec2-user-nginx-ssl-proxy-1  | Using the webroot path /usr/share/nginx/html for all unmatched domains.
ec2-user-nginx-ssl-proxy-1  | Waiting for verification...
ec2-user-nginx-ssl-proxy-1  | 3.142.122.14 - - [27/Mar/2022:11:33:06 +0000] "GET /.well-known/acme-challenge/7b0r6IA_YYnvVh3vG3RkyNibjNhd--3ASIdH6L8u6R0 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"
ec2-user-nginx-ssl-proxy-1  | 34.221.255.206 - - [27/Mar/2022:11:33:06 +0000] "GET /.well-known/acme-challenge/7b0r6IA_YYnvVh3vG3RkyNibjNhd--3ASIdH6L8u6R0 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"
ec2-user-nginx-ssl-proxy-1  | 64.78.149.164 - - [27/Mar/2022:11:33:06 +0000] "GET /.well-known/acme-challenge/7b0r6IA_YYnvVh3vG3RkyNibjNhd--3ASIdH6L8u6R0 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"
ec2-user-nginx-ssl-proxy-1  | 18.159.196.172 - - [27/Mar/2022:11:33:06 +0000] "GET /.well-known/acme-challenge/7b0r6IA_YYnvVh3vG3RkyNibjNhd--3ASIdH6L8u6R0 HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)" "-"
ec2-user-nginx-ssl-proxy-1  | Cleaning up challenges
ec2-user-nginx-ssl-proxy-1  | IMPORTANT NOTES:
ec2-user-nginx-ssl-proxy-1  |  - Congratulations! Your certificate and chain have been saved at:
ec2-user-nginx-ssl-proxy-1  |    /etc/letsencrypt/live/hogehoge.morifuji-is.ninja/fullchain.pem
ec2-user-nginx-ssl-proxy-1  |    Your key file has been saved at:
ec2-user-nginx-ssl-proxy-1  |    /etc/letsencrypt/live/hogehoge.morifuji-is.ninja/privkey.pem
ec2-user-nginx-ssl-proxy-1  |    Your cert will expire on 2022-06-25. To obtain a new or tweaked
ec2-user-nginx-ssl-proxy-1  |    version of this certificate in the future, simply run certbot
ec2-user-nginx-ssl-proxy-1  |    again. To non-interactively renew *all* of your certificates, run
ec2-user-nginx-ssl-proxy-1  |    "certbot renew"
ec2-user-nginx-ssl-proxy-1  |  - Your account credentials have been saved in your Certbot
ec2-user-nginx-ssl-proxy-1  |    configuration directory at /etc/letsencrypt. You should make a
ec2-user-nginx-ssl-proxy-1  |    secure backup of this folder now. This configuration directory will
ec2-user-nginx-ssl-proxy-1  |    also contain certificates and private keys obtained by Certbot so
ec2-user-nginx-ssl-proxy-1  |    making regular backups of this folder is ideal.
ec2-user-nginx-ssl-proxy-1  |  - If you like Certbot, please consider supporting our work by:
ec2-user-nginx-ssl-proxy-1  | 
ec2-user-nginx-ssl-proxy-1  |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
ec2-user-nginx-ssl-proxy-1  |    Donating to EFF:                    https://eff.org/donate-le
ec2-user-nginx-ssl-proxy-1  | 
ec2-user-nginx-ssl-proxy-1  | 2022/03/27 11:33:14 [notice] 146#146: signal process started

ログをよくみると、環境変数SERVERNAMEに設定したドメインへHTTPリクエスト(http://hogehoge.morifuji-is.ninja/)を送り、SSL/TLS証明書を発行しています。ここら辺はcertbotの挙動のようです。

また、一度SSL/TLS証明書を発行すると、以降は443ポートへのアクセスを全てSSL終端した上で環境変数UPSTREAMに設定したドメインに対してリクエストをプロキシするようになります。

なので この状態で https://hogehoge.morifuji-is.ninjaにアクセスすると正しくnginxの画面が表示されます。これで設定は終わりです。

わかりやすく図を作ってみた

/img/2022-04-12/001.png

docker-compose起動時。

/img/2022-04-12/002.png

SSL/TLS証明書が作成される

/img/2022-04-12/003.png

HTTPSリクエストがアプリケーションへプロキシされる

まとめ

ALBよりもお金をかけずに、CloudFrontよりも簡単に(なおかつSSL/TLS証明書の自動更新付きで)EC2に対してHTTPS接続をすることができました。今度から個人の開発でHTTPS接続を使いたくなった場合はこの構成を多用していこうと思います

おまけ

どうやって証明書の自動更新を行なっているのか気になったので実装を読んでみました。

https://github.com/DanielDent/docker-nginx-ssl-proxy/blob/d6655f891e30fda86b54245121132fbeca614df5/services.d/certbot/run#L27

どうやらLinuxのserviceを使って裏で定期的にcertbotのコマンドを叩いてチェックをしているようですね。シェルスクリプトは自分で書くとめんどくさいので、先人のエンジニアの方々の努力のおかげでこんな楽にHTTPS接続ができる環境にいるんだなとしみじみ思いました🙏


See also