社内で手軽にHTTPSを使いたい!kintone × nginxで圧倒的生産性向上!

こんにちは。生産性向上チームの宮田(@miyajan)です。

サイボウズには、生産性向上チームという、組織横断で使える開発基盤を整備するためのチームが存在します。「チーム」となっていますが、今のところ私一人です。

今回は、弊社製品のkintoneとオープンソースのnginxによるリバースプロクシを組み合わせて、社内のエンジニアが手軽にHTTPSを使えるようにした話を書きます。

モチベーション

開発者が手元の開発環境でHTTPSを使いたい場面というのは、意外とたくさんあります。以下は、実際に社内であったケースです。

  • HTTPSでのみ発生する問題を手元で再現させたい
  • 社内システムをHTTPSで運用する必要がある
    • 例1) SAML認証でSSOするためにHTTPSが必須
    • 例2) Service Workerを利用するためにHTTPSが必須
    • 例3) 社内でDocker Registryを運用しているが、HTTPだと利用者全員に--insecure-registryというオプションを設定してもらう必要がある
    • 例4) iOSアプリ開発でHTTPSによる接続を要求される

自分で証明書を作成する、いわゆるオレオレ証明書で運用する手もあるのですが、信頼されないオレオレ証明書では警告や例外設定といった問題が発生しがちです。また、開発者が個別にそういった作業を行うのは手間なので、もっと手軽にHTTPSを使えるようにしたいと考えました。

そこで、ドメインとワイルドカード証明書を会社で購入し、社内の開発者が手軽にHTTPSを使えるシステムを構築することにしました。

構成

ここからは、開発用のドメインをdev.cybozu.xyzとし、*.dev.cybozu.xyzのワイルドカード証明書を持っているとします。

f:id:cybozuinsideout:20160215150346p:plain

構成としては、上の図のように証明書を配置したリバースプロクシを用意し、クライアントから*.dev.cybozu.xyzへのリクエストはリバースプロクシを経由してサーバーに送られるようにします。クライアント⇔リバースプロクシ間の通信はHTTPSとなりますが、リバースプロクシ⇔サーバー間の通信はHTTPのままで問題ありません。なので、なんらかのサーバーを立てる開発者側はHTTPSまわりの設定はなにもしなくてよくなります。必要なのは、リバースプロクシにドメイン名とIPアドレス+ポート番号の対応を設定するだけです。

f:id:cybozuinsideout:20160215150356p:plain

リバースプロクシの設定が大変だと意味がないので、開発者は上の図のようにkintoneに登録するだけにします。cronで実行されるスクリプトが定期的にkintoneからデータを取得し、DNSとリバースプロクシの設定変更を行います。DNSには登録されたドメインがリバースプロクシを指すように設定し、リバースプロクシには登録されたドメインが指定のアドレスに転送されるように設定します。

kintone

今回のシステムでkintoneの役割は以下になります。

  • データの蓄積
  • データ登録用フォームをGUIで提供
  • 他システムがデータを取得するためのAPIを提供

これらの部分を自作するとなると、かなりの工数が必要となってしまいます。このような連携込みの基幹システムがお手軽簡単に構築できてしまうところが、kintoneの素晴らしい点です(ステマ)。

kintoneだと、以下のようなフォームを持つ業務アプリが、慣れれば3分ほどで作成できます。

f:id:cybozuinsideout:20160215154239p:plain

DNS

DNSは、PowerDNSを使っています。PowerDNSにはREST APIがあるので、連携しやすいと考えたのが理由です。とはいえ、直接ファイルシステムを書き換えるとかでも正直問題ないので、どのDNSソフトを使っても問題ないでしょう。

DNSがあるマシンではcronで毎分スクリプトが実行され、kintoneからHTTPS化したいドメインを取得してきて、PowerDNSが参照するMySQLにリバースプロクシを指すように設定します。

あとは、社内のDNSサーバーから、dev.cybozu.xyzの名前解決を転送するように設定してもらえばOKです。

リバースプロクシ

リバースプロクシは、nginx+lua-nginx-moduleredisの組み合わせで実現しています。nginxの設定ファイルは以下のようになります。

worker_processes  auto;

# redisの接続先を環境変数から取得
env REDIS_ADDR;
env REDIS_PORT;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  *.dev.cybozu.xyz;
        return 301 https://$host$request_uri;
    }

    server {
        listen       443 ssl;
        server_name  *.dev.cybozu.xyz;

        ssl_certificate /etc/nginx/crt/nginx.crt;
        ssl_certificate_key /etc/nginx/crt/nginx.key;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA;
        ssl_prefer_server_ciphers on;

        add_header        Strict-Transport-Security 'max-age=31536000; includeSubDomains';
        proxy_hide_header Strict-Transport-Security;

        client_max_body_size 0;

        location / {
            set $upstream "";

            # luaでredisからプロクシ先を取得する
            rewrite_by_lua '
                local redis = require "redis"
                local client = redis.connect(os.getenv("REDIS_ADDR"), tonumber(os.getenv("REDIS_PORT")))
                route = client:get(ngx.var.http_host)

                if route ~= nil then
                    ngx.var.upstream = route
                else
                    ngx.log(ngx.ERR, "Host not found: ", ngx.var.http_host)
                    ngx.exit(ngx.HTTP_NOT_FOUND)
                end
            ';

            proxy_set_header    Host $host;
            proxy_set_header    HTTPS on;
            proxy_set_header    Forwarded "proto=https; for=$remote_addr; host=$host";
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header    X-Forwarded-Proto $scheme;
            proxy_set_header    X-Forwarded-Host $host;
            proxy_set_header    X-Real-IP $remote_addr;
            proxy_set_header    Connection "";

            proxy_redirect      http:// https://;
            proxy_pass          http://$upstream;

            proxy_http_version 1.1;

            proxy_buffering off;
            proxy_request_buffering off;
        }
    }

}

rewrite_by_luaの部分でプロクシ先をredisから取得して動的に設定しています。

nginxがあるマシンでもcronで毎分スクリプトが実行され、kintoneからドメインと対応するプロクシ先を取得してきて、nginxが参照するredisに登録します。

まとめ

社内の開発者が手軽にHTTPSを使えるように、kintoneとnginxで構築したシステムについて説明させていただきました。

このシステムを今年初めに社内公開したところ、現時点で10人以上の開発者に活用していただいています。想定していたよりも社内でHTTPSを利用する機会は多かったようです。

ちなみに、独自CAを運用する方法もありますが、ワイルドカード証明書には証明書発行や利用者のブラウザに証明書を登録する手間が不要であるメリットがあります。

このように、生産性向上チームでは、社内の開発者が本質的な開発に集中できるようにするための活動を行っています。あまり一般的な職種ではないかもしれませんが、組織を横断して生産性を向上させることにより、よりお客様への価値提供を促進していくことがミッションです。

今回この記事を書いたことによって世の中の生産性向上へもつながれば幸いです。