Repository: zeromicro/go-zero
Pull request #647 (2021-04-22 04:12:51)

support secure RPCclient
support #624 

> zrpc client 连接服务端 dialoptions 直接写死 withInsecure 了，是不是可以考虑支持一下证书设置

可通过RpcClientConf添加Secure

```
RpcClientConf struct {
		Etcd      discov.EtcdConf `json:",optional"`
		Endpoints []string        `json:",optional=!Etcd"`
		App       string          `json:",optional"`
		Token     string          `json:",optional"`
		Timeout   int64           `json:",default=2000"`
                //0==withInsecure ;
		SecureOpt bool            `json:",default=0"`
}
```


Patch:
diff --git a/zrpc/client.go b/zrpc/client.go
index f671d2e7..e8a679b4 100644
--- a/zrpc/client.go
+++ b/zrpc/client.go
@@ -18,6 +18,12 @@ var (
 	WithRetry = internal.WithRetry
 	// WithUnaryClientInterceptor is an alias of internal.WithUnaryClientInterceptor.
 	WithUnaryClientInterceptor = internal.WithUnaryClientInterceptor
+	// WithInsecure is an alias of internal.WithInsecure.
+	WithInsecure = internal.WithInsecure
+	// WithTlsClientFromUnilateralism is an alias of internal.WithTlsClientFromUnilateralism
+	WithTlsClientFromUnilateralism = internal.WithTlsClientFromUnilateralism
+	// WithTlsClientFromMutual is an alias of internal.WithTlsClientFromMutual
+	WithTlsClientFromMutual = internal.WithTlsClientFromMutual
 )
 
 type (
@@ -58,6 +64,9 @@ func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
 		opts = append(opts, WithRetry())
 	}
 	opts = append(opts, options...)
+	if !c.HasSslVerify() {
+		opts = append(opts, WithInsecure())
+	}
 
 	var target string
 	var err error
diff --git a/zrpc/config.go b/zrpc/config.go
index 07b72724..0b05df84 100644
--- a/zrpc/config.go
+++ b/zrpc/config.go
@@ -30,6 +30,7 @@ type (
 		Token     string          `json:",optional"`
 		Retry     bool            `json:",optional"` // grpc auto retry
 		Timeout   int64           `json:",default=2000"`
+		InsecureVerify bool            `json:",default=false"`
 	}
 )
 
@@ -72,3 +73,8 @@ func (sc RpcServerConf) Validate() error {
 func (cc RpcClientConf) HasCredential() bool {
 	return len(cc.App) > 0 && len(cc.Token) > 0
 }
+
+//HasTls checks if there is a SSL in config.
+func (cc RpcClientConf) HasSslVerify() bool {
+	return cc.InsecureVerify
+}
diff --git a/zrpc/internal/client.go b/zrpc/internal/client.go
index 861982ef..cad7c53e 100644
--- a/zrpc/internal/client.go
+++ b/zrpc/internal/client.go
@@ -2,8 +2,12 @@ package internal
 
 import (
 	"context"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"log"
 	"strings"
 	"time"
 
@@ -11,6 +15,7 @@ import (
 	"github.com/tal-tech/go-zero/zrpc/internal/clientinterceptors"
 	"github.com/tal-tech/go-zero/zrpc/internal/resolver"
 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
 )
 
 const (
@@ -65,7 +70,6 @@ func (c *client) buildDialOptions(opts ...ClientOption) []grpc.DialOption {
 	}
 
 	options := []grpc.DialOption{
-		grpc.WithInsecure(),
 		grpc.WithBlock(),
 		WithUnaryClientInterceptors(
 			clientinterceptors.UnaryTracingInterceptor,
@@ -112,6 +116,13 @@ func WithDialOption(opt grpc.DialOption) ClientOption {
 	}
 }
 
+// WithInsecure returns a func to customize a ClientOptions with secure option.
+func WithInsecure() ClientOption {
+	return func(options *ClientOptions) {
+		options.DialOptions = append(options.DialOptions, grpc.WithInsecure())
+	}
+}
+
 // WithTimeout returns a func to customize a ClientOptions with given timeout.
 func WithTimeout(timeout time.Duration) ClientOption {
 	return func(options *ClientOptions) {
@@ -132,3 +143,40 @@ func WithUnaryClientInterceptor(interceptor grpc.UnaryClientInterceptor) ClientO
 		options.DialOptions = append(options.DialOptions, WithUnaryClientInterceptors(interceptor))
 	}
 }
+
+// WithTlsClientFromUnilateralism return a func to customize a ClientOptions Verify with Unilateralism authentication.
+func WithTlsClientFromUnilateralism(crt, domainName string) ClientOption {
+	return func(options *ClientOptions) {
+		c, err := credentials.NewClientTLSFromFile(crt, domainName)
+		if err != nil {
+			log.Fatalf("credentials.NewClientTLSFromFile err: %v", err)
+		}
+		options.DialOptions = append(options.DialOptions, grpc.WithTransportCredentials(c))
+	}
+}
+
+// WithTlsClientFromMutual return a func to customize a ClientOptions Verify with mutual authentication.
+func WithTlsClientFromMutual(crtFile, keyFile, caFile string) ClientOption {
+	return func(options *ClientOptions) {
+		cert, err := tls.LoadX509KeyPair(crtFile, keyFile)
+		if err != nil {
+			log.Fatalf("tls.LoadX509KeyPair err: %v", err)
+		}
+		certPool := x509.NewCertPool()
+		ca, err := ioutil.ReadFile(caFile)
+		if err != nil {
+			log.Fatalf("credentials: failed to ReadFile CA certificates err: %v", err)
+		}
+
+		if !certPool.AppendCertsFromPEM(ca) {
+			log.Fatalf("credentials: failed to append certificates err: %v", err)
+		}
+
+		config := &tls.Config{
+			Certificates: []tls.Certificate{cert},
+			RootCAs:      certPool,
+		}
+
+		options.DialOptions = append(options.DialOptions, grpc.WithTransportCredentials(credentials.NewTLS(config)))
+	}
+}


Test patch:
diff --git a/zrpc/client_test.go b/zrpc/client_test.go
index d9b05bb4..c42931c7 100644
--- a/zrpc/client_test.go
+++ b/zrpc/client_test.go
@@ -76,7 +76,6 @@ func TestDepositServer_Deposit(t *testing.T) {
 			Token:     "bar",
 			Timeout:   1000,
 		},
-		WithDialOption(grpc.WithInsecure()),
 		WithDialOption(grpc.WithContextDialer(dialer())),
 		WithUnaryClientInterceptor(func(ctx context.Context, method string, req, reply interface{},
 			cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
diff --git a/zrpc/config_test.go b/zrpc/config_test.go
index 67d2a4b3..45a366d7 100644
--- a/zrpc/config_test.go
+++ b/zrpc/config_test.go
@@ -14,6 +14,11 @@ func TestRpcClientConf(t *testing.T) {
 	assert.True(t, conf.HasCredential())
 	conf = NewEtcdClientConf([]string{"localhost:1234", "localhost:5678"}, "key", "foo", "bar")
 	assert.True(t, conf.HasCredential())
+	// ssl on
+	conf = NewDirectClientConf([]string{"localhost:1234", "localhost:5678"}, "foo", "bar")
+	assert.False(t, conf.HasSslVerify())
+	conf.InsecureVerify = true
+	assert.True(t, conf.HasSslVerify())
 }
 
 func TestRpcServerConf(t *testing.T) {