devひよこのあしあと

いつでもひよっこな気持ちで学びと挑戦を

Amazon LinuxでLet's Encrypt

Amazon LinuxLet's Encryptを使ってSSLサーバー証明書を取得してnginxに設定する方法を紹介します。

TL;DR(とりあえず結論)

コマンド一発でちょー簡単に証明書発行できてしまいます。ただし、Amazon Linux上ではLet's Encryptクライアントアプリがまだ実験段階のようです。とりあえず正常に発行できましたが、ご利用は自己責任でお願いいたします。

システム構成

せっかくなんでリリースされたばかりのAmazon Linux AMI 2016.03を利用してみます。

EC2のセットアップ

以下、Ansibleを使ってEC2インスタンスとnginxをセットアップしていきますが、そのへんはあまり本質ではないのでびゃーっと記事中段あたりまで読み飛ばして頂いても構いません。

事前作業

Ansibleが利用するAWSのアクセスキー&シークレットキーなどplaybook内には直書きしたくない情報を環境変数としてセットしておきます。 私の場合は普段direnvを利用しているので、ローカルの.envrcに登録して環境変数に反映されるようにしました。

$ cd REPO_ROOT
$ cat .envrc
export AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXX
export AWS_REGION=ap-northeast-1

$ direnv allow

インベントリ

AWSリソースを用いますのでDynamic Inventoryを利用します。Dynamic Inventoryの解説は下記サイトなどを参照ください。

ec2.iniとec2.pyを取得します。

$ cd REPO_ROOT/provision/inventories
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/ec2.ini
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/ec2.py
$ chmod u+x ec2.py

site.yml

AWSリソースのセットアップタスクで、elasticacheロールとec2ロールを呼び出します。

# PLAYBOOK_ROOT/site.yml

---

- name: AWSリソースのセットアップ
  hosts: localhost
  connection: local
  roles:
    - ec2
  vars:
    service_name: nslides01
    group_name: application
    region: ap-northeast-1

- name: アプリケーションサーバーのセットアップ
  hosts: tag_Name_application
  remote_user: ec2-user
  become: yes
  roles:
    - nginx
  vars:
    private_key: ~/.ssh/nslides01.pem

ec2ロール

ec2ロールではセキュリティグループとEC2インスタンスを作成します。 EC2用セキュリティグループではSSH接続とHTTP/HTTPS接続のみを許可しておきます。

# PLAYBOOK_ROOT/roles/ec2/tasks/main.yml

---

- name: EC2用セキュリティグループを作成
  ec2_group:
    name: "{{ group_name }}"
    description: "{{ group_name }}"
    region: ap-northeast-1
    rules:
      - proto: tcp
        from_port: 22
        to_port:   22
        cidr_ip:   0.0.0.0/0
      - proto: tcp
        from_port: 80
        to_port:   80
        cidr_ip:   0.0.0.0/0
      - proto: tcp
        from_port: 443
        to_port:   443
        cidr_ip:   0.0.0.0/0
    rules_egress:
      - proto: all
        from_port: 0
        to_port:   65535
        cidr_ip:   0.0.0.0/0

- name: EC2インスタンスを作成
  ec2:
    image:         "{{ ami_image }}"
    instance_type: "{{ instance_type }}"
    region:        "{{ region }}"
    key_name:      "{{ service_name }}"
    group:         "{{ group_name }}"
    instance_tags:
      Name:        "{{ group_name }}"
    exact_count:   1
    count_tag:
      Name:        "{{ group_name }}"
    wait:          yes
    wait_timeout:  300
    volumes:
      - device_name: "{{ device_name }}"
        volume_type: "{{ volume_type }}"
        volume_size: "{{ volume_size }}"
        delete_on_termination: yes
    instance_profile_name: ap_servers
  register: ec2


- name: SSHで接続できるようになるまで待機
  wait_for: port=22 host="{{ item.public_ip }}" timeout=300 state=started
  with_items: ec2.instances

- name: 作成したEC2インスタンスをインベントリに追加
  add_host: hostname="{{ item.public_ip }}" groupname="tag_Name_{{ group_name }}"
  with_items: ec2.instances

上記で参照している変数にはAWSの無料枠で収まるように最低限のインスタンスサイズを下記のように指定しています。(節約♪節約♪)

# PLAYBOOK_ROOT/roles/ec2/defaults/main.yml

---

ami_image: ami-f80e0596  # ← Amazon Linux AMI 2016.03.0
instance_type: t2.micro
device_name: /dev/xvda
volume_type: gp2
volume_size: 30

nginxのセットアップ

# PLAYBOOK_ROOT/roles/nginx/tasks/main.yml

---

- yum: name={{item}} state=latest
  with_items:
    - nginx
    - git

