あすたぴのブログ

astap(あすたぴ)のブログ

https化の話しとLet’s Encrypt、ACMEプロトコルについて

近頃、https化が話題になっている。

https://japan.cnet.com/article/35100589/

chromehttpsではないサイトに警告を出すようになった。 それを受けて、このはてなブログなど個別に与えられるサブドメインがhttpであることが問題になったりしている。はてなはてなブログhttps化を進めていることを発表している。また現時点では同じようなサービスではMediumが独自ドメインhttpsが使用できる。他のサービスではBASEやSTORES JP(http://officialmag.stores.jp/entry/kaigyou/kinou-ssltaiou)が対応している。

https にすると何がいいのか

色んなところですでに話されていることで知っているだろうけども。 httpsssecueres でhttpをsecureにしたということ。 具体的にはインターネットを流れている情報を暗号化してやり取りをするようになる。 暗号化されていないと何が問題なのかというと、パスワードやクレジットカードの番号をそのまま送ることになる。 そのまま送るといってもその情報がどのように送られているかイメージが出来ないと思う。インターネットの通信というのは通信先のIPアドレスを特定し、そのIPアドレスに向かってルーターとかをいくつか経由して相手に通信先に届く。その経由するところに、パスワードやクレジットカードが渡るということになる。悪意を持ってデータを収集されていなければ何事もないかもしれないが、リスクがあるという話しになる。

また他には、httpsでなければ使用できない技術が増えてきている。例えば、httpといってもhttpにはいくつかバージョンがあり一般的には1.1というバージョンが使用されているが、2.0というバージョンも存在していてそれはhttpsでなければ使用ができない。 service workerというブラウザ上で動かせるプログラムもhttpsが必須になっている。

なぜhttpsにするのが大変なのか

暗号化されていて、安全ならみんなhttpsにすればいいんじゃないか?と思うよね。 昔はhttpsを使っているサイトだとしても重要な情報をサブミットするとき、ログイン時などだけhttpsを使用する。という運用が行われていた。それは何故かというと暗号化には復号化という処理が伴うのだが、この2つの処理をするにあたりパソコンのCPUを使用する。この処理はそこそこ時間がかかるもので全通信をhttpsにするとサイト全体が重いということになり、ユーザーの体験が損なわれる。それで重要な情報をやり取りしないページではhttpにしサイトを軽くしていた。 技術は進化し、パソコンのCPUは強くなり、暗号化に使用しているアルゴリズム(暗号の種類)もより計算量が少ないものになったりした。それによって暗号化、復号化にかかる時間は減り、すべてのページを安全にしたほうがいいんじゃない。という成行き。だと思う。

とはいえ、httpにsを付けるにはどうすればいいのか。 それを行うためには、SSL証明書というものが必要になる。(現在使用されているのはTLSだが、SSLと呼ぶことにする) SSL証明書というのは暗号化を行うために必要であるものと共に、そのサイトの正当性を証明するものでもある。 SSL証明書を発行するためには、https://jp.globalsign.com/ こういうところで購入することになる。SSL証明書にも種類があり、一番安いDV証明書でもここを見る限りは年34800円かかる。気軽に自分のサイトを作って公開できたりしていたインターネットはどうなっちゃうだよ。って思う値段だ。また証明書を発行するのに暗号化に使用する鍵を作成したりする。(むずかしい) とてもじゃないが、趣味レベルの個人サイトで行うのは無理だ。

https化してくれるサービスはそれだけお金払ってるの?

ここでタイトルにあるLet's Encryptというのが出て来る。無料で証明書を発行してくれる認証局になる。 ただ、DV証明書しか発行できなかったり、証明書の期限が90日だったりと制限はあったりする。とはいえ、34800円がタダになるのはすごい。BASEなどのサービスはユーザーの代わりにLet's Encryptに申請して証明書を取得している。ことになる。

ACMEプロトコル

とはいっても従来の認証局のような手続きを1ユーザー、1ユーザーに対して行うのは骨が折れる。それを解決する為に策定が進められているのがACMEプロトコル、Automatic Certificate Management Environmentの略で証明書の自動発行、管理の方法を取り決めている。 Let's Encryptはこれに従いつつ自動で証明書を発行できるサーバーになる。そのサーバーはOSSで開発されている( https://github.com/letsencrypt/boulder ) RFCgithubで議論されてたりする。 ( https://github.com/ietf-wg-acme/acme )

雑にACMEプロトコルを説明すると以下になる

  • ユーザー登録
  • 申請
  • チャレンジ
  • 証明書取得

ユーザー登録し、このドメインの証明書をくれー。ってお願いして、チャレンジと呼ばれるドメインを所持していることを証明する作業を行い。証明書発行側でそれを確認できたら証明書を発行できるようになる。 これをプログラムで自動に行うことでユーザーが使用する証明書を取得する。

ACMEプロトコルの詳細は気が向いたら書く。

Railsで暗号化するときのメモ的なアレ

背景

ログインに使用するパスワードは不可逆な暗号化を行って、DBに保存にするのは一般的。 それとは別でDBに保存しておきたいんだけど普通に生データとして持つとセキュリティ事故でデータが漏れた場合にそのまま見えてしまう困るようなデータを可逆な暗号化で保存しておきたい。データが漏れないようにセキュリティを強化すると共に、万一データが漏れた場合に容易に流出しないように2重の対策をすることが目的。

ActiveSupport::MessageEncryptor

ActiveSupport::MessageEncryptorを使う。 http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html

RubyのOpenSSL::Cipherのラッパーだと思う。 https://docs.ruby-lang.org/ja/latest/class/OpenSSL=3a=3aCipher.html

デフォルトの暗号化方式として aes-256-cbc が使用される。 暗号化方式については詳しくないのでそこの説明はできない。後日ちゃんと学びたい。

サンプルにあるように、以下のように ActiveSupport::KeyGenerator を使用して暗号化に使用する鍵を生成する

salt  = SecureRandom.random_bytes(64)
key   = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..."

ユーザーのデータを暗号化する場合は、saltはユーザーごとに生成して保存しておく。saltは漏れても問題がないデータになる。 パスワードは一意の情報を用意するがこれは漏れてはいけない。環境変数などでアプリ実行時に渡す形にする必要がある。

その後は、その鍵から ActiveSupport::MessageEncryptor のオブジェクトを生成して暗号化、復号化を行う。

この処理はそれなりに重い処理になる為、頻繁に行うと辛いことになる。

いまの自分にわかってるのはこの程度なのでメモレベル

ECS環境構築時のポイントをまとめておく

また作るときにハマると辛いからね。

SSHして見に行ってもいいんだけど、入らなくても状況がわかるようにしておくと楽。

大前提

すべてのdockerイメージで実行するプロセスのログはstdout,srderrに出しておく。 普通のアプリケーションだとログファイルに出力してfluentdとかで収集して、S3に保存しておいたりするんだけど、dockerでは標準出力以外は拾いません。 なので、nginxとかunicornとかのログはstdoutに設定しておきます。

これは普段開発する時も同じようなコンテナ構成にして、普段から標準出力で開発するようにしておくといい。

awslogsログドライバーを最初から設定する

こちらです。

これを設定すると、cloudwatch logsにdockerのログを送ってくれます ここに送られる内容はdockerコンテナに対して、docker logs コンテナ名 or コンテナIDのコマンドを叩いたときに出力されるものと同じです。

awslogsを簡単に見れるようにしておく

https://github.com/jorgebastida/awslogs awslogsコマンドを使うと、指定し cloudwatch logsのログをtailする感じで見れる。

ここまで設定しておけば、

ECSクラスターにコンテナを配置しているんだけど、何故かすぐに停止する。 ヘルスチェックが通らないというトラブル時にも対処が簡単になる。

ホストOSを通して通信できるようにする

コンテナをたてるEC2インスタンスにイカを設定しておく。 たぶん、デフォルトで設定されているはずなんだけどいつからかdockerコンテナから外へ出れなくなっていたので、一応設定しておく。

sysctl -w net.ipv4.ip_forward=1

fs.inotify.max_user_watchesを増やしておく。

inotifyの監視対象ファイル数の上限を上げる(Rails起動時に足りなくなる場合がある)

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

コンテナインスタンスで、dockerが使用するポートを全て開けておく

32768 ~ 61000番です。 ここにのってる。

CircleCI2.0 の Workflow を使っているよ

いまいちやる気が出ない時は一度立ち止まってブログを書きます。

Workflow

こちらですね。 ジョブを複数定義し、ジョブごとに依存を定義できる。

今までのCircleCI

いままでのビルドは1フローのみでした。 A-B-C-D というイメージ。 だけど実際はジョブが1つしか定義出来なかった(出来るけど使用するには自分でAPIを呼ぶ必要があった。)ので、A1-A2-A3-A4 みたいな感じだった。

A4はA1に依存してるかもしれないけど、A3には依存していない。とか、 buildとdeployは明確に分けることが出来るんだけど、deployにもいくつかdeploy対象があって、それらは並列でいいとか、あるけど必ず順次実行だった。

具体的にどう使っているか

CIのdeploy対象としては以下の2種類がある。

  • assets(js,css,image)
  • dockerイメージ

dockerイメージも複数種類があったりして、直列に処理をしていると結構な時間がかかる。 以下のような workflow にしている。

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - app_deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - master
      - assets_deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - master
      - build_node_app:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - master
      - node_app_deploy:
          requires:
            - build_node_app
          filters:
            branches:
              only:
                - develop
                - master

まず build ジョブが走る。実際内容は rspec の実行なんだけど、たぶん buildっていうジョブ名が固定?

それが成功すると、各deployジョブが走る。

app_deploy はRailsのdockerイメージをbuildしてECRにpushしている。

assets_deployは、webpackのbuildを行ってその成果物をS3にアップしている。

build_node_appはexpress用のjsをwebpackでbuildしている。

node_app_deployはbuild_node_appの成果物を受け取って、dockerイメージをbuildしてECRにpushしている。

ここを少し気をつける必要があるのは、各job自体がdockerで動いているということ。 そのdockerイメージは自分で指定するので、自分が行いたいbuild,deployが出来るdockerイメージを用意する必要がある。

app_deployの部分は以前に書いた、multi stage buildなのでdockerではなくmachineビルドになっている。

assets_deployは、webpackのbuildを行うので、nodejs, yarn, そしてS3コマンドを使うため、aws cliが入っているdockerイメージを用意している。

build_node_appは、assets_deployと同じでいい。nodejs, yarnがあるイメージ

node_app_deployは、dockerが入っているdockerが必要になる。そして、ECRにpushするので、aws cliが必要。 あと、build_node_appからartifact経由で成果物を受け取るときにtarコマンドが必要なのでそれも入れておく。

dockerが入っているdockerってなんやねん。って思うかもしれないけど、公式に用意されている。 イカのようなDockerfileからdockerイメージをbuildしてdocker hubとかに置いておく。 何も見られて困るようなものは入ってないので、普通にpublicで良い。

FROM docker:17.05.0-ce

RUN apk update && \
    apk add \
    ca-certificates \
    git \
    openssl \
    zip \
    unzip \
    wget \
    python \
    py-pip \
    py-setuptools \
    groff \
    less \
    tar && \
    pip install awscli

という感じで、色々と準備は必要なものの、CircleCI2.0より前の場合は、 machineビルドで、用意されたVMにその場で色々インストールして、buildしてdeployしていた。 それに比べると、純粋にbuildとdeploy以外は事前に済ませておけるので実行時間は早くなる。

つまり docker は最高。