git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #484 from oauth2-proxy/cookie-options-rename
eae652dJoel Speed
CHANGELOG.md+6−2
@@ -11,14 +11,18 @@
11- Migration from Pusher to independent org may have introduced breaking changes for your environment.
11- Migration from Pusher to independent org may have introduced breaking changes for your environment.
12 - See the changes listed below for PR [#464](https://github.com/oauth2-proxy/oauth2-proxy/pull/464) for full details
12 - See the changes listed below for PR [#464](https://github.com/oauth2-proxy/oauth2-proxy/pull/464) for full details
13 - Binaries renamed from `oauth2_proxy` to `oauth2-proxy`
13 - Binaries renamed from `oauth2_proxy` to `oauth2-proxy`
14−
15- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)
14- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)
16− - The Azure AD Graph API has been [deprecated](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-graph-api) and is being replaced by the Microsoft Graph API.
15+ - The Azure AD Graph API has been [deprecated](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-graph-api) and is being replaced by the Microsoft Graph API.
17 If your application relies on the access token being passed to it to access the Azure AD Graph API, you should migrate your application to use the Microsoft Graph API.
16 If your application relies on the access token being passed to it to access the Azure AD Graph API, you should migrate your application to use the Microsoft Graph API.
18 Existing behaviour can be retained by setting `-resource=https://graph.windows.net`.
17 Existing behaviour can be retained by setting `-resource=https://graph.windows.net`.
18+- [#484](https://github.com/oauth2-proxy/oauth2-proxy/pull/484) Configuration loading has been replaced with Viper and PFlag
19+ - Flags now require a `--` prefix before the option
20+ - Previously flags allowed either `-` or `--` to prefix the option name
21+ - Eg `-provider` must now be `--provider`
19
22
20## Changes since v5.1.0
23## Changes since v5.1.0
21
24
25+- [#484](https://github.com/oauth2-proxy/oauth2-proxy/pull/484) Replace configuration loading with Viper (@JoelSpeed)
22- [#499](https://github.com/oauth2-proxy/oauth2-proxy/pull/469) Add `-user-id-claim` to support generic claims in addition to email
26- [#499](https://github.com/oauth2-proxy/oauth2-proxy/pull/469) Add `-user-id-claim` to support generic claims in addition to email
23- [#486](https://github.com/oauth2-proxy/oauth2-proxy/pull/486) Add new linters (@johejo)
27- [#486](https://github.com/oauth2-proxy/oauth2-proxy/pull/486) Add new linters (@johejo)
24- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)
28- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)
env_options.godeleted−54
@@ -1,54 +0,0 @@
1−package main
2−
3−import (
4− "os"
5− "reflect"
6− "strings"
7−)
8−
9−// EnvOptions holds program options loaded from the process environment
10−type EnvOptions map[string]interface{}
11−
12−// LoadEnvForStruct loads environment variables for each field in an options
13−// struct passed into it.
14−//
15−// Fields in the options struct must have an `env` and `cfg` tag to be read
16−// from the environment
17−func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
18− val := reflect.ValueOf(options)
19− var typ reflect.Type
20− if val.Kind() == reflect.Ptr {
21− typ = val.Elem().Type()
22− } else {
23− typ = val.Type()
24− }
25−
26− for i := 0; i < typ.NumField(); i++ {
27− // pull out the struct tags:
28− // flag - the name of the command line flag
29− // deprecated - (optional) the name of the deprecated command line flag
30− // cfg - (optional, defaults to underscored flag) the name of the config file option
31− field := typ.Field(i)
32− fieldV := reflect.Indirect(val).Field(i)
33−
34− if field.Type.Kind() == reflect.Struct && field.Anonymous {
35− cfg.LoadEnvForStruct(fieldV.Interface())
36− continue
37− }
38−
39− flagName := field.Tag.Get("flag")
40− envName := field.Tag.Get("env")
41− cfgName := field.Tag.Get("cfg")
42− if cfgName == "" && flagName != "" {
43− cfgName = strings.ReplaceAll(flagName, "-", "_")
44− }
45− if envName == "" || cfgName == "" {
46− // resolvable fields must have the `env` and `cfg` struct tag
47− continue
48− }
49− v := os.Getenv(envName)
50− if v != "" {
51− cfg[cfgName] = v
52− }
53− }
54−}
env_options_test.godeleted−46
@@ -1,46 +0,0 @@
1−package main_test
2−
3−import (
4− "os"
5− "testing"
6−
7− proxy "github.com/oauth2-proxy/oauth2-proxy"
8− "github.com/stretchr/testify/assert"
9−)
10−
11−type EnvTest struct {
12− TestField string `cfg:"target_field" env:"TEST_ENV_FIELD"`
13− EnvTestEmbed
14−}
15−
16−type EnvTestEmbed struct {
17− TestFieldEmbed string `cfg:"target_field_embed" env:"TEST_ENV_FIELD_EMBED"`
18−}
19−
20−func TestLoadEnvForStruct(t *testing.T) {
21−
22− cfg := make(proxy.EnvOptions)
23− cfg.LoadEnvForStruct(&EnvTest{})
24−
25− _, ok := cfg["target_field"]
26− assert.Equal(t, ok, false)
27−
28− os.Setenv("TEST_ENV_FIELD", "1234abcd")
29− cfg.LoadEnvForStruct(&EnvTest{})
30− v := cfg["target_field"]
31− assert.Equal(t, v, "1234abcd")
32−}
33−
34−func TestLoadEnvForStructWithEmbeddedFields(t *testing.T) {
35−
36− cfg := make(proxy.EnvOptions)
37− cfg.LoadEnvForStruct(&EnvTest{})
38−
39− _, ok := cfg["target_field_embed"]
40− assert.Equal(t, ok, false)
41−
42− os.Setenv("TEST_ENV_FIELD_EMBED", "1234abcd")
43− cfg.LoadEnvForStruct(&EnvTest{})
44− v := cfg["target_field_embed"]
45− assert.Equal(t, v, "1234abcd")
46−}
go.mod+3−2
@@ -3,7 +3,6 @@ module github.com/oauth2-proxy/oauth2-proxy
3go 1.14
3go 1.14
4
4
5require (
5require (
6− github.com/BurntSushi/toml v0.3.1
7 github.com/alicebob/miniredis/v2 v2.11.2
6 github.com/alicebob/miniredis/v2 v2.11.2
8 github.com/bitly/go-simplejson v0.5.0
7 github.com/bitly/go-simplejson v0.5.0
9 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
8 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
@@ -13,10 +12,12 @@ require (
13 github.com/go-redis/redis/v7 v7.2.0
12 github.com/go-redis/redis/v7 v7.2.0
14 github.com/kr/pretty v0.2.0 // indirect
13 github.com/kr/pretty v0.2.0 // indirect
15 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
14 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
16− github.com/mreiferson/go-options v1.0.0
15+ github.com/mitchellh/mapstructure v1.1.2
17 github.com/onsi/ginkgo v1.12.0
16 github.com/onsi/ginkgo v1.12.0
18 github.com/onsi/gomega v1.9.0
17 github.com/onsi/gomega v1.9.0
19 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
18 github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
19+ github.com/spf13/pflag v1.0.3
20+ github.com/spf13/viper v1.6.3
20 github.com/stretchr/testify v1.5.1
21 github.com/stretchr/testify v1.5.1
21 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
22 github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
22 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
23 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
go.sum+112−2
@@ -4,67 +4,118 @@ cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
4cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
5github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
5github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
6github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
7+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
8+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
9+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
7github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
10github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
8github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
11github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
9github.com/alicebob/miniredis/v2 v2.11.2 h1:OtWO7akm5otuhssnE/sNfsWxG4gZ8DbGhShDtRrByJs=
12github.com/alicebob/miniredis/v2 v2.11.2 h1:OtWO7akm5otuhssnE/sNfsWxG4gZ8DbGhShDtRrByJs=
10github.com/alicebob/miniredis/v2 v2.11.2/go.mod h1:VL3UDEfAH59bSa7MuHMuFToxkqyHh69s/WUbYlOAuyg=
13github.com/alicebob/miniredis/v2 v2.11.2/go.mod h1:VL3UDEfAH59bSa7MuHMuFToxkqyHh69s/WUbYlOAuyg=
14+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
15+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
16+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
11github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
17github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
12github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
18github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
13github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
19github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
14github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
20github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
15github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
21github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
22+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
16github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
23github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
17github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
24github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
18github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
25github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
19github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
26github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
27+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
28+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
20github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
29github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
21github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
30github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
31+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
32+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
33+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
22github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
34github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
23github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
36+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
37+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
38github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
25github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
39github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
40+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
26github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
41github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
27github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
42github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
28github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
43github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
29github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
44github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
30github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
45github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
31github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
46github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
47+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
48+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
49+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
50+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
32github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
51github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
33github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
52github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
53+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
54+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
55+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
34github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
56github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
35github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
57github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
58+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
36github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
59github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
37github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
60github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
38github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
61github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
62+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
39github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
63github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
40github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
64github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
41github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
65github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
42github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
66github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
43github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
67github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
68+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
44github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
69github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
45github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
70github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
46github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
71github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
72+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
47github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
73github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
48github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
74github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
49github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
75github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
50github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
76github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
51github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
77github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
78+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
79+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
80+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
81+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
82+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
83+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
52github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
84github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
53github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
85github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
54github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
86github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
87+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
88+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
55github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
89github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
56github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
90github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
91+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
92+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
57github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
93github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
94+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
95+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
96+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
97+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
98+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
99+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
100+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
58github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
101github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
59github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
102github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
60github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
103github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
61github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
104github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
62github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
105github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
63github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
106github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
107+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
108+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
109+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
64github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
110github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
65github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
111github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
66−github.com/mreiferson/go-options v1.0.0 h1:RMLidydGlDWpL+lQTXo0bVIf/XT2CTq7AEJMoz5/VWs=
112+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
67−github.com/mreiferson/go-options v1.0.0/go.mod h1:zHtCks/HQvOt8ATyfwVe3JJq2PPuImzXINPRTC03+9w=
113+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
114+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
115+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
116+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
117+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
118+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
68github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
119github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
69github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
120github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
70github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
121github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
@@ -73,20 +124,63 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
73github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
124github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
74github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
125github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
75github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
126github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
127+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
128+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
129+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
76github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
130github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
77github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
131github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
78github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
132github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
79github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
133github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
134+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
135+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
136+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
137+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
80github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
138github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
139+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
140+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
141+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
142+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
143+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
144+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
145+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
146+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
147+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
148+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
149+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
150+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
151+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
152+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
153+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
154+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
155+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
156+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
157+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
158+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
159+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
160+github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
161+github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
81github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
162github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
163+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
164+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
165+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
82github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
166github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
83github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
167github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
168+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
169+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
170+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
171+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
172+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
84github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
173github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
85github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE=
174github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE=
86github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
175github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
87github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
176github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
177+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
88go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
178go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
89go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
179go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
180+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
181+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
182+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
183+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
90golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
184golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
91golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
185golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
92golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
186golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -98,11 +192,14 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl
98golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
192golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
99golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
193golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
100golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
194golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
195+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
196+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
101golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
197golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
102golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
198golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
103golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
199golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
104golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
200golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
105golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
201golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
202+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
106golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
203golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
107golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
204golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
108golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
205golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -117,7 +214,10 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
117golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
214golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
118golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
215golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
119golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
216golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
217+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
120golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
218golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
219+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
220+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
121golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
221golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
122golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
222golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
123golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
223golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
@@ -131,11 +231,14 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
131golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
231golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
132golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
232golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
133golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
233golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
234+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
235+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
134golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
236golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
135golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
237golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
136golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
238golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
137golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
239golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
138golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
240golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
241+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
139golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
242golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
140golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
243golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
141golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
244golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -152,21 +255,28 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
152google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
255google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
153google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
256google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
154google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
257google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
258+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
155google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
259google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
156google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
260google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
157google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
261google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
262+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
158gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
263gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
159gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
264gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
265+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
160gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
266gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
161gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
267gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
162gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
268gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
163gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
269gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
270+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
271+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
164gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
272gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
165gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
273gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
274+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
166gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
275gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
167gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
276gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
168gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
277gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
169gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
278gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
279+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
170gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
280gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
171gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
281gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
172gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
282gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
main.go+11−13
@@ -12,9 +12,9 @@ import (
12 "syscall"
12 "syscall"
13 "time"
13 "time"
14
14
15− "github.com/BurntSushi/toml"
15+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
16− options "github.com/mreiferson/go-options"
17 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
16 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
17+ "github.com/spf13/pflag"
18)
18)
19
19
20func main() {
20func main() {
@@ -149,7 +149,10 @@ func main() {
149
149
150 flagSet.String("user-id-claim", "email", "which claim contains the user ID")
150 flagSet.String("user-id-claim", "email", "which claim contains the user ID")
151
151
152+ pflagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError)
153+ pflagSet.AddGoFlagSet(flagSet)
154+
152− flagSet.Parse(os.Args[1:])
155+ pflagSet.Parse(os.Args[1:])
153
156
154 if *showVersion {
157 if *showVersion {
155 fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version())
158 fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version())
@@ -157,18 +160,13 @@ func main() {
157 }
160 }
158
161
159 opts := NewOptions()
162 opts := NewOptions()
160−
163+ err := options.Load(*config, pflagSet, opts)
161− cfg := make(EnvOptions)
162− if *config != "" {
163− _, err := toml.DecodeFile(*config, &cfg)
164− if err != nil {
164+ if err != nil {
165− logger.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
165+ logger.Printf("ERROR: Failed to load config: %v", err)
166− }
166+ os.Exit(1)
167 }
167 }
168− cfg.LoadEnvForStruct(opts)
169− options.Resolve(opts, flagSet, cfg)
170
168
171− err := opts.Validate()
169+ err = opts.Validate()
172 if err != nil {
170 if err != nil {
173 logger.Printf("%s", err)
171 logger.Printf("%s", err)
174 os.Exit(1)
172 os.Exit(1)
oauthproxy.go+15−15
@@ -247,7 +247,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
247 panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
247 panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
248 }
248 }
249 }
249 }
250− for _, u := range opts.CompiledRegex {
250+ for _, u := range opts.compiledRegex {
251 logger.Printf("compiled skip-auth-regex => %q", u)
251 logger.Printf("compiled skip-auth-regex => %q", u)
252 }
252 }
253
253
@@ -264,23 +264,23 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
264
264
265 logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
265 logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
266 refresh := "disabled"
266 refresh := "disabled"
267− if opts.CookieRefresh != time.Duration(0) {
267+ if opts.Cookie.Refresh != time.Duration(0) {
268− refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
268+ refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
269 }
269 }
270
270
271− logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, strings.Join(opts.CookieDomains, ","), opts.CookiePath, opts.CookieSameSite, refresh)
271+ logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Secure, opts.Cookie.HTTPOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh)
272
272
273 return &OAuthProxy{
273 return &OAuthProxy{
274− CookieName: opts.CookieName,
274+ CookieName: opts.Cookie.Name,
275− CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
275+ CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
276− CookieSeed: opts.CookieSecret,
276+ CookieSeed: opts.Cookie.Secret,
277− CookieDomains: opts.CookieDomains,
277+ CookieDomains: opts.Cookie.Domains,
278− CookiePath: opts.CookiePath,
278+ CookiePath: opts.Cookie.Path,
279− CookieSecure: opts.CookieSecure,
279+ CookieSecure: opts.Cookie.Secure,
280− CookieHTTPOnly: opts.CookieHTTPOnly,
280+ CookieHTTPOnly: opts.Cookie.HTTPOnly,
281− CookieExpire: opts.CookieExpire,
281+ CookieExpire: opts.Cookie.Expire,
282− CookieRefresh: opts.CookieRefresh,
282+ CookieRefresh: opts.Cookie.Refresh,
283− CookieSameSite: opts.CookieSameSite,
283+ CookieSameSite: opts.Cookie.SameSite,
284 Validator: validator,
284 Validator: validator,
285
285
286 RobotsPath: "/robots.txt",
286 RobotsPath: "/robots.txt",
@@ -303,7 +303,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
303 skipAuthPreflight: opts.SkipAuthPreflight,
303 skipAuthPreflight: opts.SkipAuthPreflight,
304 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
304 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
305 jwtBearerVerifiers: opts.jwtBearerVerifiers,
305 jwtBearerVerifiers: opts.jwtBearerVerifiers,
306− compiledRegex: opts.CompiledRegex,
306+ compiledRegex: opts.compiledRegex,
307 SetXAuthRequest: opts.SetXAuthRequest,
307 SetXAuthRequest: opts.SetXAuthRequest,
308 PassBasicAuth: opts.PassBasicAuth,
308 PassBasicAuth: opts.PassBasicAuth,
309 SetBasicAuth: opts.SetBasicAuth,
309 SetBasicAuth: opts.SetBasicAuth,
oauthproxy_test.go+24−24
@@ -164,7 +164,7 @@ func TestRobotsTxt(t *testing.T) {
164 opts := NewOptions()
164 opts := NewOptions()
165 opts.ClientID = "asdlkjx"
165 opts.ClientID = "asdlkjx"
166 opts.ClientSecret = "alkgks"
166 opts.ClientSecret = "alkgks"
167− opts.CookieSecret = "asdkugkj"
167+ opts.Cookie.Secret = "asdkugkj"
168 opts.Validate()
168 opts.Validate()
169
169
170 proxy := NewOAuthProxy(opts, func(string) bool { return true })
170 proxy := NewOAuthProxy(opts, func(string) bool { return true })
@@ -179,7 +179,7 @@ func TestIsValidRedirect(t *testing.T) {
179 opts := NewOptions()
179 opts := NewOptions()
180 opts.ClientID = "skdlfj"
180 opts.ClientID = "skdlfj"
181 opts.ClientSecret = "fgkdsgj"
181 opts.ClientSecret = "fgkdsgj"
182− opts.CookieSecret = "ljgiogbj"
182+ opts.Cookie.Secret = "ljgiogbj"
183 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
183 // Should match domains that are exactly foo.bar and any subdomain of bar.foo
184 opts.WhitelistDomains = []string{
184 opts.WhitelistDomains = []string{
185 "foo.bar",
185 "foo.bar",
@@ -398,10 +398,10 @@ func TestBasicAuthPassword(t *testing.T) {
398 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
398 opts.Upstreams = append(opts.Upstreams, providerServer.URL)
399 // The CookieSecret must be 32 bytes in order to create the AES
399 // The CookieSecret must be 32 bytes in order to create the AES
400 // cipher.
400 // cipher.
401− opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
401+ opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
402 opts.ClientID = "dlgkj"
402 opts.ClientID = "dlgkj"
403 opts.ClientSecret = "alkgret"
403 opts.ClientSecret = "alkgret"
404− opts.CookieSecure = false
404+ opts.Cookie.Secure = false
405 opts.PassBasicAuth = true
405 opts.PassBasicAuth = true
406 opts.SetBasicAuth = true
406 opts.SetBasicAuth = true
407 opts.PassUserHeaders = true
407 opts.PassUserHeaders = true
@@ -582,10 +582,10 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
582 }
582 }
583 // The CookieSecret must be 32 bytes in order to create the AES
583 // The CookieSecret must be 32 bytes in order to create the AES
584 // cipher.
584 // cipher.
585− t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
585+ t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
586 t.opts.ClientID = "slgkj"
586 t.opts.ClientID = "slgkj"
587 t.opts.ClientSecret = "gfjgojl"
587 t.opts.ClientSecret = "gfjgojl"
588− t.opts.CookieSecure = false
588+ t.opts.Cookie.Secure = false
589 t.opts.PassAccessToken = opts.PassAccessToken
589 t.opts.PassAccessToken = opts.PassAccessToken
590 t.opts.Validate()
590 t.opts.Validate()
591
591
@@ -735,7 +735,7 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest {
735 var sipTest SignInPageTest
735 var sipTest SignInPageTest
736
736
737 sipTest.opts = NewOptions()
737 sipTest.opts = NewOptions()
738− sipTest.opts.CookieSecret = "adklsj2"
738+ sipTest.opts.Cookie.Secret = "adklsj2"
739 sipTest.opts.ClientID = "lkdgj"
739 sipTest.opts.ClientID = "lkdgj"
740 sipTest.opts.ClientSecret = "sgiufgoi"
740 sipTest.opts.ClientSecret = "sgiufgoi"
741 sipTest.opts.SkipProviderButton = skipProvider
741 sipTest.opts.SkipProviderButton = skipProvider
@@ -841,10 +841,10 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
841 }
841 }
842 pcTest.opts.ClientID = "asdfljk"
842 pcTest.opts.ClientID = "asdfljk"
843 pcTest.opts.ClientSecret = "lkjfdsig"
843 pcTest.opts.ClientSecret = "lkjfdsig"
844− pcTest.opts.CookieSecret = "0123456789abcdefabcd"
844+ pcTest.opts.Cookie.Secret = "0123456789abcdefabcd"
845 // First, set the CookieRefresh option so proxy.AesCipher is created,
845 // First, set the CookieRefresh option so proxy.AesCipher is created,
846 // needed to encrypt the access_token.
846 // needed to encrypt the access_token.
847− pcTest.opts.CookieRefresh = time.Hour
847+ pcTest.opts.Cookie.Refresh = time.Hour
848 pcTest.opts.Validate()
848 pcTest.opts.Validate()
849
849
850 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
850 pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
@@ -915,7 +915,7 @@ func TestProcessCookieNoCookieError(t *testing.T) {
915
915
916func TestProcessCookieRefreshNotSet(t *testing.T) {
916func TestProcessCookieRefreshNotSet(t *testing.T) {
917 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
917 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
918− opts.CookieExpire = time.Duration(23) * time.Hour
918+ opts.Cookie.Expire = time.Duration(23) * time.Hour
919 })
919 })
920 reference := time.Now().Add(time.Duration(-2) * time.Hour)
920 reference := time.Now().Add(time.Duration(-2) * time.Hour)
921
921
@@ -932,7 +932,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
932
932
933func TestProcessCookieFailIfCookieExpired(t *testing.T) {
933func TestProcessCookieFailIfCookieExpired(t *testing.T) {
934 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
934 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
935− opts.CookieExpire = time.Duration(24) * time.Hour
935+ opts.Cookie.Expire = time.Duration(24) * time.Hour
936 })
936 })
937 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
937 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
938 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
938 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
@@ -947,7 +947,7 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
947
947
948func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
948func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
949 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
949 pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
950− opts.CookieExpire = time.Duration(24) * time.Hour
950+ opts.Cookie.Expire = time.Duration(24) * time.Hour
951 })
951 })
952 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
952 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
953 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
953 startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
@@ -1017,7 +1017,7 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
1017
1017
1018func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1018func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
1019 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1019 test := NewAuthOnlyEndpointTest(func(opts *Options) {
1020− opts.CookieExpire = time.Duration(24) * time.Hour
1020+ opts.Cookie.Expire = time.Duration(24) * time.Hour
1021 })
1021 })
1022 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1022 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
1023 startSession := &sessions.SessionState{
1023 startSession := &sessions.SessionState{
@@ -1149,7 +1149,7 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
1149 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1149 opts.Upstreams = append(opts.Upstreams, upstream.URL)
1150 opts.ClientID = "aljsal"
1150 opts.ClientID = "aljsal"
1151 opts.ClientSecret = "jglkfsdgj"
1151 opts.ClientSecret = "jglkfsdgj"
1152− opts.CookieSecret = "dkfjgdls"
1152+ opts.Cookie.Secret = "dkfjgdls"
1153 opts.SkipAuthPreflight = true
1153 opts.SkipAuthPreflight = true
1154 opts.Validate()
1154 opts.Validate()
1155
1155
@@ -1196,7 +1196,7 @@ type SignatureTest struct {
1196
1196
1197func NewSignatureTest() *SignatureTest {
1197func NewSignatureTest() *SignatureTest {
1198 opts := NewOptions()
1198 opts := NewOptions()
1199− opts.CookieSecret = "cookie secret"
1199+ opts.Cookie.Secret = "cookie secret"
1200 opts.ClientID = "client ID"
1200 opts.ClientID = "client ID"
1201 opts.ClientSecret = "client secret"
1201 opts.ClientSecret = "client secret"
1202 opts.EmailDomains = []string{"acm.org"}
1202 opts.EmailDomains = []string{"acm.org"}
@@ -1343,7 +1343,7 @@ type ajaxRequestTest struct {
1343func newAjaxRequestTest() *ajaxRequestTest {
1343func newAjaxRequestTest() *ajaxRequestTest {
1344 test := &ajaxRequestTest{}
1344 test := &ajaxRequestTest{}
1345 test.opts = NewOptions()
1345 test.opts = NewOptions()
1346− test.opts.CookieSecret = "sdflsw"
1346+ test.opts.Cookie.Secret = "sdflsw"
1347 test.opts.ClientID = "gkljfdl"
1347 test.opts.ClientID = "gkljfdl"
1348 test.opts.ClientSecret = "sdflkjs"
1348 test.opts.ClientSecret = "sdflkjs"
1349 test.opts.Validate()
1349 test.opts.Validate()
@@ -1401,11 +1401,11 @@ func TestAjaxForbiddendRequest(t *testing.T) {
1401
1401
1402func TestClearSplitCookie(t *testing.T) {
1402func TestClearSplitCookie(t *testing.T) {
1403 opts := NewOptions()
1403 opts := NewOptions()
1404− opts.CookieName = "oauth2"
1404+ opts.Cookie.Name = "oauth2"
1405− opts.CookieDomains = []string{"abc"}
1405+ opts.Cookie.Domains = []string{"abc"}
1406− store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1406+ store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1407 assert.Equal(t, err, nil)
1407 assert.Equal(t, err, nil)
1408− p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
1408+ p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1409 var rw = httptest.NewRecorder()
1409 var rw = httptest.NewRecorder()
1410 req := httptest.NewRequest("get", "/", nil)
1410 req := httptest.NewRequest("get", "/", nil)
1411
1411
@@ -1430,11 +1430,11 @@ func TestClearSplitCookie(t *testing.T) {
1430
1430
1431func TestClearSingleCookie(t *testing.T) {
1431func TestClearSingleCookie(t *testing.T) {
1432 opts := NewOptions()
1432 opts := NewOptions()
1433− opts.CookieName = "oauth2"
1433+ opts.Cookie.Name = "oauth2"
1434− opts.CookieDomains = []string{"abc"}
1434+ opts.Cookie.Domains = []string{"abc"}
1435− store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
1435+ store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
1436 assert.Equal(t, err, nil)
1436 assert.Equal(t, err, nil)
1437− p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
1437+ p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
1438 var rw = httptest.NewRecorder()
1438 var rw = httptest.NewRecorder()
1439 req := httptest.NewRequest("get", "/", nil)
1439 req := httptest.NewRequest("get", "/", nil)
1440
1440
options.go+30−33
@@ -64,11 +64,8 @@ type Options struct {
64 Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
64 Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
65 Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
65 Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
66
66
67− // Embed CookieOptions
68− options.CookieOptions
67+ Cookie options.CookieOptions `cfg:",squash"`
69−
70− // Embed SessionOptions
71− options.SessionOptions
68+ Session options.SessionOptions `cfg:",squash"`
72
69
73 Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
70 Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
74 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
71 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
@@ -134,7 +131,7 @@ type Options struct {
134 // internal values that are set after config validation
131 // internal values that are set after config validation
135 redirectURL *url.URL
132 redirectURL *url.URL
136 proxyURLs []*url.URL
133 proxyURLs []*url.URL
137− CompiledRegex []*regexp.Regexp
134+ compiledRegex []*regexp.Regexp
138 provider providers.Provider
135 provider providers.Provider
139 sessionStore sessionsapi.SessionStore
136 sessionStore sessionsapi.SessionStore
140 signatureData *SignatureData
137 signatureData *SignatureData
@@ -158,14 +155,14 @@ func NewOptions() *Options {
158 HTTPSAddress: ":443",
155 HTTPSAddress: ":443",
159 ForceHTTPS: false,
156 ForceHTTPS: false,
160 DisplayHtpasswdForm: true,
157 DisplayHtpasswdForm: true,
161− CookieOptions: options.CookieOptions{
158+ Cookie: options.CookieOptions{
162− CookieName: "_oauth2_proxy",
159+ Name: "_oauth2_proxy",
163− CookieSecure: true,
160+ Secure: true,
164− CookieHTTPOnly: true,
161+ HTTPOnly: true,
165− CookieExpire: time.Duration(168) * time.Hour,
162+ Expire: time.Duration(168) * time.Hour,
166− CookieRefresh: time.Duration(0),
163+ Refresh: time.Duration(0),
167 },
164 },
168− SessionOptions: options.SessionOptions{
165+ Session: options.SessionOptions{
169 Type: "cookie",
166 Type: "cookie",
170 },
167 },
171 SetXAuthRequest: false,
168 SetXAuthRequest: false,
@@ -227,7 +224,7 @@ func (o *Options) Validate() error {
227 }
224 }
228
225
229 msgs := make([]string, 0)
226 msgs := make([]string, 0)
230− if o.CookieSecret == "" {
227+ if o.Cookie.Secret == "" {
231 msgs = append(msgs, "missing setting: cookie-secret")
228 msgs = append(msgs, "missing setting: cookie-secret")
232 }
229 }
233 if o.ClientID == "" {
230 if o.ClientID == "" {
@@ -372,61 +369,61 @@ func (o *Options) Validate() error {
372 }
369 }
373
370
374 for _, u := range o.SkipAuthRegex {
371 for _, u := range o.SkipAuthRegex {
375− CompiledRegex, err := regexp.Compile(u)
372+ compiledRegex, err := regexp.Compile(u)
376 if err != nil {
373 if err != nil {
377 msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
374 msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
378 continue
375 continue
379 }
376 }
380− o.CompiledRegex = append(o.CompiledRegex, CompiledRegex)
377+ o.compiledRegex = append(o.compiledRegex, compiledRegex)
381 }
378 }
382 msgs = parseProviderInfo(o, msgs)
379 msgs = parseProviderInfo(o, msgs)
383
380
384 var cipher *encryption.Cipher
381 var cipher *encryption.Cipher
385− if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) {
382+ if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.Cookie.Refresh != time.Duration(0)) {
386 validCookieSecretSize := false
383 validCookieSecretSize := false
387 for _, i := range []int{16, 24, 32} {
384 for _, i := range []int{16, 24, 32} {
388− if len(secretBytes(o.CookieSecret)) == i {
385+ if len(secretBytes(o.Cookie.Secret)) == i {
389 validCookieSecretSize = true
386 validCookieSecretSize = true
390 }
387 }
391 }
388 }
392 var decoded bool
389 var decoded bool
393− if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
390+ if string(secretBytes(o.Cookie.Secret)) != o.Cookie.Secret {
394 decoded = true
391 decoded = true
395 }
392 }
396 if !validCookieSecretSize {
393 if !validCookieSecretSize {
397 var suffix string
394 var suffix string
398 if decoded {
395 if decoded {
399− suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
396+ suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.Cookie.Secret)
400 }
397 }
401 msgs = append(msgs, fmt.Sprintf(
398 msgs = append(msgs, fmt.Sprintf(
402 "cookie_secret must be 16, 24, or 32 bytes "+
399 "cookie_secret must be 16, 24, or 32 bytes "+
403 "to create an AES cipher when "+
400 "to create an AES cipher when "+
404 "pass_access_token == true or "+
401 "pass_access_token == true or "+
405 "cookie_refresh != 0, but is %d bytes.%s",
402 "cookie_refresh != 0, but is %d bytes.%s",
406− len(secretBytes(o.CookieSecret)), suffix))
403+ len(secretBytes(o.Cookie.Secret)), suffix))
407 } else {
404 } else {
408 var err error
405 var err error
409− cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret))
406+ cipher, err = encryption.NewCipher(secretBytes(o.Cookie.Secret))
410 if err != nil {
407 if err != nil {
411 msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
408 msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
412 }
409 }
413 }
410 }
414 }
411 }
415
412
416− o.SessionOptions.Cipher = cipher
413+ o.Session.Cipher = cipher
417− sessionStore, err := sessions.NewSessionStore(&o.SessionOptions, &o.CookieOptions)
414+ sessionStore, err := sessions.NewSessionStore(&o.Session, &o.Cookie)
418 if err != nil {
415 if err != nil {
419 msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
416 msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
420 } else {
417 } else {
421 o.sessionStore = sessionStore
418 o.sessionStore = sessionStore
422 }
419 }
423
420
424− if o.CookieRefresh >= o.CookieExpire {
421+ if o.Cookie.Refresh >= o.Cookie.Expire {
425 msgs = append(msgs, fmt.Sprintf(
422 msgs = append(msgs, fmt.Sprintf(
426 "cookie_refresh (%s) must be less than "+
423 "cookie_refresh (%s) must be less than "+
427 "cookie_expire (%s)",
424 "cookie_expire (%s)",
428− o.CookieRefresh.String(),
425+ o.Cookie.Refresh.String(),
429− o.CookieExpire.String()))
426+ o.Cookie.Expire.String()))
430 }
427 }
431
428
432 if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
429 if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
@@ -441,16 +438,16 @@ func (o *Options) Validate() error {
441 }
438 }
442 }
439 }
443
440
444− switch o.CookieSameSite {
441+ switch o.Cookie.SameSite {
445 case "", "none", "lax", "strict":
442 case "", "none", "lax", "strict":
446 default:
443 default:
447− msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
444+ msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.Cookie.SameSite))
448 }
445 }
449
446
450 // Sort cookie domains by length, so that we try longer (and more specific)
447 // Sort cookie domains by length, so that we try longer (and more specific)
451 // domains first
448 // domains first
452− sort.Slice(o.CookieDomains, func(i, j int) bool {
449+ sort.Slice(o.Cookie.Domains, func(i, j int) bool {
453− return len(o.CookieDomains[i]) > len(o.CookieDomains[j])
450+ return len(o.Cookie.Domains[i]) > len(o.Cookie.Domains[j])
454 })
451 })
455
452
456 msgs = parseSignatureKey(o, msgs)
453 msgs = parseSignatureKey(o, msgs)
@@ -627,9 +624,9 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error
627}
624}
628
625
629func validateCookieName(o *Options, msgs []string) []string {
626func validateCookieName(o *Options, msgs []string) []string {
630− cookie := &http.Cookie{Name: o.CookieName}
627+ cookie := &http.Cookie{Name: o.Cookie.Name}
631 if cookie.String() == "" {
628 if cookie.String() == "" {
632− return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.CookieName))
629+ return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name))
633 }
630 }
634 return msgs
631 return msgs
635}
632}
options_test.go+20−20
@@ -22,7 +22,7 @@ const (
22func testOptions() *Options {
22func testOptions() *Options {
23 o := NewOptions()
23 o := NewOptions()
24 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
24 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
25− o.CookieSecret = cookieSecret
25+ o.Cookie.Secret = cookieSecret
26 o.ClientID = clientID
26 o.ClientID = clientID
27 o.ClientSecret = clientSecret
27 o.ClientSecret = clientSecret
28 o.EmailDomains = []string{"*"}
28 o.EmailDomains = []string{"*"}
@@ -51,7 +51,7 @@ func TestNewOptions(t *testing.T) {
51
51
52func TestClientSecretFileOptionFails(t *testing.T) {
52func TestClientSecretFileOptionFails(t *testing.T) {
53 o := NewOptions()
53 o := NewOptions()
54− o.CookieSecret = cookieSecret
54+ o.Cookie.Secret = cookieSecret
55 o.ClientID = clientID
55 o.ClientID = clientID
56 o.ClientSecretFile = clientSecret
56 o.ClientSecretFile = clientSecret
57 o.EmailDomains = []string{"*"}
57 o.EmailDomains = []string{"*"}
@@ -81,7 +81,7 @@ func TestClientSecretFileOption(t *testing.T) {
81 defer os.Remove(clientSecretFileName)
81 defer os.Remove(clientSecretFileName)
82
82
83 o := NewOptions()
83 o := NewOptions()
84− o.CookieSecret = cookieSecret
84+ o.Cookie.Secret = cookieSecret
85 o.ClientID = clientID
85 o.ClientID = clientID
86 o.ClientSecretFile = clientSecretFileName
86 o.ClientSecretFile = clientSecretFileName
87 o.EmailDomains = []string{"*"}
87 o.EmailDomains = []string{"*"}
@@ -165,7 +165,7 @@ func TestCompiledRegex(t *testing.T) {
165 o.SkipAuthRegex = regexps
165 o.SkipAuthRegex = regexps
166 assert.Equal(t, nil, o.Validate())
166 assert.Equal(t, nil, o.Validate())
167 actual := make([]string, 0)
167 actual := make([]string, 0)
168− for _, regex := range o.CompiledRegex {
168+ for _, regex := range o.compiledRegex {
169 actual = append(actual, regex.String())
169 actual = append(actual, regex.String())
170 }
170 }
171 assert.Equal(t, regexps, actual)
171 assert.Equal(t, regexps, actual)
@@ -212,20 +212,20 @@ func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
212
212
213 assert.Equal(t, false, o.PassAccessToken)
213 assert.Equal(t, false, o.PassAccessToken)
214 o.PassAccessToken = true
214 o.PassAccessToken = true
215− o.CookieSecret = "cookie of invalid length-"
215+ o.Cookie.Secret = "cookie of invalid length-"
216 assert.NotEqual(t, nil, o.Validate())
216 assert.NotEqual(t, nil, o.Validate())
217
217
218 o.PassAccessToken = false
218 o.PassAccessToken = false
219− o.CookieRefresh = time.Duration(24) * time.Hour
219+ o.Cookie.Refresh = time.Duration(24) * time.Hour
220 assert.NotEqual(t, nil, o.Validate())
220 assert.NotEqual(t, nil, o.Validate())
221
221
222− o.CookieSecret = "16 bytes AES-128"
222+ o.Cookie.Secret = "16 bytes AES-128"
223 assert.Equal(t, nil, o.Validate())
223 assert.Equal(t, nil, o.Validate())
224
224
225− o.CookieSecret = "24 byte secret AES-192--"
225+ o.Cookie.Secret = "24 byte secret AES-192--"
226 assert.Equal(t, nil, o.Validate())
226 assert.Equal(t, nil, o.Validate())
227
227
228− o.CookieSecret = "32 byte secret for AES-256------"
228+ o.Cookie.Secret = "32 byte secret for AES-256------"
229 assert.Equal(t, nil, o.Validate())
229 assert.Equal(t, nil, o.Validate())
230}
230}
231
231
@@ -233,11 +233,11 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
233 o := testOptions()
233 o := testOptions()
234 assert.Equal(t, nil, o.Validate())
234 assert.Equal(t, nil, o.Validate())
235
235
236− o.CookieSecret = "0123456789abcdefabcd"
236+ o.Cookie.Secret = "0123456789abcdefabcd"
237− o.CookieRefresh = o.CookieExpire
237+ o.Cookie.Refresh = o.Cookie.Expire
238 assert.NotEqual(t, nil, o.Validate())
238 assert.NotEqual(t, nil, o.Validate())
239
239
240− o.CookieRefresh -= time.Duration(1)
240+ o.Cookie.Refresh -= time.Duration(1)
241 assert.Equal(t, nil, o.Validate())
241 assert.Equal(t, nil, o.Validate())
242}
242}
243
243
@@ -246,23 +246,23 @@ func TestBase64CookieSecret(t *testing.T) {
246 assert.Equal(t, nil, o.Validate())
246 assert.Equal(t, nil, o.Validate())
247
247
248 // 32 byte, base64 (urlsafe) encoded key
248 // 32 byte, base64 (urlsafe) encoded key
249− o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
249+ o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
250 assert.Equal(t, nil, o.Validate())
250 assert.Equal(t, nil, o.Validate())
251
251
252 // 32 byte, base64 (urlsafe) encoded key, w/o padding
252 // 32 byte, base64 (urlsafe) encoded key, w/o padding
253− o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
253+ o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
254 assert.Equal(t, nil, o.Validate())
254 assert.Equal(t, nil, o.Validate())
255
255
256 // 24 byte, base64 (urlsafe) encoded key
256 // 24 byte, base64 (urlsafe) encoded key
257− o.CookieSecret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
257+ o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
258 assert.Equal(t, nil, o.Validate())
258 assert.Equal(t, nil, o.Validate())
259
259
260 // 16 byte, base64 (urlsafe) encoded key
260 // 16 byte, base64 (urlsafe) encoded key
261− o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA=="
261+ o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA=="
262 assert.Equal(t, nil, o.Validate())
262 assert.Equal(t, nil, o.Validate())
263
263
264 // 16 byte, base64 (urlsafe) encoded key, w/o padding
264 // 16 byte, base64 (urlsafe) encoded key, w/o padding
265− o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA"
265+ o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA"
266 assert.Equal(t, nil, o.Validate())
266 assert.Equal(t, nil, o.Validate())
267}
267}
268
268
@@ -292,16 +292,16 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
292
292
293func TestValidateCookie(t *testing.T) {
293func TestValidateCookie(t *testing.T) {
294 o := testOptions()
294 o := testOptions()
295− o.CookieName = "_valid_cookie_name"
295+ o.Cookie.Name = "_valid_cookie_name"
296 assert.Equal(t, nil, o.Validate())
296 assert.Equal(t, nil, o.Validate())
297}
297}
298
298
299func TestValidateCookieBadName(t *testing.T) {
299func TestValidateCookieBadName(t *testing.T) {
300 o := testOptions()
300 o := testOptions()
301− o.CookieName = "_bad_cookie_name{}"
301+ o.Cookie.Name = "_bad_cookie_name{}"
302 err := o.Validate()
302 err := o.Validate()
303 assert.Equal(t, err.Error(), "invalid configuration:\n"+
303 assert.Equal(t, err.Error(), "invalid configuration:\n"+
304− fmt.Sprintf(" invalid cookie name: %q", o.CookieName))
304+ fmt.Sprintf(" invalid cookie name: %q", o.Cookie.Name))
305}
305}
306
306
307func TestSkipOIDCDiscovery(t *testing.T) {
307func TestSkipOIDCDiscovery(t *testing.T) {
pkg/apis/options/cookie.go+9−9
@@ -4,13 +4,13 @@ import "time"
4
4
5// CookieOptions contains configuration options relating to Cookie configuration
5// CookieOptions contains configuration options relating to Cookie configuration
6type CookieOptions struct {
6type CookieOptions struct {
7− CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
7+ Name string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
8− CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
8+ Secret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
9− CookieDomains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
9+ Domains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
10− CookiePath string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
10+ Path string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
11− CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
11+ Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
12− CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
12+ Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
13− CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
13+ Secure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
14− CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
14+ HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
15− CookieSameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
15+ SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
16}
16}
pkg/apis/options/load.gonew file+134
@@ -0,0 +1,134 @@
1+package options
2+
3+import (
4+ "fmt"
5+ "reflect"
6+ "strings"
7+
8+ "github.com/mitchellh/mapstructure"
9+ "github.com/spf13/pflag"
10+ "github.com/spf13/viper"
11+)
12+
13+// Load reads in the config file at the path given, then merges in environment
14+// variables (prefixed with `OAUTH2_PROXY`) and finally merges in flags from the flagSet.
15+// If a config value is unset and the flag has a non-zero value default, this default will be used.
16+// Eg. A field defined:
17+// FooBar `cfg:"foo_bar" flag:"foo-bar"`
18+// Can be set in the config file as `foo_bar="baz"`, in the environment as `OAUTH2_PROXY_FOO_BAR=baz`,
19+// or via the command line flag `--foo-bar=baz`.
20+func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error {
21+ v := viper.New()
22+ v.SetConfigFile(configFileName)
23+ v.SetConfigType("toml") // Config is in toml format
24+ v.SetEnvPrefix("OAUTH2_PROXY")
25+ v.AutomaticEnv()
26+ v.SetTypeByDefaultValue(true)
27+
28+ if configFileName != "" {
29+ err := v.ReadInConfig()
30+ if err != nil {
31+ return fmt.Errorf("unable to load config file: %w", err)
32+ }
33+ }
34+
35+ err := registerFlags(v, "", flagSet, into)
36+ if err != nil {
37+ // This should only happen if there is a programming error
38+ return fmt.Errorf("unable to register flags: %w", err)
39+ }
40+
41+ // UnmarhsalExact will return an error if the config includes options that are
42+ // not mapped to felds of the into struct
43+ err = v.UnmarshalExact(into, decodeFromCfgTag)
44+ if err != nil {
45+ return fmt.Errorf("error unmarshalling config: %w", err)
46+ }
47+
48+ return nil
49+}
50+
51+// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
52+// to the fields in the options interface provided.
53+// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
54+// - For fields, set `cfg` and `flag` so that `flag` is the name of the flag associated to this config option
55+// - For exported fields that are not user facing, set the `cfg` to `,internal`
56+// - For structs containing user facing fields, set the `cfg` to `,squash`
57+func registerFlags(v *viper.Viper, prefix string, flagSet *pflag.FlagSet, options interface{}) error {
58+ val := reflect.ValueOf(options)
59+ var typ reflect.Type
60+ if val.Kind() == reflect.Ptr {
61+ typ = val.Elem().Type()
62+ } else {
63+ typ = val.Type()
64+ }
65+
66+ for i := 0; i < typ.NumField(); i++ {
67+ // pull out the struct tags:
68+ // flag - the name of the command line flag
69+ // cfg - the name of the config file option
70+ field := typ.Field(i)
71+ fieldV := reflect.Indirect(val).Field(i)
72+ fieldName := strings.Join([]string{prefix, field.Name}, ".")
73+
74+ cfgName := field.Tag.Get("cfg")
75+ if cfgName == ",internal" {
76+ // Public but internal types that should not be exposed to users, skip them
77+ continue
78+ }
79+
80+ if isUnexported(field.Name) {
81+ // Unexported fields cannot be set by a user, so won't have tags or flags, skip them
82+ continue
83+ }
84+
85+ if field.Type.Kind() == reflect.Struct {
86+ if cfgName != ",squash" {
87+ return fmt.Errorf("field %q does not have required cfg tag: `,squash`", fieldName)
88+ }
89+ err := registerFlags(v, fieldName, flagSet, fieldV.Interface())
90+ if err != nil {
91+ return err
92+ }
93+ continue
94+ }
95+
96+ flagName := field.Tag.Get("flag")
97+ if flagName == "" || cfgName == "" {
98+ return fmt.Errorf("field %q does not have required tags (cfg, flag)", fieldName)
99+ }
100+
101+ if flagSet == nil {
102+ return fmt.Errorf("flagset cannot be nil")
103+ }
104+
105+ f := flagSet.Lookup(flagName)
106+ if f == nil {
107+ return fmt.Errorf("field %q does not have a registered flag", flagName)
108+ }
109+ err := v.BindPFlag(cfgName, f)
110+ if err != nil {
111+ return fmt.Errorf("error binding flag for field %q: %w", fieldName, err)
112+ }
113+ }
114+
115+ return nil
116+}
117+
118+// decodeFromCfgTag sets the Viper decoder to read the names from the `cfg` tag
119+// on each struct entry.
120+func decodeFromCfgTag(c *mapstructure.DecoderConfig) {
121+ c.TagName = "cfg"
122+}
123+
124+// isUnexported checks if a field name starts with a lowercase letter and therefore
125+// if it is unexported.
126+func isUnexported(name string) bool {
127+ if len(name) == 0 {
128+ // This should never happen
129+ panic("field name has len 0")
130+ }
131+
132+ first := string(name[0])
133+ return first == strings.ToLower(first)
134+}
pkg/apis/options/load_test.gonew file+300
@@ -0,0 +1,300 @@
1+package options
2+
3+import (
4+ "fmt"
5+ "io/ioutil"
6+ "os"
7+ "testing"
8+
9+ . "github.com/onsi/ginkgo"
10+ . "github.com/onsi/ginkgo/extensions/table"
11+ . "github.com/onsi/gomega"
12+ "github.com/spf13/pflag"
13+)
14+
15+func TestOptionsSuite(t *testing.T) {
16+ RegisterFailHandler(Fail)
17+ RunSpecs(t, "Options Suite")
18+}
19+
20+var _ = Describe("Load", func() {
21+ Context("with a testOptions structure", func() {
22+ type TestOptionSubStruct struct {
23+ StringSliceOption []string `flag:"string-slice-option" cfg:"string_slice_option"`
24+ }
25+
26+ type TestOptions struct {
27+ StringOption string `flag:"string-option" cfg:"string_option"`
28+ Sub TestOptionSubStruct `cfg:",squash"`
29+ // Check exported but internal fields do not break loading
30+ Internal *string `cfg:",internal"`
31+ // Check unexported fields do not break loading
32+ unexported string
33+ }
34+
35+ type MissingSquashTestOptions struct {
36+ StringOption string `flag:"string-option" cfg:"string_option"`
37+ Sub TestOptionSubStruct
38+ }
39+
40+ type MissingCfgTestOptions struct {
41+ StringOption string `flag:"string-option"`
42+ Sub TestOptionSubStruct `cfg:",squash"`
43+ }
44+
45+ type MissingFlagTestOptions struct {
46+ StringOption string `cfg:"string_option"`
47+ Sub TestOptionSubStruct `cfg:",squash"`
48+ }
49+
50+ var testOptionsConfigBytes = []byte(`
51+ string_option="foo"
52+ string_slice_option="a,b,c,d"
53+ `)
54+
55+ var testOptionsFlagSet *pflag.FlagSet
56+
57+ type testOptionsTableInput struct {
58+ env map[string]string
59+ args []string
60+ configFile []byte
61+ flagSet func() *pflag.FlagSet
62+ expectedErr error
63+ input interface{}
64+ expectedOutput interface{}
65+ }
66+
67+ BeforeEach(func() {
68+ testOptionsFlagSet = pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
69+ testOptionsFlagSet.String("string-option", "default", "")
70+ testOptionsFlagSet.StringSlice("string-slice-option", []string{"a", "b"}, "")
71+ })
72+
73+ DescribeTable("Load",
74+ func(o *testOptionsTableInput) {
75+ var configFileName string
76+
77+ if o.configFile != nil {
78+ By("Creating a config file")
79+ configFile, err := ioutil.TempFile("", "oauth2-proxy-test-legacy-config-file")
80+ Expect(err).ToNot(HaveOccurred())
81+ defer configFile.Close()
82+
83+ _, err = configFile.Write(o.configFile)
84+ Expect(err).ToNot(HaveOccurred())
85+ defer os.Remove(configFile.Name())
86+
87+ configFileName = configFile.Name()
88+ }
89+
90+ if len(o.env) > 0 {
91+ By("Setting environment variables")
92+ for k, v := range o.env {
93+ os.Setenv(k, v)
94+ defer os.Unsetenv(k)
95+ }
96+ }
97+
98+ Expect(o.flagSet).ToNot(BeNil())
99+ flagSet := o.flagSet()
100+ Expect(flagSet).ToNot(BeNil())
101+
102+ if len(o.args) > 0 {
103+ By("Parsing flag arguments")
104+ Expect(flagSet.Parse(o.args)).To(Succeed())
105+ }
106+
107+ var input interface{}
108+ if o.input != nil {
109+ input = o.input
110+ } else {
111+ input = &TestOptions{}
112+ }
113+ err := Load(configFileName, flagSet, input)
114+ if o.expectedErr != nil {
115+ Expect(err).To(MatchError(o.expectedErr.Error()))
116+ } else {
117+ Expect(err).ToNot(HaveOccurred())
118+ }
119+ Expect(input).To(Equal(o.expectedOutput))
120+ },
121+ Entry("with just a config file", &testOptionsTableInput{
122+ configFile: testOptionsConfigBytes,
123+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
124+ expectedOutput: &TestOptions{
125+ StringOption: "foo",
126+ Sub: TestOptionSubStruct{
127+ StringSliceOption: []string{"a", "b", "c", "d"},
128+ },
129+ },
130+ }),
131+ Entry("when setting env variables", &testOptionsTableInput{
132+ configFile: testOptionsConfigBytes,
133+ env: map[string]string{
134+ "OAUTH2_PROXY_STRING_OPTION": "bar",
135+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
136+ },
137+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
138+ expectedOutput: &TestOptions{
139+ StringOption: "bar",
140+ Sub: TestOptionSubStruct{
141+ StringSliceOption: []string{"a", "b", "c"},
142+ },
143+ },
144+ }),
145+ Entry("when setting flags", &testOptionsTableInput{
146+ configFile: testOptionsConfigBytes,
147+ env: map[string]string{
148+ "OAUTH2_PROXY_STRING_OPTION": "bar",
149+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
150+ },
151+ args: []string{
152+ "--string-option", "baz",
153+ "--string-slice-option", "a,b,c,d,e",
154+ },
155+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
156+ expectedOutput: &TestOptions{
157+ StringOption: "baz",
158+ Sub: TestOptionSubStruct{
159+ StringSliceOption: []string{"a", "b", "c", "d", "e"},
160+ },
161+ },
162+ }),
163+ Entry("when setting flags multiple times", &testOptionsTableInput{
164+ configFile: testOptionsConfigBytes,
165+ env: map[string]string{
166+ "OAUTH2_PROXY_STRING_OPTION": "bar",
167+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
168+ },
169+ args: []string{
170+ "--string-option", "baz",
171+ "--string-slice-option", "x",
172+ "--string-slice-option", "y",
173+ "--string-slice-option", "z",
174+ },
175+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
176+ expectedOutput: &TestOptions{
177+ StringOption: "baz",
178+ Sub: TestOptionSubStruct{
179+ StringSliceOption: []string{"x", "y", "z"},
180+ },
181+ },
182+ }),
183+ Entry("when setting env variables without a config file", &testOptionsTableInput{
184+ env: map[string]string{
185+ "OAUTH2_PROXY_STRING_OPTION": "bar",
186+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
187+ },
188+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
189+ expectedOutput: &TestOptions{
190+ StringOption: "bar",
191+ Sub: TestOptionSubStruct{
192+ StringSliceOption: []string{"a", "b", "c"},
193+ },
194+ },
195+ }),
196+ Entry("when setting flags without a config file", &testOptionsTableInput{
197+ env: map[string]string{
198+ "OAUTH2_PROXY_STRING_OPTION": "bar",
199+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
200+ },
201+ args: []string{
202+ "--string-option", "baz",
203+ "--string-slice-option", "a,b,c,d,e",
204+ },
205+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
206+ expectedOutput: &TestOptions{
207+ StringOption: "baz",
208+ Sub: TestOptionSubStruct{
209+ StringSliceOption: []string{"a", "b", "c", "d", "e"},
210+ },
211+ },
212+ }),
213+ Entry("when setting flags without a config file", &testOptionsTableInput{
214+ env: map[string]string{
215+ "OAUTH2_PROXY_STRING_OPTION": "bar",
216+ "OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
217+ },
218+ args: []string{
219+ "--string-option", "baz",
220+ "--string-slice-option", "a,b,c,d,e",
221+ },
222+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
223+ expectedOutput: &TestOptions{
224+ StringOption: "baz",
225+ Sub: TestOptionSubStruct{
226+ StringSliceOption: []string{"a", "b", "c", "d", "e"},
227+ },
228+ },
229+ }),
230+ Entry("when nothing is set it should use flag defaults", &testOptionsTableInput{
231+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
232+ expectedOutput: &TestOptions{
233+ StringOption: "default",
234+ Sub: TestOptionSubStruct{
235+ StringSliceOption: []string{"a", "b"},
236+ },
237+ },
238+ }),
239+ Entry("with an invalid config file", &testOptionsTableInput{
240+ configFile: []byte(`slice_option = foo`),
241+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
242+ expectedErr: fmt.Errorf("unable to load config file: While parsing config: (1, 16): never reached"),
243+ expectedOutput: &TestOptions{},
244+ }),
245+ Entry("with an invalid flagset", &testOptionsTableInput{
246+ flagSet: func() *pflag.FlagSet {
247+ // Missing a flag
248+ f := pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
249+ f.String("string-option", "default", "")
250+ return f
251+ },
252+ expectedErr: fmt.Errorf("unable to register flags: field \"string-slice-option\" does not have a registered flag"),
253+ expectedOutput: &TestOptions{},
254+ }),
255+ Entry("with an struct is missing the squash tag", &testOptionsTableInput{
256+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
257+ expectedErr: fmt.Errorf("unable to register flags: field \".Sub\" does not have required cfg tag: `,squash`"),
258+ input: &MissingSquashTestOptions{},
259+ expectedOutput: &MissingSquashTestOptions{},
260+ }),
261+ Entry("with a field is missing the cfg tag", &testOptionsTableInput{
262+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
263+ expectedErr: fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
264+ input: &MissingCfgTestOptions{},
265+ expectedOutput: &MissingCfgTestOptions{},
266+ }),
267+ Entry("with a field is missing the flag tag", &testOptionsTableInput{
268+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
269+ expectedErr: fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
270+ input: &MissingFlagTestOptions{},
271+ expectedOutput: &MissingFlagTestOptions{},
272+ }),
273+ Entry("with existing unexported fields", &testOptionsTableInput{
274+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
275+ input: &TestOptions{
276+ unexported: "unexported",
277+ },
278+ expectedOutput: &TestOptions{
279+ StringOption: "default",
280+ Sub: TestOptionSubStruct{
281+ StringSliceOption: []string{"a", "b"},
282+ },
283+ unexported: "unexported",
284+ },
285+ }),
286+ Entry("with an unknown option in the config file", &testOptionsTableInput{
287+ configFile: []byte(`unknown_option="foo"`),
288+ flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
289+ expectedErr: fmt.Errorf("error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_option"),
290+ // Viper will unmarshal before returning the error, so this is the default output
291+ expectedOutput: &TestOptions{
292+ StringOption: "default",
293+ Sub: TestOptionSubStruct{
294+ StringSliceOption: []string{"a", "b"},
295+ },
296+ },
297+ }),
298+ )
299+ })
300+})
pkg/apis/options/sessions.go+6−10
@@ -4,31 +4,27 @@ import "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
4
4
5// SessionOptions contains configuration options for the SessionStore providers.
5// SessionOptions contains configuration options for the SessionStore providers.
6type SessionOptions struct {
6type SessionOptions struct {
7− Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
7+ Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
8− Cipher *encryption.Cipher
8+ Cipher *encryption.Cipher `cfg:",internal"`
9− CookieStoreOptions
10− RedisStoreOptions
9+ Redis RedisStoreOptions `cfg:",squash"`
11}
10}
12
11
13// CookieSessionStoreType is used to indicate the CookieSessionStore should be
12// CookieSessionStoreType is used to indicate the CookieSessionStore should be
14// used for storing sessions.
13// used for storing sessions.
15var CookieSessionStoreType = "cookie"
14var CookieSessionStoreType = "cookie"
16
15
17−// CookieStoreOptions contains configuration options for the CookieSessionStore.
18−type CookieStoreOptions struct{}
19−
20// RedisSessionStoreType is used to indicate the RedisSessionStore should be
16// RedisSessionStoreType is used to indicate the RedisSessionStore should be
21// used for storing sessions.
17// used for storing sessions.
22var RedisSessionStoreType = "redis"
18var RedisSessionStoreType = "redis"
23
19
24// RedisStoreOptions contains configuration options for the RedisSessionStore.
20// RedisStoreOptions contains configuration options for the RedisSessionStore.
25type RedisStoreOptions struct {
21type RedisStoreOptions struct {
26− RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
22+ ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
27 UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
23 UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
28 SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
24 SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
29 SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
25 SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
30 UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"`
26 UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"`
31 ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"`
27 ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"`
32− RedisCAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
28+ CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
33− RedisInsecureTLS bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
29+ InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
34}
30}
pkg/cookies/cookies.go+7−7
@@ -38,19 +38,19 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
38
38
39// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
39// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
40// value and creation time
40// value and creation time
41−func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
41+func MakeCookieFromOptions(req *http.Request, name string, value string, cookieOpts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
42− domain := GetCookieDomain(req, opts.CookieDomains)
42+ domain := GetCookieDomain(req, cookieOpts.Domains)
43
43
44 if domain != "" {
44 if domain != "" {
45− return MakeCookie(req, name, value, opts.CookiePath, domain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
45+ return MakeCookie(req, name, value, cookieOpts.Path, domain, cookieOpts.HTTPOnly, cookieOpts.Secure, expiration, now, ParseSameSite(cookieOpts.SameSite))
46 }
46 }
47 // If nothing matches, create the cookie with the shortest domain
47 // If nothing matches, create the cookie with the shortest domain
48− logger.Printf("Warning: request host %q did not match any of the specific cookie domains of %q", GetRequestHost(req), strings.Join(opts.CookieDomains, ","))
48+ logger.Printf("Warning: request host %q did not match any of the specific cookie domains of %q", GetRequestHost(req), strings.Join(cookieOpts.Domains, ","))
49 defaultDomain := ""
49 defaultDomain := ""
50− if len(opts.CookieDomains) > 0 {
50+ if len(cookieOpts.Domains) > 0 {
51− defaultDomain = opts.CookieDomains[len(opts.CookieDomains)-1]
51+ defaultDomain = cookieOpts.Domains[len(cookieOpts.Domains)-1]
52 }
52 }
53− return MakeCookie(req, name, value, opts.CookiePath, defaultDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
53+ return MakeCookie(req, name, value, cookieOpts.Path, defaultDomain, cookieOpts.HTTPOnly, cookieOpts.Secure, expiration, now, ParseSameSite(cookieOpts.SameSite))
54}
54}
55
55
56// GetCookieDomain returns the correct cookie domain given a list of domains
56// GetCookieDomain returns the correct cookie domain given a list of domains
pkg/sessions/cookie/session_store.go+7−7
@@ -49,12 +49,12 @@ func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessi
49// Load reads sessions.SessionState information from Cookies within the
49// Load reads sessions.SessionState information from Cookies within the
50// HTTP request object
50// HTTP request object
51func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
51func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
52− c, err := loadCookie(req, s.CookieOptions.CookieName)
52+ c, err := loadCookie(req, s.CookieOptions.Name)
53 if err != nil {
53 if err != nil {
54 // always http.ErrNoCookie
54 // always http.ErrNoCookie
55− return nil, fmt.Errorf("cookie %q not present", s.CookieOptions.CookieName)
55+ return nil, fmt.Errorf("cookie %q not present", s.CookieOptions.Name)
56 }
56 }
57− val, _, ok := encryption.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire)
57+ val, _, ok := encryption.Validate(c, s.CookieOptions.Secret, s.CookieOptions.Expire)
58 if !ok {
58 if !ok {
59 return nil, errors.New("cookie signature not valid")
59 return nil, errors.New("cookie signature not valid")
60 }
60 }
@@ -70,7 +70,7 @@ func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
70// clear the session
70// clear the session
71func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
71func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
72 // matches CookieName, CookieName_<number>
72 // matches CookieName, CookieName_<number>
73− var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieOptions.CookieName))
73+ var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieOptions.Name))
74
74
75 for _, c := range req.Cookies() {
75 for _, c := range req.Cookies() {
76 if cookieNameRegex.MatchString(c.Name) {
76 if cookieNameRegex.MatchString(c.Name) {
@@ -94,10 +94,10 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
94// authentication details
94// authentication details
95func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
95func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
96 if value != "" {
96 if value != "" {
97− value = encryption.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now)
97+ value = encryption.SignedValue(s.CookieOptions.Secret, s.CookieOptions.Name, value, now)
98 }
98 }
99− c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now)
99+ c := s.makeCookie(req, s.CookieOptions.Name, value, s.CookieOptions.Expire, now)
100− if len(c.Value) > 4096-len(s.CookieOptions.CookieName) {
100+ if len(c.Value) > 4096-len(s.CookieOptions.Name) {
101 return splitCookie(c)
101 return splitCookie(c)
102 }
102 }
103 return []*http.Cookie{c}
103 return []*http.Cookie{c}
pkg/sessions/redis/redis_store.go+23−23
@@ -40,7 +40,7 @@ type SessionStore struct {
40// NewRedisSessionStore initialises a new instance of the SessionStore from
40// NewRedisSessionStore initialises a new instance of the SessionStore from
41// the configuration given
41// the configuration given
42func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
42func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
43− client, err := newRedisCmdable(opts.RedisStoreOptions)
43+ client, err := newRedisCmdable(opts.Redis)
44 if err != nil {
44 if err != nil {
45 return nil, fmt.Errorf("error constructing redis client: %v", err)
45 return nil, fmt.Errorf("error constructing redis client: %v", err)
46 }
46 }
@@ -74,16 +74,16 @@ func newRedisCmdable(opts options.RedisStoreOptions) (Client, error) {
74 return newClusterClient(client), nil
74 return newClusterClient(client), nil
75 }
75 }
76
76
77− opt, err := redis.ParseURL(opts.RedisConnectionURL)
77+ opt, err := redis.ParseURL(opts.ConnectionURL)
78 if err != nil {
78 if err != nil {
79 return nil, fmt.Errorf("unable to parse redis url: %s", err)
79 return nil, fmt.Errorf("unable to parse redis url: %s", err)
80 }
80 }
81
81
82− if opts.RedisInsecureTLS {
82+ if opts.InsecureSkipTLSVerify {
83 opt.TLSConfig.InsecureSkipVerify = true
83 opt.TLSConfig.InsecureSkipVerify = true
84 }
84 }
85
85
86− if opts.RedisCAPath != "" {
86+ if opts.CAPath != "" {
87 rootCAs, err := x509.SystemCertPool()
87 rootCAs, err := x509.SystemCertPool()
88 if err != nil {
88 if err != nil {
89 logger.Printf("failed to load system cert pool for redis connection, falling back to empty cert pool")
89 logger.Printf("failed to load system cert pool for redis connection, falling back to empty cert pool")
@@ -91,9 +91,9 @@ func newRedisCmdable(opts options.RedisStoreOptions) (Client, error) {
91 if rootCAs == nil {
91 if rootCAs == nil {
92 rootCAs = x509.NewCertPool()
92 rootCAs = x509.NewCertPool()
93 }
93 }
94− certs, err := ioutil.ReadFile(opts.RedisCAPath)
94+ certs, err := ioutil.ReadFile(opts.CAPath)
95 if err != nil {
95 if err != nil {
96− return nil, fmt.Errorf("failed to load %q, %v", opts.RedisCAPath, err)
96+ return nil, fmt.Errorf("failed to load %q, %v", opts.CAPath, err)
97 }
97 }
98
98
99 // Append our cert to the system pool
99 // Append our cert to the system pool
@@ -117,13 +117,13 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
117
117
118 // Old sessions that we are refreshing would have a request cookie
118 // Old sessions that we are refreshing would have a request cookie
119 // New sessions don't, so we ignore the error. storeValue will check requestCookie
119 // New sessions don't, so we ignore the error. storeValue will check requestCookie
120− requestCookie, _ := req.Cookie(store.CookieOptions.CookieName)
120+ requestCookie, _ := req.Cookie(store.CookieOptions.Name)
121 value, err := s.EncodeSessionState(store.CookieCipher)
121 value, err := s.EncodeSessionState(store.CookieCipher)
122 if err != nil {
122 if err != nil {
123 return err
123 return err
124 }
124 }
125 ctx := req.Context()
125 ctx := req.Context()
126− ticketString, err := store.storeValue(ctx, value, store.CookieOptions.CookieExpire, requestCookie)
126+ ticketString, err := store.storeValue(ctx, value, store.CookieOptions.Expire, requestCookie)
127 if err != nil {
127 if err != nil {
128 return err
128 return err
129 }
129 }
@@ -131,7 +131,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
131 ticketCookie := store.makeCookie(
131 ticketCookie := store.makeCookie(
132 req,
132 req,
133 ticketString,
133 ticketString,
134− store.CookieOptions.CookieExpire,
134+ store.CookieOptions.Expire,
135 s.CreatedAt,
135 s.CreatedAt,
136 )
136 )
137
137
@@ -142,12 +142,12 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
142// Load reads sessions.SessionState information from a ticket
142// Load reads sessions.SessionState information from a ticket
143// cookie within the HTTP request object
143// cookie within the HTTP request object
144func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
144func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
145− requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
145+ requestCookie, err := req.Cookie(store.CookieOptions.Name)
146 if err != nil {
146 if err != nil {
147 return nil, fmt.Errorf("error loading session: %s", err)
147 return nil, fmt.Errorf("error loading session: %s", err)
148 }
148 }
149
149
150− val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
150+ val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
151 if !ok {
151 if !ok {
152 return nil, fmt.Errorf("cookie signature not valid")
152 return nil, fmt.Errorf("cookie signature not valid")
153 }
153 }
@@ -161,12 +161,12 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
161
161
162// loadSessionFromString loads the session based on the ticket value
162// loadSessionFromString loads the session based on the ticket value
163func (store *SessionStore) loadSessionFromString(ctx context.Context, value string) (*sessions.SessionState, error) {
163func (store *SessionStore) loadSessionFromString(ctx context.Context, value string) (*sessions.SessionState, error) {
164− ticket, err := decodeTicket(store.CookieOptions.CookieName, value)
164+ ticket, err := decodeTicket(store.CookieOptions.Name, value)
165 if err != nil {
165 if err != nil {
166 return nil, err
166 return nil, err
167 }
167 }
168
168
169− resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.CookieOptions.CookieName))
169+ resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.CookieOptions.Name))
170 if err != nil {
170 if err != nil {
171 return nil, err
171 return nil, err
172 }
172 }
@@ -199,7 +199,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
199 http.SetCookie(rw, clearCookie)
199 http.SetCookie(rw, clearCookie)
200
200
201 // If there was an existing cookie we should clear the session in redis
201 // If there was an existing cookie we should clear the session in redis
202− requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
202+ requestCookie, err := req.Cookie(store.CookieOptions.Name)
203 if err != nil && err == http.ErrNoCookie {
203 if err != nil && err == http.ErrNoCookie {
204 // No existing cookie so can't clear redis
204 // No existing cookie so can't clear redis
205 return nil
205 return nil
@@ -207,17 +207,17 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
207 return fmt.Errorf("error retrieving cookie: %v", err)
207 return fmt.Errorf("error retrieving cookie: %v", err)
208 }
208 }
209
209
210− val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
210+ val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
211 if !ok {
211 if !ok {
212 return fmt.Errorf("cookie signature not valid")
212 return fmt.Errorf("cookie signature not valid")
213 }
213 }
214
214
215 // We only return an error if we had an issue with redis
215 // We only return an error if we had an issue with redis
216 // If there's an issue decoding the ticket, ignore it
216 // If there's an issue decoding the ticket, ignore it
217− ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
217+ ticket, _ := decodeTicket(store.CookieOptions.Name, val)
218 if ticket != nil {
218 if ticket != nil {
219 ctx := req.Context()
219 ctx := req.Context()
220− err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.CookieName))
220+ err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.Name))
221 if err != nil {
221 if err != nil {
222 return fmt.Errorf("error clearing cookie from redis: %s", err)
222 return fmt.Errorf("error clearing cookie from redis: %s", err)
223 }
223 }
@@ -228,11 +228,11 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
228// makeCookie makes a cookie, signing the value if present
228// makeCookie makes a cookie, signing the value if present
229func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
229func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
230 if value != "" {
230 if value != "" {
231− value = encryption.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now)
231+ value = encryption.SignedValue(store.CookieOptions.Secret, store.CookieOptions.Name, value, now)
232 }
232 }
233 return cookies.MakeCookieFromOptions(
233 return cookies.MakeCookieFromOptions(
234 req,
234 req,
235− store.CookieOptions.CookieName,
235+ store.CookieOptions.Name,
236 value,
236 value,
237 store.CookieOptions,
237 store.CookieOptions,
238 expires,
238 expires,
@@ -256,12 +256,12 @@ func (store *SessionStore) storeValue(ctx context.Context, value string, expirat
256 stream := cipher.NewCFBEncrypter(block, ticket.Secret)
256 stream := cipher.NewCFBEncrypter(block, ticket.Secret)
257 stream.XORKeyStream(ciphertext, []byte(value))
257 stream.XORKeyStream(ciphertext, []byte(value))
258
258
259− handle := ticket.asHandle(store.CookieOptions.CookieName)
259+ handle := ticket.asHandle(store.CookieOptions.Name)
260 err = store.Client.Set(ctx, handle, ciphertext, expiration)
260 err = store.Client.Set(ctx, handle, ciphertext, expiration)
261 if err != nil {
261 if err != nil {
262 return "", err
262 return "", err
263 }
263 }
264− return ticket.encodeTicket(store.CookieOptions.CookieName), nil
264+ return ticket.encodeTicket(store.CookieOptions.Name), nil
265}
265}
266
266
267// getTicket retrieves an existing ticket from the cookie if present,
267// getTicket retrieves an existing ticket from the cookie if present,
@@ -272,14 +272,14 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e
272 }
272 }
273
273
274 // An existing cookie exists, try to retrieve the ticket
274 // An existing cookie exists, try to retrieve the ticket
275− val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
275+ val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
276 if !ok {
276 if !ok {
277 // Cookie is invalid, create a new ticket
277 // Cookie is invalid, create a new ticket
278 return newTicket()
278 return newTicket()
279 }
279 }
280
280
281 // Valid cookie, decode the ticket
281 // Valid cookie, decode the ticket
282− ticket, err := decodeTicket(store.CookieOptions.CookieName, val)
282+ ticket, err := decodeTicket(store.CookieOptions.Name, val)
283 if err != nil {
283 if err != nil {
284 // If we can't decode the ticket we have to create a new one
284 // If we can't decode the ticket we have to create a new one
285 return newTicket()
285 return newTicket()
pkg/sessions/session_store_test.go+31−31
@@ -47,25 +47,25 @@ var _ = Describe("NewSessionStore", func() {
47
47
48 It("have the correct name set", func() {
48 It("have the correct name set", func() {
49 if len(cookies) == 1 {
49 if len(cookies) == 1 {
50− Expect(cookies[0].Name).To(Equal(cookieOpts.CookieName))
50+ Expect(cookies[0].Name).To(Equal(cookieOpts.Name))
51 } else {
51 } else {
52 for _, cookie := range cookies {
52 for _, cookie := range cookies {
53− Expect(cookie.Name).To(ContainSubstring(cookieOpts.CookieName))
53+ Expect(cookie.Name).To(ContainSubstring(cookieOpts.Name))
54 }
54 }
55 }
55 }
56 })
56 })
57
57
58 It("have the correct path set", func() {
58 It("have the correct path set", func() {
59 for _, cookie := range cookies {
59 for _, cookie := range cookies {
60− Expect(cookie.Path).To(Equal(cookieOpts.CookiePath))
60+ Expect(cookie.Path).To(Equal(cookieOpts.Path))
61 }
61 }
62 })
62 })
63
63
64 It("have the correct domain set", func() {
64 It("have the correct domain set", func() {
65 for _, cookie := range cookies {
65 for _, cookie := range cookies {
66 specifiedDomain := ""
66 specifiedDomain := ""
67− if len(cookieOpts.CookieDomains) > 0 {
67+ if len(cookieOpts.Domains) > 0 {
68− specifiedDomain = cookieOpts.CookieDomains[0]
68+ specifiedDomain = cookieOpts.Domains[0]
69 }
69 }
70 Expect(cookie.Domain).To(Equal(specifiedDomain))
70 Expect(cookie.Domain).To(Equal(specifiedDomain))
71 }
71 }
@@ -73,19 +73,19 @@ var _ = Describe("NewSessionStore", func() {
73
73
74 It("have the correct HTTPOnly set", func() {
74 It("have the correct HTTPOnly set", func() {
75 for _, cookie := range cookies {
75 for _, cookie := range cookies {
76− Expect(cookie.HttpOnly).To(Equal(cookieOpts.CookieHTTPOnly))
76+ Expect(cookie.HttpOnly).To(Equal(cookieOpts.HTTPOnly))
77 }
77 }
78 })
78 })
79
79
80 It("have the correct secure set", func() {
80 It("have the correct secure set", func() {
81 for _, cookie := range cookies {
81 for _, cookie := range cookies {
82− Expect(cookie.Secure).To(Equal(cookieOpts.CookieSecure))
82+ Expect(cookie.Secure).To(Equal(cookieOpts.Secure))
83 }
83 }
84 })
84 })
85
85
86 It("have the correct SameSite set", func() {
86 It("have the correct SameSite set", func() {
87 for _, cookie := range cookies {
87 for _, cookie := range cookies {
88− Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.CookieSameSite)))
88+ Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.SameSite)))
89 }
89 }
90 })
90 })
91
91
@@ -168,8 +168,8 @@ var _ = Describe("NewSessionStore", func() {
168 BeforeEach(func() {
168 BeforeEach(func() {
169 By("Using a valid cookie with a different providers session encoding")
169 By("Using a valid cookie with a different providers session encoding")
170 broken := "BrokenSessionFromADifferentSessionImplementation"
170 broken := "BrokenSessionFromADifferentSessionImplementation"
171− value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
171+ value := encryption.SignedValue(cookieOpts.Secret, cookieOpts.Name, broken, time.Now())
172− cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
172+ cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.Name, value, cookieOpts, cookieOpts.Expire, time.Now())
173 request.AddCookie(cookie)
173 request.AddCookie(cookie)
174
174
175 err := ss.Save(response, request, session)
175 err := ss.Save(response, request, session)
@@ -245,7 +245,7 @@ var _ = Describe("NewSessionStore", func() {
245 })
245 })
246
246
247 It("loads a session equal to the original session", func() {
247 It("loads a session equal to the original session", func() {
248− if cookieOpts.CookieSecret == "" {
248+ if cookieOpts.Secret == "" {
249 // Only Email and User stored in session when encrypted
249 // Only Email and User stored in session when encrypted
250 Expect(loadedSession.Email).To(Equal(session.Email))
250 Expect(loadedSession.Email).To(Equal(session.Email))
251 Expect(loadedSession.User).To(Equal(session.User))
251 Expect(loadedSession.User).To(Equal(session.User))
@@ -290,7 +290,7 @@ var _ = Describe("NewSessionStore", func() {
290 BeforeEach(func() {
290 BeforeEach(func() {
291 switch ss.(type) {
291 switch ss.(type) {
292 case *redis.SessionStore:
292 case *redis.SessionStore:
293− mr.FastForward(cookieOpts.CookieRefresh + time.Minute)
293+ mr.FastForward(cookieOpts.Refresh + time.Minute)
294 }
294 }
295 })
295 })
296
296
@@ -304,7 +304,7 @@ var _ = Describe("NewSessionStore", func() {
304 BeforeEach(func() {
304 BeforeEach(func() {
305 switch ss.(type) {
305 switch ss.(type) {
306 case *redis.SessionStore:
306 case *redis.SessionStore:
307− mr.FastForward(cookieOpts.CookieExpire + time.Minute)
307+ mr.FastForward(cookieOpts.Expire + time.Minute)
308 }
308 }
309
309
310 loadedSession, err = ss.Load(request)
310 loadedSession, err = ss.Load(request)
@@ -341,14 +341,14 @@ var _ = Describe("NewSessionStore", func() {
341 Context("with non-default options", func() {
341 Context("with non-default options", func() {
342 BeforeEach(func() {
342 BeforeEach(func() {
343 cookieOpts = &options.CookieOptions{
343 cookieOpts = &options.CookieOptions{
344− CookieName: "_cookie_name",
344+ Name: "_cookie_name",
345− CookiePath: "/path",
345+ Path: "/path",
346− CookieExpire: time.Duration(72) * time.Hour,
346+ Expire: time.Duration(72) * time.Hour,
347− CookieRefresh: time.Duration(2) * time.Hour,
347+ Refresh: time.Duration(2) * time.Hour,
348− CookieSecure: false,
348+ Secure: false,
349− CookieHTTPOnly: false,
349+ HTTPOnly: false,
350− CookieDomains: []string{"example.com"},
350+ Domains: []string{"example.com"},
351− CookieSameSite: "strict",
351+ SameSite: "strict",
352 }
352 }
353
353
354 var err error
354 var err error
@@ -364,8 +364,8 @@ var _ = Describe("NewSessionStore", func() {
364 secret := make([]byte, 32)
364 secret := make([]byte, 32)
365 _, err := rand.Read(secret)
365 _, err := rand.Read(secret)
366 Expect(err).ToNot(HaveOccurred())
366 Expect(err).ToNot(HaveOccurred())
367− cookieOpts.CookieSecret = base64.URLEncoding.EncodeToString(secret)
367+ cookieOpts.Secret = base64.URLEncoding.EncodeToString(secret)
368− cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
368+ cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.Secret))
369 Expect(err).ToNot(HaveOccurred())
369 Expect(err).ToNot(HaveOccurred())
370 Expect(cipher).ToNot(BeNil())
370 Expect(cipher).ToNot(BeNil())
371 opts.Cipher = cipher
371 opts.Cipher = cipher
@@ -384,13 +384,13 @@ var _ = Describe("NewSessionStore", func() {
384
384
385 // Set default options in CookieOptions
385 // Set default options in CookieOptions
386 cookieOpts = &options.CookieOptions{
386 cookieOpts = &options.CookieOptions{
387− CookieName: "_oauth2_proxy",
387+ Name: "_oauth2_proxy",
388− CookiePath: "/",
388+ Path: "/",
389− CookieExpire: time.Duration(168) * time.Hour,
389+ Expire: time.Duration(168) * time.Hour,
390− CookieRefresh: time.Duration(1) * time.Hour,
390+ Refresh: time.Duration(1) * time.Hour,
391− CookieSecure: true,
391+ Secure: true,
392− CookieHTTPOnly: true,
392+ HTTPOnly: true,
393− CookieSameSite: "",
393+ SameSite: "",
394 }
394 }
395
395
396 session = &sessionsapi.SessionState{
396 session = &sessionsapi.SessionState{
@@ -428,7 +428,7 @@ var _ = Describe("NewSessionStore", func() {
428 mr, err = miniredis.Run()
428 mr, err = miniredis.Run()
429 Expect(err).ToNot(HaveOccurred())
429 Expect(err).ToNot(HaveOccurred())
430 opts.Type = options.RedisSessionStoreType
430 opts.Type = options.RedisSessionStoreType
431− opts.RedisConnectionURL = "redis://" + mr.Addr()
431+ opts.Redis.ConnectionURL = "redis://" + mr.Addr()
432 })
432 })
433
433
434 AfterEach(func() {
434 AfterEach(func() {