commit
089a4e0f0a
@ -0,0 +1,81 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package grpcutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// RegisterServiceHandlerFunc is a function that registers ServiceHandlers with
|
||||
// a ServeMux.
|
||||
type RegisterServiceHandlerFunc func(context.Context, *runtime.ServeMux, *grpc.ClientConn) error
|
||||
|
||||
// NewGateway creates a new http.Handler and grpc.ClientConn with the provided
|
||||
// gRPC Services registered.
|
||||
func NewGateway(addr string, tlsConfig *tls.Config, funcs []RegisterServiceHandlerFunc) (http.Handler, *grpc.ClientConn, error) {
|
||||
// Configure the right DialOptions the for TLS configuration.
|
||||
var dialOpts []grpc.DialOption
|
||||
if tlsConfig != nil {
|
||||
var gwTLSConfig *tls.Config
|
||||
gwTLSConfig = tlsConfig.Clone()
|
||||
gwTLSConfig.InsecureSkipVerify = true // Trust the local server.
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(gwTLSConfig)))
|
||||
} else {
|
||||
dialOpts = append(dialOpts, grpc.WithInsecure())
|
||||
}
|
||||
|
||||
conn, err := grpc.DialContext(context.TODO(), addr, dialOpts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Register services.
|
||||
srvmux := runtime.NewServeMux()
|
||||
for _, fn := range funcs {
|
||||
err = fn(context.TODO(), srvmux, conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return srvmux, conn, nil
|
||||
}
|
||||
|
||||
// IsGRPCRequest returns true if the provided request came from a gRPC client.
|
||||
//
|
||||
// Its logic is a partial recreation of gRPC's internal checks, see:
|
||||
// https://github.com/grpc/grpc-go/blob/01de3de/transport/handler_server.go#L61:L69
|
||||
func IsGRPCRequest(r *http.Request) bool {
|
||||
return r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc")
|
||||
}
|
||||
|
||||
// HandlerFunc returns an http.Handler that delegates to grpc.Server on
|
||||
// incoming gRPC connections otherwise serves with the provided handler.
|
||||
func HandlerFunc(grpcServer *grpc.Server, otherwise http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if IsGRPCRequest(r) {
|
||||
grpcServer.ServeHTTP(w, r)
|
||||
} else {
|
||||
otherwise.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package grpcutil implements various utilities around managing gRPC services.
|
||||
package grpcutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/cockroachdb/cmux"
|
||||
|
||||
"github.com/coreos/clair/pkg/httputil"
|
||||
)
|
||||
|
||||
// MuxedGRPCServer defines the parameters for running a gRPC Server alongside
|
||||
// a Gateway server on the same port.
|
||||
type MuxedGRPCServer struct {
|
||||
Addr string
|
||||
TLSConfig *tls.Config
|
||||
ServicesFunc RegisterServicesFunc
|
||||
ServiceHandlerFuncs []RegisterServiceHandlerFunc
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address srv.Addr and handles both
|
||||
// gRPC and JSON requests over HTTP. An optional HTTP middleware can be
|
||||
// provided to wrap the output of each request.
|
||||
//
|
||||
// Internally, it muxes the Listener based on whether the request is gRPC or
|
||||
// HTTP and runs multiple servers.
|
||||
func (srv *MuxedGRPCServer) ListenAndServe(mw httputil.Middleware) error {
|
||||
l, err := net.Listen("tcp", srv.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tcpMux := cmux.New(l)
|
||||
|
||||
grpcListener := tcpMux.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
|
||||
defer grpcListener.Close()
|
||||
|
||||
httpListener := tcpMux.Match(cmux.Any())
|
||||
defer httpListener.Close()
|
||||
|
||||
httpHandler, conn, err := NewGateway(httpListener.Addr().String(), nil, srv.ServiceHandlerFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
gsrv := NewServer(nil, srv.ServicesFunc)
|
||||
defer gsrv.Stop()
|
||||
|
||||
go func() { tcpMux.Serve() }()
|
||||
go func() { gsrv.Serve(grpcListener) }()
|
||||
|
||||
if mw != nil {
|
||||
httpHandler = mw(httpHandler)
|
||||
}
|
||||
|
||||
httpsrv := &http.Server{
|
||||
Handler: httpHandler,
|
||||
}
|
||||
httpsrv.Serve(httpListener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureCA(tlsConfig *tls.Config, caPath string) error {
|
||||
caCert, err := ioutil.ReadFile(caPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
tlsConfig.ClientCAs = caCertPool
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureCertificate(tlsConfig *tls.Config, certFile, keyFile string) error {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
tlsConfig.NextProtos = []string{"h2"}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListenAndServeTLS listens on the TCP network address srv.Addr and handles both
|
||||
// gRPC and JSON requests over HTTP over TLS. An optional HTTP middleware can
|
||||
// be provided to wrap the output of each request.
|
||||
//
|
||||
// Internally, the same net.Listener is used because the http.Handler will
|
||||
// pivot based on whether the request is gRPC or HTTP.
|
||||
func (srv *MuxedGRPCServer) ListenAndServeTLS(certFile, keyFile, caPath string, mw httputil.Middleware) error {
|
||||
if srv.TLSConfig == nil {
|
||||
srv.TLSConfig = &tls.Config{}
|
||||
}
|
||||
configureCA(srv.TLSConfig, caPath)
|
||||
configureCertificate(srv.TLSConfig, certFile, keyFile)
|
||||
|
||||
listener, err := tls.Listen("tcp", srv.Addr, srv.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gwHandler, conn, err := NewGateway(listener.Addr().String(), srv.TLSConfig, srv.ServiceHandlerFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
gsrv := NewServer(srv.TLSConfig, srv.ServicesFunc)
|
||||
defer gsrv.Stop()
|
||||
|
||||
httpHandler := HandlerFunc(gsrv, gwHandler)
|
||||
if mw != nil {
|
||||
httpHandler = mw(httpHandler)
|
||||
}
|
||||
|
||||
httpsrv := &http.Server{
|
||||
Handler: httpHandler,
|
||||
}
|
||||
httpsrv.Serve(listener)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package grpcutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-prometheus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// RegisterServicesFunc is a function that registers gRPC services with a given
|
||||
// server.
|
||||
type RegisterServicesFunc func(*grpc.Server)
|
||||
|
||||
// NewServer allocates a new grpc.Server and handles some some boilerplate
|
||||
// configuration.
|
||||
func NewServer(tlsConfig *tls.Config, fn RegisterServicesFunc) *grpc.Server {
|
||||
// Default ServerOptions
|
||||
grpcOpts := []grpc.ServerOption{
|
||||
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
|
||||
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
||||
}
|
||||
|
||||
// Register services with a new grpc.Server.
|
||||
gsrv := grpc.NewServer(grpcOpts...)
|
||||
fn(gsrv)
|
||||
return gsrv
|
||||
}
|
Loading…
Reference in new issue