mrubyでOpenSSLを利用し、SSL証明書を作成管理する

同僚である@matsumotoryのFastCertificateの実装について、ngx_mrubyからシェルスクリプトで構築されたコマンドであるdehydratedを利用して実装されているのですが、この場合、mrubyから証明書取得のステータスが管理しづらかったり、どうせならmrubyで全てコントロールしたいよねという話から、mruby-acme-clientを書きました。

中身の実装としてはCRubyのunixcharles/acme-clientとほぼ同じものなのですが、mrubyにはまだOpenSSLのバインディング実装がなかったことから、この機会に必要なものはCRubyの実装をCloneしました。

中にはmrubyに未実装なCRubyのメソッドなどがruby/opensslで利用されていたりして、苦労しましたがCRubyそのものの実装を読んだりしながら進めました。副次的な効果として、OpenSSLがどのようなバインディングで利用されているか、CRubyの細かい実装を読むことができたり、深く潜るきっかけになったなと思います。

証明書は下記のようなコードでLetsEncryptを利用して取得が可能です。

private_key = OpenSSL::PKey::RSA.new(4096)
endpoint = "http://127.0.0.1:4000/"
client = Acme::Client.new(
  private_key,
  endpoint,
  { request: { open_timeout: 5, timeout: 5 } }
)

# クライアントの登録
registration = client.register('mailto:contact@example.org')
registration.agree_terms

domains = %w(eample.org www.example.org)

# ドメインの保持を証明する認証
domains.each do |n|
  authorization = client.authorize(n)

  challenge = authorization.http01
  challenge.put_content '/home/xs549470/ten-snapon.com/public_html'

  challenge = client.fetch_authorization(authorization.uri).http01
  challenge.request_verification # => true
  puts challenge.authorization.verify_status # => 'pending'

  sleep(1)
  puts challenge.authorization.verify_status # => 'valid'
end

# 証明書の作成
csr = Acme::Client::CertificateRequest.new(domains)
certificate = client.new_certificate(csr)
{
  'privkey.pem' => certificate.request.private_key.to_pem,
  "cert.pem" => certificate.to_pem,
  "chain.pem" => certificate.chain_to_pem,
  "fullchain.pem" => certificate.fullchain_to_pem
}.each do |k,v|
  File.open(k, 'w'){|fp|
    fp.puts v
  }
end

開発中においてはletsencrypt/boulderを利用し、docker-composeでローカルにLetsEncrypt側のAPIを起動(127.0.0.1:4000)して開発を進めました。これを使うと、デバッグが非常にはかどりました。内容としてはdocker-composeの出来がよく、起動ごとに毎回Golangで書かれたAPIをコンパイルして起動してくれるので、簡単にAPI側にデバッグログを追加できたりして、どこの部分が失敗してるのかを追いやすい仕組みになっています。

mrubyにこの実装ができたことで、今後FactCertificateのようなことはもちろんのこと、例えば内部用のシステムだけど、LetsEncryptの証明書を使いたい、だけど認証のためだけにSGやiptablesを開けるのはなぁ・・・・といった漠然とした不安に対してもngx_mrubyでコントロールしつつ証明書の作成、更新を行うことが可能になります。

最後に

OpenSSLの実装がすごい量で大変であることを聞きながら、同僚にやると言ってしまった手前、何とかモチベーションを保ちつつも進めることができましたが、今回僕が実装しただけでも結構な量で本当にコンピューター言語のメンテナって大変なんだなぁと思いました。僕らが何気なく使っているライブラリも日々誰かの時間で成り立っていることを改めて感じたので、今よりもっと言語コミュニティに貢献していかねばという強い気持ちを持った。