diff --git a/Makefile b/Makefile index 70fa6f3..50e75b3 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ help: ## display this help .PHONY: run run: ## run application - @LOG_LEVEL=debug JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/v1/graphql go run *.go + @LOG_LEVEL=debug BLOCK_SCHEMA_INTROSPECTION=true JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/v1/graphql go run *.go .PHONY: build build: ## build the binary diff --git a/README.md b/README.md index d910c82..234f2c4 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,6 @@ Creates a passthrough proxy to a graphql endpoint(s), allowing you for analysis `ENABLE_CACHE` - enable the cache (default: `false`) `CACHE_TTL` - the cache TTL (default: `60s`) -`LOG_LEVEL` - the log level (default: `info`) \ No newline at end of file +`LOG_LEVEL` - the log level (default: `info`) + +`BLOCK_SCHEMA_INTROSPECTION` - blocks the schema introspection (default: `false`) \ No newline at end of file diff --git a/go.mod b/go.mod index 88851f3..502e267 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/gookit/goutil v0.6.12 github.com/graphql-go/graphql v0.8.1 github.com/json-iterator/go v1.1.12 + github.com/k0kubun/pp v3.0.1+incompatible github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415 github.com/lukaszraczylo/go-simple-graphql v1.1.31 github.com/telegram-bot-app/libpack v0.0.0-20231008100411-9f7f8bf94315 @@ -20,6 +21,7 @@ require ( github.com/avast/retry-go/v4 v4.5.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gookit/color v1.5.4 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/lukaszraczylo/pandati v0.0.29 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 09d94cb..21a2618 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,10 @@ github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuM github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415 h1:lvI8Wlbg4PxkRcg2f10wgoaRpfN19v+YdRek3+dLtlM= diff --git a/graphql.go b/graphql.go index f973578..39776f6 100644 --- a/graphql.go +++ b/graphql.go @@ -4,10 +4,34 @@ import ( fiber "github.com/gofiber/fiber/v2" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/parser" + "github.com/k0kubun/pp" libpack_monitoring "github.com/telegram-bot-app/libpack/monitoring" ) -func parseGraphQLQuery(c *fiber.Ctx) (operationType, operationName string, cacheRequest bool) { +var retrospection_queries = []string{ + "__schema", + "__type", + "__typename", + "__directive", + "__directivelocation", + "__field", + "__inputvalue", + "__enumvalue", + "__typekind", + "__fieldtype", + "__inputobjecttype", + "__enumtype", + "__uniontype", + "__scalars", + "__objects", + "__interfaces", + "__unions", + "__enums", + "__inputobjects", + "__directives", +} + +func parseGraphQLQuery(c *fiber.Ctx) (operationType, operationName string, cacheRequest bool, should_block bool) { m := make(map[string]interface{}) err := json.Unmarshal(c.Body(), &m) if err != nil { @@ -44,7 +68,24 @@ func parseGraphQLQuery(c *fiber.Ctx) (operationType, operationName string, cache cacheRequest = true } } + if cfg.Security.BlockIntrospection { + for _, s := range oper.SelectionSet.Selections { + for _, s2 := range s.GetSelectionSet().Selections { + pp.Println(s2.(*ast.Field).Name.Value) + for _, introspection_query := range retrospection_queries { + if s2.(*ast.Field).Name.Value == introspection_query { + cfg.Logger.Warning("Introspection query blocked", m) + cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil) + c.Status(403).SendString("Introspection queries are not allowed") + should_block = true + return + } + } + } + } + } } } + return } diff --git a/main.go b/main.go index 1c93906..a412df3 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func parseConfig() { c.Client.JWTUserClaimPath = envutil.Getenv("JWT_USER_CLAIM_PATH", "") c.Cache.CacheEnable = envutil.GetBool("CACHE_ENABLE", false) c.Cache.CacheTTL = envutil.GetInt("CACHE_TTL", 60) + c.Security.BlockIntrospection = envutil.GetBool("BLOCK_SCHEMA_INTROSPECTION", false) c.Logger = libpack_logging.NewLogger() c.Client.GQLClient = graphql.NewConnection() c.Client.GQLClient.SetEndpoint(c.Server.HostGraphQL) diff --git a/server.go b/server.go index 68d9ecf..5116fc5 100644 --- a/server.go +++ b/server.go @@ -50,7 +50,11 @@ func processGraphQLRequest(c *fiber.Ctx) error { if authorization != nil && len(cfg.Client.JWTUserClaimPath) > 0 { extracted_user_id = extractClaimsFromJWTHeader(string(authorization)) } - opType, opName, cache_from_query := parseGraphQLQuery(c) + opType, opName, cache_from_query, should_block := parseGraphQLQuery(c) + + if should_block { + return nil + } was_cached := false diff --git a/struct_config.go b/struct_config.go index 5b290fe..493228c 100644 --- a/struct_config.go +++ b/struct_config.go @@ -29,4 +29,8 @@ type config struct { CacheTTL int CacheClient *cache.Cache } + + Security struct { + BlockIntrospection bool + } }