gRPC 客户端与服务端通信时,除了可以使用WithInsecure(),还可以选择客户端使用服务端证书(见之前的文章),有些需求下,需要分别管理客户端与服务端证书,记录下相关的实践过程。

  1. CA

a).生成根证书私钥(key文件)

openssl genrsa -aes256 -out ca.key 2048

b).生成根证书签发申请文件(csr文件)

openssl req -new -sha256 -key ca.key -out ca.csr

c).自签发根证书(crt文件)

openssl x509 -req -days 36500 -sha1 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.crt
  1. server

a).生成根证书私钥(key文件)

openssl genrsa -aes256 -out server.key 2048

如果要移除 key 的密钥,则执行

openssl rsa -in server.key -out server.key

b).生成根证书签发申请文件(csr文件)

openssl req -new -sha256 -key server.key -out server.csr

c).使用根证书签发服务端证书

openssl ca -days 36500 -notext -md sha256 -keyfile ca.key -cert ca.crt -in server.csr -out server.crt 
  1. client

a).生成根证书私钥(key文件)

openssl genrsa -aes256 -out client.key 2048

如果要移除 key 的密钥,则执行

openssl rsa -in client.key -out client.key

b).生成根证书签发申请文件(csr文件)

openssl req -new -key client.key -out client.csr

c).使用根证书签发客户端证书

openssl ca -days 36500 -notext -md sha256 -keyfile ca.key -cert ca.crt -in client.csr -out client.crt

server.go

package main

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

    "../../pb"

    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc"
    "golang.org/x/net/context"
    "google.golang.org/grpc/peer"
)

var (
    crt  = "server.crt"
    key  = "server.key"
    ca   = "ca.crt"
    addr = ":8080"
)

func main() {
    // Load the certificates from disk
    certificate, err := tls.LoadX509KeyPair(crt, key)
    if err != nil {
        log.Fatalf("could not load server key pair: %s", err)
    }

    // Create a certificate pool from the certificate authority
    certPool := x509.NewCertPool()
    ca, err := ioutil.ReadFile(ca)
    if err != nil {
        log.Fatalf("could not read ca certificate: %s", err)
    }

    // Append the client certificates from the CA
    if ok := certPool.AppendCertsFromPEM(ca); !ok {
        log.Fatalf("failed to append client certs")
    }

    // Create the channel to listen on
    lis, err := net.Listen("tcp", addr)
    if err != nil {
        log.Fatalf("could not list on %s: %s", addr, err)
    }

    // Create the TLS credentials
    creds := credentials.NewTLS(&tls.Config{
        ClientAuth:   tls.RequireAndVerifyClientCert,
        Certificates: []tls.Certificate{certificate},
        ClientCAs:    certPool,
    })

    // Create the gRPC server with the credentials
    srv := grpc.NewServer(grpc.Creds(creds))

    // Register the handler object
    pb.RegisterProxyServiceServer(srv, &proxy{})

    // Serve and Listen
    if err := srv.Serve(lis); err != nil {
        log.Fatalf("grpc serve error: %s", err)
    }
}

type proxy struct {
}

func (p *proxy) Echo(ctx context.Context, req *pb.Payload) (*pb.Payload, error) {
    pr, ok := peer.FromContext(ctx)

    if ok {
        fmt.Printf("%#v\n", pr.AuthInfo)
    }

    return req, nil
}

func (p *proxy) Pipeline(stream pb.ProxyService_PipelineServer) error {
    return nil
}

func (p *proxy) PipelineUDP(stream pb.ProxyService_PipelineUDPServer) error {
    return nil
}

client.go

package main

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

    "../../pb"

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

var (
    crt  = "client.crt"
    key  = "client.key"
    ca   = "ca.crt"
    addr = ":8080"
)

func main() {
    // Load the client certificates from disk
    certificate, err := tls.LoadX509KeyPair(crt, key)
    if err != nil {
        log.Fatalf("could not load client key pair: %s", err)
    }

    // Create a certificate pool from the certificate authority
    certPool := x509.NewCertPool()
    ca, err := ioutil.ReadFile(ca)
    if err != nil {
        log.Fatalf("could not read ca certificate: %s", err)
    }

    // Append the certificates from the CA
    if ok := certPool.AppendCertsFromPEM(ca); !ok {
        log.Fatalf("failed to append ca certs")
    }

    creds := credentials.NewTLS(&tls.Config{
        ServerName:   "xxxx-server",
        Certificates: []tls.Certificate{certificate},
        RootCAs:      certPool,
        //InsecureSkipVerify: true,
    })

    // Create a connection with the TLS credentials
    conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("could not dial %s: %s", addr, err)
    }

    // Initialize the client and make the request
    client := pb.NewProxyServiceClient(conn)
    pong, err := client.Echo(context.Background(), &pb.Payload{Data: []byte{1, 0, 2, 4}})
    if err != nil {
        log.Fatalf("could not ping %s: %s", addr, err)
    }

    // Log the ping
    log.Printf("%s\n", pong.String())
}

https://bbengfort.github.io/programmer/2017/03/03/secure-grpc.html