Bring in gRPC:TLS certificate authentication

  golang, grpc, php, ssl, tls

Bring in gRPC:TLS certificate authentication

Original address:Bring in gRPC:TLS certificate authentication
Project address:https://github.com/EDDYCJY/go …

Preface

In the previous chapters, we introduced four API usage methods of gRPC. Is it very simple

At this time, there is a security problem. In the previous example, gRPC Client/Server was transmitted in clear text. Is there any risk of eavesdropping?

In conclusion, there is. In the case of plain text communication, your request is streaking and may be maliciously tampered with or forged as “illegal” data by a third party.

Grab a bag

image

image

Well, the plaintext was transmitted without error. This is a problem. Next, we will transform our gRPC in order to solve this problem.

Certificate generation

Private key

openssl ecparam -genkey -name secp384r1 -out server.key

Self-signed public key

openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650

Fill in the information

Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:go-grpc-example
Email Address []:

Generation completed

After generating the certificate, put the certificate related files under conf/. directory structure:

$ tree go-grpc-example 
go-grpc-example
├── client
├── conf
│   ├── server.key
│   └── server.pem
├── proto
└── server
    ├── simple_server
    └── stream_server

Since this article is biased towards gRPC, please refer toProduction Certificate. Details may be expanded later on.

Why didn’t you need a certificate before?

In simple_server, why can “do nothing” run without a certificate?

Server

grpc.NewServer()

Obviously, no DialOptions were passed in at the server.

Client

conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())

In the client noticedgrpc.WithInsecure()Method

func WithInsecure() DialOption {
    return newFuncDialOption(func(o *dialOptions) {
        o.insecure = true
    })
}

As can be seen in the methodWithInsecureReturn oneDialOptionAnd it will eventually disable secure transmission by reading the set value

So where did it “end up” being handled? Let’s move our eyes togrpc.Dial()Within the method

func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
    ...
    
    for _, opt := range opts {
        opt.apply(&cc.dopts)
    }
    ...
    
    if !cc.dopts.insecure {
        if cc.dopts.copts.TransportCredentials == nil {
            return nil, errNoTransportSecurity
        }
    } else {
        if cc.dopts.copts.TransportCredentials != nil {
            return nil, errCredentialsConflict
        }
        for _, cd := range cc.dopts.copts.PerRPCCredentials {
            if cd.RequireTransportSecurity() {
                return nil, errTransportCredentialsMissing
            }
        }
    }
    ...
    
    creds := cc.dopts.copts.TransportCredentials
    if creds != nil && creds.Info().ServerName != "" {
        cc.authority = creds.Info().ServerName
    } else if cc.dopts.insecure && cc.dopts.authority != "" {
        cc.authority = cc.dopts.authority
    } else {
        // Use endpoint from "scheme://authority/endpoint" as the default
        // authority for ClientConn.
        cc.authority = cc.parsedTarget.Endpoint
    }
    ...
}

gRPC

Next, we will formally start coding and implement TLS certificate authentication support on gRPC Client/Server

TLS Server

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"

    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

...

const PORT = "9001"

func main() {
    c, err := credentials.NewServerTLSFromFile("../../conf/server.pem", "../../conf/server.key")
    if err != nil {
        log.Fatalf("credentials.NewServerTLSFromFile err: %v", err)
    }

    server := grpc.NewServer(grpc.Creds(c))
    pb.RegisterSearchServiceServer(server, &SearchService{})

    lis, err := net.Listen("tcp", ":"+PORT)
    if err != nil {
        log.Fatalf("net.Listen err: %v", err)
    }

    server.Serve(lis)
}
  • Credential. newservertlsfromfile: constructs TLS certificate according to certificate file and key input by server
func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error) {
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return nil, err
    }
    return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
}
  • Grpc.Creds (): Returns a ServerOption that sets the credentials for the server connection. be used forgrpc.NewServer(opt ...ServerOption)Set connection options for gRPC Server
func Creds(c credentials.TransportCredentials) ServerOption {
    return func(o *options) {
        o.creds = c
    }
}

After the above two simple steps, gRPC Server has established the service requiring certificate authentication

TLS Client

package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"

    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

const PORT = "9001"

func main() {
    c, err := credentials.NewClientTLSFromFile("../../conf/server.pem", "go-grpc-example")
    if err != nil {
        log.Fatalf("credentials.NewClientTLSFromFile err: %v", err)
    }

    conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
    if err != nil {
        log.Fatalf("grpc.Dial err: %v", err)
    }
    defer conn.Close()

    client := pb.NewSearchServiceClient(conn)
    resp, err := client.Search(context.Background(), &pb.SearchRequest{
        Request: "gRPC",
    })
    if err != nil {
        log.Fatalf("client.Search err: %v", err)
    }

    log.Printf("resp: %s", resp.GetResponse())
}
  • Newclienttlsfromfile (): constructs TLS credentials based on the certificate file and key entered by the client. ServerNameOverride is the service name
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
    b, err := ioutil.ReadFile(certFile)
    if err != nil {
        return nil, err
    }
    cp := x509.NewCertPool()
    if !cp.AppendCertsFromPEM(b) {
        return nil, fmt.Errorf("credentials: failed to append certificates")
    }
    return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}), nil
}
  • Grpc.WithTransportCredentials (): returns a DialOption to configure the connection. be used forgrpc.Dial(target string, opts ...DialOption)Set connection options
func WithTransportCredentials(creds credentials.TransportCredentials) DialOption {
    return newFuncDialOption(func(o *dialOptions) {
        o.copts.TransportCredentials = creds
    })
}

Verification

Request

Restart server.go and execute client.go to get the response result.

$ go run client.go
2018/09/30 20:00:21 resp: gRPC Server

Grab a bag

image

Success

Summary

In this chapter, we have implemented gRPC TLS Client/Servert. Do you think it is done? I don’t.

Problem

If you take a closer look, Client builds the request based on the server-side certificate and service name. In this case, you will need to give the Server’s certificate to the Client side through various means, otherwise you will not be able to complete this task.

The problem also comes, you can’t guarantee your “various means” are safe, after all, the current network environment is very dangerous, in case of being …

We will solve this problem in the next chapter to ensure its reliability.

References

This series of sample codes

Series catalog