KotlinでgRPC。grpc-javaのTLS with JDKとTLS with OpenSSLの使い方をまとめた。

grpc-java/SECURITY.mdを読み進めるとTLSを有効にしたgRPCサーバの起動には2つのプロトコルプロバイダーを選択できる。 JDKOpenSSLがその2つである。それぞれを有効にする方法は異なりドキュメントも豊富ではない。この機会にまとめていきたいというのが今回のモチベーション。

github.com

TLS with JDK

まずはJDKからみていきたい。grpc-java/SECURITY.mdにも記載があるとおり、この方式は推奨されていない。 JavaTLS実装にALPNがサポートされるのはJava9からでありJava8(Java < 8)を利用している場合は、JVMオプションにjavaagentを加える。加えたjavaagentjetty-alpn-agentを指定する。 起動までの流れとしては次ようになる。

mkdir -p /var/lib
wget -q -O /var/lib/jetty-alpn-agent.jar https://repo.maven.apache.org/maven2/org/mortbay/jetty/alpn/jetty-alpn-agent/2.0.6/jetty-alpn-agent-2.0.6.jar

java $JAVA_OPTS -javaagent:/var/lib/jetty-alpn-agent.jar -jar /usr/local/your-project/lib/app-name.jar

これでTLS with JDKで起動できる。

TLS with OpenSSL

次にOpenSSLである。grpc-java/SECURITY.mdにも記載があるとおりstaticなnetty-tcnativeを使わない場合はOpenSSL version >= 1.0.2Apache APR library (libapr-1) version >= 1.5.2.が必要になる。 DockerコンテナでJVMを起動する場合は事前にインストールをしておく。

apt-get install -y ca-certificates openssl libc6 libapr1

libc6はprotoBufferを生成する際に必要となる。コンテナ起動時にビルドのタスクに.protoからprotoBufferを生成している。

OpenSSLのハマりポイントがありalpineをコンテナイメージのベースにしていたが上手く起動できなかった。ubuntuベースにするとサクッと起動した。 alpineで起動すると次のようなエラーに遭遇する。netty-tcnativeを使うためにalpineで何かが足りていないのだろう。今後深追いしていきたい。

Caused by: java.lang.UnsatisfiedLinkError: /tmp/libnetty-tcnative3270913869898400045.so: Error relocating /tmp/libnetty-tcnative3270913869898400045.so: SSL_add0_chain_cert: symbol not found
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
        at java.lang.Runtime.load0(Runtime.java:809)
        at java.lang.System.load(System.java:1086)
        at io.netty.util.internal.NativeLibraryUtil.loadLibrary(NativeLibraryUtil.java:36)
        ... 21 more

ubutuを許容できるのであればubuntuベースでOpenSSLを利用するのがよさそう。

最後に必要なnetty-tcnativeはドキュメントのGetting netty-tcnative from Gradleが参考になる。

これでTLS with OpenSSLが起動できる。

JDKとOpenSSLを有効にしたDockerfileをまとめた

ここまでJDKOpenSSLについてまとめてきたがコードの断片しかなく参考にならないため、それぞれのDockerfileと動作確認環境をシュッと起動できるdocker-composeをまとめたので詳細はgithubを参照してほしい。

github.com

READMEに動作確認手順をまとめているが、docker-composeでJDKとOpenSSLが有効になったgRPCサーバを起ち上げ、gRPCクライアントを含むアプリの起動時にサーバの環境変数を切り替えている。

ここまでgrpc-javaについて触れていたけどgithubに置いているコードはkotlinで書いている。適宜javaに置き換えて参照いただきたい。

grpc-javaはそこまでドキュメントが豊富とは言いづらい。積極的にアウトプットしていきたい。

PagerDutyとAsanaをzapierをつかって連携させてみた

zapierをつかってPagerDutyとAsanaを連携させる方法をまとめます。

zapier.com

モチベーション

PagerDutyのIncidentをSlackに通知をして、その対応をAsanaでタスク化して運用をするうえで Asanaのタスク化を自動化させたい。そんなときzaiperは zap という単位でAサービスで発生したイベントをトリガーにBサービスにアクションを行うという一連の運用フローを自動化することができる。

PagerDutyでIncidentが新しく作られたら Asanaでタスクを作成する、このような運用フローがzaiperのダッシュボードからポチポチと設定するだけで自動化できる。

連携させてみた

f:id:n_soushi:20170426093701p:plain

zaiperのダッシュボードは直感的で設定フローの理解もすぐにできた。
PagerDutyをトリガーにAsanaでタスクを作るというフローだけど、他にもステップを追加することができる。例えばGoogleCalendarにイベントを追加したりgithubでissue化したり認証できるサービスであれば多様なステップを作ることができる。

ここからはzaiperの設定フローで補足したいところをまとめていく。

設定フローの補足

