mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-12 00:19:36 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b1af9cafe
|
|||
|
5f44ce797a
|
|||
|
1b949583c7
|
|||
|
16f29488c5
|
|||
|
5ca37fc9fb
|
|||
|
ed1de61e2e
|
|||
|
e7b2cc1deb
|
|||
|
3ac7c115aa
|
|||
|
eee6016b5a
|
|||
|
3b8df8ee76
|
|||
|
f9e917f2ea
|
|||
|
8673f1caf8
|
@@ -0,0 +1,2 @@
|
||||
github: [ lukaszraczylo ]
|
||||
custom: [ monzo.me/lukaszraczylo ]
|
||||
@@ -4,9 +4,11 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**/**.md'
|
||||
- '**/**.yaml'
|
||||
- 'static/**'
|
||||
branches:
|
||||
- "*"
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
shared:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Lukasz Raczylo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -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
|
||||
|
||||
@@ -2,21 +2,63 @@
|
||||
|
||||
Creates a passthrough proxy to a graphql endpoint(s), allowing you for analysis of the queries and responses, producing the prometheus metrics at a fraction of the cost - because as we know - $0 is a fair price.
|
||||
|
||||
This project is in active use by [telegram-bot.app](https://telegram-bot.app), and was tested with 30k queries per second on a single instance, consuming 10mb of RAM and 0.1% CPU.
|
||||
|
||||

|
||||
|
||||
You can find the example of the kubernetes manifest in the [example deployment](static/kubernetes-deployment.yaml) file.
|
||||
|
||||
### Why this project exists
|
||||
|
||||
I wanted to monitor the queries and responses of our graphql endpoint, but we didn't want to pay the price of the graphql server itself ( and I will not point fingers and certain well-known project), as monitoring and basic security features should be a common, free functionality.
|
||||
|
||||
### Endpoints
|
||||
|
||||
/v1/graphql - the graphql endpoint
|
||||
/metrics - the prometheus metrics endpoint
|
||||
/healthz - the healthcheck endpoint
|
||||
* `:8080/v1/graphql` - the graphql endpoint
|
||||
* `:9393/metrics` - the prometheus metrics endpoint
|
||||
* `:8080/healthz` - the healthcheck endpoint
|
||||
|
||||
### Features
|
||||
|
||||
* MONITORING: Prometheus / VictoriaMetrics metrics
|
||||
* MONITORING: Extracting user id from JWT token and adding it as a label to the metrics
|
||||
* MONITORING: Extracting the query name and type and adding it as a label to the metrics
|
||||
* MONITORING: Calculating the query duration and adding it to the metrics
|
||||
* SPEED: Caching the queries
|
||||
* SECURITY: Blocking schema introspection
|
||||
|
||||
### Configuration
|
||||
|
||||
`MONITORING_PORT` - the port to expose the metrics endpoint on (default: 9393)
|
||||
`PORT_GRAPHQL` - the port to expose the graphql endpoint on (default: 8080)
|
||||
`HOST_GRAPHQL` - the host to proxy the graphql endpoint to (default: `localhost/v1/graphql`)
|
||||
* `MONITORING_PORT` - the port to expose the metrics endpoint on (default: 9393)
|
||||
* `PORT_GRAPHQL` - the port to expose the graphql endpoint on (default: 8080)
|
||||
* `HOST_GRAPHQL` - the host to proxy the graphql endpoint to (default: `http://localhost/v1/graphql`)
|
||||
* `JWT_USER_CLAIM_PATH` - the path to the user claim in the JWT token (default: ``)
|
||||
* `ENABLE_GLOBAL_CACHE` - enable the cache (default: `false`)
|
||||
* `CACHE_TTL` - the cache TTL (default: `60s`)
|
||||
* `LOG_LEVEL` - the log level (default: `info`)
|
||||
* `BLOCK_SCHEMA_INTROSPECTION` - blocks the schema introspection (default: `false`)
|
||||
* `ENABLE_ACCESS_LOG` - enable the access log (default: `false`)
|
||||
|
||||
`JWT_USER_CLAIM_PATH` - the path to the user claim in the JWT token (default: ``)
|
||||
### Caching
|
||||
|
||||
`ENABLE_CACHE` - enable the cache (default: `false`)
|
||||
`CACHE_TTL` - the cache TTL (default: `60s`)
|
||||
Cache engine is enabled in background as it does not use any additional resources.
|
||||
You can then start using the cache by setting the `ENABLE_GLOBAL_CACHE` environment variable to `true` - which will enable the cache for all queries, without introspection of the query. You can leave the global cache disabled and enable the cache for specific queries by adding the `@cache` directive to the query.
|
||||
|
||||
`LOG_LEVEL` - the log level (default: `info`)
|
||||
### Monitoring endpoint
|
||||
|
||||
Example metrics produced by the proxy:
|
||||
|
||||
```
|
||||
graphql_proxy_timed_query_bucket{cached="false",user_id="-",op_type="mutation",op_name="updateUserDetails",vmrange="1.000e-02...1.136e-02"} 6
|
||||
graphql_proxy_timed_query_count{op_name="",cached="false",user_id="-",op_type=""} 78
|
||||
graphql_proxy_timed_query_bucket{op_name="MyQuery",cached="false",user_id="-",op_type="query",vmrange="5.995e+00...6.813e+00"} 1
|
||||
graphql_proxy_timed_query_sum{op_name="MyQuery",cached="false",user_id="-",op_type="query"} 6
|
||||
graphql_proxy_timed_query_count{op_name="MyQuery",cached="false",user_id="-",op_type="query"} 1
|
||||
graphql_proxy_executed_query{user_id="-",op_type="mutation",op_name="updateKnownSpammer",cached="false"} 1486
|
||||
graphql_proxy_executed_query{user_id="-",op_type="query",op_name="checkIfAdminsNeedRefreshing",cached="false"} 13167
|
||||
graphql_proxy_executed_query{user_id="1337",op_type="query",op_name="checkIfKnownMedia",cached="false"} 429
|
||||
graphql_proxy_executed_query{user_id="-",op_type="query",op_name="checkIfSpamAIRequiresUpdate",cached="false"} 8891
|
||||
graphql_proxy_requests_failed 324
|
||||
graphql_proxy_requests_skipped 0
|
||||
graphql_proxy_requests_succesful 454823
|
||||
```
|
||||
@@ -17,7 +17,7 @@ func enableCache() {
|
||||
var err error
|
||||
cfg.Cache.CacheClient = cache.New(time.Duration(cfg.Cache.CacheTTL) * time.Second * 2)
|
||||
if err != nil {
|
||||
fmt.Println(">> Error while creating cache client;", "error", err.Error())
|
||||
cfg.Logger.Critical("Can't create cache client", map[string]interface{}{"error": err.Error()})
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,20 @@ 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/telegram-bot-app/libpack v0.0.0-20231007021518-909ce2741a36
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.1.31
|
||||
github.com/telegram-bot-app/libpack v0.0.0-20231008100411-9f7f8bf94315
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/VictoriaMetrics/metrics v1.24.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
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
|
||||
@@ -35,6 +39,7 @@ require (
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/wI2L/jsondiff v0.4.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/net v0.16.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
|
||||
@@ -6,6 +6,8 @@ github.com/akyoto/cache v1.0.6 h1:5XGVVYoi2i+DZLLPuVIXtsNIJ/qaAM16XT0LaBaXd2k=
|
||||
github.com/akyoto/cache v1.0.6/go.mod h1:WfxTRqKhfgAG71Xh6E3WLpjhBtZI37O53G4h5s+3iM4=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/avast/retry-go/v4 v4.5.0 h1:QoRAZZ90cj5oni2Lsgl2GW8mNTnUCnmpx/iKpwVisHg=
|
||||
github.com/avast/retry-go/v4 v4.5.0/go.mod h1:7hLEXp0oku2Nir2xBAsg0PTphp9z71bN5Aq1fboC3+I=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
@@ -26,10 +28,16 @@ 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=
|
||||
github.com/lukaszraczylo/ask v0.0.0-20230927103145-2ff1123b4415/go.mod h1:M+UVdyqZs++xtEPrascaVmZdOMhCnxjZ2SgH+xHpR0c=
|
||||
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/pandati v0.0.29 h1:WUEWm1+hWjE5KJbIL8OctG00x2dk4XKGJSlrjhxZ55k=
|
||||
github.com/lukaszraczylo/pandati v0.0.29/go.mod h1:+DyTWKFaXd+jIfe7GW5w2S5PyTko/RXxMyOa+Vl713A=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@@ -59,8 +67,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/telegram-bot-app/lib-logging v0.0.19 h1:zbyFr2ygeBY+yuaB9moXyOGk8dIBCn0jPJQjvx7YvLE=
|
||||
github.com/telegram-bot-app/lib-logging v0.0.19/go.mod h1:n8d29fRUTdgJhC4RZ8s4lP2RHiGCCRYEj2ENEClUGc8=
|
||||
github.com/telegram-bot-app/libpack v0.0.0-20231007021518-909ce2741a36 h1:DqXg0y57Q7BziHDu85OXgo/b8OlP7/+gDZvASQCkaW0=
|
||||
github.com/telegram-bot-app/libpack v0.0.0-20231007021518-909ce2741a36/go.mod h1:W2kWHcfNNS0r++dJ1T2XX/C4cTSxI3MsoiMbOtyqu+I=
|
||||
github.com/telegram-bot-app/libpack v0.0.0-20231008100411-9f7f8bf94315 h1:gf+3gFgtdh48RQNmLNdK1IcGqpuTuj6RAdHxDMd/YPY=
|
||||
github.com/telegram-bot-app/libpack v0.0.0-20231008100411-9f7f8bf94315/go.mod h1:W2kWHcfNNS0r++dJ1T2XX/C4cTSxI3MsoiMbOtyqu+I=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
@@ -77,6 +85,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
+46
-2
@@ -7,7 +7,33 @@ import (
|
||||
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",
|
||||
}
|
||||
|
||||
// Saving the introspection queries as a map O(1) operation instead of O(n) for a slice.
|
||||
var retrospectionQuerySet = make(map[string]struct{}, len(retrospection_queries))
|
||||
|
||||
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 {
|
||||
@@ -34,13 +60,31 @@ func parseGraphQLQuery(c *fiber.Ctx) (operationType, operationName string, cache
|
||||
for _, d := range p.Definitions {
|
||||
if oper, ok := d.(*ast.OperationDefinition); ok {
|
||||
operationType = oper.Operation
|
||||
operationName = oper.Name.Value
|
||||
if oper.Name != nil {
|
||||
operationName = oper.Name.Value
|
||||
} else {
|
||||
operationName = "undefined"
|
||||
}
|
||||
for _, dir := range oper.Directives {
|
||||
if dir.Name.Value == "cached" {
|
||||
cacheRequest = true
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,22 +2,33 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gookit/goutil/envutil"
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
libpack_config "github.com/telegram-bot-app/libpack/config"
|
||||
libpack_logging "github.com/telegram-bot-app/libpack/logging"
|
||||
)
|
||||
|
||||
var cfg *config
|
||||
|
||||
func init() {
|
||||
for _, query := range retrospection_queries {
|
||||
retrospectionQuerySet[query] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfig() {
|
||||
libpack_config.PKG_NAME = "graphql_proxy"
|
||||
var c config
|
||||
c.Server.PortGraphQL = envutil.GetInt("PORT_GRAPHQL", 8080)
|
||||
c.Server.PortMonitoring = envutil.GetInt("MONITORING_PORT", 9393)
|
||||
c.Server.HostGraphQL = envutil.Getenv("HOST_GRAPHQL", "localhost/v1/graphql")
|
||||
c.Server.HostGraphQL = envutil.Getenv("HOST_GRAPHQL", "http://localhost/v1/graphql")
|
||||
c.Client.JWTUserClaimPath = envutil.Getenv("JWT_USER_CLAIM_PATH", "")
|
||||
c.Cache.CacheEnable = envutil.GetBool("CACHE_ENABLE", false)
|
||||
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.Logger = libpack_logging.NewLogger()
|
||||
c.Client.GQLClient = graphql.NewConnection()
|
||||
c.Client.GQLClient.SetEndpoint(c.Server.HostGraphQL)
|
||||
c.Server.AccessLog = envutil.GetBool("ENABLE_ACCESS_LOG", false)
|
||||
cfg = &c
|
||||
enableCache() // takes close to no resources, but can be used with dynamic query cache
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/proxy"
|
||||
@@ -19,7 +18,7 @@ func proxyTheRequest(c *fiber.Ctx) error {
|
||||
|
||||
err := proxy.DoRedirects(c, cfg.Server.HostGraphQL, 3)
|
||||
if err != nil {
|
||||
fmt.Println("Can't proxy the request: ", err)
|
||||
cfg.Logger.Error("Can't proxy the request", map[string]interface{}{"error": err.Error()})
|
||||
cfg.Monitoring.Increment(libpack_monitoring.MetricsFailed, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ version: 1
|
||||
force:
|
||||
existing: true
|
||||
strict: false
|
||||
minor: 1
|
||||
wording:
|
||||
patch:
|
||||
- update
|
||||
|
||||
@@ -25,12 +25,19 @@ func StartHTTPProxy() {
|
||||
server.Get("/healthz", healthCheck)
|
||||
err := server.Listen(fmt.Sprintf(":%d", cfg.Server.PortGraphQL))
|
||||
if err != nil {
|
||||
fmt.Println("Can't start the service: ", err)
|
||||
cfg.Logger.Critical("Can't start the service", map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
|
||||
func healthCheck(c *fiber.Ctx) error {
|
||||
return c.SendString("OK")
|
||||
// query := `{ __typename }`
|
||||
// _, err := cfg.Client.GQLClient.Query(query, nil, nil)
|
||||
// if err != nil {
|
||||
// cfg.Logger.Error("Can't reach the GraphQL server", map[string]interface{}{"error": err.Error()})
|
||||
// cfg.Monitoring.Increment(libpack_monitoring.MetricsFailed, nil)
|
||||
// return c.SendStatus(500)
|
||||
// }
|
||||
return c.SendStatus(200)
|
||||
}
|
||||
|
||||
func processGraphQLRequest(c *fiber.Ctx) error {
|
||||
@@ -43,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
|
||||
|
||||
@@ -66,7 +77,9 @@ func processGraphQLRequest(c *fiber.Ctx) error {
|
||||
}
|
||||
time_taken := time.Since(t)
|
||||
|
||||
cfg.Logger.Info("Request processed", map[string]interface{}{"ip": c.IP(), "user_id": extracted_user_id, "op_type": opType, "op_name": opName, "time": time_taken, "cache": was_cached})
|
||||
if cfg.Server.AccessLog {
|
||||
cfg.Logger.Info("Request processed", map[string]interface{}{"ip": c.IP(), "user_id": extracted_user_id, "op_type": opType, "op_name": opName, "time": time_taken, "cache": was_cached})
|
||||
}
|
||||
cfg.Monitoring.Increment(libpack_monitoring.MetricsSucceeded, nil)
|
||||
|
||||
labels := map[string]string{
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: hasura-proxy-internal
|
||||
labels:
|
||||
app: hasura-proxy-internal
|
||||
type: support
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hasura-proxy-internal
|
||||
type: support
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hasura-proxy-internal
|
||||
type: support
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9393"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
securityContext:
|
||||
runAsUser: 65534 # nobody
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/worker
|
||||
operator: Exists
|
||||
containers:
|
||||
- name: graphql-proxy
|
||||
image: ghcr.io/lukaszraczylo/graphql-monitoring-proxy:latest
|
||||
imagePullPolicy: Always
|
||||
resources:
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "640Mi"
|
||||
requests:
|
||||
cpu: "0.75"
|
||||
memory: "512Mi"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
ports:
|
||||
- name: web
|
||||
containerPort: 8080
|
||||
- name: monitoring
|
||||
containerPort: 9393
|
||||
env:
|
||||
- name: PORT_GRAPHQL
|
||||
value: "8080"
|
||||
- name: MONITORING_PORT
|
||||
value: "9393"
|
||||
- name: HOST_GRAPHQL
|
||||
value: http://hasura-internal:8080/v1/graphql
|
||||
- name: ENABLE_GLOBAL_CACHE
|
||||
value: "true"
|
||||
- name: CACHE_TTL
|
||||
value: "10"
|
||||
- name: BLOCK_SCHEMA_INTROSPECTION
|
||||
value: "true"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: hasura-proxy-internal
|
||||
labels:
|
||||
app: hasura-proxy-internal
|
||||
type: support
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9393"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
- name: monitoring
|
||||
port: 9393
|
||||
targetPort: 9393
|
||||
selector:
|
||||
app: hasura-proxy-internal
|
||||
type: support
|
||||
type: ClusterIP
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 336 KiB |
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/akyoto/cache"
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
libpack_logging "github.com/telegram-bot-app/libpack/logging"
|
||||
libpack_monitoring "github.com/telegram-bot-app/libpack/monitoring"
|
||||
)
|
||||
@@ -16,10 +17,12 @@ type config struct {
|
||||
PortGraphQL int
|
||||
PortMonitoring int
|
||||
HostGraphQL string
|
||||
AccessLog bool
|
||||
}
|
||||
|
||||
Client struct {
|
||||
JWTUserClaimPath string
|
||||
GQLClient *graphql.BaseClient
|
||||
}
|
||||
|
||||
Cache struct {
|
||||
@@ -27,4 +30,8 @@ type config struct {
|
||||
CacheTTL int
|
||||
CacheClient *cache.Cache
|
||||
}
|
||||
|
||||
Security struct {
|
||||
BlockIntrospection bool
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user