diff --git a/README.md b/README.md index b56bba9..b1ffc82 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ I wanted to monitor the queries and responses of our graphql endpoint. Still, we | `CACHE_TTL` | The cache TTL | `60` | | `LOG_LEVEL` | The log level | `info` | | `BLOCK_SCHEMA_INTROSPECTION`| Blocks the schema introspection | `false` | +| `ALLOWED_INTROSPECTION` | Allow only certain queries in introspection | `` | | `ENABLE_ACCESS_LOG` | Enable the access log | `false` | | `READ_ONLY_MODE` | Enable the read only mode | `false` | | `ALLOWED_URLS` | Allow access only to certain URLs | `/v1/graphql,/v1/version` | @@ -107,6 +108,15 @@ You can enable the read-only mode by setting the `READ_ONLY_MODE` environment va You can allow access only to certain URLs by setting the `ALLOWED_URLS` environment variable to a comma-separated list of URLs. If enabled - other URLs will return `403 Forbidden` error and request will **not** reach the proxied service. +### Blocking introspection + +You can block the schema introspection by setting the `BLOCK_SCHEMA_INTROSPECTION` environment variable to `true` - which will block all the queries with introspection parts, like: + +`__schema`, `__type`, `__typename`, `__directive`, `__directivelocation`, `__field`, `__inputvalue`, `__enumvalue`, `__typekind`, `__fieldtype`, `__inputobjecttype`, `__enumtype`, `__uniontype`, `__scalars`, `__objects`, `__interfaces`, `__unions`, `__enums`, `__inputobjects`, `__directives` + +If you'd like to keep blocking of the schema introspection on but allow one or more of from the list of above for any reason, you can use the `ALLOWED_INTROSPECTION` environment variable to specify the list of allowed queries. + +`ALLOWED_INTROSPECTION="__typename,__type"` ### Monitoring endpoint diff --git a/graphql.go b/graphql.go index 0898557..384396d 100644 --- a/graphql.go +++ b/graphql.go @@ -96,7 +96,15 @@ func parseGraphQLQuery(c *fiber.Ctx) (operationType, operationName string, cache if cfg.Security.BlockIntrospection { for _, s := range oper.SelectionSet.Selections { for _, s2 := range s.GetSelectionSet().Selections { - if _, exists := retrospectionQuerySet[s2.(*ast.Field).Name.Value]; exists { + if _, exists := retrospectionQuerySet[strings.ToLower(s2.(*ast.Field).Name.Value)]; exists { + if len(cfg.Security.IntrospectionAllowed) > 0 { + for _, introspectionQueryAllowed := range cfg.Security.IntrospectionAllowed { + if strings.EqualFold(strings.ToLower(introspectionQueryAllowed), strings.ToLower(s2.(*ast.Field).Name.Value)) { + cfg.Logger.Debug("Introspection query allowed, passing through", m) + return + } + } + } cfg.Logger.Warning("Introspection query blocked", m) cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil) c.Status(403).SendString("Introspection queries are not allowed") diff --git a/main.go b/main.go index 3f559a5..0f3a326 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,13 @@ func parseConfig() { c.Cache.CacheEnable = envutil.GetBool("ENABLE_GLOBAL_CACHE", false) c.Cache.CacheTTL = envutil.GetInt("CACHE_TTL", 60) c.Security.BlockIntrospection = envutil.GetBool("BLOCK_SCHEMA_INTROSPECTION", false) + c.Security.IntrospectionAllowed = func() []string { + urls := envutil.Getenv("ALLOWED_INTROSPECTION", "") + if urls == "" { + return nil + } + return strings.Split(urls, ",") + }() c.Logger = libpack_logging.NewLogger() c.Client.GQLClient = graphql.NewConnection() c.Client.GQLClient.SetEndpoint(c.Server.HostGraphQL) diff --git a/struct_config.go b/struct_config.go index b357955..4e48f8e 100644 --- a/struct_config.go +++ b/struct_config.go @@ -40,6 +40,7 @@ type config struct { } Security struct { - BlockIntrospection bool + BlockIntrospection bool + IntrospectionAllowed []string } }