PagerDutyの設定

  • Incidentの発生条件はオプションで設定できる。
    • Incidentが誰にアサインされたかを指定したり、どのイベントタイプをトリガーにするか指定できる。
    • 今回は誰にアサインされたかは指定せず、イベントタイプはTriggerとした。つまり認知していない新規のIncidentの発生がトリガーとなる。
  • zaiperはPagerDutyのイベントを検知するためにEndpoinURLを生成するのでPagerDuty側でそのURLをWebhookに追加する必要がある。

Asanaの設定

  • 今回はAsanaのタスクを作成するアクションにしたが、StoryProjectなど他のアクションも設定できる。
  • タスクを生成するうえでタスクのタイトルやメモなどをテンプレートとして設定できる。
    • PagerDutyのIncidentから要素を取得することができるので、タイトルにIncidentのServiceNameやメモにIncidentのURLStatusなどを変数としてテンプレート化できる。
    • 今回は次のように設定してみた。 f:id:n_soushi:20170426100129p:plain
  • その他にもタスクを誰にアサインするかなどタスクの要素を細かく設定できる。 f:id:n_soushi:20170426100510p:plain

動かしてみる

あらかじめPagerDutyとAsanaはSlackと連携させているのでIncidentとタスクのイベントに応じてSlackに通知されることが確認できた。

f:id:n_soushi:20170426100917p:plain

実際にPagerDutyでIncidentを発生させてみて、Incidentが発生した通知の後にAsanaのタスクが作成されていることが分かる。

f:id:n_soushi:20170426101532p:plain

AsanaのタスクにはIncidentの情報が参照されていてzaiperで設定したAsanaのテンプレートが適応されていることが確認できる。

まとめ

zaiperをつかって運用フローの自動化を試してみた。zaiperのダッシュボードは使いやすい印象でつくってる方たちも好印象だった。
zaiperのプランはzapとタスクの数でグレードが分かれている。あとはzapierが稼働するのがBasicだと15分毎でBusiness以降は5分毎なので運用要件に合わせたプラン選択が必要だと感じた。

他のzapの組み合わせとしてはGoogleカレンダーにイベントが登録されたのをトリガーにSlackにイベントを通知する連携方法なども試しみたりした。

f:id:n_soushi:20170426161411p:plain

GoogleカレンダーのイベントをトリガーにしてSlackで告知するような運用はよくあるケースなので、まずはこういったところから導入を始めて、運用に合わせてzapを増やしていくとよさそうである。

KotlinでgRPC。SSL/TLSを有効にする方法をまとめた。

前回のエントリではgrpc/grpc-javaをベースにkotlinでgRPCを試しました。今回はSSL/TLSを有効にする方法をまとめていきます。grpc/grpc-java/SECURITY.mdを参照しながら進めました。

証明書を準備する

手元に適当な証明書がなかったのでgrpc-go/testdataにある証明書を利用しました。

Subject Alternative Name を確認するとマルチドメイン*.test.google.fr, waterzooi.test.google.be, *.test.youtube.comが定義されていますので、hostsにwaterzooi.test.google.beを追加しました。

openssl x509 -text -in ./server1.pem
127.0.0.1 waterzooi.test.google.be

OpenSSLを有効にする

netty-tcnativeをプロジェクトに追加します。

buildscript {
  repositories {
    mavenCentral()
  }
}

dependencies {
    compile 'io.netty:netty-tcnative-boringssl-static:1.1.33.Fork26'
}

netty-tcnativeはDynamicStaticがあります。このエントリではStaticを利用しました。grpc/grpc-java/SECURITY.mdを参照するとStaticの利用を推奨していますがOpenSSLのセキュリティパッチが提供された場合、Staticではパッチ反映が即座には行われないためプロジェクト方針によってはDynamicの利用を検討する必要があります。

GrpcSslContextsでSslContextを生成する

サーバ側とクライアント側ではGrpcSslContextsに定義されているforServerforClientのメソッドをつかってSslContextを生成します。

サーバ側

(serverBuilder as NettyServerBuilder).sslContext(
        GrpcSslContexts.forServer(
                File(classLoader.getResource("server1.pem").file),
                File(classLoader.getResource("server1.key").file))
                .clientAuth(ClientAuth.OPTIONAL)
                .build())

クライアント側

NettyChannelBuilder.forAddress("waterzooi.test.google.be", 50051)
        .sslContext(
                GrpcSslContexts.forClient()
                        .trustManager(File(classLoader.getResource("ca.pem").file))
                        .build())
        .build()

上記の実装を行えばSSL/TLSが有効になります。

コードを公開しています

このエントリのコードはgithubに公開しています。
SSL/TLSを有効にするにあたり情報が少ない印象をもちました。java or kotlinで実装をする方に少しでも参考になると嬉しいです。

github.com

関連エントリ

naruto-io.hatenablog.com