Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
feat: Add support for calling an external policy agent to control aut…
Browse files Browse the repository at this point in the history
…horizing field level access for GraphQL requests. Fiexes #33
  • Loading branch information
chirino committed Jul 14, 2020
1 parent bc67e65 commit 9cf6998
Show file tree
Hide file tree
Showing 16 changed files with 1,085 additions and 24 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ require (
github.com/ghodss/yaml v1.0.0
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/render v1.0.1
github.com/golang/protobuf v1.3.1
github.com/pkg/errors v0.9.1
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.5.1
golang.org/x/sys v0.0.0-20200620081246-981b61492c35 // indirect
google.golang.org/grpc v1.21.0
gopkg.in/yaml.v2 v2.3.0
)

Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand All @@ -65,6 +67,7 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
Expand Down Expand Up @@ -202,8 +205,10 @@ golang.org/x/tools v0.0.0-20200128220307-520188d60f50 h1:0qnG0gwzB6QPiLDow10WJDd
golang.org/x/tools v0.0.0-20200128220307-520188d60f50/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/config/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

type Config struct {
Server *httptest.Server `yaml:"-"`
Gateway *gateway.Gateway `yaml:"-"`
Listen string `yaml:"listen"`
gateway.Config `yaml:"-,inline"`
}
Expand Down
9 changes: 6 additions & 3 deletions internal/cmd/serve/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func run(_ *cobra.Command, _ []string) {
// restart the server on a port change...
if lastConfig.Listen != nextConfig.Listen {
lastConfig.Server.Close()
lastConfig.Gateway.Close()

err = startServer(&nextConfig)
if err != nil {
Expand Down Expand Up @@ -106,11 +107,13 @@ func run(_ *cobra.Command, _ []string) {
case syscall.SIGINT:
log.Println("shutting down due to SIGINT signal")
lastConfig.Server.Close()
lastConfig.Gateway.Close()
os.Exit(0)

case syscall.SIGTERM:
log.Println("shutting down due to SIGTERM signal")
lastConfig.Server.Close()
lastConfig.Gateway.Close()
os.Exit(0)

case syscall.SIGHUP:
Expand Down Expand Up @@ -162,12 +165,12 @@ func startServer(config *config.Config) error {
return mountGatewayOnHttpServer(config)
}

func mountGatewayOnHttpServer(c *config.Config) error {
engine, err := gateway.New(c.Config)
func mountGatewayOnHttpServer(c *config.Config) (err error) {
c.Gateway, err = gateway.New(c.Config)
if err != nil {
return err
}
gatewayHandler := gateway.CreateHttpHandler(engine.ServeGraphQLStream).(*httpgql.Handler)
gatewayHandler := gateway.CreateHttpHandler(c.Gateway.ServeGraphQLStream).(*httpgql.Handler)
// Enable pretty printed json results when in dev mode.
if !Production {
gatewayHandler.Indent = " "
Expand Down
4 changes: 1 addition & 3 deletions internal/gateway/dataloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ type DataLoaders struct {
loaders map[string]*UpstreamDataLoader
}

type dataLoadersKey byte

const DataLoadersKey = dataLoadersKey(0)
const DataLoadersKey = "DataLoadersKey"

type UpstreamDataLoader struct {
ctx context.Context
Expand Down
77 changes: 77 additions & 0 deletions internal/gateway/examples/policyagent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"net"

"github.com/golang/glog"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/testdata"

gw "github.com/chirino/graphql-gw/internal/gateway/policyagent/proto"
)

var (
tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP")
certFile = flag.String("cert_file", "", "The TLS cert file")
keyFile = flag.String("key_file", "", "The TLS key file")
grpcPort = flag.Int("grpc-port", 10000, "The server port")
)

func run() error {
var opts []grpc.ServerOption
if *tls {
if *certFile == "" {
*certFile = testdata.Path("server1.pem")
}
if *keyFile == "" {
*keyFile = testdata.Path("server1.key")
}
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
opts = []grpc.ServerOption{grpc.Creds(creds)}
}

lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *grpcPort))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

grpcServer := grpc.NewServer(opts...)
gw.RegisterPolicyAgentServer(grpcServer, &server{})

log.Println("GRPC service available at:", lis.Addr())
return grpcServer.Serve(lis)

}

type server struct {
}

func (s *server) Check(ctx context.Context, req *gw.CheckRequest) (*gw.CheckResponse, error) {
resp := gw.CheckResponse{}
for i, f := range req.Graphql.GetFields() {
if (i % 2) == 1 {
resp.Fields = append(resp.Fields, &gw.GraphQLFieldResponse{
Path: f.Path,
Error: "You are not allowed to access odd fields",
})
}
}
return &resp, nil
}

func main() {
flag.Parse()
defer glog.Flush()

if err := run(); err != nil {
glog.Fatal(err)
}
}
33 changes: 30 additions & 3 deletions internal/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ type SchemaConfig struct {
Subscription string `yaml:"subscription,omitempty"`
}

type PolicyAgentConfig struct {
Address string `yaml:"address,omitempty"`
// InsecureClient allows connections to servers that do not have a valid TLS certificate.
InsecureClient bool `yaml:"insecure-client,omitempty",json:"insecure-client,omitempty"`
}

type Config struct {
ConfigDirectory string `yaml:"-"`
Log *log.Logger `yaml:"-"`
Expand All @@ -40,11 +46,25 @@ type Config struct {
Upstreams map[string]UpstreamWrapper `yaml:"upstreams"`
Schema *SchemaConfig `yaml:"schema,omitempty"`
Types []TypeConfig `yaml:"types"`
PolicyAgent PolicyAgentConfig `yaml:"policy-agent"`
}

var validGraphQLIdentifierRegex = regexp.MustCompile(`^[A-Za-z_][A-Za-z_0-9]*$`)

func New(config Config) (*graphql.Engine, error) {
type Gateway struct {
*graphql.Engine
onClose []func()
}

func (gw *Gateway) Close() {
funcs := gw.onClose
gw.onClose = nil
for _, f := range funcs {
f()
}
}

func New(config Config) (*Gateway, error) {
if config.Log == nil {
config.Log = NoLog
}
Expand All @@ -56,7 +76,7 @@ func New(config Config) (*graphql.Engine, error) {
}

fieldResolver := resolvers.TypeAndFieldResolver{}
gateway := graphql.New()
gateway := &Gateway{Engine: graphql.New()}
err := gateway.Schema.Parse(`
schema {
query: Query
Expand Down Expand Up @@ -138,10 +158,11 @@ type Subscription {}
}

actionRunner := actionRunner{
Gateway: gateway,
Gateway: gateway.Engine,
Endpoints: upstreams,
Resolver: fieldResolver,
}

for _, typeConfig := range config.Types {
object := gateway.Schema.Types[typeConfig.Name]
if object == nil {
Expand Down Expand Up @@ -186,6 +207,12 @@ type Subscription {}
return nil, errors.Errorf("can only configure fields on OBJECT types: %s is a %s", typeConfig.Name, object.Kind())
}
}

err = initPolicyAgent(config, gateway)
if err != nil {
return nil, err
}

return gateway, nil
}

Expand Down
34 changes: 19 additions & 15 deletions internal/gateway/http.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gateway

import (
"context"
"fmt"
"log"
"net"
Expand All @@ -18,30 +19,33 @@ func CreateHttpHandler(f graphql.ServeGraphQLStreamFunc) http.Handler {
return &httpgql.Handler{ServeGraphQLStream: f}
}

func (p *UpstreamInfo) RoundTrip(req *http.Request) (*http.Response, error) {

ctx := req.Context()
func getHttpRequest(ctx context.Context) *http.Request {
if ctx != nil {

value := ctx.Value("*net/http.Request")
if value != nil {
originalRequest := value.(*http.Request)
return value.(*http.Request)
}
}
return nil
}

toHeaders := req.Header
func (p *UpstreamInfo) RoundTrip(req *http.Request) (*http.Response, error) {

if !p.Headers.DisableForwarding {
proxyHeaders(toHeaders, originalRequest)
}
originalRequest := getHttpRequest(req.Context())
if originalRequest != nil {
toHeaders := req.Header

for _, h := range p.Headers.Remove {
toHeaders.Del(h)
}
if !p.Headers.DisableForwarding {
proxyHeaders(toHeaders, originalRequest)
}

for _, hl := range p.Headers.Set {
toHeaders.Set(hl.Name, hl.Value)
}
for _, h := range p.Headers.Remove {
toHeaders.Del(h)
}

for _, hl := range p.Headers.Set {
toHeaders.Set(hl.Name, hl.Value)
}
}
return http.DefaultTransport.RoundTrip(req)
}
Expand Down
Loading

0 comments on commit 9cf6998

Please sign in to comment.