From 03a14eb2bd52cecf95d75d90448eba6c8279b931 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 25 Aug 2021 08:22:34 +0200 Subject: [PATCH] refactor: clean up id_token validation --- cmd/wonderwall/main.go | 19 ++++--- go.mod | 4 -- go.sum | 20 ++----- pkg/auth/validate.go | 27 --------- pkg/keyset/keyset.go | 30 ++++++++++ pkg/router/idporten_mock_server_test.go | 10 ++++ pkg/router/router.go | 74 +++++++++++-------------- pkg/router/router_test.go | 25 ++++----- pkg/token/token.go | 57 ++++++++++++++++++- 9 files changed, 156 insertions(+), 110 deletions(-) delete mode 100644 pkg/auth/validate.go create mode 100644 pkg/keyset/keyset.go diff --git a/cmd/wonderwall/main.go b/cmd/wonderwall/main.go index 5eeef43..d82cbab 100644 --- a/cmd/wonderwall/main.go +++ b/cmd/wonderwall/main.go @@ -4,14 +4,16 @@ import ( "context" "encoding/base64" "fmt" - "github.com/go-redis/redis/v8" - "github.com/nais/wonderwall/pkg/session" "net/http" "os" + "github.com/go-redis/redis/v8" + "github.com/lestrrat-go/jwx/jwk" + + "github.com/nais/wonderwall/pkg/session" + "github.com/nais/wonderwall/pkg/token" - "github.com/coreos/go-oidc" "github.com/nais/liberator/pkg/conftools" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" @@ -88,6 +90,11 @@ func run() error { Scopes: scopes, } + jwkSet, err := jwk.Fetch(context.Background(), cfg.IDPorten.WellKnown.JwksURI) + if err != nil { + return fmt.Errorf("fetching jwks: %w", err) + } + handler := &router.Handler{ Config: cfg.IDPorten, Crypter: crypt, @@ -95,11 +102,7 @@ func run() error { UpstreamHost: cfg.UpstreamHost, SecureCookies: true, Sessions: sessionStore, - IdTokenVerifier: oidc.NewVerifier( - cfg.IDPorten.WellKnown.Issuer, - oidc.NewRemoteKeySet(context.Background(), cfg.IDPorten.WellKnown.JwksURI), - &oidc.Config{ClientID: cfg.IDPorten.ClientID}, - ), + JwkSet: jwkSet, } r := router.New(handler) diff --git a/go.mod b/go.mod index 3331f02..d906a79 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,6 @@ module github.com/nais/wonderwall go 1.16 require ( - github.com/cespare/xxhash v1.1.0 // indirect - github.com/coreos/go-oidc v2.1.0+incompatible github.com/go-chi/chi v1.5.4 github.com/go-redis/redis/v8 v8.11.3 github.com/google/uuid v1.1.2 @@ -15,9 +13,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 - go.opentelemetry.io/otel v0.6.0 // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 - golang.org/x/text v0.3.6 // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/square/go-jose.v2 v2.6.0 ) diff --git a/go.sum b/go.sum index 3f1c157..2171079 100644 --- a/go.sum +++ b/go.sum @@ -47,9 +47,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -66,15 +64,12 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/benbjohnson/clock v1.0.0/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -87,7 +82,6 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -245,8 +239,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -391,6 +385,7 @@ github.com/nais/liberator v0.0.0-20210809103005-edb0141d646d/go.mod h1:Dw7TPRYZk github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -398,14 +393,15 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -421,7 +417,6 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -448,7 +443,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= @@ -509,7 +503,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofSXw4Ek= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -614,7 +607,6 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= @@ -826,7 +818,6 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= @@ -897,7 +888,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -911,13 +901,13 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/pkg/auth/validate.go b/pkg/auth/validate.go deleted file mode 100644 index eddcedf..0000000 --- a/pkg/auth/validate.go +++ /dev/null @@ -1,27 +0,0 @@ -package auth - -import ( - "context" - "fmt" - - "github.com/coreos/go-oidc" - "golang.org/x/oauth2" -) - -func ValidateIdToken(ctx context.Context, verifier *oidc.IDTokenVerifier, token *oauth2.Token, nonce string) (*oidc.IDToken, error) { - raw, ok := token.Extra("id_token").(string) - if !ok { - return nil, fmt.Errorf("missing id_token in token response") - } - - idToken, err := verifier.Verify(ctx, raw) - if err != nil { - return nil, err - } - - if idToken.Nonce != nonce { - return nil, fmt.Errorf("nonce does not match") - } - - return idToken, nil -} diff --git a/pkg/keyset/keyset.go b/pkg/keyset/keyset.go new file mode 100644 index 0000000..8b5e901 --- /dev/null +++ b/pkg/keyset/keyset.go @@ -0,0 +1,30 @@ +package keyset + +import ( + "context" + "fmt" + + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" +) + +// EnsureValid sets fields for the keys in the given keyset if missing. +// We only accept keys with the "alg" value set to RS256. +func EnsureValid(ctx context.Context, jwks jwk.Set) error { + for iter := jwks.Iterate(ctx); iter.Next(ctx); { + pair := iter.Pair() + key := pair.Value.(jwk.Key) + + if len(key.Algorithm()) == 0 { + err := key.Set(jwk.AlgorithmKey, jwa.RS256) + if err != nil { + return fmt.Errorf("setting key algorithm") + } + } + + if key.Algorithm() != string(jwa.RS256) { + jwks.Remove(key) + } + } + return nil +} diff --git a/pkg/router/idporten_mock_server_test.go b/pkg/router/idporten_mock_server_test.go index 0b620ad..fc7f35e 100644 --- a/pkg/router/idporten_mock_server_test.go +++ b/pkg/router/idporten_mock_server_test.go @@ -41,6 +41,16 @@ func NewIDPorten(clients map[string]string) *IDPorten { panic(err) } + err = jwk.AssignKeyID(key) + if err != nil { + panic(err) + } + + err = key.Set(jwk.AlgorithmKey, jwa.RS256) + if err != nil { + panic(err) + } + keys := jwk.NewSet() keys.Add(key) diff --git a/pkg/router/router.go b/pkg/router/router.go index 5cb2c93..42f97cb 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -6,27 +6,24 @@ import ( "crypto/sha256" "encoding/base64" "fmt" - "github.com/nais/wonderwall/pkg/session" - "gopkg.in/square/go-jose.v2/jwt" "io" "net/http" "net/url" "sync" "time" - "github.com/coreos/go-oidc" - "github.com/nais/wonderwall/pkg/token" - - "github.com/go-chi/chi/middleware" - log "github.com/sirupsen/logrus" - - "github.com/nais/wonderwall/pkg/auth" - "github.com/nais/wonderwall/pkg/cryptutil" - - "github.com/go-chi/chi" - "golang.org/x/oauth2" + "github.com/lestrrat-go/jwx/jwt" "github.com/nais/wonderwall/pkg/config" + "github.com/nais/wonderwall/pkg/cryptutil" + "github.com/nais/wonderwall/pkg/session" + "github.com/nais/wonderwall/pkg/token" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "github.com/lestrrat-go/jwx/jwk" + log "github.com/sirupsen/logrus" + "golang.org/x/oauth2" ) const ( @@ -39,14 +36,14 @@ const ( ) type Handler struct { - Config config.IDPorten - OauthConfig oauth2.Config - Crypter cryptutil.Crypter - UpstreamHost string - IdTokenVerifier *oidc.IDTokenVerifier - SecureCookies bool - Sessions session.Store - lock sync.Mutex + Config config.IDPorten + OauthConfig oauth2.Config + Crypter cryptutil.Crypter + UpstreamHost string + JwkSet jwk.Set + SecureCookies bool + Sessions session.Store + lock sync.Mutex } type loginParams struct { @@ -112,6 +109,7 @@ func (h *Handler) LoginURL() (*loginParams, error) { func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { params, err := h.LoginURL() if err != nil { + log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } @@ -122,6 +120,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { NewCookie(CodeVerifierCookieName, params.codeVerifier, LoginCookieLifetime), ) if err != nil { + log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } @@ -170,32 +169,35 @@ func (h *Handler) Callback(w http.ResponseWriter, r *http.Request) { return } - idToken, err := auth.ValidateIdToken(r.Context(), h.IdTokenVerifier, tokens, cookies.Nonce) + idToken, err := token.ParseIDToken(r.Context(), h.JwkSet, tokens) if err != nil { log.Error(err) w.WriteHeader(http.StatusUnauthorized) return } - var claims struct { - SessionID string `json:"sid"` + validateOpts := []jwt.ValidateOption{ + jwt.WithAudience(h.Config.ClientID), + jwt.WithClaimValue("nonce", cookies.Nonce), + jwt.WithIssuer(h.Config.WellKnown.Issuer), } - if err := idToken.Claims(&claims); err != nil { + err = idToken.Validate(validateOpts...) + if err != nil { log.Error(err) w.WriteHeader(http.StatusUnauthorized) return } - err = h.setEncryptedCookie(w, SessionCookieName, claims.SessionID, SessionMaxLifetime) + err = h.setEncryptedCookie(w, SessionCookieName, idToken.SessionID, SessionMaxLifetime) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } - err = h.Sessions.Write(r.Context(), claims.SessionID, &session.Data{ - ID: claims.SessionID, + err = h.Sessions.Write(r.Context(), idToken.SessionID, &session.Data{ + ID: idToken.SessionID, Token: tokens, }, SessionMaxLifetime) if err != nil { @@ -287,7 +289,7 @@ func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, u.String(), http.StatusTemporaryRedirect) } -// Logout triggers self-initiated for the current user +// FrontChannelLogout triggers logout triggered by a third-party. func (h *Handler) FrontChannelLogout(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() @@ -307,25 +309,15 @@ func (h *Handler) FrontChannelLogout(w http.ResponseWriter, r *http.Request) { } // From here on, check that 'iss' from request matches data found in access token. - tok, err := jwt.ParseSigned(sess.Token.AccessToken) + tok, err := jwt.Parse([]byte(sess.Token.AccessToken)) if err != nil { log.Error(err) w.WriteHeader(http.StatusInternalServerError) return } - var claims struct { - Issuer string `json:"iss"` - } - - err = tok.UnsafeClaimsWithoutVerification(&claims) + err = jwt.Validate(tok, jwt.WithClaimValue("iss", iss)) if err != nil { - log.Error(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - if claims.Issuer != iss { w.WriteHeader(http.StatusBadRequest) return } diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go index 6f6322c..13ffb3c 100644 --- a/pkg/router/router_test.go +++ b/pkg/router/router_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" "fmt" - "github.com/nais/wonderwall/pkg/session" "net/http" "net/http/cookiejar" "net/http/httptest" @@ -12,9 +11,11 @@ import ( "testing" "time" - "golang.org/x/oauth2" + "github.com/lestrrat-go/jwx/jwk" - "github.com/coreos/go-oidc" + "github.com/nais/wonderwall/pkg/session" + + "golang.org/x/oauth2" "github.com/nais/wonderwall/pkg/cryptutil" @@ -77,7 +78,6 @@ func handler() *router.Handler { }, Crypter: cryptutil.New(encryptionKey), UpstreamHost: "", - IdTokenVerifier: nil, } return &handler } @@ -147,11 +147,9 @@ func TestHandler_Callback_and_Logout(t *testing.T) { h.Config.WellKnown.EndSessionEndpoint = idpserver.URL + "/endsession" h.Config.RedirectURI = server.URL + "/oauth2/callback" h.Config.PostLogoutRedirectURI = server.URL - h.IdTokenVerifier = oidc.NewVerifier( - cfg.WellKnown.Issuer, - oidc.NewRemoteKeySet(context.Background(), idpserver.URL+"/jwks"), - &oidc.Config{ClientID: cfg.ClientID}, - ) + jwkSet, err := jwk.Fetch(context.Background(), idpserver.URL+"/jwks") + assert.NoError(t, err) + h.JwkSet = jwkSet jar, err := cookiejar.New(nil) assert.NoError(t, err) @@ -241,11 +239,10 @@ func TestHandler_FrontChannelLogout(t *testing.T) { h.Config.WellKnown.EndSessionEndpoint = idpserver.URL + "/endsession" h.Config.RedirectURI = server.URL + "/oauth2/callback" h.Config.PostLogoutRedirectURI = server.URL - h.IdTokenVerifier = oidc.NewVerifier( - cfg.WellKnown.Issuer, - oidc.NewRemoteKeySet(context.Background(), idpserver.URL+"/jwks"), - &oidc.Config{ClientID: cfg.ClientID}, - ) + + jwkSet, err := jwk.Fetch(context.Background(), idpserver.URL+"/jwks") + assert.NoError(t, err) + h.JwkSet = jwkSet jar, err := cookiejar.New(nil) assert.NoError(t, err) diff --git a/pkg/token/token.go b/pkg/token/token.go index 08dc774..13198f5 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -1,6 +1,17 @@ package token -const ScopeOpenID = "openid" +import ( + "context" + "fmt" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jwt" + "golang.org/x/oauth2" + + "github.com/nais/wonderwall/pkg/keyset" +) + +const ScopeOpenID = "openid" type JWTTokenRequest struct { Issuer string `json:"iss"` @@ -10,3 +21,47 @@ type JWTTokenRequest struct { IssuedAt int64 `json:"iat"` ExpiresAt int64 `json:"exp"` } + +type IDToken struct { + Raw string + SessionID string + Token jwt.Token +} + +func (in *IDToken) Validate(opts ...jwt.ValidateOption) error { + return jwt.Validate(in.Token, opts...) +} + +func ParseIDToken(ctx context.Context, jwks jwk.Set, token *oauth2.Token) (*IDToken, error) { + raw, ok := token.Extra("id_token").(string) + if !ok { + return nil, fmt.Errorf("missing id_token in token response") + } + + err := keyset.EnsureValid(ctx, jwks) + if err != nil { + return nil, err + } + + parseOpts := []jwt.ParseOption{ + jwt.WithKeySet(jwks), + jwt.WithRequiredClaim("sid"), + } + idToken, err := jwt.Parse([]byte(raw), parseOpts...) + if err != nil { + return nil, fmt.Errorf("parsing jwt: %w", err) + } + + sessionID, ok := idToken.Get("sid") + if !ok { + return nil, fmt.Errorf("missing 'sid' claim in id_token") + } + + result := &IDToken{ + Raw: raw, + SessionID: sessionID.(string), + Token: idToken, + } + + return result, nil +}