- name: change log_dir permission
  become: yes
  file:
    path: /var/log/nginx
    state: directory
    owner: nginx
    group: nginx
    mode: 0755

- name: service登録
  service:
    name: nginx
    state: started
    enabled: yes

ここまでで、EC2インスタンスを立ち上げてnginxをHTTPのみで起動した状態となりました。

Let's Encrypt

ここからはLet's EncryptをセットアップしてSSLサーバー証明書を発行してみます。

クライアントアプリのインストール

Let's Encryptクライアントアプリをインストールします。今回は/opt以下に配置するようにしました。

Gitリポジトリをクローンします。

$ cd /opt
$ sudo git clone https://github.com/letsencrypt/letsencrypt
Cloning into 'letsencrypt'...
remote: Counting objects: 33135, done.
remote: Compressing objects: 100% (82/82), done.
remote: Total 33135 (delta 48), reused 0 (delta 0), pack-reused 33053
Receiving objects: 100% (33135/33135), 8.74 MiB | 4.66 MiB/s, done.
Resolving deltas: 100% (23496/23496), done.
Checking connectivity... done.

クライアントアプリを実行してみます。

$ ./letsencrypt-auto --help
WARNING: Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!

おや? なんかAmazon Linuxは実験的なサポートしかしてないよって警告でましたね...

--debugオプションをつけると先に進めるようなので、自己責任の元、すすめてみましょう。

$ ./letsencrypt-auto --help --debug
Bootstrapping dependencies via Amazon Linux...
yum は /usr/bin/yum です
読み込んだプラグイン:priorities, update-motd, upgrade-helper
パッケージ python-tools は利用できません。
依存性の解決をしています
--> トランザクションの確認を実行しています。
   (中略)

上記のメッセージがでたあと、ずらずらーっと依存パッケージのインストールが実行されます。インストールされるパッケージは下記とそれらが依存しているパッケージのようです。

  • python26
  • python26-devel
  • python26-pip
  • python26-virtualenv
  • augeas-libs
  • dialog
  • gcc
  • libffi-devel
  • openssl-devel
  • system-rpm-config

上記のインストールが完了したところで画面には下記のようなメッセージが出力されました。

  (中略)
Checking for new version...
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Requesting root privileges to run letsencrypt...
   sudo /home/ec2-user/.local/share/letsencrypt/bin/letsencrypt --help --debug

  letsencrypt-auto [SUBCOMMAND] [options] [-d domain] [-d domain] ...

The Let's Encrypt agent can obtain and install HTTPS/TLS/SSL certificates.  By
default, it will attempt to use a webserver both for obtaining and installing
the cert. Major SUBCOMMANDS are:

  (default) run        Obtain & install a cert in your current webserver
  certonly             Obtain cert, but do not install it (aka "auth")
  install              Install a previously obtained cert in a server
  renew                Renew previously obtained certs that are near expiry
  revoke               Revoke a previously obtained certificate
  rollback             Rollback server configuration changes made during install
  config_changes       Show changes made to server config during installation
  plugins              Display information about installed plugins

Choice of server plugins for obtaining and installing cert:

  --apache          Use the Apache plugin for authentication & installation
  --standalone      Run a standalone webserver for authentication
  (nginx support is experimental, buggy, and not installed by default)
  --webroot         Place files in a server's webroot folder for authentication

OR use different plugins to obtain (authenticate) the cert and then install it:

  --authenticator standalone --installer apache

More detailed help:

  -h, --help [topic]    print this message, or detailed help on a topic;
                        the available topics are:

   all, automation, paths, security, testing, or any of the subcommands or
   plugins (certonly, install, nginx, apache, standalone, webroot, etc)

とりあえず実行準備は整ったようです。

SSLサーバー証明書の発行

下記のコマンドを実行します。

$ ./letsencrypt-auto certonly --webroot -w /usr/share/nginx/html -d nslides.devchick.link --agree-tos -m xxx@example.com

オプションの意味はそれぞれ下記のとおりです。

  • --webroot : HTTPサーバーが既に稼働している場合に指定します。今回はnginxが既に稼働している状態なのでこのオプションを利用します。HTTPサーバーが稼働していないマシンの場合は--standaloneオプションを指定すれば、Let's EncryptクライアントアプリがHTTPサーバーを一時的に立ち上げてくれるようです。
  • -w : HTTPサーバーのドキュメントルートのパスを指定します。このパスの下に.well-known/acme-challenge/xxxxxxxxxxxxxxxxxxxxというようなファイルを一時的に作成しLet's Encryptサーバー側から読み取ってもらうことでドメイン保有者であることを証明しているようです。認証完了後にはこのファイルは削除されます。
  • -d : 取得したいSSLサーバー証明書ドメインを指定します。
  • --agree-tos : Let's Encrypt の利用規約に同意します。事前に利用規約に目を通しておきましょう。https://letsencrypt.org/repository/
  • -m : 自分の連絡先メールアドレスを指定します。緊急の通知や鍵を紛失したときの復旧に使われます。
