From 5491b2a8faaf7bcbc1849e8cfe8540264c3ef8b2 Mon Sep 17 00:00:00 2001 From: "Lawrence, Rendall" Date: Mon, 1 Jan 2024 20:08:19 +0300 Subject: [PATCH] update JWT middleware --- go.mod | 7 +- go.sum | 24 ++++++- middleware/jwt/jwt.go | 127 +++++++++++++------------------------ middleware/jwt/jwt_test.go | 58 ++++++++--------- 4 files changed, 96 insertions(+), 120 deletions(-) diff --git a/go.mod b/go.mod index 59ca917..e76ef1e 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/sot-tech/mochi -go 1.20 +go 1.21.5 require ( code.cloudfoundry.org/go-diodes v0.0.0-20231218170342-258647f3c6ec - github.com/MicahParks/keyfunc v1.9.0 + github.com/MicahParks/keyfunc/v3 v3.1.1 github.com/anacrolix/torrent v1.53.2 github.com/cespare/xxhash/v2 v2.2.0 - github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/jackc/pgx/v5 v5.5.1 github.com/libp2p/go-reuseport v0.4.0 github.com/minio/sha256-simd v1.0.1 @@ -21,6 +21,7 @@ require ( ) require ( + github.com/MicahParks/jwkset v0.5.4 // indirect github.com/anacrolix/dht/v2 v2.21.0 // indirect github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 // indirect github.com/anacrolix/log v0.14.5 // indirect diff --git a/go.sum b/go.sum index 39b29c1..212f347 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,10 @@ code.cloudfoundry.org/go-diodes v0.0.0-20231218170342-258647f3c6ec/go.mod h1:1KY crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= -github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/MicahParks/jwkset v0.5.4 h1:59s9OUNIKF3g+IXYm3pa4vPXXEudRNetyy3+H6KpKdw= +github.com/MicahParks/jwkset v0.5.4/go.mod h1:fOx7dCX+XgPDzcRbZzi9DMY3vyebWXmsz7XPqstr3ms= +github.com/MicahParks/keyfunc/v3 v3.1.1 h1:ghC5jcuU4/TTQQ9Ns7TEVuhnscQOH+WL4//Jmsy5/DA= +github.com/MicahParks/keyfunc/v3 v3.1.1/go.mod h1:Qmrhb9tkHX1i/kCiLAPDOCWIEfN9yq7u/tkP16lmLL8= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= @@ -22,6 +24,7 @@ github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhU github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk= +github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0= github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 h1:qwOprPTDMM3BASJRf84mmZnTXRsPGGJ8xoHKQS7m3so= github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= @@ -59,7 +62,9 @@ github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2w github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -77,6 +82,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= @@ -91,14 +97,17 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -120,6 +129,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8= github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= @@ -158,9 +168,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -187,8 +199,10 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= +github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -225,6 +239,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds= github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -278,6 +293,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -315,6 +331,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -337,6 +354,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/middleware/jwt/jwt.go b/middleware/jwt/jwt.go index aa23c3b..08d7ac4 100644 --- a/middleware/jwt/jwt.go +++ b/middleware/jwt/jwt.go @@ -9,11 +9,13 @@ import ( "context" "errors" "fmt" + "net/url" "strings" "time" - "github.com/MicahParks/keyfunc" - "github.com/golang-jwt/jwt/v4" + "github.com/MicahParks/jwkset" + "github.com/MicahParks/keyfunc/v3" + "github.com/golang-jwt/jwt/v5" "github.com/sot-tech/mochi/bittorrent" "github.com/sot-tech/mochi/middleware" @@ -63,8 +65,9 @@ type Config struct { } type hook struct { - cfg Config - jwks *keyfunc.JWKS + cfg Config + jwks keyfunc.Keyfunc + parser *jwt.Parser } func build(config conf.MapConfig, _ storage.PeerStorage) (h middleware.Hook, err error) { @@ -83,23 +86,32 @@ func build(config conf.MapConfig, _ storage.PeerStorage) (h middleware.Hook, err Msg("falling back to default configuration") } - var jwks *keyfunc.JWKS + var jwks keyfunc.Keyfunc if cfg.HandleAnnounce || cfg.HandleScrape { - jwks, err = keyfunc.Get(cfg.JWKSetURL, keyfunc.Options{ - Ctx: context.Background(), - RefreshErrorHandler: func(err error) { - logger.Error().Err(err).Msg("error occurred while updating JWKs") - }, - RefreshInterval: cfg.JWKUpdateInterval, - RefreshUnknownKID: true, - }) + var jwkURL *url.URL + jwkURL, err = url.Parse(cfg.JWKSetURL) + if err == nil { + var httpStorage jwkset.Storage + httpStorage, err = jwkset.NewStorageFromHTTP(jwkURL, jwkset.HTTPClientStorageOptions{ + NoErrorReturnFirstHTTPReq: true, + RefreshErrorHandler: func(_ context.Context, err error) { + logger.Error().Err(err).Msg("error occurred while updating JWKs") + }, + RefreshInterval: cfg.JWKUpdateInterval, + Storage: nil, + }) + if err == nil { + jwks, err = keyfunc.New(keyfunc.Options{Storage: httpStorage}) + } + } } else { logger.Warn().Msg("both announce and scrape handle disabled") } if err == nil { h = &hook{ - cfg: cfg, - jwks: jwks, + cfg: cfg, + jwks: jwks, + parser: jwt.NewParser(jwt.WithAudience(cfg.Audience), jwt.WithIssuer(cfg.Issuer), hmacAlgorithms), } } } else { @@ -109,36 +121,8 @@ func build(config conf.MapConfig, _ storage.PeerStorage) (h middleware.Hook, err return } -func (h *hook) Close() error { - logger.Debug().Msg("attempting to shutdown JWT middleware") - if h.jwks != nil { - h.jwks.EndBackground() - } - return nil -} - -type verifiableClaims interface { - jwt.Claims - VerifyIssuer(iss string, req bool) bool - GetIssuer() string - VerifyAudience(aud string, req bool) bool - GetAudience() []string -} - -type registeredClaimsWrapper struct { - jwt.RegisteredClaims -} - -func (rc registeredClaimsWrapper) GetIssuer() string { - return rc.Issuer -} - -func (rc registeredClaimsWrapper) GetAudience() []string { - return rc.Audience -} - type announceClaims struct { - registeredClaimsWrapper + jwt.RegisteredClaims InfoHash string `json:"infohash,omitempty"` } @@ -152,13 +136,7 @@ func (h *hook) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceReque err = ErrMissingJWT } else { claims := new(announceClaims) - if errs := h.validateBaseJWT(jwtParam, claims); len(errs) > 0 { - logger.Info(). - Errs("errors", errs). - Object("source", req.RequestPeer). - Msg("JWT validation failed") - err = ErrInvalidJWT - } else { + if _, jwtErr := h.parser.ParseWithClaims(jwtParam, claims, h.jwks.Keyfunc); jwtErr == nil { var claimIH bittorrent.InfoHash if claimIH, err = bittorrent.NewInfoHashString(claims.InfoHash); err != nil { logger.Info(). @@ -175,6 +153,12 @@ func (h *hook) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceReque Msg("unequal 'infohash' claim when validating JWT") err = ErrInvalidJWT } + } else { + logger.Info(). + Err(jwtErr). + Object("source", req.RequestPeer). + Msg("JWT validation failed") + err = ErrInvalidJWT } } @@ -182,7 +166,7 @@ func (h *hook) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceReque } type scrapeClaims struct { - registeredClaimsWrapper + jwt.RegisteredClaims InfoHashes []string `json:"infohashes,omitempty"` } @@ -197,13 +181,7 @@ func (h *hook) HandleScrape(ctx context.Context, req *bittorrent.ScrapeRequest, err = ErrMissingJWT } else { claims := new(scrapeClaims) - if errs := h.validateBaseJWT(jwtParam, claims); len(errs) > 0 { - logger.Info(). - Errs("errors", errs). - Array("source", &req.RequestAddresses). - Msg("JWT validation failed") - err = ErrInvalidJWT - } else { + if _, jwtErr := h.parser.ParseWithClaims(jwtParam, claims, h.jwks.Keyfunc); jwtErr == nil { var claimIHs bittorrent.InfoHashes for _, s := range claims.InfoHashes { if providedIh, err := bittorrent.NewInfoHashString(s); err == nil { @@ -239,6 +217,12 @@ func (h *hook) HandleScrape(ctx context.Context, req *bittorrent.ScrapeRequest, Msg("unequal 'infohashes' claim when validating JWT") err = ErrInvalidJWT } + } else { + logger.Info(). + Err(jwtErr). + Array("source", &req.RequestAddresses). + Msg("JWT validation failed") + err = ErrInvalidJWT } } @@ -256,28 +240,3 @@ func (h *hook) getJWTString(params bittorrent.Params) (jwt string) { } return } - -func (h *hook) validateBaseJWT(jwtParam string, claims verifiableClaims) (errs []error) { - if _, err := jwt.ParseWithClaims(jwtParam, claims, h.jwks.Keyfunc, hmacAlgorithms); err != nil { - errs = append(errs, err) - } - if err := claims.Valid(); err != nil { - errs = append(errs, err) - } - - if !claims.VerifyIssuer(h.cfg.Issuer, true) { - logger.Debug(). - Str("provided", claims.GetIssuer()). - Str("required", h.cfg.Issuer). - Msg("unequal or missing issuer when validating JWT") - errs = append(errs, jwt.ErrTokenInvalidIssuer) - } - if !claims.VerifyAudience(h.cfg.Audience, true) { - logger.Debug(). - Strs("provided", claims.GetAudience()). - Str("required", h.cfg.Audience). - Msg("unequal or missing audience when validating JWT") - errs = append(errs, jwt.ErrTokenInvalidAudience) - } - return -} diff --git a/middleware/jwt/jwt_test.go b/middleware/jwt/jwt_test.go index ade3931..edf793c 100644 --- a/middleware/jwt/jwt_test.go +++ b/middleware/jwt/jwt_test.go @@ -3,7 +3,6 @@ package jwt import ( "context" "crypto/ecdsa" - "crypto/elliptic" cr "crypto/rand" "encoding/base64" "encoding/json" @@ -14,7 +13,7 @@ import ( "testing" "time" - "github.com/golang-jwt/jwt/v4" + "github.com/golang-jwt/jwt/v5" "github.com/minio/sha256-simd" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -71,7 +70,12 @@ func init() { } infoHash, _ = bittorrent.NewInfoHash(ihBytes) s2 := sha256.New() - s2.Write(elliptic.Marshal(privKey.PublicKey.Curve, privKey.PublicKey.X, privKey.PublicKey.Y)) + ecdhPubKey, err := privKey.PublicKey.ECDH() + if err != nil { + panic(err) + } + s2.Write(ecdhPubKey.Bytes()) + //s2.Write(elliptic.Marshal(privKey.PublicKey.Curve, privKey.PublicKey.X, privKey.PublicKey.Y)) jwksData = JWKSKeys{Keys: []JWKSKey{ { KeyType: "EC", @@ -92,15 +96,13 @@ func TestHook_HandleAnnounceValid(t *testing.T) { defer s.Close() token := jwt.NewWithClaims(jwt.SigningMethodES256, announceClaims{ - registeredClaimsWrapper: registeredClaimsWrapper{ - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "CN=test", - Subject: "CN=test", - Audience: []string{"test"}, - ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, - NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, - ID: strconv.FormatInt(rand.Int63(), 16), - }, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "CN=test", + Subject: "CN=test", + Audience: []string{"test"}, + ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, + NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, + ID: strconv.FormatInt(rand.Int63(), 16), }, InfoHash: infoHash.String(), }) @@ -137,15 +139,13 @@ func TestHook_HandleAnnounceInvalid(t *testing.T) { // now we wll use HMAC-SHA256 with invalid random key // all errors should be nil except announce request token := jwt.NewWithClaims(jwt.SigningMethodHS256, announceClaims{ - registeredClaimsWrapper: registeredClaimsWrapper{ - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "CN=test", - Subject: "CN=test", - Audience: []string{"test"}, - ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, - NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, - ID: strconv.FormatInt(rand.Int63(), 16), - }, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "CN=test", + Subject: "CN=test", + Audience: []string{"test"}, + ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, + NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, + ID: strconv.FormatInt(rand.Int63(), 16), }, InfoHash: infoHash.String(), }) @@ -194,15 +194,13 @@ func TestHook_HandleScrapeValid(t *testing.T) { } token := jwt.NewWithClaims(jwt.SigningMethodES256, scrapeClaims{ - registeredClaimsWrapper: registeredClaimsWrapper{ - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: "CN=test", - Subject: "CN=test", - Audience: []string{"test"}, - ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, - NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, - ID: strconv.FormatInt(rand.Int63(), 16), - }, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: "CN=test", + Subject: "CN=test", + Audience: []string{"test"}, + ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(time.Hour)}, + NotBefore: &jwt.NumericDate{Time: time.Now().Add(-time.Hour)}, + ID: strconv.FormatInt(rand.Int63(), 16), }, InfoHashes: ihss, })