From a6338442067886c3fd7be9aeb972577b54ab4341 Mon Sep 17 00:00:00 2001 From: Ignasi Barrera Date: Thu, 7 Mar 2024 10:22:17 +0100 Subject: [PATCH] Upgrade to latest JWX v2 (#80) --- README.md | 8 ++--- cmd/main.go | 2 +- e2e/redis/store_test.go | 6 ++-- go.mod | 11 ++++--- go.sum | 38 +++++++---------------- internal/authz/oidc.go | 4 +-- internal/authz/oidc_test.go | 45 ++++++++++++++++++--------- internal/oidc/jwks.go | 62 +++++++++++++++++++++++++------------ internal/oidc/jwks_test.go | 62 +++++++++++++++++++++---------------- internal/oidc/token.go | 4 +-- internal/oidc/token_test.go | 6 ++-- 11 files changed, 142 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index 023a9ac..6d92671 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# authservice +# authservice-go [![CI](https://github.com/tetrateio/authservice-go/actions/workflows/ci.yaml/badge.svg)](https://github.com/tetrateio/authservice-go/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/tetrateio/authservice-go/graph/badge.svg?token=JTLsQloZo9)](https://codecov.io/gh/tetrateio/authservice-go) @@ -11,8 +11,8 @@ project from C++ to Go. ## Introduction -`authservice` helps delegate the [OIDC Authorization Code Grant Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) -to the Istio mesh. `authservice` is compatible with any standard OIDC Provider as well as other Istio End-user Auth features, +`authservice-go` helps delegate the [OIDC Authorization Code Grant Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) +to the Istio mesh. `authservice-go` is compatible with any standard OIDC Provider as well as other Istio End-user Auth features, including [Authentication Policy](https://istio.io/docs/tasks/security/authn-policy/) and [RBAC](https://istio.io/docs/tasks/security/rbac-groups/). Together, they allow developers to protect their APIs and web apps without any application code required. @@ -31,7 +31,7 @@ Some of the features it provides: ## How does authservice work? -[This flowchart](https://miro.com/app/board/o9J_kvus6b4=/) explains how `authservice` +[This flowchart](https://miro.com/app/board/o9J_kvus6b4=/) explains how `authservice-go` makes decisions at different points in the login lifecycle. ## Contributing diff --git a/cmd/main.go b/cmd/main.go index baa3c70..3ba7f62 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -35,7 +35,7 @@ func main() { configFile = &internal.LocalConfigFile{} logging = internal.NewLogSystem(log.New(), &configFile.Config) tlsPool = internal.NewTLSConfigPool(lifecycle.Context()) - jwks = oidc.NewJWKSProvider(tlsPool) + jwks = oidc.NewJWKSProvider(&configFile.Config, tlsPool) sessions = oidc.NewSessionStoreFactory(&configFile.Config) envoyAuthz = server.NewExtAuthZFilter(&configFile.Config, tlsPool, jwks, sessions) authzServer = server.New(&configFile.Config, envoyAuthz.Register) diff --git a/e2e/redis/store_test.go b/e2e/redis/store_test.go index abc19a7..a00d13e 100644 --- a/e2e/redis/store_test.go +++ b/e2e/redis/store_test.go @@ -19,8 +19,8 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/jwa" - "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" @@ -141,6 +141,6 @@ func newToken() string { Subject("user"). Expiration(time.Now().Add(time.Hour)). Build() - signed, _ := jwt.Sign(token, jwa.HS256, []byte("key")) + signed, _ := jwt.Sign(token, jwt.WithKey(jwa.HS256, []byte("key"))) return string(signed) } diff --git a/go.mod b/go.mod index ef19c82..491bbd1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/go-logr/logr v1.4.1 - github.com/lestrrat-go/jwx v1.2.28 + github.com/lestrrat-go/httprc v1.0.4 + github.com/lestrrat-go/jwx/v2 v2.0.20 github.com/redis/go-redis/v9 v9.4.0 github.com/stretchr/testify v1.8.4 github.com/tetratelabs/log v0.2.3 @@ -52,7 +53,6 @@ require ( github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect @@ -70,13 +70,14 @@ require ( github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/yuin/gopher-lua v1.1.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 8788252..e9f1c6b 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -92,17 +91,16 @@ 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/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= +github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.28 h1:uadI6o0WpOVrBSf498tRXZIwPpEtLnR9CvqPFXeI5sA= -github.com/lestrrat-go/jwx v1.2.28/go.mod h1:nF+91HEMh/MYFVwKPl5HHsBGMPscqbQb+8IDQdIazP8= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/jwx/v2 v2.0.20 h1:sAgXuWS/t8ykxS9Bi2Qtn5Qhpakw1wrcjxChudjolCc= +github.com/lestrrat-go/jwx/v2 v2.0.20/go.mod h1:UlCSmKqw+agm5BsOBfEAbTvKsEApaGNqHAEUTv5PJC4= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= @@ -138,6 +136,8 @@ github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwy github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -171,23 +171,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= @@ -196,7 +192,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -205,24 +200,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -232,7 +219,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/authz/oidc.go b/internal/authz/oidc.go index 6b8fe46..53ff57b 100644 --- a/internal/authz/oidc.go +++ b/internal/authz/oidc.go @@ -27,7 +27,7 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "github.com/lestrrat-go/jwx/jws" + "github.com/lestrrat-go/jwx/v2/jws" "github.com/tetratelabs/telemetry" "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" @@ -621,7 +621,7 @@ func (o *oidcHandler) isValidIDToken(ctx context.Context, log telemetry.Logger, return false, codes.Internal } - if _, err := jws.VerifySet([]byte(idTokenString), jwtSet); err != nil { + if _, err := jws.Verify([]byte(idTokenString), jws.WithKeySet(jwtSet)); err != nil { log.Error("error verifying id token with fetched jwks", err) return false, codes.Internal } diff --git a/internal/authz/oidc_test.go b/internal/authz/oidc_test.go index 7c8f844..c8d8f37 100644 --- a/internal/authz/oidc_test.go +++ b/internal/authz/oidc_test.go @@ -29,14 +29,15 @@ import ( envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" - "github.com/lestrrat-go/jwx/jwa" - "github.com/lestrrat-go/jwx/jwk" - "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/require" "github.com/tetratelabs/telemetry" "google.golang.org/grpc/codes" "google.golang.org/grpc/test/bufconn" + configv1 "github.com/tetrateio/authservice-go/config/gen/go/v1" oidcv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/oidc" "github.com/tetrateio/authservice-go/internal" inthttp "github.com/tetrateio/authservice-go/internal/http" @@ -193,7 +194,7 @@ func TestOIDCProcess(t *testing.T) { unknownJWKPriv, _ := newKeyPair(t) jwkPriv, jwkPub := newKeyPair(t) - bytes, err := json.Marshal(newKeySet(jwkPub)) + bytes, err := json.Marshal(newKeySet(t, jwkPub)) require.NoError(t, err) basicOIDCConfig.JwksConfig = &oidcv1.OIDCConfig_Jwks{ Jwks: string(bytes), @@ -203,7 +204,9 @@ func TestOIDCProcess(t *testing.T) { sessions := &mockSessionStoreFactory{store: oidc.NewMemoryStore(&clock, time.Hour, time.Hour)} store := sessions.Get(basicOIDCConfig) tlsPool := internal.NewTLSConfigPool(context.Background()) - h, err := NewOIDCHandler(basicOIDCConfig, tlsPool, oidc.NewJWKSProvider(tlsPool), sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) + h, err := NewOIDCHandler(basicOIDCConfig, tlsPool, + oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool), sessions, clock, + oidc.NewStaticGenerator(newSessionID, newNonce, newState)) require.NoError(t, err) ctx := context.Background() @@ -882,13 +885,13 @@ func TestOIDCProcessWithFailingSessionStore(t *testing.T) { tlsPool := internal.NewTLSConfigPool(context.Background()) jwkPriv, jwkPub := newKeyPair(t) - bytes, err := json.Marshal(newKeySet(jwkPub)) + bytes, err := json.Marshal(newKeySet(t, jwkPub)) require.NoError(t, err) basicOIDCConfig.JwksConfig = &oidcv1.OIDCConfig_Jwks{ Jwks: string(bytes), } - h, err := NewOIDCHandler(basicOIDCConfig, tlsPool, oidc.NewJWKSProvider(tlsPool), + h, err := NewOIDCHandler(basicOIDCConfig, tlsPool, oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool), sessions, oidc.Clock{}, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) require.NoError(t, err) @@ -1350,7 +1353,8 @@ func TestLoadWellKnownConfigError(t *testing.T) { clock := oidc.Clock{} tlsPool := internal.NewTLSConfigPool(context.Background()) sessions := &mockSessionStoreFactory{store: oidc.NewMemoryStore(&clock, time.Hour, time.Hour)} - _, err := NewOIDCHandler(dynamicOIDCConfig, tlsPool, oidc.NewJWKSProvider(tlsPool), sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) + _, err := NewOIDCHandler(dynamicOIDCConfig, tlsPool, oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool), + sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) require.Error(t, err) // Fail to retrieve the dynamic config since the test server is not running } @@ -1371,7 +1375,8 @@ func TestNewOIDCHandler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := NewOIDCHandler(tt.config, tlsPool, oidc.NewJWKSProvider(tlsPool), sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) + _, err := NewOIDCHandler(tt.config, tlsPool, oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool), + sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState)) if tt.wantErr { require.Error(t, err) } else { @@ -1404,10 +1409,10 @@ const ( keyAlg = jwa.RS256 ) -func newKeySet(keys ...jwk.Key) jwk.Set { +func newKeySet(t *testing.T, keys ...jwk.Key) jwk.Set { jwks := jwk.NewSet() for _, k := range keys { - jwks.Add(k) + require.NoError(t, jwks.AddKey(k)) } return jwks } @@ -1416,10 +1421,12 @@ func newKeyPair(t *testing.T) (jwk.Key, jwk.Key) { rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - priv, err := jwk.New(rsaKey) + priv, err := jwk.FromRaw(rsaKey) + require.NoError(t, err) + err = priv.Set(jwk.KeyIDKey, keyID) require.NoError(t, err) - pub, err := jwk.New(rsaKey.PublicKey) + pub, err := jwk.FromRaw(rsaKey.PublicKey) require.NoError(t, err) err = pub.Set(jwk.KeyIDKey, keyID) @@ -1431,9 +1438,9 @@ func newKeyPair(t *testing.T) (jwk.Key, jwk.Key) { } func newJWT(t *testing.T, key jwk.Key, builder *jwt.Builder) string { - token, err := builder.Build() + token, err := builder.Claim(jwk.KeyIDKey, key.KeyID()).Build() require.NoError(t, err) - signed, err := jwt.Sign(token, keyAlg, key) + signed, err := jwt.Sign(token, jwt.WithKey(keyAlg, key)) require.NoError(t, err) return string(signed) } @@ -1543,6 +1550,14 @@ func requireStandardResponseHeaders(t *testing.T, resp *envoy.CheckResponse) { } } +func newConfigFor(oidc *oidcv1.OIDCConfig) *configv1.Config { + return &configv1.Config{ + Chains: []*configv1.FilterChain{ + {Filters: []*configv1.Filter{{Type: &configv1.Filter_Oidc{Oidc: oidc}}}}, + }, + } +} + // idpServer is a mock IDP server that can be used to test the OIDC handler. // It listens on a bufconn.Listener and provides a http.Client that can be used to make requests to it. // It returns a predefined response when the /token endpoint is called, that can be set using the tokensResponse field. diff --git a/internal/oidc/jwks.go b/internal/oidc/jwks.go index 546c3b6..30a5ffb 100644 --- a/internal/oidc/jwks.go +++ b/internal/oidc/jwks.go @@ -21,10 +21,12 @@ import ( "net/http" "time" - "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/httprc" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/tetratelabs/run" "github.com/tetratelabs/telemetry" + configv1 "github.com/tetrateio/authservice-go/config/gen/go/v1" oidcv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/oidc" "github.com/tetrateio/authservice-go/internal" ) @@ -50,15 +52,17 @@ type JWKSProvider interface { // DefaultJWKSProvider provides a JWKS set type DefaultJWKSProvider struct { log telemetry.Logger - cache *jwk.AutoRefresh + cache *jwk.Cache + config *configv1.Config tlsPool internal.TLSConfigPool started chan struct{} } // NewJWKSProvider returns a new JWKSProvider. -func NewJWKSProvider(tlsPool internal.TLSConfigPool) *DefaultJWKSProvider { +func NewJWKSProvider(cfg *configv1.Config, tlsPool internal.TLSConfigPool) *DefaultJWKSProvider { return &DefaultJWKSProvider{ log: internal.Logger(internal.JWKS), + config: cfg, tlsPool: tlsPool, started: make(chan struct{}), } @@ -68,21 +72,17 @@ func NewJWKSProvider(tlsPool internal.TLSConfigPool) *DefaultJWKSProvider { func (j *DefaultJWKSProvider) Name() string { return "JWKS" } func (j *DefaultJWKSProvider) ServeContext(ctx context.Context) error { - ch := make(chan jwk.AutoRefreshError) - j.cache = jwk.NewAutoRefresh(ctx) - j.cache.ErrorSink(ch) - defer func() { close(ch) }() + errSink := httprc.ErrSinkFunc(func(err error) { + j.log.Debug("jwks auto refresh error", "error", err) + }) + j.cache = jwk.NewCache(ctx, + jwk.WithErrSink(errSink), + jwk.WithRefreshWindow(getRefreshWindow(j.config)), + ) close(j.started) // signal channel start - - for { - select { - case err := <-ch: - j.log.Debug("jwks auto refresh error", "error", err) - case <-ctx.Done(): - return nil - } - } + <-ctx.Done() + return nil } // Get the JWKS for the given OIDC configuration @@ -117,15 +117,17 @@ func (j *DefaultJWKSProvider) fetchDynamic(ctx context.Context, config *oidcv1.O log.Info("configuring JWKS auto refresh", "jwks", jwksConfig.JwksUri, "interval", refreshInterval, "skip_verify", config.GetSkipVerifyPeerCert()) - j.cache.Configure(jwksConfig.JwksUri, + if err = j.cache.Register(jwksConfig.JwksUri, jwk.WithHTTPClient(client), jwk.WithRefreshInterval(refreshInterval), - ) + ); err != nil { + return nil, fmt.Errorf("error registering JWKS: %w", err) + } } log.Debug("fetching JWKS", "jwks", jwksConfig.JwksUri) - jwks, err := j.cache.Fetch(ctx, jwksConfig.JwksUri) + jwks, err := j.cache.Get(ctx, jwksConfig.JwksUri) if err != nil { return nil, fmt.Errorf("%w: %v", ErrJWKSFetch, err) } @@ -142,3 +144,25 @@ func (j *DefaultJWKSProvider) fetchStatic(raw string) (jwk.Set, error) { } return jwks, nil } + +// getRefreshWindow returns the smallest refresh window for all the OIDC configurations. +// This is needed because the cache needs to be initialized with a default window small enough to +// accommodate all the configured intervals. +func getRefreshWindow(cfg *configv1.Config) time.Duration { + refreshWindow := DefaultFetchInterval + + for _, fc := range cfg.Chains { + for _, f := range fc.Filters { + if f.GetOidc() == nil { + continue + } + + interval := time.Duration(f.GetOidc().GetJwksFetcher().GetPeriodicFetchIntervalSec()) * time.Second + if interval > 0 && interval < refreshWindow { + refreshWindow = interval + } + } + } + + return refreshWindow +} diff --git a/internal/oidc/jwks_test.go b/internal/oidc/jwks_test.go index 5f772b2..18c00b2 100644 --- a/internal/oidc/jwks_test.go +++ b/internal/oidc/jwks_test.go @@ -26,13 +26,15 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/jwa" - "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/stretchr/testify/require" "github.com/tetratelabs/run" "github.com/tetratelabs/telemetry" "google.golang.org/protobuf/types/known/structpb" + configv1 "github.com/tetrateio/authservice-go/config/gen/go/v1" + mockv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/mock" oidcv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/oidc" "github.com/tetrateio/authservice-go/internal" ) @@ -79,10 +81,11 @@ var ( func TestStaticJWKSProvider(t *testing.T) { tlsPool := internal.NewTLSConfigPool(context.Background()) + cfg := &configv1.Config{} t.Run("invalid", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - cache := NewJWKSProvider(tlsPool) + cache := NewJWKSProvider(cfg, tlsPool) go func() { require.NoError(t, cache.ServeContext(ctx)) }() t.Cleanup(cancel) @@ -97,7 +100,7 @@ func TestStaticJWKSProvider(t *testing.T) { t.Run("single-key", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - cache := NewJWKSProvider(tlsPool) + cache := NewJWKSProvider(cfg, tlsPool) go func() { require.NoError(t, cache.ServeContext(ctx)) }() t.Cleanup(cancel) @@ -110,16 +113,16 @@ func TestStaticJWKSProvider(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, jwks.Len()) - key, ok := jwks.Get(0) + key, ok := jwks.LookupKeyID("62a93512c9ee4c7f8067b5a216dade2763d32a47") require.True(t, ok) - require.Equal(t, "RS256", key.Algorithm()) + require.Equal(t, jwa.RS256, key.Algorithm()) require.Equal(t, jwa.KeyType("RSA"), key.KeyType()) require.Equal(t, "62a93512c9ee4c7f8067b5a216dade2763d32a47", key.KeyID()) }) t.Run("multiple-keys", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - cache := NewJWKSProvider(tlsPool) + cache := NewJWKSProvider(cfg, tlsPool) go func() { require.NoError(t, cache.ServeContext(ctx)) }() t.Cleanup(cancel) @@ -132,15 +135,15 @@ func TestStaticJWKSProvider(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, jwks.Len()) - key, ok := jwks.Get(0) + key, ok := jwks.LookupKeyID("62a93512c9ee4c7f8067b5a216dade2763d32a47") require.True(t, ok) - require.Equal(t, "RS256", key.Algorithm()) + require.Equal(t, jwa.RS256, key.Algorithm()) require.Equal(t, jwa.KeyType("RSA"), key.KeyType()) require.Equal(t, "62a93512c9ee4c7f8067b5a216dade2763d32a47", key.KeyID()) - key, ok = jwks.Get(1) + key, ok = jwks.LookupKeyID("b3319a147514df7ee5e4bcdee51350cc890cc89e") require.True(t, ok) - require.Equal(t, "RS256", key.Algorithm()) + require.Equal(t, jwa.RS256, key.Algorithm()) require.Equal(t, jwa.KeyType("RSA"), key.KeyType()) require.Equal(t, "b3319a147514df7ee5e4bcdee51350cc890cc89e", key.KeyID()) }) @@ -149,11 +152,22 @@ func TestStaticJWKSProvider(t *testing.T) { func TestDynamicJWKSProvider(t *testing.T) { var ( pub = newKey(t) - jwks = newKeySet(pub) + jwks = newKeySet(t, pub) tlsPool = internal.NewTLSConfigPool(context.Background()) - newCache = func(t *testing.T) JWKSProvider { - cache := NewJWKSProvider(tlsPool) + newCache = func(t *testing.T, oidc *oidcv1.OIDCConfig) JWKSProvider { + cfg := &configv1.Config{ + Chains: []*configv1.FilterChain{ + { + Filters: []*configv1.Filter{ + {Type: &configv1.Filter_Mock{Mock: &mockv1.MockConfig{}}}, + {Type: &configv1.Filter_Oidc{Oidc: oidc}}, + }, + }, + }, + } + + cache := NewJWKSProvider(cfg, tlsPool) g := run.Group{Logger: telemetry.NoopLogger()} g.Register(cache) go func() { _ = g.Run() }() @@ -166,8 +180,6 @@ func TestDynamicJWKSProvider(t *testing.T) { t.Run("invalid url", func(t *testing.T) { server := newTestServer(t, jwks) - cache := newCache(t) - config := &oidcv1.OIDCConfig{ JwksConfig: &oidcv1.OIDCConfig_JwksFetcher{ JwksFetcher: &oidcv1.OIDCConfig_JwksFetcherConfig{ @@ -175,6 +187,7 @@ func TestDynamicJWKSProvider(t *testing.T) { }, }, } + cache := newCache(t, config) _, err := cache.Get(context.Background(), config) @@ -184,8 +197,6 @@ func TestDynamicJWKSProvider(t *testing.T) { t.Run("cache load", func(t *testing.T) { server := newTestServer(t, jwks) - cache := newCache(t) - config := &oidcv1.OIDCConfig{ JwksConfig: &oidcv1.OIDCConfig_JwksFetcher{ JwksFetcher: &oidcv1.OIDCConfig_JwksFetcherConfig{ @@ -195,6 +206,7 @@ func TestDynamicJWKSProvider(t *testing.T) { }, SkipVerifyPeerCert: structpb.NewBoolValue(true), } + cache := newCache(t, config) keys, err := cache.Get(context.Background(), config) require.NoError(t, err) @@ -204,8 +216,6 @@ func TestDynamicJWKSProvider(t *testing.T) { t.Run("cached results", func(t *testing.T) { server := newTestServer(t, jwks) - cache := newCache(t) - config := &oidcv1.OIDCConfig{ JwksConfig: &oidcv1.OIDCConfig_JwksFetcher{ JwksFetcher: &oidcv1.OIDCConfig_JwksFetcherConfig{ @@ -214,6 +224,7 @@ func TestDynamicJWKSProvider(t *testing.T) { }, }, } + cache := newCache(t, config) for i := 0; i < 5; i++ { keys, err := cache.Get(context.Background(), config) @@ -225,8 +236,6 @@ func TestDynamicJWKSProvider(t *testing.T) { t.Run("cache refresh", func(t *testing.T) { server := newTestServer(t, jwks) - cache := newCache(t) - config := &oidcv1.OIDCConfig{ JwksConfig: &oidcv1.OIDCConfig_JwksFetcher{ JwksFetcher: &oidcv1.OIDCConfig_JwksFetcherConfig{ @@ -235,11 +244,12 @@ func TestDynamicJWKSProvider(t *testing.T) { }, }, } + cache := newCache(t, config) // Load the entry in the cache and remove it to let the background refresher refresh it _, err := cache.Get(context.Background(), config) require.NoError(t, err) - jwks.Remove(pub) + require.NoError(t, jwks.RemoveKey(pub)) // Wait for the refresh period and check that the JWKS has been refreshed require.Eventually(t, func() bool { @@ -275,10 +285,10 @@ func newTestServer(t *testing.T, jwks jwk.Set) *server { const keyID = "test" -func newKeySet(keys ...jwk.Key) jwk.Set { +func newKeySet(t *testing.T, keys ...jwk.Key) jwk.Set { jwks := jwk.NewSet() for _, k := range keys { - jwks.Add(k) + require.NoError(t, jwks.AddKey(k)) } return jwks } @@ -287,7 +297,7 @@ func newKey(t *testing.T) jwk.Key { rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err) - pub, err := jwk.New(rsaKey.PublicKey) + pub, err := jwk.FromRaw(rsaKey.PublicKey) require.NoError(t, err) err = pub.Set(jwk.KeyIDKey, keyID) diff --git a/internal/oidc/token.go b/internal/oidc/token.go index ce25cae..834e97a 100644 --- a/internal/oidc/token.go +++ b/internal/oidc/token.go @@ -17,7 +17,7 @@ package oidc import ( "time" - "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/v2/jwt" ) // TokenResponse contains information about the tokens returned by the Identity Provider. @@ -33,5 +33,5 @@ func (t *TokenResponse) ParseIDToken() (jwt.Token, error) { return ParseToken(t. // ParseToken parses the token string and returns the token and an error if any. func ParseToken(token string) (jwt.Token, error) { - return jwt.Parse([]byte(token), jwt.WithValidate(false)) + return jwt.Parse([]byte(token), jwt.WithValidate(false), jwt.WithVerify(false)) } diff --git a/internal/oidc/token_test.go b/internal/oidc/token_test.go index ad4d304..5871424 100644 --- a/internal/oidc/token_test.go +++ b/internal/oidc/token_test.go @@ -18,8 +18,8 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/jwa" - "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/v2/jwa" + "github.com/lestrrat-go/jwx/v2/jwt" "github.com/stretchr/testify/require" ) @@ -47,6 +47,6 @@ func newToken() string { Subject("user"). Expiration(time.Now().Add(time.Hour)). Build() - signed, _ := jwt.Sign(token, jwa.HS256, []byte("key")) + signed, _ := jwt.Sign(token, jwt.WithKey(jwa.HS256, []byte("key"))) return string(signed) }