mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-11 00:09:37 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3c0f9f49fd
|
|||
|
bf9ec2c877
|
|||
|
815a6841ed
|
@@ -30,6 +30,7 @@ I wanted to monitor the queries and responses of our graphql endpoint. Still, we
|
||||
| security | Blocking schema introspection |
|
||||
| security | Rate limiting queries based on user role |
|
||||
| security | Blocking mutations in read-only mode |
|
||||
| security | Allow access only to listed URLs |
|
||||
|
||||
|
||||
### Configuration
|
||||
@@ -49,6 +50,7 @@ I wanted to monitor the queries and responses of our graphql endpoint. Still, we
|
||||
| `BLOCK_SCHEMA_INTROSPECTION`| Blocks the schema introspection | `false` |
|
||||
| `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` |
|
||||
|
||||
|
||||
### Caching
|
||||
@@ -101,6 +103,11 @@ If rate limit has been reached - the proxy will return `429 Too Many Requests` e
|
||||
|
||||
You can enable the read-only mode by setting the `READ_ONLY_MODE` environment variable to `true` - which will block all the `mutation` queries.
|
||||
|
||||
### Allowing access to listed URLs
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Monitoring endpoint
|
||||
|
||||
Example metrics produced by the proxy:
|
||||
|
||||
@@ -12,9 +12,10 @@ require (
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415
|
||||
github.com/lukaszraczylo/go-ratecounter v0.1.8
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.31
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.32
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/valyala/fasthttp v1.50.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -34,7 +35,6 @@ require (
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
|
||||
@@ -40,8 +40,8 @@ github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415 h1:lvI8Wlbg4PxkR
|
||||
github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415/go.mod h1:M+UVdyqZs++xtEPrascaVmZdOMhCnxjZ2SgH+xHpR0c=
|
||||
github.com/lukaszraczylo/go-ratecounter v0.1.8 h1:ZYm6Wkn58ZAlFWRmC7PaD4oAYHWcu8/0MUDWGe3PnJQ=
|
||||
github.com/lukaszraczylo/go-ratecounter v0.1.8/go.mod h1:TqXEOCtFJStk1i0tkipprv1kiDHGon1MVUisjSTBSKM=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.31 h1:UA3f8M1cV+XnO8UZlAqveW0qF/2NN512eB/gRqe+BHs=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.31/go.mod h1:MyftQ8jTdtkYImPXJpHoxz6+E53Ydv+7q9+Jr+eT8WU=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.32 h1:udiz2hnLNO2b/hc9Z0xcjYt+HcFbChQQuIY4HZmky80=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.32/go.mod h1:c1nN/qtjsvpqpkBFsnBCYCDLdLMuWzy/zxeJzTjm5qg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/envutil"
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
@@ -33,6 +35,14 @@ func parseConfig() {
|
||||
c.Client.GQLClient.SetEndpoint(c.Server.HostGraphQL)
|
||||
c.Server.AccessLog = envutil.GetBool("ENABLE_ACCESS_LOG", false)
|
||||
c.Server.ReadOnlyMode = envutil.GetBool("READ_ONLY_MODE", false)
|
||||
c.Server.AllowURLs = func() []string {
|
||||
urls := envutil.Getenv("ALLOWED_URLS", "")
|
||||
if urls == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(urls, ",")
|
||||
}()
|
||||
c.Client.FastProxyClient = createFasthttpClient()
|
||||
cfg = &c
|
||||
enableCache() // takes close to no resources, but can be used with dynamic query cache
|
||||
loadRatelimitConfig()
|
||||
|
||||
@@ -2,26 +2,50 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/proxy"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
func createFasthttpClient() *fasthttp.Client {
|
||||
return &fasthttp.Client{
|
||||
Name: "graphql_proxy",
|
||||
NoDefaultUserAgentHeader: true,
|
||||
TLSConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
MaxConnsPerHost: 100,
|
||||
MaxIdleConnDuration: 2 * time.Minute,
|
||||
ReadTimeout: time.Second * 10,
|
||||
WriteTimeout: time.Second * 10,
|
||||
DisableHeaderNamesNormalizing: true,
|
||||
}
|
||||
}
|
||||
|
||||
func proxyTheRequest(c *fiber.Ctx) error {
|
||||
if !checkAllowedURLs(c) {
|
||||
cfg.Logger.Error("Request blocked", map[string]interface{}{"path": c.Path()})
|
||||
cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil)
|
||||
c.Status(403).SendString("Request blocked - not allowed URL")
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Request().Header.Add("X-Real-IP", c.IP())
|
||||
c.Request().Header.Add("X-Forwarded-For", c.IP())
|
||||
c.Request().Header.Add(fiber.HeaderXForwardedFor, string(c.Request().Header.Peek("X-Forwarded-For")))
|
||||
|
||||
proxy.WithTlsConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
proxy.WithClient(cfg.Client.FastProxyClient)
|
||||
|
||||
cfg.Logger.Debug("Proxying the request", map[string]interface{}{"path": c.Path(), "body": string(c.Request().Body())})
|
||||
err := proxy.DoRedirects(c, cfg.Server.HostGraphQL+c.Path(), 3)
|
||||
if err != nil {
|
||||
cfg.Logger.Error("Can't proxy the request", map[string]interface{}{"error": err.Error()})
|
||||
cfg.Monitoring.Increment(libpack_monitoring.MetricsFailed, nil)
|
||||
return err
|
||||
}
|
||||
cfg.Logger.Debug("Received proxied response", map[string]interface{}{"path": c.Path(), "response_body": string(c.Response().Body()), "response_code": c.Response().StatusCode()})
|
||||
|
||||
c.Response().Header.Del(fiber.HeaderServer)
|
||||
return nil
|
||||
|
||||
@@ -31,6 +31,18 @@ func StartHTTPProxy() {
|
||||
}
|
||||
}
|
||||
|
||||
func checkAllowedURLs(c *fiber.Ctx) bool {
|
||||
if len(cfg.Server.AllowURLs) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, allowedURL := range cfg.Server.AllowURLs {
|
||||
if c.Path() == allowedURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func healthCheck(c *fiber.Ctx) error {
|
||||
// query := `{ __typename }`
|
||||
// _, err := cfg.Client.GQLClient.Query(query, nil, nil)
|
||||
@@ -131,6 +143,7 @@ func logAndMonitorRequest(c *fiber.Ctx, userID, opType, opName string, wasCached
|
||||
if cfg.Server.AccessLog {
|
||||
cfg.Logger.Info("Request processed", map[string]interface{}{
|
||||
"ip": c.IP(),
|
||||
"fwd-ip": string(c.Request().Header.Peek("X-Forwarded-For")),
|
||||
"user_id": userID,
|
||||
"op_type": opType,
|
||||
"op_name": opName,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// config is a struct that holds the configuration of the application.
|
||||
@@ -19,6 +20,7 @@ type config struct {
|
||||
HostGraphQL string
|
||||
AccessLog bool
|
||||
ReadOnlyMode bool
|
||||
AllowURLs []string
|
||||
}
|
||||
|
||||
Client struct {
|
||||
@@ -27,6 +29,8 @@ type config struct {
|
||||
RoleRateLimit bool
|
||||
RoleFromHeader string
|
||||
GQLClient *graphql.BaseClient
|
||||
FastProxyClient *fasthttp.Client
|
||||
proxy string
|
||||
}
|
||||
|
||||
Cache struct {
|
||||
|
||||
Reference in New Issue
Block a user