preface

Hi, I’m Fish fry. In the previous chapter, we introduced four ways to use the gRPC API. Isn’t that easy?

In this case, there is a security problem. In the previous example, gRPC Client/Server is transmitted in plaintext. Is there any risk of eavesdropping?

In conclusion, yes. In the case of plaintext communication, your request is naked and could be maliciously tampered with or falsified as “illegal” data by a third party

Grab a bag

Well, clear text transmission is correct. This is a problem, next we will transform our gRPC, in order to solve this problem 😤

Certificate generated

The private key

openssl ecparam -genkey -name secp384r1 -out server.key
Copy the code

Since the sign the public key

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

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 []:
Copy the code

Is generated

After the certificate is generated, place the certificate file in conf/.

$├─ ├─ ├─ $├─ ├─ ├─ $├─ ├── ├─ ├─ $├─ ├── ├─ ├─ $├─ ├── ├── ├── ─ $├─ ├── ── ── ── ── ── ── ── ── Simple_server └ ─ ─ stream_serverCopy the code

As this article is biased towards gRPC, please refer to “Making Certificates” for details. Further details may be provided 👌

Why didn’t you need a certificate before

In Simple_server, why does “nothing” work without a certificate?

Server

grpc.NewServer()
Copy the code

There are obviously no DialOptions passed in on the server side

Client

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

Notice the grpc.withInsecure () method on the client side

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

Within the method you can see that WithInsecure returns a DialOption, and it will eventually disable secure transport by reading the set value

So where does it end up? Let’s move to the grpc.dial () 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 {
		ifcc.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
	ifcreds ! = nil && creds.Info().ServerName ! ="" {
		cc.authority = creds.Info().ServerName
	} else ifcc.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
	}
	...
}
Copy the code

gRPC

Next we will 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")
	iferr ! = nil { log.Fatalf("credentials.NewServerTLSFromFile err: %v", err)
	}

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

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

	server.Serve(lis)
}
Copy the code
  • Credentials. NewServerTLSFromFile: according to the service side input the certificate files and key structure of the TLS certificate
func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	iferr ! = nil {return nil, err
	}
	return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
}
Copy the code
  • Grpc.creds () : Returns a ServerOption that sets the credentials for the server connection. Used forgrpc.NewServer(opt ... ServerOption)Set connection options for gRPC Server
func Creds(c credentials.TransportCredentials) ServerOption {
	return func(o *options) {
		o.creds = c
	}
}
Copy the code

After the above two simple steps, gRPC Server set up the certificate authentication service 🤔

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")
	iferr ! = nil { log.Fatalf("credentials.NewClientTLSFromFile err: %v", err)
	}

	conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
	iferr ! = 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",})iferr ! = nil { log.Fatalf("client.Search err: %v", err)
	}

	log.Printf("resp: %s", resp.GetResponse())
}
Copy the code
  • Credentials. NewClientTLSFromFile () : according to the client input the certificate files and key structure of the TLS certificate. ServerNameOverride is the service name
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
	b, err := ioutil.ReadFile(certFile)
	iferr ! = 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
}
Copy the code
  • GRPC. WithTransportCredentials () : returns a DialOption configuration connection options. Used forgrpc.Dial(target string, opts ... DialOption)Setting connection Options
func WithTransportCredentials(creds credentials.TransportCredentials) DialOption {
	return newFuncDialOption(func(o *dialOptions) {
		o.copts.TransportCredentials = creds
	})
}
Copy the code

validation

request

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

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

Grab a bag

Success.

conclusion

In this chapter we have implemented gRPC TLS Client/Servert, do you think you are done? I don’t 😤

The problem

If you look carefully, the Client establishes the request based on the certificate and service name of the Server. In this case, you need to send the Server’s certificate to the Client through various means, otherwise this task cannot be completed

The problem is, you can’t guarantee that your “means” are safe. After all, the current Internet environment is very dangerous.

We will address this problem in the next section to ensure its reliability.

?

If you have any questions or mistakes, welcome to raise questions or give correction opinions on issues. If you like or are helpful to you, welcome Star, which is a kind of encouragement and promotion for the author.

My official account

reference

Sample code for this series

  • go-grpc-example