gRPC over SSL/TLS with ClientCertification
gRPCを利用したサービスを作ることになったので、その際に調べた認証・認可周りのメモ
キーワード:grpc, grpcs, client認証
gRPCにおける認証方法
gRPCの認証は公式サイトによると以下の方式がビルトインされてるらしい
また、認証情報は以下の方式で設定できる
- Channel credential
- SSL証明書などのように
gRPC Channel
に紐付ける方式
- SSL証明書などのように
- Call credential
- Request時にClientContextに対して設定する方式
今回はgRPCServiceをグローバルに公開する予定があったのと通信を暗号化したかったので、クライアントに自己署名証明書(通称、オレオレ証明書)を払い出して、その証明書のCommonNameで認可を行う SSL/TLS + Channel credential
の方式で試してみた。
[利用方法のイメージ] +------- Internet ---------+ +---------+ | | | Client | -------(gRPC over SSL/TLS)---------> [gRPCService] +---------+ | ( with ClientCert ) | :443 +--------------------------+
準備
オレオレ認証局
クライアントに払い出す証明書を生成する必要があるので、オレオレ認証局を オレオレ証明書の発行はこちらのQiita記事を参考にキーの出力フォーマットをなどを調整したものをGithubに上げた
https://github.com/tmarcus87/grpc-training-self-signed-ca
クライアントコード
Serverが1台の場合
以下のような感じでSSLで待ち受けるサーバを実装して起動すると動くはず
Serverが複数台の場合
公式のドキュメントによるとLoadBalancingは以下の3種の方法が提案されている
- Proxy方式
- 負荷分散に対応したClientを利用する方式 GoとJavaだとそういう対応クライアントがあるらしい(未検証)
- 外部のLoadBalcningServiceを利用する方式
- ちゃんと読んでないけど、EtcdとかZooKeeperとかEurekaとか使ってDNS応答を変化させてやる方式?
Proxy以外の方式では1IP:1Serverである必要があるっぽい?
Proxy方式でnginxを利用する場合以下の選択肢がある
- L4 load balancing
- L7 load balancing
// todo Plain接続の場合のアプリ側のコードを書く
// todo L4 Load balancingの場合のnginx.confを貼り付ける
L7 Load balancingの場合のnginx.confは以下の通り
[nginxの設定] $ cat /etc/nginx/nginx.conf user nginx; worker_processes 1; error_log /var/log/nginx/error.log debug; pid /var/run/nginx.pid; events { worker_connections 1024; } http { upstream grpc_servers { server grpc_server1:5000; server grpc_server2:5000; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 443 ssl http2; ssl_certificate /opt/server/dev-gateway-api.example.com.crt; ssl_certificate_key /opt/server/dev-gateway-api.example.com.pem; ssl_client_certificate /opt/ca/ca.crt; ssl_verify_client on; proxy_set_header ssl-client-cert $ssl_client_escaped_cert; proxy_set_header ssl-client-verify $ssl_client_verify; proxy_set_header ssl-client-subject-dn $ssl_client_s_dn; proxy_set_header ssl-client-issuer-dn $ssl_client_i_dn; grpc_set_header ssl-client-cert $ssl_client_escaped_cert; grpc_set_header ssl-client-verify $ssl_client_verify; grpc_set_header ssl-client-subject-dn $ssl_client_s_dn; grpc_set_header ssl-client-issuer-dn $ssl_client_i_dn; location /error502grpc { internal; default_type application/grpc; add_header grpc-status 14; add_header grpc-message "unavailable"; return 204; } location / { grpc_pass grpc://grpc_servers; error_page 502 = /error502grpc; } } }
サーバ側で以下のデータがmetadataから取れる
こんな感じの関数をUnaryServerInterceptorに差し込めば func getMetaFromHeader(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { if md, ok := metadata.FromIncomingContext(ctx); ok { json, _ := json.Marshal(md) fmt.Println(string(json)) } return handler(ctx, req) } こんなデータが取れるはず { ":authority": [ "grpc_servers" ], "content-type": [ "application/grpc" ], "ssl-client-cert": [ "-----BEGIN%20CERTIFICATE-----なんやかんや証明書の中身%0A-----END%20CERTIFICATE-----%0A" ], "ssl-client-issuer-dn": [ "emailAddress=grpc@example.com,CN=gateway-gateway.example.com,OU=DevelopmentHQ,O=Hoge\\,Inc.,L=Sumida-ku,ST=Tokyo,C=JP" ], "ssl-client-subject-dn": [ "emailAddress=service1@example.com,CN=service1,OU=DevelopmentHQ,O=Hoge\\,Inc.,L=Sumida-ku,ST=Tokyo,C=JP" ], "ssl-client-verify": [ "SUCCESS" ], "user-agent": [ "grpc-go/1.15.0" ] }