Compare commits

...

22 Commits

Author SHA1 Message Date
lukaszraczylo 19b3b3e596 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-12 03:15:48 +00:00
lukaszraczylo 5852a4c356 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-11 03:14:53 +00:00
lukaszraczylo e814345069 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-10 03:17:22 +00:00
lukaszraczylo 984e448ff0 fixup! fixup! Fixes the code for additional test cases. 2024-12-06 13:27:59 +00:00
lukaszraczylo 5799f8ca7c fixup! Fixes the code for additional test cases. 2024-12-06 13:22:18 +00:00
lukaszraczylo ac84c69812 Fixes the code for additional test cases. 2024-12-06 12:54:36 +00:00
lukaszraczylo e54bbe8249 Additional tests to ensure that schema introspection is working as expected 2024-12-06 12:03:37 +00:00
lukaszraczylo ed3966e577 If the field is allowed, continue checking remaining fields. 2024-12-06 11:58:34 +00:00
lukaszraczylo 6a52a9f673 Fixes the issue with case comparison. 2024-12-06 11:49:47 +00:00
lukaszraczylo 1ca05a7a2a Release 0.24.x - changes the query introspection. 2024-12-06 11:27:01 +00:00
lukaszraczylo eb1b4b4eb7 Enhance the tests to cover the end status code as well. 2024-12-06 11:15:38 +00:00
lukaszraczylo fc9bab47fb Fix query introspection blocking on deeply nested types. 2024-12-06 11:04:26 +00:00
lukaszraczylo cbe2afe539 Gather cleaner event errors and display as a group rather than separately. 2024-12-06 09:39:26 +00:00
lukaszraczylo 2190744729 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-06 03:14:57 +00:00
lukaszraczylo 0a96d139b6 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-12-05 03:15:42 +00:00
lukaszraczylo 1c1ac06e11 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-10 03:06:36 +00:00
lukaszraczylo b2a67df3b6 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-09 03:03:58 +00:00
lukaszraczylo 3805e63f95 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-08 03:04:48 +00:00
lukaszraczylo 8abf731867 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-29 03:08:04 +00:00
lukaszraczylo 4e9db9a5c7 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-18 03:07:24 +00:00
lukaszraczylo 615836ab36 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-15 03:08:28 +00:00
lukaszraczylo a51f37c0a2 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-12 03:04:33 +00:00
9 changed files with 425 additions and 90 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ help: ## display this help
.PHONY: run
run: build ## run application
@LOG_LEVEL=debug PURGE_METRICS_ON_CRAWL=true BLOCK_SCHEMA_INTROSPECTION=false CACHE_TTL=10 JWT_ROLE_RATE_LIMIT=false JWT_ROLE_CLAIM_PATH="Hasura.x-hasura-default-role" JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/ HEALTHCHECK_GRAPHQL_URL=https://hasura8.lan/v1/graphql ./graphql-proxy
@LOG_LEVEL=debug PURGE_METRICS_ON_CRAWL=true BLOCK_SCHEMA_INTROSPECTION=true CACHE_TTL=10 JWT_ROLE_RATE_LIMIT=false JWT_ROLE_CLAIM_PATH="Hasura.x-hasura-default-role" JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/ HEALTHCHECK_GRAPHQL_URL=https://hasura8.lan/v1/graphql PORT_GRAPHQL=8111 ./graphql-proxy
.PHONY: build
build: ## build the binary
+2
View File
@@ -40,6 +40,8 @@ I wanted to monitor the queries and responses of our graphql endpoint. Still, we
You should always try to stick to the latest and greatest version of the graphql-proxy to ensure that it's as much bug-free as possible. Following list will be kept to the maximum of five "most important" bugs and enhancements included in the latest versions.
* **06/12/2024 - 0.25.12** - Fixes the bug where deeply nested introspection queries were blocked despite of being present on the whitelist. GraphQL proxy will now inspect the queries in depth to find any possible nested introspections.
* **20/08/2024 - 0.23.21+** - Fixes the bug when timeouts were not respected on proxy-graphql line. Affected versions before that were timeouting after 30 seconds which was set as default ( thanks to Jurica Železnjak for reporting ). It also provides a temporary fix for running within kubernetes deployment, when graphql server ( for example - hasura ) took more time to start than the proxy, causing avalanche of errors with "can't proxy the request".
* **19/08/2024 - 0.21.82+** - Fixed the issue when proxy failed to start if global cache was disabled, therefore not initialized and proxy tried to perform the cache operations during normal query operations.
+19 -4
View File
@@ -71,13 +71,14 @@ func enableHasuraEventCleaner() {
func cleanEvents(pool *pgxpool.Pool) {
ctx := context.Background()
var errors []error
var failedQueries []string
for _, query := range delQueries {
_, err := pool.Exec(ctx, fmt.Sprintf(query, cfg.HasuraEventCleaner.ClearOlderThan))
if err != nil {
cfg.Logger.Error(&libpack_logger.LogMessage{
Message: "Failed to execute query",
Pairs: map[string]interface{}{"query": query, "error": err.Error()},
})
errors = append(errors, err)
failedQueries = append(failedQueries, query)
} else {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Successfully executed query",
@@ -85,4 +86,18 @@ func cleanEvents(pool *pgxpool.Pool) {
})
}
}
if len(errors) > 0 {
var errMsgs []string
for _, err := range errors {
errMsgs = append(errMsgs, err.Error())
}
cfg.Logger.Error(&libpack_logger.LogMessage{
Message: "Failed to execute some queries",
Pairs: map[string]interface{}{
"failed_queries": failedQueries,
"errors": errMsgs,
},
})
}
}
+12 -12
View File
@@ -6,19 +6,19 @@ require (
github.com/VictoriaMetrics/metrics v1.35.1
github.com/alicebob/miniredis/v2 v2.33.0
github.com/avast/retry-go/v4 v4.6.0
github.com/goccy/go-json v0.10.3
github.com/goccy/go-json v0.10.4
github.com/gofiber/fiber/v2 v2.52.5
github.com/gofrs/flock v0.12.1
github.com/google/uuid v1.6.0
github.com/gookit/goutil v0.6.17
github.com/gookit/goutil v0.6.18
github.com/graphql-go/graphql v0.8.1
github.com/jackc/pgx/v5 v5.7.1
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9
github.com/lukaszraczylo/go-ratecounter v0.1.12
github.com/lukaszraczylo/go-simple-graphql v1.2.29
github.com/redis/go-redis/v9 v9.6.1
github.com/lukaszraczylo/go-simple-graphql v1.2.33
github.com/redis/go-redis/v9 v9.7.0
github.com/stretchr/testify v1.9.0
github.com/valyala/fasthttp v1.56.0
github.com/valyala/fasthttp v1.58.0
)
require (
@@ -31,7 +31,7 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.17.10 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -45,11 +45,11 @@ require (
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+24 -24
View File
@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
@@ -32,8 +32,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/goutil v0.6.17 h1:SxmbDz2sn2V+O+xJjJhJT/sq1/kQh6rCJ7vLBiRPZjI=
github.com/gookit/goutil v0.6.17/go.mod h1:rSw1LchE1I3TDWITZvefoAC9tS09SFu3lHXLCV7EaEY=
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -44,8 +44,8 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -54,8 +54,8 @@ github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9 h1:pL8B9mjv6RPUf
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9/go.mod h1:M+UVdyqZs++xtEPrascaVmZdOMhCnxjZ2SgH+xHpR0c=
github.com/lukaszraczylo/go-ratecounter v0.1.12 h1:VO6hHYGw/Jy9JUizXf/bS0AI2QX1ueWWAWckMFVJ/w4=
github.com/lukaszraczylo/go-ratecounter v0.1.12/go.mod h1:TqXEOCtFJStk1i0tkipprv1kiDHGon1MVUisjSTBSKM=
github.com/lukaszraczylo/go-simple-graphql v1.2.29 h1:Fo/3SN4vrST1pyX1UJ5Nd+pQCkurZNJSck4pyx5B/Fk=
github.com/lukaszraczylo/go-simple-graphql v1.2.29/go.mod h1:kCvRu01tLxj0iKash5qwL7Em+SltQmZ82bs0yu2aOrk=
github.com/lukaszraczylo/go-simple-graphql v1.2.33 h1:77znJSjPCCbJg8qBjGrx+uRLn/JncBKDDPOwO3I64DM=
github.com/lukaszraczylo/go-simple-graphql v1.2.33/go.mod h1:UOhnDW0SGIQglxwCbHLzpCQZ755JAUdQUa6v/c+V3dU=
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=
@@ -65,8 +65,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -79,8 +79,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW8U=
github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
@@ -93,22 +93,22 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+66 -48
View File
@@ -27,12 +27,18 @@ var (
)
func prepareQueriesAndExemptions() {
introspectionAllowedQueries = make(map[string]struct{})
allowedUrls = make(map[string]struct{})
// Process allowed introspection queries
for _, q := range cfg.Security.IntrospectionAllowed {
introspectionAllowedQueries[strings.ToLower(q)] = struct{}{}
cleanQuery := strings.Trim(strings.TrimSpace(q), `"`)
introspectionAllowedQueries[strings.ToLower(cleanQuery)] = struct{}{}
}
// Process allowed URLs
for _, u := range cfg.Server.AllowURLs {
allowedUrls[u] = struct{}{}
allowedUrls[u] = struct{}{}
}
}
@@ -80,10 +86,6 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
if ifNotInTest() {
cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil)
}
if res.shouldBlock {
resultPool.Put(res)
return res
}
return res
}
@@ -96,7 +98,6 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
if ifNotInTest() {
cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil)
}
resultPool.Put(res)
return res
}
@@ -109,7 +110,6 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
if ifNotInTest() {
cfg.Monitoring.Increment(libpack_monitoring.MetricsFailed, nil)
}
resultPool.Put(res)
return res
}
@@ -126,21 +126,11 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
}
if cfg.Server.HostGraphQLReadOnly != "" {
if res.operationType == "" {
res.activeEndpoint = cfg.Server.HostGraphQLReadOnly
} else if res.operationType != "mutation" {
if res.operationType == "" || res.operationType != "mutation" {
res.activeEndpoint = cfg.Server.HostGraphQLReadOnly
}
}
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Endpoint selection",
Pairs: map[string]interface{}{
"operationType": res.operationType,
"selectedEndpoint": res.activeEndpoint,
},
})
if res.operationType == "mutation" && cfg.Server.ReadOnlyMode {
cfg.Logger.Warning(&libpack_logger.LogMessage{
Message: "Mutation blocked - server in read-only mode",
@@ -174,8 +164,9 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
}
if cfg.Security.BlockIntrospection {
res.shouldBlock = checkSelections(c, oper.GetSelectionSet().Selections)
if res.shouldBlock {
if checkSelections(c, oper.GetSelectionSet().Selections) {
_ = c.Status(403).SendString("Introspection queries are not allowed")
res.shouldBlock = true
resultPool.Put(res)
return res
}
@@ -186,43 +177,70 @@ func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
}
func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool {
stack := make([]ast.Selection, len(selections))
copy(stack, selections)
for len(stack) > 0 {
var s ast.Selection
s, stack = stack[len(stack)-1], stack[:len(stack)-1]
if field, ok := s.(*ast.Field); ok {
if checkIfContainsIntrospection(c, field.Name.Value) {
return true
for _, s := range selections {
switch sel := s.(type) {
case *ast.Field:
fieldName := strings.ToLower(sel.Name.Value)
if _, exists := introspectionQueries[fieldName]; exists {
if len(cfg.Security.IntrospectionAllowed) > 0 {
_, allowed := introspectionAllowedQueries[fieldName]
if !allowed {
return true // Block if this field isn't allowed
}
// Even if this field is allowed, we need to check its nested selections
} else {
return true // Block if no allowlist exists
}
}
// Always check nested selections
if sel.SelectionSet != nil {
if checkSelections(c, sel.GetSelectionSet().Selections) {
return true
}
}
case *ast.InlineFragment:
if sel.SelectionSet != nil {
if checkSelections(c, sel.GetSelectionSet().Selections) {
return true
}
}
}
if field.SelectionSet != nil {
stack = append(stack, field.GetSelectionSet().Selections...)
}
}
}
return false
}
func checkIfContainsIntrospection(c *fiber.Ctx, whatever string) bool {
whateverLower := strings.ToLower(whatever)
if _, exists := introspectionQueries[whateverLower]; exists {
if len(cfg.Security.IntrospectionAllowed) > 0 {
if _, allowed := introspectionAllowedQueries[whateverLower]; allowed {
cfg.Logger.Debug(&libpack_logger.LogMessage{
Message: "Introspection query allowed, passing through",
Pairs: map[string]interface{}{"query": whatever},
})
return false
func checkIfContainsIntrospection(c *fiber.Ctx, query string) bool {
blocked := false
// Try parsing as a complete query first
p, err := parser.Parse(parser.ParseParams{Source: query})
if err == nil {
// It's a complete query, check all selections
for _, def := range p.Definitions {
if op, ok := def.(*ast.OperationDefinition); ok {
if op.SelectionSet != nil {
blocked = checkSelections(c, op.GetSelectionSet().Selections)
}
}
}
} else {
// Not a complete query, check as a field name
whateverLower := strings.ToLower(query)
if _, exists := introspectionQueries[whateverLower]; exists {
if len(cfg.Security.IntrospectionAllowed) > 0 {
if _, allowed := introspectionAllowedQueries[whateverLower]; !allowed {
blocked = true
}
} else {
blocked = true
}
}
}
if blocked {
if ifNotInTest() {
cfg.Monitoring.Increment(libpack_monitoring.MetricsSkipped, nil)
}
_ = c.Status(403).SendString("Introspection queries are not allowed")
return true
}
return false
return blocked
}
+179
View File
@@ -3,8 +3,12 @@ package main
import (
"fmt"
"strings"
"testing"
"github.com/goccy/go-json"
fiber "github.com/gofiber/fiber/v2"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/parser"
"github.com/valyala/fasthttp"
)
@@ -408,6 +412,8 @@ func (suite *Tests) Test_checkIfContainsIntrospection() {
{"allowed introspection", "__schema", []string{"__schema"}, false},
{"disallowed introspection", "__type", []string{"__schema"}, true},
{"non-introspection query", "normalQuery", []string{}, false},
{"allowed introspection with deep nesting of __typename", "{__schema {queryType {fields {name description __typename}}}}", []string{"__schema", "__typename"}, false},
{"disallowed introspection with deep nesting of __typename", "{__type {queryType {fields {name description __typename}}}}", []string{"__type"}, true},
}
for _, tt := range tests {
@@ -430,3 +436,176 @@ func createTestContext(body string) *fiber.Ctx {
ctx.Request().SetBody([]byte(body))
return ctx
}
func (suite *Tests) Test_DeepIntrospectionQueries() {
tests := []struct {
name string
query string
allowed []string
expected bool
}{
{
name: "deeply nested single introspection",
query: "query { users { profiles { settings { preferences { __typename } } } } }",
allowed: []string{},
expected: true,
},
{
name: "multiple nested introspections",
query: "query { users { __typename profiles { __schema settings { __type } } } }",
allowed: []string{},
expected: true,
},
{
name: "nested with selective allowlist",
query: "query { users { __typename profiles { __schema settings { __type } } } }",
allowed: []string{"__typename"},
expected: true,
},
{
name: "deeply nested with full allowlist",
query: "query { users { __typename profiles { __schema settings { __type } } } }",
allowed: []string{"__typename", "__schema", "__type"},
expected: false,
},
{
name: "deeply nested with repeated item from allowlist",
query: "query PreloadStaticData {\n scenario {\n id\n name\n __typename\n }\n impact {\n id\n description\n __typename\n }\n likelihood {\n id\n description\n __typename\n }\n consequence {\n name\n __typename\n }\n risk_categories {\n name\n abbreviation\n __typename\n }\n mitigation {\n name\n __typename\n }\n}",
allowed: []string{"__type", "__typename"},
expected: false,
},
{
name: "deeply nested with repeated item denied",
query: "query PreloadStaticData {\n scenario {\n id\n name\n __typename\n }\n impact {\n id\n description\n __typename\n }\n likelihood {\n id\n description\n __typename\n }\n consequence {\n name\n __typename\n }\n risk_categories {\n name\n abbreviation\n __typename\n }\n mitigation {\n name\n __typename\n }\n}",
allowed: []string{},
expected: true,
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
cfg.Security.BlockIntrospection = true
cfg.Security.IntrospectionAllowed = tt.allowed
introspectionAllowedQueries = make(map[string]struct{})
for _, q := range tt.allowed {
introspectionAllowedQueries[strings.ToLower(q)] = struct{}{}
}
body := map[string]interface{}{
"query": tt.query,
}
bodyBytes, _ := json.Marshal(body)
ctx := fiber.New().AcquireCtx(&fasthttp.RequestCtx{})
ctx.Request().SetBody(bodyBytes)
parseGraphQLQuery(ctx)
if tt.expected {
suite.Equal(403, ctx.Response().StatusCode())
} else {
suite.Equal(200, ctx.Response().StatusCode())
}
})
}
}
func TestIntrospectionQueryHandling(t *testing.T) {
tests := []struct {
name string
blockIntrospection bool
allowedQueries []string
query string
wantBlocked bool
}{
{
name: "allows __typename when in allowed list",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
users {
id
name
__typename
}
}`,
wantBlocked: false,
},
{
name: "case insensitive matching for allowed queries",
blockIntrospection: true,
allowedQueries: []string{"__TYPENAME"},
query: `{
users {
__typename
}
}`,
wantBlocked: false,
},
{
name: "blocks other introspection queries",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
__schema {
types {
name
}
}
}`,
wantBlocked: true,
},
{
name: "allows multiple __typename occurrences",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
users {
__typename
posts {
__typename
}
}
}`,
wantBlocked: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup config
cfg = &config{
Security: struct {
IntrospectionAllowed []string
BlockIntrospection bool
}{
IntrospectionAllowed: tt.allowedQueries,
BlockIntrospection: tt.blockIntrospection,
},
}
// Initialize allowed queries
prepareQueriesAndExemptions()
// Parse query
p, err := parser.Parse(parser.ParseParams{Source: tt.query})
if err != nil {
t.Fatalf("failed to parse query: %v", err)
}
// Create mock fiber context
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
// Check selections
var blocked bool
for _, def := range p.Definitions {
if op, ok := def.(*ast.OperationDefinition); ok {
blocked = checkSelections(ctx, op.GetSelectionSet().Selections)
break
}
}
if blocked != tt.wantBlocked {
t.Errorf("checkSelections() blocked = %v, want %v", blocked, tt.wantBlocked)
}
})
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ func parseConfig() {
go enableApi()
go enableHasuraEventCleaner()
})
prepareQueriesAndExemptions() // Ensure this function is defined elsewhere
prepareQueriesAndExemptions()
}
func main() {
+121
View File
@@ -1,6 +1,7 @@
package main
import (
"fmt"
"os"
"testing"
"time"
@@ -11,6 +12,7 @@ import (
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
assertions "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
)
type Tests struct {
@@ -138,3 +140,122 @@ func (suite *Tests) Test_getDetailsFromEnv() {
})
}
}
func (suite *Tests) TestIntrospectionEnvironmentConfig() {
// Save original env vars
oldEnv := make(map[string]string)
varsToSave := []string{
"BLOCK_SCHEMA_INTROSPECTION",
"ALLOWED_INTROSPECTION",
"GMP_BLOCK_SCHEMA_INTROSPECTION",
"GMP_ALLOWED_INTROSPECTION",
}
for _, env := range varsToSave {
if val, exists := os.LookupEnv(env); exists {
oldEnv[env] = val
os.Unsetenv(env)
}
}
defer func() {
// Restore original env vars
for k, v := range oldEnv {
os.Setenv(k, v)
}
}()
tests := []struct {
name string
envVars map[string]string
query string
wantBlocked bool
wantEndpoint string
}{
{
name: "basic typename allowed",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__typename",
},
query: `{
users {
id
__typename
}
}`,
wantBlocked: false,
},
{
name: "GMP prefix takes precedence",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "false",
"GMP_BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__type",
"GMP_ALLOWED_INTROSPECTION": "__typename",
},
query: `{
users {
__typename
}
}`,
wantBlocked: false,
},
{
name: "multiple allowed queries",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__typename,__schema",
},
query: `{
__schema {
types {
name
__typename
}
}
}`,
wantBlocked: false,
},
{
name: "multiple allowed queries with one of them blocked",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__schema",
},
query: `{
__schema {
types {
name
__typename
}
}
}`,
wantBlocked: true,
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
// Set test env vars
for k, v := range tt.envVars {
os.Setenv(k, v)
}
// Reset global config
cfg = nil
parseConfig()
// Create test request
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
ctx.Request().Header.SetMethod("POST")
ctx.Request().SetBody([]byte(fmt.Sprintf(`{"query": %q}`, tt.query)))
result := parseGraphQLQuery(ctx)
assert.Equal(tt.wantBlocked, result.shouldBlock)
for k := range tt.envVars {
os.Unsetenv(k)
}
})
}
}