client-server

什么是grpc?

grpc是google开发的一个Remote Procedure Call (RPC) framework。

生成TLS Certificate

  1. 生成ca(Certificate Authority)私钥和自签名证书
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 -keyout ca-key.pem -out ca-cert.pem -subj "/C=TR/ST=ASIA/L=ISTANBUL/O=DEV/OU=TUTORIAL/CN=*.hack.com/emailAddress=hack@foxmail.com"

脚本返回结果:

winsun@unbuntu64:~/test/grpc-tls-go/cert$ ./generator.sh
[1] 删除所有pem文件
[2] 生成ca私钥和自签名证书
....+.....+....+..+.+...+..+.............+.........
...................................................
-----
winsun@unbuntu64:~/test/grpc-tls-go/cert$ ll
total 20
drwxrwxr-x 2 winsun winsun 4096  3月 14 11:01 ./
drwxrwxr-x 3 winsun winsun 4096  3月 14 10:51 ../
-rw-rw-r-- 1 winsun winsun 2139  3月 14 11:01 ca-cert.pem
-rw------- 1 winsun winsun 3272  3月 14 11:01 ca-key.pem
-rwxrwxr-x 1 winsun winsun  303  3月 14 11:01 generator.sh*
  • -x509是公钥证书格式,也常被称为数字证书,自己拥有公钥可以进行签名校验
  • -newkey rsa:4096, 同时提供4096比特的ras密钥和证书申请
  • -nodes,不加密private key
  • -days,证书有效期
  • -keyout,创建的密钥存入ca-key.pem
  • -out,证书存入ca-cert.pem
  • -subj 命令解释如下:
/C=CN 国家
/ST=ZHEJIANG 省份
/L=HANGZHOU 本地城市
/O=HACK 组织
/OU=TUTORIAL 组织部门
/CN=*.hack.com 公共名或域名
/emailAddress=hack@tutorial.com email地址
  1. 生成server key和csr(certificate siging request)
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=cn/ST=zhejiang/L=hangzhou/O=DEV/OU=BLOG/CN=*.hack.com/emailAddress=hack@foxmail.com"

server-req.pem并非证书,只是证书签名申请,如下所示:

winsun@unbuntu64:~/test/grpc-tls-go/cert$ cat server-req.pem
-----BEGIN CERTIFICATE REQUEST-----
MIIE3TCCAsUCAQAwgZcxCzAJBgNVBAYTAmNuMREwDwYDVQQIDAh6aGVqaWFuZzER
MA8GA1UEBwwIaGFuZ3pob3UxETAPBgNVBAoMCGZpZ2h0YmVlMREwDwYDVQQLDAhz
ZWN1cml0eTEXMBUGA1UEAwwOKi5maWdodGJlZS5jb20xIzAhBgkqhkiG9w0BCQEW
FHdpbnN1bnh1QGZveG1haWwuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAn58bmfJ3wguXKIAPOCNnRq/feMI5KOIYvj+1ZaMkLo9J22GELpqg1vuM
vyqNaUEXWC7p0pMzNKsShnIhSdNfNvjJw48SExtRgCaxM2zbvb7j/EfUvTqu1nar
dtBH1b52ItQ64dpfOb9EJxOKrlj5S9X3P+skg78jxnlhM3jw9nsUMxjEQkiRkcxQ
gxReyjtisdtNbquTLsqessW4Ae+h25zWWwXYHjwulKWdoV6qn1AmDkFisSWoojhn
imnOO0vYf3y3gAA7uMg0GkyL38BI59w/Wm4QzrzKFQqIk241O7GfCtMfnARqT5Wc
B7fiuKQ04dLbtLuJbXT7jxfKFhxJjZftvaITbpSqX8iHiEgLzp2Oi04FHwNCEYKL
f11BvXy5FN/0UMP5HVgm3ISTGXgIAOJzI/W+RlyJdOcOsM9ggK6j5oKxShuozvT1
dOGGJOHe1sj0nZB+EK3L9bDzEggqVYxndm5jsbF/hDi634ySYgZpNpUy9SXwPKTn
o5wQSlOUtDmL7gF7jAx4hEjLf3Ye+kIQ6fcsCkSBpukeK/+BX/nF6iFkVG5pIvUR
blSfB1Vh5K1ECAmQvcAIfm+ZpfG6vSN6pXDekEZ+ub42+9iGiujvY8jt9AtRp3Ey
DxzYCHXReDhj1bWPqvRGYMomNTANVJ09eAjhSj14cuXTtIgT8j0CAwEAAaAAMA0G
CSqGSIb3DQEBCwUAA4ICAQAFoWgBkVOazjVVf0nwwg7Sx0bIfRO7K4Q1X0L4tfWA
Kn+5cZqsJnZDJmHFztVQEdm6LmJAWqNb2qM/HQQPuQ2Or0bT8ZrEwgWCEOFFgN9o
X7KXMGIs+lvcSMQ3BKLT6IVbLKrciaFUXGmiPj4P/8DV3uPyK4WHfW0mcXipeJJF
4sUFg4+3EHEtL67bRI/SIxRlxyvegFJuHdw+ZQMVd8iUM9lWQLevlk9NiToMndgP
VLUK35f23Jz6ywOzHL0dYCtheGSrorrZlXho+zX6Fj3roXKrnTRJIT0YuVZJe4EN
1OX0pQJ+ES254WLgsNn9yZpLDbD1A8XA9nCSZMKPdcsLQE11mRQWAcS3/CTGWa1T
IvOdq3V3m6sLiNAfidTcqaa5Pvnhwd5K4UidmC7E1gFYwe8yJlY73NrrKDHLsV+l
Gr2LSiGxMuGI+Gzf2zypDXr8X7TCzfkcgnMIs/c1rJFIt0Q8E3zvj6q4jdf/fQzd
dqEp1p44zF7Fn5AEmdRNjgkvrJwipSFxK2eVfHU11m6W4MWWqUMN9AuRogFEV3N3
3OGOmWNzaf7APqvJ3uiTwJxO1J9u6a8G+TfLv8CK6hFlZGc1cow76Op71OjEx3Hv
ldCYID7xRZqYcUyhNn7NS0yN9RAfA3EznjNS8OW8R52DBo2x0LsPHDE8A1wJCSGr
EQ==
-----END CERTIFICATE REQUEST-----
  1. 对csr进行签名
openssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.conf
  • -req 将传入证书申请
  • -in 证书签名申请文件,server-req.pem
  • -CA ca的证书文件ca-cert.pem
  • -CAKey ca的私钥ca-key.pem
  • -CAcreateserial 确保证书的签名带有唯一序列号
  • -out 输出签名后的证书文件server-cert.pem
  • extfile 可选项,告诉openssl我们有额外的配置,比如alternative name, email, IP address …
  1. 查看证书
openssl x509 -in server-cert.pem -noout -text

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            17:6c:fb:d9:7f:60:9b:bd:16:26:86:94:ab:79:6b:94:ba:33:6c:7a
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = cn, ST = zhejiang, L = hangzhou, O = fightbee, OU = security, CN = *.fightbee.com, emailAddress = winsunxu@foxmail.com
        Validity
            Not Before: Mar 14 03:45:45 2023 GMT
            Not After : Apr 13 03:45:45 2023 GMT
        Subject: C = cn, ST = zhejiang, L = hangzhou, O = fightbee, OU = security, CN = *.fightbee.com, emailAddress = winsunxu@foxmail.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:b9:e3:29:fd:37:51:dd:da:e4:d1:5b:33:fc:fb:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:*.fightbee.com, DNS:*.fightbee.org, IP Address:0.0.0.0
            X509v3 Subject Key Identifier:
                0D:CA:70:EE:F6:AA:3B:D1:65:F7:DF:E1:02:9B:67:9C:83:4C:00:9F
            X509v3 Authority Key Identifier:
                EC:BB:86:67:BD:3A:02:8A:82:1C:D3:E2:59:DD:8E:9E:98:A9:39:C8
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        02:d6:b8:d7:0a:ab:a7:94:ce:b8:12:13:44:e6:40:8b:b5:07:
       ...

grpc server

server目录结构:

winsun@unbuntu64:~/test/grpc-tls-go$ tree -L 2
.
├── cert
│   ├── server-cert.pem
│   ├── server-ext.conf
│   ├── server-key.pem
|
├── go.mod
├── go.sum
├── proto
│   ├── helloworld_grpc.pb.go
│   ├── helloworld.pb.go
│   └── helloworld.proto
└── server.go

server 代码:

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"log"
	"net"

	pb "grpctls/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

type greeterService struct {
	pb.UnimplementedGreeterServer
}

func (s *greeterService) SayHello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("received name: %v", request.GetName())
	return &pb.HelloReply{Message: "Hello " + request.GetName()}, nil
}

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

	// read ca's cert, verify to client's certificate
	caPem, err := ioutil.ReadFile("cert/ca-cert.pem")
	if err != nil {
		log.Fatal(err)
	}

	// create cert pool and append ca's cert
	certPool := x509.NewCertPool()
	if !certPool.AppendCertsFromPEM(caPem) {
		log.Fatal(err)
	}

	// read server cert & key
	serverCert, err := tls.LoadX509KeyPair("cert/server-cert.pem", "cert/server-key.pem")
	if err != nil {
		log.Fatal(err)
	}

	// configuration of the certificate what we want to
	conf := &tls.Config{
		Certificates: []tls.Certificate{serverCert},
		//ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    certPool,
	}

  //create tls certificate
	tlsCredentials := credentials.NewTLS(conf)

	// create grpc server
	grpcServer := grpc.NewServer(grpc.Creds(tlsCredentials))

	// register service into grpc server
	pb.RegisterGreeterServer(grpcServer, &greeterService{})

	log.Printf("listening at %v", lis.Addr())

	// listen port
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("grpc serve err: %v", err)
	}
}

grpc client

client 目录结构:

(base) ➜  grpc-client tree -L 3
.
├── clien.go
├── go.mod
├── go.sum
├── proto
│   ├── helloworld.pb.go
│   ├── helloworld.proto
│   └── helloworld_grpc.pb.go
└── server-cert.pem

client主代码

package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	pb "grpcclient/proto"
	"log"
	"time"
)

func main(){
	//从输入的证书文件中为客户端构造TLS凭证
	creds, err := credentials.NewClientTLSFromFile("server-cert.pem", "fightbee.com")
	if err != nil {
		log.Fatalf("Failed to create TLS credentials %v", err)
	}
	// 连接服务器
	conn, err := grpc.Dial("192.168.44.133:9000", grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatalf("net.Connect err: %v", err)
	}
	defer conn.Close()

	client := pb.NewGreeterClient(conn)
	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "winsun xu"})
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Greeting: %s", resp.GetMessage())
}

protoc 编译 .proto文件

protoc --go_out=. --go_opt=paths=source_relative   --go-grpc_out=. --go-grpc_opt=paths=source_relative   *.proto

参考