From 5a49d7193dfa310fd3347903b0b3beb037c0d2cd Mon Sep 17 00:00:00 2001 From: Robin Thellend Date: Fri, 30 Jan 2026 09:23:20 -0800 Subject: [PATCH 1/4] Refactor JWKS into its own module and update examples --- examples/backend/backend.go | 55 ++-- examples/backend/go.mod | 12 +- examples/backend/go.sum | 38 +-- examples/oauth2server/go.mod | 13 +- examples/oauth2server/go.sum | 51 +--- examples/oauth2server/oauth2server.go | 41 ++- go.mod | 1 + go.sum | 2 + .../cookiemanager/trusted_issuers_test.go | 24 +- proxy/internal/tokenmanager/tokenmanager.go | 245 ++---------------- proxy/proxy.go | 7 +- scripts/update-go-version.sh | 2 +- 12 files changed, 109 insertions(+), 382 deletions(-) diff --git a/examples/backend/backend.go b/examples/backend/backend.go index 6b14cdff..08852d57 100644 --- a/examples/backend/backend.go +++ b/examples/backend/backend.go @@ -26,7 +26,6 @@ package main import ( "context" - "crypto/ecdsa" "crypto/tls" "flag" "fmt" @@ -39,12 +38,12 @@ import ( "os/signal" "strings" "syscall" - "time" "github.com/blend/go-sdk/envoyutil" "github.com/c2FmZQ/tlsproxy/certmanager" + "github.com/c2FmZQ/tlsproxy/jwks" jwt "github.com/golang-jwt/jwt/v5" - "github.com/lestrrat-go/jwx/jwk" + "github.com/hashicorp/go-retryablehttp" "github.com/quic-go/quic-go/http3" ) @@ -131,50 +130,46 @@ func main() { type service struct { ctx context.Context - ar *jwk.AutoRefresh + remote *jwks.Remote jwksURL string } func newService(ctx context.Context, jwksURL string, insecureSkipVerify bool) *service { - ar := jwk.NewAutoRefresh(ctx) - opts := []jwk.AutoRefreshOption{ - jwk.WithRefreshInterval(60 * time.Minute), - } - if insecureSkipVerify { - opts = append(opts, jwk.WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, + client := retryablehttp.NewClient() + client.HTTPClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, }, - })) + }, } - ar.Configure(jwksURL, opts...) + client.Logger = nil + + remote := jwks.NewRemote(client, nil) + remote.SetIssuers([]jwks.Issuer{ + { + Issuer: "https://login.example.com", // This needs to match the issuer in the token + JWKSURI: jwksURL, + }, + }) + go func() { + <-ctx.Done() + remote.Stop() + }() + return &service{ ctx: ctx, - ar: ar, + remote: remote, jwksURL: jwksURL, } } func (s *service) getKey(token *jwt.Token) (interface{}, error) { - set, err := s.ar.Fetch(s.ctx, s.jwksURL) - if err != nil { - return nil, err - } kid, ok := token.Header["kid"].(string) if !ok { return nil, fmt.Errorf("kid is %T", token.Header["kid"]) } - if key, ok := set.LookupKeyID(kid); ok { - var pubKey ecdsa.PublicKey - if err := key.Raw(&pubKey); err != nil { - return nil, err - } - return &pubKey, nil - } - - return nil, fmt.Errorf("%s not found", kid) + return s.remote.GetKey(kid) } func (s *service) ServeHTTP(w http.ResponseWriter, req *http.Request) { diff --git a/examples/backend/go.mod b/examples/backend/go.mod index 1de1a2c6..74c1b982 100644 --- a/examples/backend/go.mod +++ b/examples/backend/go.mod @@ -5,21 +5,15 @@ go 1.25.5 require ( github.com/blend/go-sdk v1.20240719.1 github.com/c2FmZQ/tlsproxy v0.24.2 + github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/lestrrat-go/jwx v1.2.31 + github.com/hashicorp/go-retryablehttp v0.7.8 github.com/quic-go/quic-go v0.59.0 ) require ( - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/quic-go/qpack v0.6.0 // indirect golang.org/x/crypto v0.47.0 // indirect golang.org/x/net v0.49.0 // indirect diff --git a/examples/backend/go.sum b/examples/backend/go.sum index e0f84ab3..f16b357e 100644 --- a/examples/backend/go.sum +++ b/examples/backend/go.sum @@ -2,36 +2,30 @@ github.com/blend/go-sdk v1.20240719.1 h1:eyispDP9DzQuNE+y7j1xSqwRm6ndMS4jgwlOQU4 github.com/blend/go-sdk v1.20240719.1/go.mod h1:aTw/exIbMHDYcJLTiqeWMMVhUs9+72BDe26AA0A6jno= github.com/c2FmZQ/tlsproxy v0.24.2 h1:uK8Dy4u0R6t+lgVphdHXFrivs2Q+PYwp1/GN8vvh7Sg= github.com/c2FmZQ/tlsproxy v0.24.2/go.mod h1:MWZUQ+DEhYO766iQHe/OtnfovDFGwdEoLCzFF4VF+Xs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 h1:X9sQsvaKIg79vUJ6ER/isN5fDkuEoWsBnEHYm4Z/WF4= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7/go.mod h1:OmTz0Nc+C8Ent4TvkXv+0zQGdvMFHU/6ZTon629wjOk= 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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= 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= 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.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= -github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -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/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.31 h1:/OM9oNl/fzyldpv5HKZ9m7bTywa7COUfg8gujd9nJ54= -github.com/lestrrat-go/jwx v1.2.31/go.mod h1:eQJKoRwWcLg4PfD5CFA5gIZGxhPgoPYq9pZISdxLf0c= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= @@ -40,9 +34,6 @@ github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SA github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= @@ -58,6 +49,5 @@ golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= 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= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/oauth2server/go.mod b/examples/oauth2server/go.mod index 91793301..f6aa5d2f 100644 --- a/examples/oauth2server/go.mod +++ b/examples/oauth2server/go.mod @@ -1,20 +1,15 @@ module github.com/c2FmZQ/tlsproxy/example/oauth2server -go 1.24.6 +go 1.25.5 require ( + github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/lestrrat-go/httprc/v3 v3.0.3 - github.com/lestrrat-go/jwx/v3 v3.0.13 golang.org/x/oauth2 v0.34.0 ) require ( - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/option/v2 v2.0.0 // indirect - github.com/segmentio/asm v1.2.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect golang.org/x/sys v0.40.0 // indirect ) diff --git a/examples/oauth2server/go.sum b/examples/oauth2server/go.sum index f1ad4bd6..f3a3a225 100644 --- a/examples/oauth2server/go.sum +++ b/examples/oauth2server/go.sum @@ -1,43 +1,20 @@ -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/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 h1:X9sQsvaKIg79vUJ6ER/isN5fDkuEoWsBnEHYm4Z/WF4= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7/go.mod h1:OmTz0Nc+C8Ent4TvkXv+0zQGdvMFHU/6ZTon629wjOk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= -github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= -github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= -github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= -github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= -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/v3 v3.0.3 h1:WjLHWkDkgWXeIUrKi/7lS/sGq2DjkSAwdTbH5RHXAKs= -github.com/lestrrat-go/httprc/v3 v3.0.3/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= -github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk= -github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU= -github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= -github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= -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/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= -github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= -github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/oauth2server/oauth2server.go b/examples/oauth2server/oauth2server.go index df1958be..702fdd84 100644 --- a/examples/oauth2server/oauth2server.go +++ b/examples/oauth2server/oauth2server.go @@ -17,9 +17,8 @@ import ( "strings" "time" + "github.com/c2FmZQ/tlsproxy/jwks" jwt "github.com/golang-jwt/jwt/v5" - "github.com/lestrrat-go/httprc/v3" - "github.com/lestrrat-go/jwx/v3/jwk" "golang.org/x/oauth2" ) @@ -97,26 +96,32 @@ type Server struct { UserinfoEndpoint string `json:"userinfo_endpoint"` JWKSURI string `json:"jwks_uri"` } - jwkCache *jwk.Cache + remote *jwks.Remote } func (s *Server) Run(ctx context.Context) error { s.ctx = ctx resp, err := http.Get(s.OpenIDConfigURL) if err != nil { - return fmt.Errorf("%s: %w", s.OpenIDConfigURL) + return fmt.Errorf("%s: %w", s.OpenIDConfigURL, err) } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&s.openIDConfig); err != nil { - return fmt.Errorf("openid config: %w", s.OpenIDConfigURL) - } - if s.jwkCache, err = jwk.NewCache(ctx, httprc.NewClient()); err != nil { - return fmt.Errorf("jwk.NewCache: %w", err) - } - if err := s.jwkCache.Register(ctx, s.openIDConfig.JWKSURI); err != nil { - return fmt.Errorf("jwkCache.Register: %w", err) + return fmt.Errorf("openid config: %w", err) } + s.remote = jwks.NewRemote(nil, nil) + s.remote.SetIssuers([]jwks.Issuer{ + { + Issuer: "https://login.example.com", // This needs to match the issuer in the token + JWKSURI: s.openIDConfig.JWKSURI, + }, + }) + go func() { + <-ctx.Done() + s.remote.Stop() + }() + mux := http.NewServeMux() mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, req *http.Request) { log.Printf("%s %s", req.Method, req.RequestURI) @@ -226,17 +231,5 @@ func (s *Server) getKey(token *jwt.Token) (interface{}, error) { if !ok { return nil, fmt.Errorf("kid is %T", token.Header["kid"]) } - keySet, err := s.jwkCache.Lookup(s.ctx, s.openIDConfig.JWKSURI) - if err != nil { - return nil, fmt.Errorf("%s: %v", s.openIDConfig.JWKSURI) - } - key, ok := keySet.LookupKeyID(kid) - if !ok { - return nil, fmt.Errorf("%s not found", kid) - } - var pub any - if err := jwk.Export(key, &pub); err != nil { - return nil, err - } - return pub, nil + return s.remote.GetKey(kid) } diff --git a/go.mod b/go.mod index 7bf80d61..9e9d68e2 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/c2FmZQ/http3-go v0.59.0 github.com/c2FmZQ/quic-api v0.59.0 github.com/c2FmZQ/storage v0.3.2 + github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 github.com/c2FmZQ/tpm v0.4.3 github.com/fxamacker/cbor/v2 v2.9.0 github.com/go-test/deep v1.1.0 diff --git a/go.sum b/go.sum index 82cf09ad..e892c458 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/c2FmZQ/quic-api v0.59.0 h1:bLq5g0hX60Uy/SuDhplq07jTun/9GMe28oXLemW54x github.com/c2FmZQ/quic-api v0.59.0/go.mod h1:eo7AOXIR//Pf2pfQ+rP18fQTjwyIPPp3cBopMNr/NTU= github.com/c2FmZQ/storage v0.3.2 h1:/Qy/uoZTEw1XxrkHK4MAiQliwpZ02Y5w/whym8Q4+qQ= github.com/c2FmZQ/storage v0.3.2/go.mod h1:kgLL2yKcpWW62FmdoWwETSAWKpF95dBfhD6N7zfE1PE= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 h1:X9sQsvaKIg79vUJ6ER/isN5fDkuEoWsBnEHYm4Z/WF4= +github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7/go.mod h1:OmTz0Nc+C8Ent4TvkXv+0zQGdvMFHU/6ZTon629wjOk= github.com/c2FmZQ/tpm v0.4.3 h1:b5jjlqVyf5rdU0z0uohi5QylE6hb6vxryWGr3b3qCos= github.com/c2FmZQ/tpm v0.4.3/go.mod h1:WOLrMYIX1ueasQqAdWcBjCwAJ+blrHEUxUcT1K0w360= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/proxy/internal/cookiemanager/trusted_issuers_test.go b/proxy/internal/cookiemanager/trusted_issuers_test.go index 1e08e9c6..a1d7740c 100644 --- a/proxy/internal/cookiemanager/trusted_issuers_test.go +++ b/proxy/internal/cookiemanager/trusted_issuers_test.go @@ -11,26 +11,14 @@ import ( "testing" "time" + jwt "github.com/golang-jwt/jwt/v5" + "github.com/c2FmZQ/storage" "github.com/c2FmZQ/storage/crypto" + "github.com/c2FmZQ/tlsproxy/jwks" "github.com/c2FmZQ/tlsproxy/proxy/internal/tokenmanager" - jwt "github.com/golang-jwt/jwt/v5" ) -type jwks struct { - Keys []jwk `json:"keys"` -} - -type jwk struct { - Type string `json:"kty"` - Use string `json:"use"` - ID string `json:"kid"` - Alg string `json:"alg"` - Curve string `json:"crv,omitempty"` - X string `json:"x,omitempty"` - Y string `json:"y,omitempty"` -} - func TestTrustedIssuers(t *testing.T) { // 1. Setup Mock JWKS Server privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -38,8 +26,8 @@ func TestTrustedIssuers(t *testing.T) { t.Fatalf("ecdsa.GenerateKey: %v", err) } - mockJWKS := jwks{ - Keys: []jwk{ + mockJWKS := jwks.JWKS{ + Keys: []jwks.JWK{ { Type: "EC", Use: "sig", @@ -71,7 +59,7 @@ func TestTrustedIssuers(t *testing.T) { } trustedIssuer := "https://trusted.example.com" - tm.SetTrustedIssuers([]struct{ Issuer, JWKSURI string }{ + tm.SetTrustedIssuers([]jwks.Issuer{ { Issuer: trustedIssuer, JWKSURI: server.URL, diff --git a/proxy/internal/tokenmanager/tokenmanager.go b/proxy/internal/tokenmanager/tokenmanager.go index ac06f426..dd9232b1 100644 --- a/proxy/internal/tokenmanager/tokenmanager.go +++ b/proxy/internal/tokenmanager/tokenmanager.go @@ -38,22 +38,19 @@ import ( "crypto/sha256" "crypto/x509" "encoding/asn1" - "encoding/base64" "encoding/hex" "encoding/json" "errors" - "fmt" "io" "log" "math/big" "net/http" "slices" - "strconv" - "strings" "sync" "time" "github.com/c2FmZQ/storage" + "github.com/c2FmZQ/tlsproxy/jwks" "github.com/c2FmZQ/tpm" jwt "github.com/golang-jwt/jwt/v5" "github.com/hashicorp/go-retryablehttp" @@ -99,17 +96,9 @@ type TokenManager struct { logger logger client *retryablehttp.Client - mu sync.Mutex - keys tokenKeys - trustedIssuers map[string]*trustedIssuer -} - -type trustedIssuer struct { - issuer string - jwksURI string - publicKeys map[string]crypto.PublicKey - nextUpdate time.Time - cancel func() + mu sync.Mutex + keys tokenKeys + remote *jwks.Remote } // New returns a new TokenManager. @@ -118,13 +107,13 @@ func New(store *storage.Storage, tpm *tpm.TPM, logger logger) (*TokenManager, er logger = defaultLogger{} } tm := TokenManager{ - store: store, - tpm: tpm, - logger: logger, - client: retryablehttp.NewClient(), - trustedIssuers: make(map[string]*trustedIssuer), + store: store, + tpm: tpm, + logger: logger, + client: retryablehttp.NewClient(), } tm.client.Logger = nil + tm.remote = jwks.NewRemote(tm.client, logger) store.CreateEmptyFile(tokenKeyFile, &tm.keys) if err := tm.rotateKeys(); err != nil { return nil, err @@ -146,118 +135,8 @@ func (tm *TokenManager) KeyRotationLoop(ctx context.Context) { } } -func (tm *TokenManager) SetTrustedIssuers(issuers []struct{ Issuer, JWKSURI string }) { - tm.mu.Lock() - defer tm.mu.Unlock() - - inUse := make(map[string]bool) - for _, cfg := range issuers { - inUse[cfg.Issuer] = true - ti, exists := tm.trustedIssuers[cfg.Issuer] - if !exists { - ctx, cancel := context.WithCancel(context.Background()) - ti = &trustedIssuer{ - issuer: cfg.Issuer, - cancel: cancel, - } - tm.trustedIssuers[cfg.Issuer] = ti - go tm.backgroundJWKSRefresh(ctx, ti) - } - // update mutable fields - ti.jwksURI = cfg.JWKSURI - if ti.nextUpdate.IsZero() { - ti.nextUpdate = time.Now() // Trigger immediate update - } - } - - for k, ti := range tm.trustedIssuers { - if !inUse[k] { - ti.cancel() - delete(tm.trustedIssuers, k) - } - } -} - -func (tm *TokenManager) backgroundJWKSRefresh(ctx context.Context, ti *trustedIssuer) { - for { - tm.mu.Lock() - nextUpdate := ti.nextUpdate - tm.mu.Unlock() - - select { - case <-ctx.Done(): - return - case <-time.After(time.Until(nextUpdate)): - } - - if err := tm.fetchJWKS(ctx, ti); err != nil { - tm.logger.Errorf("ERR fetchJWKS(%s): %v", ti.issuer, err) - // Retry sooner on error - tm.mu.Lock() - ti.nextUpdate = time.Now().Add(5 * time.Minute) - tm.mu.Unlock() - } - } -} - -func (tm *TokenManager) fetchJWKS(ctx context.Context, ti *trustedIssuer) error { - tm.mu.Lock() - uri := ti.jwksURI - tm.mu.Unlock() - - req, err := retryablehttp.NewRequestWithContext(ctx, http.MethodGet, uri, nil) - if err != nil { - return err - } - resp, err := tm.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } - - var keys jwks - if err := json.NewDecoder(&io.LimitedReader{R: resp.Body, N: 1048576}).Decode(&keys); err != nil { - return err - } - publicKeys := make(map[string]crypto.PublicKey) - for _, k := range keys.Keys { - pk, err := k.PublicKey() - if err != nil { - tm.logger.Errorf("ERR JWK %s: %v", k.ID, err) - continue - } - publicKeys[k.ID] = pk - } - - // Parse Cache-Control - ttl := time.Hour - if cc := resp.Header.Get("cache-control"); cc != "" { - for _, part := range strings.Split(cc, ",") { - part = strings.TrimSpace(part) - if strings.HasPrefix(part, "max-age=") { - if v, err := strconv.Atoi(part[8:]); err == nil && v > 0 { - ttl = time.Duration(v) * time.Second - } - } - } - } - if age := resp.Header.Get("age"); age != "" { - if v, err := strconv.Atoi(age); err == nil && v > 0 { - ttl -= time.Duration(v) * time.Second - } - } - if ttl < 5*time.Minute { - ttl = 5 * time.Minute - } - - tm.mu.Lock() - ti.publicKeys = publicKeys - ti.nextUpdate = time.Now().Add(ttl) - tm.mu.Unlock() - return nil +func (tm *TokenManager) SetTrustedIssuers(issuers []jwks.Issuer) { + tm.remote.SetIssuers(slices.Clone(issuers)) } func (tm *TokenManager) HMAC(b []byte) []byte { @@ -574,10 +453,8 @@ func (tm *TokenManager) getKey(tok *jwt.Token) (interface{}, error) { return tk.privKey.Public(), nil } } - for _, ti := range tm.trustedIssuers { - if pk, ok := ti.publicKeys[kid]; ok { - return pk, nil - } + if pk, err := tm.remote.GetKey(kid); err == nil { + return pk, nil } return nil, errors.New("not found") } @@ -591,12 +468,7 @@ func (tm *TokenManager) IssuerForKey(kid string) (string, bool) { return "", true } } - for _, ti := range tm.trustedIssuers { - if _, ok := ti.publicKeys[kid]; ok { - return ti.issuer, true - } - } - return "", false + return tm.remote.IssuerForKey(kid) } // ValidateToken validates a JSON Web Token (JWT). @@ -605,95 +477,14 @@ func (tm *TokenManager) ValidateToken(t string, opts ...jwt.ParserOption) (*jwt. return jwt.ParseWithClaims(t, jwt.MapClaims{}, tm.getKey, opts...) } -type jwks struct { - Keys []jwk `json:"keys"` -} - -type jwk struct { - Type string `json:"kty"` - Use string `json:"use"` - ID string `json:"kid"` - Alg string `json:"alg"` - // EC - Curve string `json:"crv,omitempty"` - X string `json:"x,omitempty"` - Y string `json:"y,omitempty"` - // RSA - N string `json:"n,omitempty"` - E string `json:"e,omitempty"` -} - -func (k jwk) PublicKey() (crypto.PublicKey, error) { - switch k.Type { - case "EC": - if k.Curve != "" && k.Curve != "P-256" { - return nil, fmt.Errorf("unsupported EC curve %q", k.Curve) - } - curve := elliptic.P256() // Only P-256 is supported for now - x, err := base64.RawURLEncoding.DecodeString(k.X) - if err != nil { - return nil, err - } - y, err := base64.RawURLEncoding.DecodeString(k.Y) - if err != nil { - return nil, err - } - return &ecdsa.PublicKey{Curve: curve, X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y)}, nil - case "RSA": - n, err := base64.RawURLEncoding.DecodeString(k.N) - if err != nil { - return nil, err - } - eBytes, err := base64.RawURLEncoding.DecodeString(k.E) - if err != nil { - return nil, err - } - return &rsa.PublicKey{N: new(big.Int).SetBytes(n), E: int(new(big.Int).SetBytes(eBytes).Int64())}, nil - case "OKP": // EdDSA - x, err := base64.RawURLEncoding.DecodeString(k.X) - if err != nil { - return nil, err - } - return ed25519.PublicKey(x), nil - default: - return nil, fmt.Errorf("unknown key type %q", k.Type) - } -} - // ServeJWKS returns the current public keys as a JSON Web Key Set (JWKS). func (tm *TokenManager) ServeJWKS(w http.ResponseWriter, req *http.Request) { tm.mu.Lock() - var out jwks + var out jwks.JWKS for _, key := range tm.keys.Keys { - switch pub := key.privKey.Public().(type) { - case *ecdsa.PublicKey: - out.Keys = append(out.Keys, jwk{ - Type: "EC", - Use: "sig", - ID: key.ID, - Alg: "ES256", - Curve: "P-256", - X: base64.RawURLEncoding.EncodeToString(pub.X.Bytes()), - Y: base64.RawURLEncoding.EncodeToString(pub.Y.Bytes()), - }) - case ed25519.PublicKey: - out.Keys = append(out.Keys, jwk{ - Type: "OKP", - Use: "sig", - ID: key.ID, - Alg: "EdDSA", - Curve: "Ed25519", - X: base64.RawURLEncoding.EncodeToString(pub), - }) - case *rsa.PublicKey: - out.Keys = append(out.Keys, jwk{ - Type: "RSA", - Use: "sig", - ID: key.ID, - Alg: "RS256", - N: base64.RawURLEncoding.EncodeToString(pub.N.Bytes()), - E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes()), - }) + if k := jwks.PublicKeyToJWK(key.privKey.Public()); k != nil { + k.ID = key.ID + out.Keys = append(out.Keys, *k) } } tm.mu.Unlock() diff --git a/proxy/proxy.go b/proxy/proxy.go index 076ffa79..98176fad 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -66,6 +66,7 @@ import ( "golang.org/x/time/rate" "github.com/c2FmZQ/tlsproxy/certmanager" + "github.com/c2FmZQ/tlsproxy/jwks" "github.com/c2FmZQ/tlsproxy/proxy/internal/cookiemanager" "github.com/c2FmZQ/tlsproxy/proxy/internal/counter" "github.com/c2FmZQ/tlsproxy/proxy/internal/fromctx" @@ -298,11 +299,11 @@ func NewTestProxy(cfg *Config) (*Proxy, error) { return p, nil } -func extractTrustedIssuers(issuers []*TrustedIssuer, all *[]struct{ Issuer, JWKSURI string }) []string { +func extractTrustedIssuers(issuers []*TrustedIssuer, all *[]jwks.Issuer) []string { var out []string for _, ti := range issuers { out = append(out, ti.Issuer) - *all = append(*all, struct{ Issuer, JWKSURI string }{ti.Issuer, ti.JWKSURI}) + *all = append(*all, jwks.Issuer{ti.Issuer, ti.JWKSURI}) } return out } @@ -337,7 +338,7 @@ func (p *Proxy) Reconfigure(cfg *Config) error { } er := eventRecorder{record: p.recordEvent} identityProviders := make(map[string]idp) - var allTrustedIssuers []struct{ Issuer, JWKSURI string } + var allTrustedIssuers []jwks.Issuer for _, pp := range cfg.OIDCProviders { _, host, _, _ := hostAndPath(pp.RedirectURL) diff --git a/scripts/update-go-version.sh b/scripts/update-go-version.sh index 9d0163bc..49b9a27b 100755 --- a/scripts/update-go-version.sh +++ b/scripts/update-go-version.sh @@ -28,7 +28,7 @@ echo >> CHANGELOG.md-new sed -n '3,$p' < CHANGELOG.md >> CHANGELOG.md-new mv CHANGELOG.md-new CHANGELOG.md -for dir in $(find examples/ go.mod); do +for dir in $(find examples/ jwks/ go.mod); do exdep=$((cd "$(dirname "${dir}")" && go get -u ./... 2>&1 && go mod tidy) | grep upgrade | sed -re 's/go: //g') if [[ -n "${exdeps}" ]]; then echo "* Update go dependencies in ${dir}:" From 2138a2040eabadc6885e92e695a7761544c4353a Mon Sep 17 00:00:00 2001 From: Robin Thellend Date: Fri, 30 Jan 2026 09:31:15 -0800 Subject: [PATCH 2/4] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aaed35e..b40c608d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ ### :wrench: Misc +* Refactored JWKS into a new module. * Update go dependencies: + * added github.com/c2FmZQ/tlsproxy/jwks v0.0.0-20260130170702-223855195ae7 * upgraded github.com/golang-jwt/jwt/v5 v5.3.0 => v5.3.1 * upgraded github.com/pires/go-proxyproto v0.8.1 => v0.9.2 From 941db57f0314d61518020b3802d5e3d9cf77830a Mon Sep 17 00:00:00 2001 From: Robin Thellend Date: Fri, 30 Jan 2026 09:34:35 -0800 Subject: [PATCH 3/4] Fix workflow --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c1e9d364..9ef5726c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -56,7 +56,7 @@ jobs: run: go test -race -timeout=5m -failfast ./... - name: Run govulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./... - - name Test jwks + - name: Test jwks run: | cd ./jwks go build . From 7b7e7219ed71ddc2d46099dd8f07ad633a38df5d Mon Sep 17 00:00:00 2001 From: Robin Thellend Date: Fri, 30 Jan 2026 09:39:05 -0800 Subject: [PATCH 4/4] Fix go vet error --- proxy/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/proxy.go b/proxy/proxy.go index 98176fad..973abd47 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -303,7 +303,7 @@ func extractTrustedIssuers(issuers []*TrustedIssuer, all *[]jwks.Issuer) []strin var out []string for _, ti := range issuers { out = append(out, ti.Issuer) - *all = append(*all, jwks.Issuer{ti.Issuer, ti.JWKSURI}) + *all = append(*all, jwks.Issuer{Issuer: ti.Issuer, JWKSURI: ti.JWKSURI}) } return out }