$ ./letsencrypt-auto certonly --webroot -w /usr/share/nginx/html -d nslides.devchick.link --agree-tos -m xxx@example.com
Checking for new version...
Requesting root privileges to run letsencrypt...
   sudo /home/ec2-user/.local/share/letsencrypt/bin/letsencrypt certonly --webroot -w /usr/share/nginx/html -d nslides.devchick.link --agree-tos -m xxx@example.com
Version: 1.1-20080819
Version: 1.1-20080819

IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to xxx@example.com.
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/nslides.devchick.link/fullchain.pem. Your cert  # ← ここ!
   will expire on 2016-06-21. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

実行すると上記のようなメッセージが出力されます。「ここ!」と記載した箇所に発行されたSSLサーバー証明書が保存されました。 フォルダの中を確認してみると下記のようにシンボリックリンクが張られた状態になります。

$ sudo ls -al /etc/letsencrypt/live/nslides.devchick.link
合計 8
drwxr-xr-x 2 root root 4096  3月 23 13:35 .
drwx------ 3 root root 4096  3月 23 13:35 ..
lrwxrwxrwx 1 root root   41  3月 23 13:35 cert.pem -> ../../archive/nslides.devchick.link/cert1.pem
lrwxrwxrwx 1 root root   42  3月 23 13:35 chain.pem -> ../../archive/nslides.devchick.link/chain1.pem
lrwxrwxrwx 1 root root   46  3月 23 13:35 fullchain.pem -> ../../archive/nslides.devchick.link/fullchain1.pem
lrwxrwxrwx 1 root root   44  3月 23 13:35 privkey.pem -> ../../archive/nslides.devchick.link/privkey1.pem

シンボリックリンク先のcert1.pem1の部分は証明書を発行した回数が連番で記載されます。今後再発行をしていくとカウントアップされていきます。

ちなみに4つのファイルはそれぞれ下記を意味しています。

  • cert.pem : 今回発行したSSLサーバー証明書単体 (Subject: CN=nslides.devchick.link)
  • chain.pem : 上記のSSLサーバー証明書を発行した認証局の証明書 (Subject: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X1)
  • fullchain.pem : 上記の2つの証明書を1つにまとめたもの
  • privkey.pem : cert.pemに対応する秘密鍵

nginxのセットアップ その2

nginxの設定を修正してHTTPSで接続できるようにします。デフォルトの設定ファイルの中にサンプルがあるので、コメントアウトを解除します。

# /etc/nginx/nginx.conf

    server {
        listen       443 ssl;
        listen       [::]:443 ssl;
        server_name  localhost;
        root         /usr/share/nginx/html;

        ssl_certificate     "/etc/letsencrypt/live/nslides.devchick.link/fullchain.pem";  # ← ここ変更
        ssl_certificate_key "/etc/letsencrypt/live/nslides.devchick.link/privkey.pem";    # ← ここ変更
        # It is *strongly* recommended to generate unique DH parameters
        # Generate them with: openssl dhparam -out /etc/pki/nginx/dhparams.pem 2048
        #ssl_dhparam "/etc/pki/nginx/dhparams.pem";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

「ここ変更」とマークした箇所を修正して先ほど発行されたSSLサーバー証明書のfullchainと秘密鍵を指定します。

動作確認

Webブラウザでサイトにアクセスします。

f:id:devchick:20160324000026p:plain

やった! 無事にSSLサーバー証明書が認識されました。

更新処理

Let's Encryptで発行される証明書は有効期限が90日間と非常に短いものとなっています。なので、定期的に更新してあげないといけません。

cronで1ヶ月に1回更新させるように、/etc/cron.monthlyディレクトリ以下にスクリプトをひとつセットしておきます。

$ sudo vi /etc/cron.monthly/letsencrypt
/opt/letsencrypt/letsencrypt-auto renew --webroot -w /usr/share/nginx/html --force-renew --debug
/sbin/service nginx reload

$ sudo chmod a+x /etc/cron.monthly/letsencrypt

--debugオプションを付けていないとまたAmazon Linuxは実験的なサポートしかしてないよって警告がでて処理失敗してしまうので付けています。

おわりに

非常に簡単に、かつ無料でSSLサーバー証明書を発行&設定できてしまいました。

HTTP/2の恩恵を受けるためにはHTTPS通信が必要ですが、Let's Encryptのおかげでいろいろなサイトで利用できるようになりそうです。

参考