gRPCでTLS認証して通信する
本記事の全ソースはこちら
gRPCクライアント側の簡単な実装では、通信開始時に grpc.WithInsecure() という引数を渡していましたが、これは「認証をせずに接続する」という指定のため、実運用向きではありません。
ローカルの環境でとりあえず試したいという場合は有効です。
# 抜粋
// client.go
conn, err := grpc.Dial(addr, grpc.WithInsecure())
TLS認証と暗号化
gRPC では標準で⽤意されている認証の⽅式があります。それが TLS(TransportLayer Security) です。TLS は Web の世界では広く使われている認証⽅式で、⼤きく次のふたつの機能を持ちます。
• 認証: 通信相⼿が想定通りの相⼿であることを確認する
• 暗号化: 通信内容を第三者に盗聴されても読めないようにする
TLSを使ったgRPC通信でHelloWorldしてみる
gRPCチュートリアルで作成したGreeter に TLS を組み込んでいきます。TLS は通信相⼿が想定通りであることを確かにするため、証明書 (Certificate) を使うしくみとなっています。
本来は信頼されている証明書を使って通信するのですが、検証なのでgRPC 証明書を⾃作してサーバー・クライアント両⽅に設置します。
証明書の作成
証明書を作るツールとして OpenSSL を使います。
$ brew install openssl
$ openssl version
LibreSSL 2.8.3
実際に TLS 認証のための証明書を作っていきます。作業は Greeter のサー
バー側のコードがある場所で⾏います。
$ cd server
証明書は通常いくつかのステップを踏んで作っていく形となります。
• 秘密鍵をつくる
• 秘密鍵を元に署名要求 (CSR) をつくる
• 秘密鍵を使って署名を⾏って、証明書をつくる
opensslで作成する場合は以下の手順になります
・openssl genrsa コマンドで秘密鍵をつくる
• openssl req コマンドで署名要求 (CSR) をつくる
• openssl x509 コマンドで秘密鍵を使って署名を⾏って、証明書をつくる
具体的なコマンドについては以下を参照してserver.crt,server.keyファイルを作成してください
- gRPC公式
- TLS、React,Golangでの gRPC使用
生成ファイルはファイルは以下になります
• 秘密鍵ファイル server.key
• 署名要求 (CSR) ファイル server.csr
• 証明書ファイル server.crt
サーバー側がこの証明書を使って、任意のクライアントと TLS で通信するためには、クライアント側からサーバーの証明書を認識できなければなりません。
よって、サーバーの証明書 server.crt をクライアント側にもコピーします。
$ cp server.crt ../client
重要なポイントは、秘密鍵はサーバー内で秘密にしておくことと、証明書はクライアント側にも教える必要があるということの 2 点です。
結果以下のような構成になります。
server
├── server.key
├── server.crt
├── server.csr
└── server.go
client
├── client.go
└── server.crt
gRPCサーバのコード修正
credentials.NewServerTLSFromFile(...) で秘密鍵と証明書から認証情報を作り、grpc.NewServer() に渡して TLS 認証がついた状態でサーバーを起動していることがわかります。
//server/server.go (+TLS)
package main
import (
"context"
"log"
"net"
"time"
pb "github.com/leslesnoa/grpcdemo01/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// ---service----
type GreeterService struct {
}
func (s *GreeterService) SayHello(ctx context.Context, message *pb.HelloRequest) (*pb.HelloReply, error) {
log.Println(message)
log.Printf("Received: %v", message.Name)
time.Sleep(3 * time.Second)
return &pb.HelloReply{Message: "Hello " + message.Name}, nil
}
// -------------
func main() {
addr := ":50051"
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// -------TLS認証処理を追加-------
cred, err := credentials.NewClientTLSFromFile("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer(grpc.Creds(cred))
// -----------------------------
// s := grpc.NewServer()
pb.RegisterGreeterServer(s, &GreeterService{})
log.Printf("gRPC server listening on " + addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gRPCクライアント側のソースを修正
今まではgrpc.WithInsecure() で認証をしなかったところを
grpc.WithTransportCredentials()に変更していおり、認証時にサーバの証明書を読み込むように修正していきます。
# client/client.go
package main
import (
"context"
"log"
"os"
"github.com/leslesnoa/grpcdemo01/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func main() {
addr := "localhost:50051"
// TLS認証を追加
creds, err := credentials.NewClientTLSFromFile("server.crt", "")
if err != nil {
log.Fatal(err)
}
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
// conn, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := os.Args[1]
ctx := context.Background()
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// go func() {
// time.Sleep(1 * time.Second)
// cancel()
// }()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
TLS通信してみる
$ cd server/
$ go run server.go
2019/08/04 16:49:41 gRPC server listening on :50051
$ cd client/
$ go run client.go world
2019/08/04 16:49:46 Greeting: Hello world
TLSで通信することができました。