git.iko.soy/typeable/oauth2-proxy
on master help sign in
Implement graceful shutdown and propagate request context (#468)

* feature: Implement graceful shutdown Propagate the request context to the Redis client. It is possible to propagate a context cancel to Redis client if the connection is closed by the HTTP client. The redis.Cmdable cannot use WithContext, so added the Client interface to handle redis.Client and redis.ClusterClient transparently. Added handling of Unix signals to http server. Upgrade go-redis/redis to v7. * Update dependencies - Upgrade golang/x/* and google-api-go - Migrate fsnotify import from gopkg.in to github.com - Replace bmizerany/assert with stretchr/testify/assert * add doc for wrapper interface * Update CHANGELOG.md * fix: upgrade fsnotify to v1.4.9 * fix: remove unnessary logging * fix: wait until all connections have been closed * refactor: move chan to main for testing * add assert to check if stop chan is empty * add an idiomatic for sync.WaitGroup with timeout

commitc7bfbdecef0a724b5bc752607edd4257928ddd40 authorMitsuo Heijo <mitsuo.heijo@gmail.com> dateSun 5 Apr 2020, 00:12 (7 years ago) parentbdc6861
11 files changed+175−53
CHANGELOG.md+1 go.mod+6−7 go.sum+18−14 http.go+26−12 http_test.go+30 main.go+10 pkg/sessions/redis/client.go+59 pkg/sessions/redis/redis_store.go+20−17 providers/keycloak_test.go+2−1 providers/oidc_test.go+1−1 watcher.go+2−1
CHANGELOG.md+1
@@ -12,6 +12,7 @@
1212
1313## Changes since v5.1.0
1414
15+- [#468](https://github.com/oauth2-proxy/oauth2-proxy/pull/468) Implement graceful shutdown and propagate request context (@johejo)
1516- [#464](https://github.com/oauth2-proxy/oauth2-proxy/pull/464) Migrate to oauth2-proxy/oauth2-proxy (@JoelSpeed)
1617 - Project renamed from `pusher/oauth2_proxy` to `oauth2-proxy`
1718 - Move Go import path from `github.com/pusher/oauth2_proxy` to `github.com/oauth2-proxy/oauth2-proxy`
go.mod+6−7
@@ -6,11 +6,11 @@ require (
66 github.com/BurntSushi/toml v0.3.1
77 github.com/alicebob/miniredis/v2 v2.11.2
88 github.com/bitly/go-simplejson v0.5.0
9− github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
9+ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
1010 github.com/coreos/go-oidc v2.2.1+incompatible
1111 github.com/dgrijalva/jwt-go v3.2.0+incompatible
12− github.com/go-redis/redis v6.15.7+incompatible
13− github.com/gomodule/redigo v2.0.0+incompatible // indirect
12+ github.com/fsnotify/fsnotify v1.4.9
13+ github.com/go-redis/redis/v7 v7.2.0
1414 github.com/kr/pretty v0.2.0 // indirect
1515 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
1616 github.com/mreiferson/go-options v1.0.0
@@ -19,11 +19,10 @@ require (
1919 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
2020 github.com/stretchr/testify v1.5.1
2121 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
22− golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
23− golang.org/x/net v0.0.0-20200226121028-0de0cce0169b
22+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
23+ golang.org/x/net v0.0.0-20190923162816-aa69164e4478
2424 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
25− google.golang.org/api v0.19.0
26− gopkg.in/fsnotify/fsnotify.v1 v1.4.7
25+ google.golang.org/api v0.20.0
2726 gopkg.in/natefinch/lumberjack.v2 v2.0.0
2827 gopkg.in/square/go-jose.v2 v2.4.1
2928)
go.sum+18−14
@@ -27,8 +27,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
2727github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
2828github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
2929github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
30−github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
31−github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
30+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
31+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
32+github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
33+github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
3234github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
3335github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
3436github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -36,9 +38,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
3638github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
3739github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
3840github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
41+github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
3942github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
40−github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
41−github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
4243github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
4344github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
4445github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
@@ -54,6 +55,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
5455github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
5556github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
5657github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
58+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
5759github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
5860github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
5961github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -64,8 +66,10 @@ github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg+
6466github.com/mreiferson/go-options v1.0.0 h1:RMLidydGlDWpL+lQTXo0bVIf/XT2CTq7AEJMoz5/VWs=
6567github.com/mreiferson/go-options v1.0.0/go.mod h1:zHtCks/HQvOt8ATyfwVe3JJq2PPuImzXINPRTC03+9w=
6668github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
69+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
6770github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
6871github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
72+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
6973github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
7074github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
7175github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
@@ -83,9 +87,8 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
8387github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
8488go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
8589go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
90+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
8691golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
87−golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
88−golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
8992golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
9093golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
9194golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -98,11 +101,10 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
98101golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
99102golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
100103golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
101−golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
102104golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
103105golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
104−golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
105−golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
106+golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
107+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
106108golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
107109golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
108110golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -118,9 +120,10 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
118120golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
119121golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
120122golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
121−golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
122123golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
123124golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
125+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
126+golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
124127golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
125128golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
126129golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -137,8 +140,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
137140golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
138141golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
139142google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
140−google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA=
141−google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
143+google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
144+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
142145google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
143146google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
144147google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
@@ -154,16 +157,17 @@ google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
154157google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
155158gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
156159gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
160+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
161+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
157162gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
158163gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
159−gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
160−gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
161164gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
162165gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
163166gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
164167gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
165168gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
166169gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
170+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
167171gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
168172gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
169173gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
http.go+26−12
@@ -1,7 +1,9 @@
11package main
22
33import (
4+ "context"
45 "crypto/tls"
6+ "errors"
57 "net"
68 "net/http"
79 "strings"
@@ -14,6 +16,7 @@ import (
1416type Server struct {
1517 Handler http.Handler
1618 Opts *Options
19+ stop chan struct{} // channel for waiting shutdown
1720}
1821
1922// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
@@ -90,13 +93,7 @@ func (s *Server) ServeHTTP() {
9093 logger.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
9194 }
9295 logger.Printf("HTTP: listening on %s", listenAddr)
93−
94− server := &http.Server{Handler: s.Handler}
95− err = server.Serve(listener)
96− if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
97− logger.Printf("ERROR: http.Serve() - %s", err)
98− }
99−
96+ s.serve(listener)
10097 logger.Printf("HTTP: closing %s", listener.Addr())
10198}
10299
@@ -125,14 +122,31 @@ func (s *Server) ServeHTTPS() {
125122 logger.Printf("HTTPS: listening on %s", ln.Addr())
126123
127124 tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
125+ s.serve(tlsListener)
126+ logger.Printf("HTTPS: closing %s", tlsListener.Addr())
127+}
128+
129+func (s *Server) serve(listener net.Listener) {
128130 srv := &http.Server{Handler: s.Handler}
129− err = srv.Serve(tlsListener)
130131
131− if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
132− logger.Printf("ERROR: https.Serve() - %s", err)
133− }
132+ // See https://golang.org/pkg/net/http/#Server.Shutdown
133+ idleConnsClosed := make(chan struct{})
134+ go func() {
135+ <-s.stop // wait notification for stopping server
134136
135− logger.Printf("HTTPS: closing %s", tlsListener.Addr())
137+ // We received an interrupt signal, shut down.
138+ if err := srv.Shutdown(context.Background()); err != nil {
139+ // Error from closing listeners, or context timeout:
140+ logger.Printf("HTTP server Shutdown: %v", err)
141+ }
142+ close(idleConnsClosed)
143+ }()
144+
145+ err := srv.Serve(listener)
146+ if err != nil && !errors.Is(err, http.ErrServerClosed) {
147+ logger.Printf("ERROR: http.Serve() - %s", err)
148+ }
149+ <-idleConnsClosed
136150}
137151
138152// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
http_test.go+30
@@ -3,7 +3,9 @@ package main
33import (
44 "net/http"
55 "net/http/httptest"
6+ "sync"
67 "testing"
8+ "time"
79
810 "github.com/stretchr/testify/assert"
911)
@@ -156,3 +158,31 @@ func TestRedirectNotWhenHTTPS(t *testing.T) {
156158
157159 assert.Equal(t, http.StatusOK, res.StatusCode, "status code should be %d, got: %d", http.StatusOK, res.StatusCode)
158160}
161+
162+func TestGracefulShutdown(t *testing.T) {
163+ opts := NewOptions()
164+ stop := make(chan struct{}, 1)
165+ srv := Server{Handler: http.DefaultServeMux, Opts: opts, stop: stop}
166+ var wg sync.WaitGroup
167+ wg.Add(1)
168+ go func() {
169+ defer wg.Done()
170+ srv.ServeHTTP()
171+ }()
172+
173+ stop <- struct{}{} // emulate catching signals
174+
175+ // An idiomatic for sync.WaitGroup with timeout
176+ c := make(chan struct{})
177+ go func() {
178+ defer close(c)
179+ wg.Wait()
180+ }()
181+ select {
182+ case <-c:
183+ case <-time.After(1 * time.Second):
184+ t.Fatal("Server should return gracefully but timeout has occurred")
185+ }
186+
187+ assert.Len(t, stop, 0) // check if stop chan is empty
188+}
main.go+10
@@ -6,8 +6,10 @@ import (
66 "math/rand"
77 "net/http"
88 "os"
9+ "os/signal"
910 "runtime"
1011 "strings"
12+ "syscall"
1113 "time"
1214
1315 "github.com/BurntSushi/toml"
@@ -204,6 +206,14 @@ func main() {
204206 s := &Server{
205207 Handler: handler,
206208 Opts: opts,
209+ stop: make(chan struct{}, 1),
207210 }
211+ // Observe signals in background goroutine.
212+ go func() {
213+ sigint := make(chan os.Signal, 1)
214+ signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)
215+ <-sigint
216+ s.stop <- struct{}{} // notify having caught signal
217+ }()
208218 s.ListenAndServe()
209219}
pkg/sessions/redis/client.gonew file+59
@@ -0,0 +1,59 @@
1+package redis
2+
3+import (
4+ "context"
5+ "time"
6+
7+ "github.com/go-redis/redis/v7"
8+)
9+
10+// Client is wrapper interface for redis.Client and redis.ClusterClient.
11+type Client interface {
12+ Get(ctx context.Context, key string) ([]byte, error)
13+ Set(ctx context.Context, key string, value []byte, expiration time.Duration) error
14+ Del(ctx context.Context, key string) error
15+}
16+
17+var _ Client = (*client)(nil)
18+
19+type client struct {
20+ *redis.Client
21+}
22+
23+func newClient(c *redis.Client) Client {
24+ return &client{Client: c}
25+}
26+
27+func (c *client) Get(ctx context.Context, key string) ([]byte, error) {
28+ return c.WithContext(ctx).Get(key).Bytes()
29+}
30+
31+func (c *client) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
32+ return c.WithContext(ctx).Set(key, value, expiration).Err()
33+}
34+
35+func (c *client) Del(ctx context.Context, key string) error {
36+ return c.WithContext(ctx).Del(key).Err()
37+}
38+
39+var _ Client = (*clusterClient)(nil)
40+
41+type clusterClient struct {
42+ *redis.ClusterClient
43+}
44+
45+func newClusterClient(c *redis.ClusterClient) Client {
46+ return &clusterClient{ClusterClient: c}
47+}
48+
49+func (c *clusterClient) Get(ctx context.Context, key string) ([]byte, error) {
50+ return c.WithContext(ctx).Get(key).Bytes()
51+}
52+
53+func (c *clusterClient) Set(ctx context.Context, key string, value []byte, expiration time.Duration) error {
54+ return c.WithContext(ctx).Set(key, value, expiration).Err()
55+}
56+
57+func (c *clusterClient) Del(ctx context.Context, key string) error {
58+ return c.WithContext(ctx).Del(key).Err()
59+}
pkg/sessions/redis/redis_store.go+20−17
@@ -1,6 +1,7 @@
11package redis
22
33import (
4+ "context"
45 "crypto/aes"
56 "crypto/cipher"
67 "crypto/rand"
@@ -14,7 +15,7 @@ import (
1415 "strings"
1516 "time"
1617
17− "github.com/go-redis/redis"
18+ "github.com/go-redis/redis/v7"
1819 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
1920 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
2021 "github.com/oauth2-proxy/oauth2-proxy/pkg/cookies"
@@ -33,19 +34,19 @@ type TicketData struct {
3334type SessionStore struct {
3435 CookieCipher *encryption.Cipher
3536 CookieOptions *options.CookieOptions
36− Cmdable redis.Cmdable
37+ Client Client
3738}
3839
3940// NewRedisSessionStore initialises a new instance of the SessionStore from
4041// the configuration given
4142func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
42− cmdable, err := newRedisCmdable(opts.RedisStoreOptions)
43+ client, err := newRedisCmdable(opts.RedisStoreOptions)
4344 if err != nil {
4445 return nil, fmt.Errorf("error constructing redis client: %v", err)
4546 }
4647
4748 rs := &SessionStore{
48− Cmdable: cmdable,
49+ Client: client,
4950 CookieCipher: opts.Cipher,
5051 CookieOptions: cookieOpts,
5152 }
@@ -53,7 +54,7 @@ func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cook
5354
5455}
5556
56−func newRedisCmdable(opts options.RedisStoreOptions) (redis.Cmdable, error) {
57+func newRedisCmdable(opts options.RedisStoreOptions) (Client, error) {
5758 if opts.UseSentinel && opts.UseCluster {
5859 return nil, fmt.Errorf("options redis-use-sentinel and redis-use-cluster are mutually exclusive")
5960 }
@@ -63,14 +64,14 @@ func newRedisCmdable(opts options.RedisStoreOptions) (redis.Cmdable, error) {
6364 MasterName: opts.SentinelMasterName,
6465 SentinelAddrs: opts.SentinelConnectionURLs,
6566 })
66− return client, nil
67+ return newClient(client), nil
6768 }
6869
6970 if opts.UseCluster {
7071 client := redis.NewClusterClient(&redis.ClusterOptions{
7172 Addrs: opts.ClusterConnectionURLs,
7273 })
73− return client, nil
74+ return newClusterClient(client), nil
7475 }
7576
7677 opt, err := redis.ParseURL(opts.RedisConnectionURL)
@@ -104,7 +105,7 @@ func newRedisCmdable(opts options.RedisStoreOptions) (redis.Cmdable, error) {
104105 }
105106
106107 client := redis.NewClient(opt)
107− return client, nil
108+ return newClient(client), nil
108109}
109110
110111// Save takes a sessions.SessionState and stores the information from it
@@ -121,7 +122,8 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
121122 if err != nil {
122123 return err
123124 }
124− ticketString, err := store.storeValue(value, store.CookieOptions.CookieExpire, requestCookie)
125+ ctx := req.Context()
126+ ticketString, err := store.storeValue(ctx, value, store.CookieOptions.CookieExpire, requestCookie)
125127 if err != nil {
126128 return err
127129 }
@@ -149,7 +151,8 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
149151 if !ok {
150152 return nil, fmt.Errorf("Cookie Signature not valid")
151153 }
152− session, err := store.loadSessionFromString(val)
154+ ctx := req.Context()
155+ session, err := store.loadSessionFromString(ctx, val)
153156 if err != nil {
154157 return nil, fmt.Errorf("error loading session: %s", err)
155158 }
@@ -157,18 +160,17 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
157160}
158161
159162// loadSessionFromString loads the session based on the ticket value
160−func (store *SessionStore) loadSessionFromString(value string) (*sessions.SessionState, error) {
163+func (store *SessionStore) loadSessionFromString(ctx context.Context, value string) (*sessions.SessionState, error) {
161164 ticket, err := decodeTicket(store.CookieOptions.CookieName, value)
162165 if err != nil {
163166 return nil, err
164167 }
165168
166− result, err := store.Cmdable.Get(ticket.asHandle(store.CookieOptions.CookieName)).Result()
169+ resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.CookieOptions.CookieName))
167170 if err != nil {
168171 return nil, err
169172 }
170173
171− resultBytes := []byte(result)
172174 block, err := aes.NewCipher(ticket.Secret)
173175 if err != nil {
174176 return nil, err
@@ -214,7 +216,8 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
214216 // If there's an issue decoding the ticket, ignore it
215217 ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
216218 if ticket != nil {
217− _, err := store.Cmdable.Del(ticket.asHandle(store.CookieOptions.CookieName)).Result()
219+ ctx := req.Context()
220+ err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.CookieName))
218221 if err != nil {
219222 return fmt.Errorf("error clearing cookie from redis: %s", err)
220223 }
@@ -237,7 +240,7 @@ func (store *SessionStore) makeCookie(req *http.Request, value string, expires t
237240 )
238241}
239242
240−func (store *SessionStore) storeValue(value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) {
243+func (store *SessionStore) storeValue(ctx context.Context, value string, expiration time.Duration, requestCookie *http.Cookie) (string, error) {
241244 ticket, err := store.getTicket(requestCookie)
242245 if err != nil {
243246 return "", fmt.Errorf("error getting ticket: %v", err)
@@ -254,7 +257,7 @@ func (store *SessionStore) storeValue(value string, expiration time.Duration, re
254257 stream.XORKeyStream(ciphertext, []byte(value))
255258
256259 handle := ticket.asHandle(store.CookieOptions.CookieName)
257− err = store.Cmdable.Set(handle, ciphertext, expiration).Err()
260+ err = store.Client.Set(ctx, handle, ciphertext, expiration)
258261 if err != nil {
259262 return "", err
260263 }
@@ -290,7 +293,7 @@ func newTicket() (*TicketData, error) {
290293 return nil, fmt.Errorf("failed to create new ticket ID %s", err)
291294 }
292295 // ticketID is hex encoded
293− ticketID := fmt.Sprintf("%x", rawID)
296+ ticketID := hex.EncodeToString(rawID)
294297
295298 secret := make([]byte, aes.BlockSize)
296299 if _, err := io.ReadFull(rand.Reader, secret); err != nil {
providers/keycloak_test.go+2−1
@@ -6,7 +6,8 @@ import (
66 "net/url"
77 "testing"
88
9− "github.com/bmizerany/assert"
9+ "github.com/stretchr/testify/assert"
10+
1011 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
1112)
1213
providers/oidc_test.go+1−1
@@ -15,9 +15,9 @@ import (
1515 "testing"
1616 "time"
1717
18− "github.com/bmizerany/assert"
1918 "github.com/coreos/go-oidc"
2019 "github.com/dgrijalva/jwt-go"
20+ "github.com/stretchr/testify/assert"
2121 "golang.org/x/oauth2"
2222
2323 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
watcher.go+2−1
@@ -7,8 +7,9 @@ import (
77 "path/filepath"
88 "time"
99
10+ "github.com/fsnotify/fsnotify"
11+
1012 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
11− fsnotify "gopkg.in/fsnotify/fsnotify.v1"
1213)
1314
1415// WaitForReplacement waits for a file to exist on disk and then starts a watch