Skip to content

Commit

Permalink
add grpc/grpcs proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
andyroyle committed Nov 22, 2018
1 parent 1f3ad2e commit 850872e
Show file tree
Hide file tree
Showing 400 changed files with 224,637 additions and 9 deletions.
9 changes: 6 additions & 3 deletions config/load.go
Expand Up @@ -335,7 +335,7 @@ func parseListen(cfg map[string]string, cs map[string]CertSource, readTimeout, w
case "proto":
l.Proto = v
switch l.Proto {
case "tcp", "tcp+sni", "http", "https":
case "tcp", "tcp+sni", "http", "https", "grpc", "grpcs":
// ok
default:
return Listen{}, fmt.Errorf("unknown protocol %q", v)
Expand Down Expand Up @@ -391,12 +391,15 @@ func parseListen(cfg map[string]string, cs map[string]CertSource, readTimeout, w
if l.Addr == "" {
return Listen{}, fmt.Errorf("need listening host:port")
}
if csName != "" && l.Proto != "https" && l.Proto != "tcp" {
return Listen{}, fmt.Errorf("cert source requires proto 'https' or 'tcp'")
if csName != "" && l.Proto != "https" && l.Proto != "tcp" && l.Proto != "grpcs" {
return Listen{}, fmt.Errorf("cert source requires proto 'https', 'tcp' or 'grpcs'")
}
if csName == "" && l.Proto == "https" {
return Listen{}, fmt.Errorf("proto 'https' requires cert source")
}
if csName == "" && l.Proto == "grpcs" {
return Listen{}, fmt.Errorf("proto 'grpcs' requires cert source")
}
if cs[csName].Type == "vault-pki" && !l.StrictMatch {
// Without StrictMatch the first issued certificate is used for all
// subsequent requests, even if the common name doesn't match.
Expand Down
21 changes: 17 additions & 4 deletions config/load_test.go
Expand Up @@ -98,6 +98,13 @@ func TestLoad(t *testing.T) {
return cfg
},
},
{
args: []string{"-proxy.addr", ":5555;proto=grpc"},
cfg: func(cfg *Config) *Config {
cfg.Listen = []Listen{{Addr: ":5555", Proto: "grpc"}}
return cfg
},
},
{
desc: "-proxy.addr with tls configs",
args: []string{"-proxy.addr", `:5555;rt=1s;wt=2s;tlsmin=0x0300;tlsmax=0x305;tlsciphers="0x123,0x456"`},
Expand Down Expand Up @@ -941,16 +948,22 @@ func TestLoad(t *testing.T) {
err: errors.New("proto 'https' requires cert source"),
},
{
desc: "-proxy.addr with cert source and proto 'http' requires proto 'https' or 'tcp'",
desc: "-proxy.addr with proto 'grpcs' requires cert source",
args: []string{"-proxy.addr", ":5555;proto=grpcs"},
cfg: func(cfg *Config) *Config { return nil },
err: errors.New("proto 'grpcs' requires cert source"),
},
{
desc: "-proxy.addr with cert source and proto 'http' requires proto 'https', 'tcp', or 'grpcs'",
args: []string{"-proxy.addr", ":5555;cs=name;proto=http", "-proxy.cs", "cs=name;type=path;cert=value"},
cfg: func(cfg *Config) *Config { return nil },
err: errors.New("cert source requires proto 'https' or 'tcp'"),
err: errors.New("cert source requires proto 'https', 'tcp' or 'grpcs'"),
},
{
desc: "-proxy.addr with cert source and proto 'tcp+sni' requires proto 'https' or 'tcp'",
desc: "-proxy.addr with cert source and proto 'tcp+sni' requires proto 'https', 'tcp' or 'grpcs'",
args: []string{"-proxy.addr", ":5555;cs=name;proto=tcp+sni", "-proxy.cs", "cs=name;type=path;cert=value"},
cfg: func(cfg *Config) *Config { return nil },
err: errors.New("cert source requires proto 'https' or 'tcp'"),
err: errors.New("cert source requires proto 'https', 'tcp' or 'grpcs'"),
},
{
desc: "-proxy.noroutestatus too small",
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Expand Up @@ -62,6 +62,7 @@ require (
github.com/mitchellh/go-homedir v0.0.0-20160606030122-1111e456ffea // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v0.0.0-20160212031839-d2dd02622084 // indirect
github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76
github.com/ncw/swift v1.0.42 // indirect
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
github.com/opentracing/opentracing-go v1.0.2
Expand Down Expand Up @@ -96,7 +97,7 @@ require (
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 // indirect
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
google.golang.org/appengine v1.3.0 // indirect
google.golang.org/grpc v1.16.0 // indirect
google.golang.org/grpc v1.16.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -165,6 +165,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160212031839-d2dd02622084 h1:Z2EJ6SUY
github.com/mitchellh/mapstructure v0.0.0-20160212031839-d2dd02622084/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76 h1:LxaK681Aane3f53b4qVTXXq83lL9fWwBTaaZOf9VizA=
github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo=
github.com/ncw/swift v1.0.42 h1:ztvRb6hs52IHOcaYt73f9lXYLIeIuWgdooRDhdyllGI=
github.com/ncw/swift v1.0.42/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=
Expand Down
13 changes: 13 additions & 0 deletions main.go
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"google.golang.org/grpc"
"io"
"log"
"net"
Expand Down Expand Up @@ -32,6 +33,7 @@ import (
"github.com/fabiolb/fabio/registry/static"
"github.com/fabiolb/fabio/route"
"github.com/fabiolb/fabio/trace"
grpc_proxy "github.com/mwitkow/grpc-proxy/proxy"
"github.com/pkg/profile"
dmp "github.com/sergi/go-diff/diffmatchpatch"
)
Expand Down Expand Up @@ -139,6 +141,10 @@ func main() {
log.Print("[INFO] Down")
}

func newGrpcProxy(cfg *config.Config, tlscfg *tls.Config) grpc.StreamHandler {
return grpc_proxy.TransparentHandler(proxy.GetGRPCDirector(cfg, tlscfg))
}

func newHTTPProxy(cfg *config.Config) http.Handler {
var w io.Writer
switch cfg.Log.AccessTarget {
Expand Down Expand Up @@ -273,6 +279,13 @@ func startServers(cfg *config.Config) {
exit.Fatal("[FATAL] ", err)
}
}()
case "grpc", "grpcs":
go func() {
h := newGrpcProxy(cfg, tlscfg)
if err := proxy.ListenAndServeGRPC(l, h, tlscfg); err != nil {
exit.Fatal("[FATAL] ", err)
}
}()
case "tcp":
go func() {
h := &tcp.Proxy{
Expand Down
89 changes: 89 additions & 0 deletions proxy/grpc_handler.go
@@ -0,0 +1,89 @@
package proxy

import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"

"github.com/fabiolb/fabio/config"
"github.com/fabiolb/fabio/route"
grpc_proxy "github.com/mwitkow/grpc-proxy/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)

type GRPCServer struct {
server *grpc.Server
}

func (s *GRPCServer) Close() error {
s.server.Stop()
return nil
}

func (s *GRPCServer) Shutdown(ctx context.Context) error {
s.server.GracefulStop()
return nil
}

func (s *GRPCServer) Serve(lis net.Listener) error {
return s.server.Serve(lis)
}

func GetGRPCDirector(cfg *config.Config, tlscfg *tls.Config) func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {

pick := route.Picker[cfg.Proxy.Strategy]
match := route.Matcher[cfg.Proxy.Matcher]

return func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {
md, ok := metadata.FromIncomingContext(ctx)

if !ok {
return ctx, nil, fmt.Errorf("error extracting metadata from request")
}

reqUrl, err := url.ParseRequestURI(fullMethodName)

if err != nil {
return ctx, nil, fmt.Errorf("error parsing request url")
}

headers := http.Header{}

for k, v := range md {
for _, h := range v {
headers.Add(k, h)
}
}

req := &http.Request{
Host: "",
URL: reqUrl,
Header: headers,
}

target := route.GetTable().Lookup(req, req.Header.Get("trace"), pick, match, cfg.GlobMatchingDisabled)

if target == nil {
return nil, nil, fmt.Errorf("no route found")
}

opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.CallCustomCodec(grpc_proxy.Codec())),
}

if target.URL.Scheme == "grpcs" && tlscfg != nil {
opts = append(opts, grpc.WithTransportCredentials(
credentials.NewTLS(&tls.Config{ClientCAs: tlscfg.ClientCAs, InsecureSkipVerify: target.TLSSkipVerify})))
}

newCtx := context.Background()
conn, err := grpc.DialContext(newCtx, target.URL.Host, opts...)

return newCtx, conn, err
}
}
18 changes: 18 additions & 0 deletions proxy/serve.go
Expand Up @@ -3,13 +3,15 @@ package proxy
import (
"context"
"crypto/tls"
"google.golang.org/grpc"
"net"
"net/http"
"sync"
"time"

"github.com/fabiolb/fabio/config"
"github.com/fabiolb/fabio/proxy/tcp"
grpc_proxy "github.com/mwitkow/grpc-proxy/proxy"
)

type Server interface {
Expand Down Expand Up @@ -69,6 +71,22 @@ func ListenAndServeHTTP(l config.Listen, h http.Handler, cfg *tls.Config) error
return serve(ln, srv)
}

func ListenAndServeGRPC(l config.Listen, h grpc.StreamHandler, cfg *tls.Config) error {
ln, err := ListenTCP(l.Addr, cfg)
if err != nil {
return err
}

srv := &GRPCServer{
server: grpc.NewServer(
grpc.CustomCodec(grpc_proxy.Codec()),
grpc.UnknownServiceHandler(h),
),
}

return serve(ln, srv)
}

func ListenAndServeTCP(l config.Listen, h tcp.Handler, cfg *tls.Config) error {
ln, err := ListenTCP(l.Addr, cfg)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions registry/consul/routecmd.go
Expand Up @@ -63,6 +63,12 @@ func (r routecmd) build() []string {
case o == "proto=https":
dst = "https://" + addr

case o == "proto=grpcs":
dst = "grpcs://" + addr

case o == "proto=grpc":
dst = "grpc://" + addr

case strings.HasPrefix(o, "weight="):
weight = o[len("weight="):]

Expand Down
3 changes: 3 additions & 0 deletions vendor/github.com/golang/protobuf/AUTHORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions vendor/github.com/golang/protobuf/CONTRIBUTORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions vendor/github.com/golang/protobuf/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 850872e

Please sign in to comment.