#SORACOM BeamでgRPCアプリのトラフィックをTLS暗号化する

2016.09.29

ども、大瀧です。
先日GAを迎えたGoogle製RPCフレームワークgRPCはマイクロサービスやモバイルアプリの文脈で紹介されることが多いですが、IoT用途でも使えないかなぁと思い立ち、SORACOM Beamとの組み合わせを考えてみました。

IoTでgRPCを利用するメリット

IoTシステムの開発では、センサーやIoTゲートウェイ(クライアントサイド)を開発する組み込みエンジニアとクラウド(サーバーサイド)を開発するWebエンジニアではスキルセットが異なることがままあると思います。クラウドとの通信はRESTful APIを利用することが多く、Web技術に明るくない組み込みエンジニアにとってHTTPのステートレスなAPIは敷居が高く感じられるかもしれません。

gRPCはWeb技術の中でも新しい部類で、仕組みとしてはHTTP/2を利用しますが、プログラミング言語に依存しないクライアント/サーバー間のインターフェース定義や複数の言語に対応するコードジェネレータがあり、通信部分の実装の多くをgRPCに任せることができます。また、認証やステートフル接続などgRPCが独自に提供する機能も豊富にあるので、エンジニアがセンサーデータの整形やアルゴリズムのコーディングに集中できるとも言えるでしょう。

SORACOM Beamの活用

gRPCは通信プロトコルとしてHTTP/2を採用し、TLSによる暗号化を前提としています。TLSは接続時の処理にCPUリソースを多く消費するため、頻繁に接続/切断する用途の場合、IoTゲートウェイ(gRPCクライアント)のリソースを多く使ってしまう恐れがあります。

SORACOM Beamは、3G/LTEモバイル網を利用するSORACOM Air SIMからのトラフィックに暗号化やヘッダ付与などの処理を施してサーバーに転送するプロキシサービスです。今回は、gRPCクライアント(暗号化なし)からの通信をBeamで暗号化し、TLSでListenしているgRPCサーバーに送信する構成をやってみます。

beam-grpc01_1

以前紹介した以下の記事と同様、インターネットを経由する箇所をセキュアにデータ転送させるための構成です。

検証環境

  • クライアント : Macbook / macOS Sierra
  • サーバー : Amazon EC2 / Amazon Linux 2016.09
  • gRPC : grpc-go バージョン1.0.1-GA

検証手順

1. gRPCサーバーの構成

まずはEC2でgRPCのサンプルプログラムをビルド、実行します。今回はTLSでListenするので、サーバー証明書一式をCertbot(Let's EncryptのCLIツール)で取得しておき、以下のように実装しました。ハイライト行が変更・追加した部分です。

greeter_server/main.go

import (
	"log"
	"net"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"

	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	port = ":50051"

	certFile = "./greeter_server/server.crt"
	keyFile  = "./greeter_server/server.key"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	//s := grpc.NewServer()
	var opts []grpc.ServerOption
	creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
	if err != nil {
		grpclog.Fatalf("Failed to generate credentials %v", err)
	}
	opts = []grpc.ServerOption{grpc.Creds(creds)}
	s := grpc.NewServer(opts...)

	pb.RegisterGreeterServer(s, &server{})
	s.Serve(lis)
}
$ sudo yum install -y go
  :(略)
$ export GOPATH=~/.go
$ go get golang.org/x/net/context google.golang.org/grpc
$ cd .go/src/google.golang.org/grpc/examples/helloworld/
$ cp ~/{server.crt,server.key} greeter_server/ # あらかじめ作成した証明書をコピー
$ go build -o greeterd greeter_server/main.go
$ ./greeterd > greeterd.log 2>&1 &
[1] 32077
$ sudo netstat -ltnp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp        0      0 0.0.0.0:111                 0.0.0.0:*                   LISTEN      2278/rpcbind
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      2497/sshd
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN      2527/sendmail
tcp        0      0 0.0.0.0:35620               0.0.0.0:*                   LISTEN      2299/rpc.statd
tcp        0      0 :::40334                    :::*                        LISTEN      2299/rpc.statd
tcp        0      0 :::111                      :::*                        LISTEN      2278/rpcbind
tcp        0      0 :::22                       :::*                        LISTEN      2497/sshd
tcp        0      0 :::50051                    :::*                        LISTEN      32077/./greeterd
$

50051番ポートをgreeterdがListenしていればOKです。作成した証明書のホスト名に合わせて、EC2インスタンスのPublic IP(もしくはElastic IP)にDNSのAレコードを向けておきましょう。

2. SORACOM Beamの構成

続いて、使用するAir SIMが所属するグループをSORACOMユーザーコンソールで作成し、Beamの[TCP→TCP/TCPSエントリーポイント]を追加します。[転送先]設定に、[プロトコル]は「TCP(over TLS)」、[ホスト名]をDNSで登録したホスト名、[ポート番号]はサーバーがListenする「50051」をセットします。

beam-grpc02

クライアントの接続先は固定なので[エントリポイント]の各項目をメモしておきましょう。

3. gRPCクライアントの構成

サーバーと同じく、今回はGo版gRPCのサンプルプログラムを利用します。こちらは、接続先を上記のエントリポイントに設定するだけです。

greeter_client/main.go

package main

import (
	"log"
	"os"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	address = "beam.soracom.io:8023"
	defaultName = "world"
)

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)
}

では、実行してみましょう。

$ go run greeter_client/main.go
2016/09/29 00:24:57 Greeting: Hello world
$

gRPCサーバーから正常にレスポンスが返って来ました!

おまけ: ELBの組み合わせ

gRPCサーバーとELBの組み合わせをこちらの記事で紹介しましたが、Classic Load BalancerをL4転送として利用しているため、今回のTLS通信も転送することが可能です。DNSをELBに向けてTCP:50051TCP:50051で転送設定しましょう。ELBのSSL TerminationはClassic Load BalancerがTLSのALPN拡張をサポートしないため不可、ということに注意しましょう。

まとめ

gRPCの変わったユースケースとして、IoT向けにSORACOM Beamとの組み合わせをご紹介しました。SORACOM BeamのTCPSエントリポイントがクライアント証明書をサポートするとgRPCの双方向認証に対応できそうなので、ちょっと期待したいところです。

参考URL