1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2024-12-10 11:10:27 +02:00

Merge pull request #484 from oauth2-proxy/cookie-options-rename

Replace configuration loading with Viper
This commit is contained in:
Joel Speed 2020-05-03 12:14:17 +01:00 committed by GitHub
commit eae652d986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 738 additions and 298 deletions

View File

@ -11,14 +11,18 @@
- Migration from Pusher to independent org may have introduced breaking changes for your environment.
- See the changes listed below for PR [#464](https://github.com/oauth2-proxy/oauth2-proxy/pull/464) for full details
- Binaries renamed from `oauth2_proxy` to `oauth2-proxy`
- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)
- 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.
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.
Existing behaviour can be retained by setting `-resource=https://graph.windows.net`.
- [#484](https://github.com/oauth2-proxy/oauth2-proxy/pull/484) Configuration loading has been replaced with Viper and PFlag
- Flags now require a `--` prefix before the option
- Previously flags allowed either `-` or `--` to prefix the option name
- Eg `-provider` must now be `--provider`
## Changes since v5.1.0
- [#484](https://github.com/oauth2-proxy/oauth2-proxy/pull/484) Replace configuration loading with Viper (@JoelSpeed)
- [#499](https://github.com/oauth2-proxy/oauth2-proxy/pull/469) Add `-user-id-claim` to support generic claims in addition to email
- [#486](https://github.com/oauth2-proxy/oauth2-proxy/pull/486) Add new linters (@johejo)
- [#440](https://github.com/oauth2-proxy/oauth2-proxy/pull/440) Switch Azure AD Graph API to Microsoft Graph API (@johejo)

View File

@ -1,54 +0,0 @@
package main
import (
"os"
"reflect"
"strings"
)
// EnvOptions holds program options loaded from the process environment
type EnvOptions map[string]interface{}
// LoadEnvForStruct loads environment variables for each field in an options
// struct passed into it.
//
// Fields in the options struct must have an `env` and `cfg` tag to be read
// from the environment
func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
val := reflect.ValueOf(options)
var typ reflect.Type
if val.Kind() == reflect.Ptr {
typ = val.Elem().Type()
} else {
typ = val.Type()
}
for i := 0; i < typ.NumField(); i++ {
// pull out the struct tags:
// flag - the name of the command line flag
// deprecated - (optional) the name of the deprecated command line flag
// cfg - (optional, defaults to underscored flag) the name of the config file option
field := typ.Field(i)
fieldV := reflect.Indirect(val).Field(i)
if field.Type.Kind() == reflect.Struct && field.Anonymous {
cfg.LoadEnvForStruct(fieldV.Interface())
continue
}
flagName := field.Tag.Get("flag")
envName := field.Tag.Get("env")
cfgName := field.Tag.Get("cfg")
if cfgName == "" && flagName != "" {
cfgName = strings.ReplaceAll(flagName, "-", "_")
}
if envName == "" || cfgName == "" {
// resolvable fields must have the `env` and `cfg` struct tag
continue
}
v := os.Getenv(envName)
if v != "" {
cfg[cfgName] = v
}
}
}

View File

@ -1,46 +0,0 @@
package main_test
import (
"os"
"testing"
proxy "github.com/oauth2-proxy/oauth2-proxy"
"github.com/stretchr/testify/assert"
)
type EnvTest struct {
TestField string `cfg:"target_field" env:"TEST_ENV_FIELD"`
EnvTestEmbed
}
type EnvTestEmbed struct {
TestFieldEmbed string `cfg:"target_field_embed" env:"TEST_ENV_FIELD_EMBED"`
}
func TestLoadEnvForStruct(t *testing.T) {
cfg := make(proxy.EnvOptions)
cfg.LoadEnvForStruct(&EnvTest{})
_, ok := cfg["target_field"]
assert.Equal(t, ok, false)
os.Setenv("TEST_ENV_FIELD", "1234abcd")
cfg.LoadEnvForStruct(&EnvTest{})
v := cfg["target_field"]
assert.Equal(t, v, "1234abcd")
}
func TestLoadEnvForStructWithEmbeddedFields(t *testing.T) {
cfg := make(proxy.EnvOptions)
cfg.LoadEnvForStruct(&EnvTest{})
_, ok := cfg["target_field_embed"]
assert.Equal(t, ok, false)
os.Setenv("TEST_ENV_FIELD_EMBED", "1234abcd")
cfg.LoadEnvForStruct(&EnvTest{})
v := cfg["target_field_embed"]
assert.Equal(t, v, "1234abcd")
}

5
go.mod
View File

@ -3,7 +3,6 @@ module github.com/oauth2-proxy/oauth2-proxy
go 1.14
require (
github.com/BurntSushi/toml v0.3.1
github.com/alicebob/miniredis/v2 v2.11.2
github.com/bitly/go-simplejson v0.5.0
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
@ -13,10 +12,12 @@ require (
github.com/go-redis/redis/v7 v7.2.0
github.com/kr/pretty v0.2.0 // indirect
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
github.com/mreiferson/go-options v1.0.0
github.com/mitchellh/mapstructure v1.1.2
github.com/onsi/ginkgo v1.12.0
github.com/onsi/gomega v1.9.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.6.3
github.com/stretchr/testify v1.5.1
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2

114
go.sum
View File

@ -4,67 +4,118 @@ cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.11.2 h1:OtWO7akm5otuhssnE/sNfsWxG4gZ8DbGhShDtRrByJs=
github.com/alicebob/miniredis/v2 v2.11.2/go.mod h1:VL3UDEfAH59bSa7MuHMuFToxkqyHh69s/WUbYlOAuyg=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
github.com/mreiferson/go-options v1.0.0 h1:RMLidydGlDWpL+lQTXo0bVIf/XT2CTq7AEJMoz5/VWs=
github.com/mreiferson/go-options v1.0.0/go.mod h1:zHtCks/HQvOt8ATyfwVe3JJq2PPuImzXINPRTC03+9w=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.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
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997 h1:1+FQ4Ns+UZtUiQ4lP0sTCyKSQ0EXoiwAdHZB0Pd5t9Q=
github.com/yhat/wsutil v0.0.0-20170731153501-1d66fa95c997/go.mod h1:DIGbh/f5XMAessMV/uaIik81gkDVjUeQ9ApdaU7wRKE=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.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
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.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
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
@ -131,11 +231,14 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.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
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

22
main.go
View File

@ -12,9 +12,9 @@ import (
"syscall"
"time"
"github.com/BurntSushi/toml"
options "github.com/mreiferson/go-options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
"github.com/spf13/pflag"
)
func main() {
@ -149,7 +149,10 @@ func main() {
flagSet.String("user-id-claim", "email", "which claim contains the user ID")
flagSet.Parse(os.Args[1:])
pflagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ExitOnError)
pflagSet.AddGoFlagSet(flagSet)
pflagSet.Parse(os.Args[1:])
if *showVersion {
fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version())
@ -157,18 +160,13 @@ func main() {
}
opts := NewOptions()
cfg := make(EnvOptions)
if *config != "" {
_, err := toml.DecodeFile(*config, &cfg)
err := options.Load(*config, pflagSet, opts)
if err != nil {
logger.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
logger.Printf("ERROR: Failed to load config: %v", err)
os.Exit(1)
}
}
cfg.LoadEnvForStruct(opts)
options.Resolve(opts, flagSet, cfg)
err := opts.Validate()
err = opts.Validate()
if err != nil {
logger.Printf("%s", err)
os.Exit(1)

View File

@ -247,7 +247,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
}
}
for _, u := range opts.CompiledRegex {
for _, u := range opts.compiledRegex {
logger.Printf("compiled skip-auth-regex => %q", u)
}
@ -264,23 +264,23 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
refresh := "disabled"
if opts.CookieRefresh != time.Duration(0) {
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
if opts.Cookie.Refresh != time.Duration(0) {
refresh = fmt.Sprintf("after %s", opts.Cookie.Refresh)
}
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)
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)
return &OAuthProxy{
CookieName: opts.CookieName,
CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
CookieSeed: opts.CookieSecret,
CookieDomains: opts.CookieDomains,
CookiePath: opts.CookiePath,
CookieSecure: opts.CookieSecure,
CookieHTTPOnly: opts.CookieHTTPOnly,
CookieExpire: opts.CookieExpire,
CookieRefresh: opts.CookieRefresh,
CookieSameSite: opts.CookieSameSite,
CookieName: opts.Cookie.Name,
CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
CookieSeed: opts.Cookie.Secret,
CookieDomains: opts.Cookie.Domains,
CookiePath: opts.Cookie.Path,
CookieSecure: opts.Cookie.Secure,
CookieHTTPOnly: opts.Cookie.HTTPOnly,
CookieExpire: opts.Cookie.Expire,
CookieRefresh: opts.Cookie.Refresh,
CookieSameSite: opts.Cookie.SameSite,
Validator: validator,
RobotsPath: "/robots.txt",
@ -303,7 +303,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
skipAuthPreflight: opts.SkipAuthPreflight,
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
jwtBearerVerifiers: opts.jwtBearerVerifiers,
compiledRegex: opts.CompiledRegex,
compiledRegex: opts.compiledRegex,
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
SetBasicAuth: opts.SetBasicAuth,

View File

@ -164,7 +164,7 @@ func TestRobotsTxt(t *testing.T) {
opts := NewOptions()
opts.ClientID = "asdlkjx"
opts.ClientSecret = "alkgks"
opts.CookieSecret = "asdkugkj"
opts.Cookie.Secret = "asdkugkj"
opts.Validate()
proxy := NewOAuthProxy(opts, func(string) bool { return true })
@ -179,7 +179,7 @@ func TestIsValidRedirect(t *testing.T) {
opts := NewOptions()
opts.ClientID = "skdlfj"
opts.ClientSecret = "fgkdsgj"
opts.CookieSecret = "ljgiogbj"
opts.Cookie.Secret = "ljgiogbj"
// Should match domains that are exactly foo.bar and any subdomain of bar.foo
opts.WhitelistDomains = []string{
"foo.bar",
@ -398,10 +398,10 @@ func TestBasicAuthPassword(t *testing.T) {
opts.Upstreams = append(opts.Upstreams, providerServer.URL)
// The CookieSecret must be 32 bytes in order to create the AES
// cipher.
opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
opts.ClientID = "dlgkj"
opts.ClientSecret = "alkgret"
opts.CookieSecure = false
opts.Cookie.Secure = false
opts.PassBasicAuth = true
opts.SetBasicAuth = true
opts.PassUserHeaders = true
@ -582,10 +582,10 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
}
// The CookieSecret must be 32 bytes in order to create the AES
// cipher.
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
t.opts.Cookie.Secret = "xyzzyplughxyzzyplughxyzzyplughxp"
t.opts.ClientID = "slgkj"
t.opts.ClientSecret = "gfjgojl"
t.opts.CookieSecure = false
t.opts.Cookie.Secure = false
t.opts.PassAccessToken = opts.PassAccessToken
t.opts.Validate()
@ -735,7 +735,7 @@ func NewSignInPageTest(skipProvider bool) *SignInPageTest {
var sipTest SignInPageTest
sipTest.opts = NewOptions()
sipTest.opts.CookieSecret = "adklsj2"
sipTest.opts.Cookie.Secret = "adklsj2"
sipTest.opts.ClientID = "lkdgj"
sipTest.opts.ClientSecret = "sgiufgoi"
sipTest.opts.SkipProviderButton = skipProvider
@ -841,10 +841,10 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
}
pcTest.opts.ClientID = "asdfljk"
pcTest.opts.ClientSecret = "lkjfdsig"
pcTest.opts.CookieSecret = "0123456789abcdefabcd"
pcTest.opts.Cookie.Secret = "0123456789abcdefabcd"
// First, set the CookieRefresh option so proxy.AesCipher is created,
// needed to encrypt the access_token.
pcTest.opts.CookieRefresh = time.Hour
pcTest.opts.Cookie.Refresh = time.Hour
pcTest.opts.Validate()
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
@ -915,7 +915,7 @@ func TestProcessCookieNoCookieError(t *testing.T) {
func TestProcessCookieRefreshNotSet(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
opts.CookieExpire = time.Duration(23) * time.Hour
opts.Cookie.Expire = time.Duration(23) * time.Hour
})
reference := time.Now().Add(time.Duration(-2) * time.Hour)
@ -932,7 +932,7 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
func TestProcessCookieFailIfCookieExpired(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
opts.CookieExpire = time.Duration(24) * time.Hour
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
@ -947,7 +947,7 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
pcTest := NewProcessCookieTestWithOptionsModifiers(func(opts *Options) {
opts.CookieExpire = time.Duration(24) * time.Hour
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
startSession := &sessions.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token", CreatedAt: reference}
@ -1017,7 +1017,7 @@ func TestAuthOnlyEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {
func TestAuthOnlyEndpointUnauthorizedOnExpiration(t *testing.T) {
test := NewAuthOnlyEndpointTest(func(opts *Options) {
opts.CookieExpire = time.Duration(24) * time.Hour
opts.Cookie.Expire = time.Duration(24) * time.Hour
})
reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
startSession := &sessions.SessionState{
@ -1149,7 +1149,7 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
opts.Upstreams = append(opts.Upstreams, upstream.URL)
opts.ClientID = "aljsal"
opts.ClientSecret = "jglkfsdgj"
opts.CookieSecret = "dkfjgdls"
opts.Cookie.Secret = "dkfjgdls"
opts.SkipAuthPreflight = true
opts.Validate()
@ -1196,7 +1196,7 @@ type SignatureTest struct {
func NewSignatureTest() *SignatureTest {
opts := NewOptions()
opts.CookieSecret = "cookie secret"
opts.Cookie.Secret = "cookie secret"
opts.ClientID = "client ID"
opts.ClientSecret = "client secret"
opts.EmailDomains = []string{"acm.org"}
@ -1343,7 +1343,7 @@ type ajaxRequestTest struct {
func newAjaxRequestTest() *ajaxRequestTest {
test := &ajaxRequestTest{}
test.opts = NewOptions()
test.opts.CookieSecret = "sdflsw"
test.opts.Cookie.Secret = "sdflsw"
test.opts.ClientID = "gkljfdl"
test.opts.ClientSecret = "sdflkjs"
test.opts.Validate()
@ -1401,11 +1401,11 @@ func TestAjaxForbiddendRequest(t *testing.T) {
func TestClearSplitCookie(t *testing.T) {
opts := NewOptions()
opts.CookieName = "oauth2"
opts.CookieDomains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
opts.Cookie.Name = "oauth2"
opts.Cookie.Domains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
assert.Equal(t, err, nil)
p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
var rw = httptest.NewRecorder()
req := httptest.NewRequest("get", "/", nil)
@ -1430,11 +1430,11 @@ func TestClearSplitCookie(t *testing.T) {
func TestClearSingleCookie(t *testing.T) {
opts := NewOptions()
opts.CookieName = "oauth2"
opts.CookieDomains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.SessionOptions, &opts.CookieOptions)
opts.Cookie.Name = "oauth2"
opts.Cookie.Domains = []string{"abc"}
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
assert.Equal(t, err, nil)
p := OAuthProxy{CookieName: opts.CookieName, CookieDomains: opts.CookieDomains, sessionStore: store}
p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
var rw = httptest.NewRecorder()
req := httptest.NewRequest("get", "/", nil)

View File

@ -64,11 +64,8 @@ type Options struct {
Banner string `flag:"banner" cfg:"banner" env:"OAUTH2_PROXY_BANNER"`
Footer string `flag:"footer" cfg:"footer" env:"OAUTH2_PROXY_FOOTER"`
// Embed CookieOptions
options.CookieOptions
// Embed SessionOptions
options.SessionOptions
Cookie options.CookieOptions `cfg:",squash"`
Session options.SessionOptions `cfg:",squash"`
Upstreams []string `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
@ -134,7 +131,7 @@ type Options struct {
// internal values that are set after config validation
redirectURL *url.URL
proxyURLs []*url.URL
CompiledRegex []*regexp.Regexp
compiledRegex []*regexp.Regexp
provider providers.Provider
sessionStore sessionsapi.SessionStore
signatureData *SignatureData
@ -158,14 +155,14 @@ func NewOptions() *Options {
HTTPSAddress: ":443",
ForceHTTPS: false,
DisplayHtpasswdForm: true,
CookieOptions: options.CookieOptions{
CookieName: "_oauth2_proxy",
CookieSecure: true,
CookieHTTPOnly: true,
CookieExpire: time.Duration(168) * time.Hour,
CookieRefresh: time.Duration(0),
Cookie: options.CookieOptions{
Name: "_oauth2_proxy",
Secure: true,
HTTPOnly: true,
Expire: time.Duration(168) * time.Hour,
Refresh: time.Duration(0),
},
SessionOptions: options.SessionOptions{
Session: options.SessionOptions{
Type: "cookie",
},
SetXAuthRequest: false,
@ -227,7 +224,7 @@ func (o *Options) Validate() error {
}
msgs := make([]string, 0)
if o.CookieSecret == "" {
if o.Cookie.Secret == "" {
msgs = append(msgs, "missing setting: cookie-secret")
}
if o.ClientID == "" {
@ -372,61 +369,61 @@ func (o *Options) Validate() error {
}
for _, u := range o.SkipAuthRegex {
CompiledRegex, err := regexp.Compile(u)
compiledRegex, err := regexp.Compile(u)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
continue
}
o.CompiledRegex = append(o.CompiledRegex, CompiledRegex)
o.compiledRegex = append(o.compiledRegex, compiledRegex)
}
msgs = parseProviderInfo(o, msgs)
var cipher *encryption.Cipher
if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.CookieRefresh != time.Duration(0)) {
if o.PassAccessToken || o.SetAuthorization || o.PassAuthorization || (o.Cookie.Refresh != time.Duration(0)) {
validCookieSecretSize := false
for _, i := range []int{16, 24, 32} {
if len(secretBytes(o.CookieSecret)) == i {
if len(secretBytes(o.Cookie.Secret)) == i {
validCookieSecretSize = true
}
}
var decoded bool
if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
if string(secretBytes(o.Cookie.Secret)) != o.Cookie.Secret {
decoded = true
}
if !validCookieSecretSize {
var suffix string
if decoded {
suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.Cookie.Secret)
}
msgs = append(msgs, fmt.Sprintf(
"cookie_secret must be 16, 24, or 32 bytes "+
"to create an AES cipher when "+
"pass_access_token == true or "+
"cookie_refresh != 0, but is %d bytes.%s",
len(secretBytes(o.CookieSecret)), suffix))
len(secretBytes(o.Cookie.Secret)), suffix))
} else {
var err error
cipher, err = encryption.NewCipher(secretBytes(o.CookieSecret))
cipher, err = encryption.NewCipher(secretBytes(o.Cookie.Secret))
if err != nil {
msgs = append(msgs, fmt.Sprintf("cookie-secret error: %v", err))
}
}
}
o.SessionOptions.Cipher = cipher
sessionStore, err := sessions.NewSessionStore(&o.SessionOptions, &o.CookieOptions)
o.Session.Cipher = cipher
sessionStore, err := sessions.NewSessionStore(&o.Session, &o.Cookie)
if err != nil {
msgs = append(msgs, fmt.Sprintf("error initialising session storage: %v", err))
} else {
o.sessionStore = sessionStore
}
if o.CookieRefresh >= o.CookieExpire {
if o.Cookie.Refresh >= o.Cookie.Expire {
msgs = append(msgs, fmt.Sprintf(
"cookie_refresh (%s) must be less than "+
"cookie_expire (%s)",
o.CookieRefresh.String(),
o.CookieExpire.String()))
o.Cookie.Refresh.String(),
o.Cookie.Expire.String()))
}
if len(o.GoogleGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
@ -441,16 +438,16 @@ func (o *Options) Validate() error {
}
}
switch o.CookieSameSite {
switch o.Cookie.SameSite {
case "", "none", "lax", "strict":
default:
msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.CookieSameSite))
msgs = append(msgs, fmt.Sprintf("cookie_samesite (%s) must be one of ['', 'lax', 'strict', 'none']", o.Cookie.SameSite))
}
// Sort cookie domains by length, so that we try longer (and more specific)
// domains first
sort.Slice(o.CookieDomains, func(i, j int) bool {
return len(o.CookieDomains[i]) > len(o.CookieDomains[j])
sort.Slice(o.Cookie.Domains, func(i, j int) bool {
return len(o.Cookie.Domains[i]) > len(o.Cookie.Domains[j])
})
msgs = parseSignatureKey(o, msgs)
@ -627,9 +624,9 @@ func newVerifierFromJwtIssuer(jwtIssuer jwtIssuer) (*oidc.IDTokenVerifier, error
}
func validateCookieName(o *Options, msgs []string) []string {
cookie := &http.Cookie{Name: o.CookieName}
cookie := &http.Cookie{Name: o.Cookie.Name}
if cookie.String() == "" {
return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.CookieName))
return append(msgs, fmt.Sprintf("invalid cookie name: %q", o.Cookie.Name))
}
return msgs
}

View File

@ -22,7 +22,7 @@ const (
func testOptions() *Options {
o := NewOptions()
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
o.CookieSecret = cookieSecret
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
o.ClientSecret = clientSecret
o.EmailDomains = []string{"*"}
@ -51,7 +51,7 @@ func TestNewOptions(t *testing.T) {
func TestClientSecretFileOptionFails(t *testing.T) {
o := NewOptions()
o.CookieSecret = cookieSecret
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
o.ClientSecretFile = clientSecret
o.EmailDomains = []string{"*"}
@ -81,7 +81,7 @@ func TestClientSecretFileOption(t *testing.T) {
defer os.Remove(clientSecretFileName)
o := NewOptions()
o.CookieSecret = cookieSecret
o.Cookie.Secret = cookieSecret
o.ClientID = clientID
o.ClientSecretFile = clientSecretFileName
o.EmailDomains = []string{"*"}
@ -165,7 +165,7 @@ func TestCompiledRegex(t *testing.T) {
o.SkipAuthRegex = regexps
assert.Equal(t, nil, o.Validate())
actual := make([]string, 0)
for _, regex := range o.CompiledRegex {
for _, regex := range o.compiledRegex {
actual = append(actual, regex.String())
}
assert.Equal(t, regexps, actual)
@ -212,20 +212,20 @@ func TestPassAccessTokenRequiresSpecificCookieSecretLengths(t *testing.T) {
assert.Equal(t, false, o.PassAccessToken)
o.PassAccessToken = true
o.CookieSecret = "cookie of invalid length-"
o.Cookie.Secret = "cookie of invalid length-"
assert.NotEqual(t, nil, o.Validate())
o.PassAccessToken = false
o.CookieRefresh = time.Duration(24) * time.Hour
o.Cookie.Refresh = time.Duration(24) * time.Hour
assert.NotEqual(t, nil, o.Validate())
o.CookieSecret = "16 bytes AES-128"
o.Cookie.Secret = "16 bytes AES-128"
assert.Equal(t, nil, o.Validate())
o.CookieSecret = "24 byte secret AES-192--"
o.Cookie.Secret = "24 byte secret AES-192--"
assert.Equal(t, nil, o.Validate())
o.CookieSecret = "32 byte secret for AES-256------"
o.Cookie.Secret = "32 byte secret for AES-256------"
assert.Equal(t, nil, o.Validate())
}
@ -233,11 +233,11 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())
o.CookieSecret = "0123456789abcdefabcd"
o.CookieRefresh = o.CookieExpire
o.Cookie.Secret = "0123456789abcdefabcd"
o.Cookie.Refresh = o.Cookie.Expire
assert.NotEqual(t, nil, o.Validate())
o.CookieRefresh -= time.Duration(1)
o.Cookie.Refresh -= time.Duration(1)
assert.Equal(t, nil, o.Validate())
}
@ -246,23 +246,23 @@ func TestBase64CookieSecret(t *testing.T) {
assert.Equal(t, nil, o.Validate())
// 32 byte, base64 (urlsafe) encoded key
o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
assert.Equal(t, nil, o.Validate())
// 32 byte, base64 (urlsafe) encoded key, w/o padding
o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
o.Cookie.Secret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
assert.Equal(t, nil, o.Validate())
// 24 byte, base64 (urlsafe) encoded key
o.CookieSecret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
o.Cookie.Secret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
assert.Equal(t, nil, o.Validate())
// 16 byte, base64 (urlsafe) encoded key
o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA=="
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA=="
assert.Equal(t, nil, o.Validate())
// 16 byte, base64 (urlsafe) encoded key, w/o padding
o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA"
o.Cookie.Secret = "LFEqZYvYUwKwzn0tEuTpLA"
assert.Equal(t, nil, o.Validate())
}
@ -292,16 +292,16 @@ func TestValidateSignatureKeyUnsupportedAlgorithm(t *testing.T) {
func TestValidateCookie(t *testing.T) {
o := testOptions()
o.CookieName = "_valid_cookie_name"
o.Cookie.Name = "_valid_cookie_name"
assert.Equal(t, nil, o.Validate())
}
func TestValidateCookieBadName(t *testing.T) {
o := testOptions()
o.CookieName = "_bad_cookie_name{}"
o.Cookie.Name = "_bad_cookie_name{}"
err := o.Validate()
assert.Equal(t, err.Error(), "invalid configuration:\n"+
fmt.Sprintf(" invalid cookie name: %q", o.CookieName))
fmt.Sprintf(" invalid cookie name: %q", o.Cookie.Name))
}
func TestSkipOIDCDiscovery(t *testing.T) {

View File

@ -4,13 +4,13 @@ import "time"
// CookieOptions contains configuration options relating to Cookie configuration
type CookieOptions struct {
CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
CookieDomains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
CookiePath string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
CookieSameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
Name string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
Secret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
Domains []string `flag:"cookie-domain" cfg:"cookie_domain" env:"OAUTH2_PROXY_COOKIE_DOMAIN"`
Path string `flag:"cookie-path" cfg:"cookie_path" env:"OAUTH2_PROXY_COOKIE_PATH"`
Expire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
Refresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
Secure bool `flag:"cookie-secure" cfg:"cookie_secure" env:"OAUTH2_PROXY_COOKIE_SECURE"`
HTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly" env:"OAUTH2_PROXY_COOKIE_HTTPONLY"`
SameSite string `flag:"cookie-samesite" cfg:"cookie_samesite" env:"OAUTH2_PROXY_COOKIE_SAMESITE"`
}

134
pkg/apis/options/load.go Normal file
View File

@ -0,0 +1,134 @@
package options
import (
"fmt"
"reflect"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
// Load reads in the config file at the path given, then merges in environment
// variables (prefixed with `OAUTH2_PROXY`) and finally merges in flags from the flagSet.
// If a config value is unset and the flag has a non-zero value default, this default will be used.
// Eg. A field defined:
// FooBar `cfg:"foo_bar" flag:"foo-bar"`
// Can be set in the config file as `foo_bar="baz"`, in the environment as `OAUTH2_PROXY_FOO_BAR=baz`,
// or via the command line flag `--foo-bar=baz`.
func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error {
v := viper.New()
v.SetConfigFile(configFileName)
v.SetConfigType("toml") // Config is in toml format
v.SetEnvPrefix("OAUTH2_PROXY")
v.AutomaticEnv()
v.SetTypeByDefaultValue(true)
if configFileName != "" {
err := v.ReadInConfig()
if err != nil {
return fmt.Errorf("unable to load config file: %w", err)
}
}
err := registerFlags(v, "", flagSet, into)
if err != nil {
// This should only happen if there is a programming error
return fmt.Errorf("unable to register flags: %w", err)
}
// UnmarhsalExact will return an error if the config includes options that are
// not mapped to felds of the into struct
err = v.UnmarshalExact(into, decodeFromCfgTag)
if err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
return nil
}
// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
// to the fields in the options interface provided.
// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
// - For fields, set `cfg` and `flag` so that `flag` is the name of the flag associated to this config option
// - For exported fields that are not user facing, set the `cfg` to `,internal`
// - For structs containing user facing fields, set the `cfg` to `,squash`
func registerFlags(v *viper.Viper, prefix string, flagSet *pflag.FlagSet, options interface{}) error {
val := reflect.ValueOf(options)
var typ reflect.Type
if val.Kind() == reflect.Ptr {
typ = val.Elem().Type()
} else {
typ = val.Type()
}
for i := 0; i < typ.NumField(); i++ {
// pull out the struct tags:
// flag - the name of the command line flag
// cfg - the name of the config file option
field := typ.Field(i)
fieldV := reflect.Indirect(val).Field(i)
fieldName := strings.Join([]string{prefix, field.Name}, ".")
cfgName := field.Tag.Get("cfg")
if cfgName == ",internal" {
// Public but internal types that should not be exposed to users, skip them
continue
}
if isUnexported(field.Name) {
// Unexported fields cannot be set by a user, so won't have tags or flags, skip them
continue
}
if field.Type.Kind() == reflect.Struct {
if cfgName != ",squash" {
return fmt.Errorf("field %q does not have required cfg tag: `,squash`", fieldName)
}
err := registerFlags(v, fieldName, flagSet, fieldV.Interface())
if err != nil {
return err
}
continue
}
flagName := field.Tag.Get("flag")
if flagName == "" || cfgName == "" {
return fmt.Errorf("field %q does not have required tags (cfg, flag)", fieldName)
}
if flagSet == nil {
return fmt.Errorf("flagset cannot be nil")
}
f := flagSet.Lookup(flagName)
if f == nil {
return fmt.Errorf("field %q does not have a registered flag", flagName)
}
err := v.BindPFlag(cfgName, f)
if err != nil {
return fmt.Errorf("error binding flag for field %q: %w", fieldName, err)
}
}
return nil
}
// decodeFromCfgTag sets the Viper decoder to read the names from the `cfg` tag
// on each struct entry.
func decodeFromCfgTag(c *mapstructure.DecoderConfig) {
c.TagName = "cfg"
}
// isUnexported checks if a field name starts with a lowercase letter and therefore
// if it is unexported.
func isUnexported(name string) bool {
if len(name) == 0 {
// This should never happen
panic("field name has len 0")
}
first := string(name[0])
return first == strings.ToLower(first)
}

View File

@ -0,0 +1,300 @@
package options
import (
"fmt"
"io/ioutil"
"os"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"github.com/spf13/pflag"
)
func TestOptionsSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Options Suite")
}
var _ = Describe("Load", func() {
Context("with a testOptions structure", func() {
type TestOptionSubStruct struct {
StringSliceOption []string `flag:"string-slice-option" cfg:"string_slice_option"`
}
type TestOptions struct {
StringOption string `flag:"string-option" cfg:"string_option"`
Sub TestOptionSubStruct `cfg:",squash"`
// Check exported but internal fields do not break loading
Internal *string `cfg:",internal"`
// Check unexported fields do not break loading
unexported string
}
type MissingSquashTestOptions struct {
StringOption string `flag:"string-option" cfg:"string_option"`
Sub TestOptionSubStruct
}
type MissingCfgTestOptions struct {
StringOption string `flag:"string-option"`
Sub TestOptionSubStruct `cfg:",squash"`
}
type MissingFlagTestOptions struct {
StringOption string `cfg:"string_option"`
Sub TestOptionSubStruct `cfg:",squash"`
}
var testOptionsConfigBytes = []byte(`
string_option="foo"
string_slice_option="a,b,c,d"
`)
var testOptionsFlagSet *pflag.FlagSet
type testOptionsTableInput struct {
env map[string]string
args []string
configFile []byte
flagSet func() *pflag.FlagSet
expectedErr error
input interface{}
expectedOutput interface{}
}
BeforeEach(func() {
testOptionsFlagSet = pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
testOptionsFlagSet.String("string-option", "default", "")
testOptionsFlagSet.StringSlice("string-slice-option", []string{"a", "b"}, "")
})
DescribeTable("Load",
func(o *testOptionsTableInput) {
var configFileName string
if o.configFile != nil {
By("Creating a config file")
configFile, err := ioutil.TempFile("", "oauth2-proxy-test-legacy-config-file")
Expect(err).ToNot(HaveOccurred())
defer configFile.Close()
_, err = configFile.Write(o.configFile)
Expect(err).ToNot(HaveOccurred())
defer os.Remove(configFile.Name())
configFileName = configFile.Name()
}
if len(o.env) > 0 {
By("Setting environment variables")
for k, v := range o.env {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
}
Expect(o.flagSet).ToNot(BeNil())
flagSet := o.flagSet()
Expect(flagSet).ToNot(BeNil())
if len(o.args) > 0 {
By("Parsing flag arguments")
Expect(flagSet.Parse(o.args)).To(Succeed())
}
var input interface{}
if o.input != nil {
input = o.input
} else {
input = &TestOptions{}
}
err := Load(configFileName, flagSet, input)
if o.expectedErr != nil {
Expect(err).To(MatchError(o.expectedErr.Error()))
} else {
Expect(err).ToNot(HaveOccurred())
}
Expect(input).To(Equal(o.expectedOutput))
},
Entry("with just a config file", &testOptionsTableInput{
configFile: testOptionsConfigBytes,
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "foo",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c", "d"},
},
},
}),
Entry("when setting env variables", &testOptionsTableInput{
configFile: testOptionsConfigBytes,
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "bar",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c"},
},
},
}),
Entry("when setting flags", &testOptionsTableInput{
configFile: testOptionsConfigBytes,
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
args: []string{
"--string-option", "baz",
"--string-slice-option", "a,b,c,d,e",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "baz",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c", "d", "e"},
},
},
}),
Entry("when setting flags multiple times", &testOptionsTableInput{
configFile: testOptionsConfigBytes,
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
args: []string{
"--string-option", "baz",
"--string-slice-option", "x",
"--string-slice-option", "y",
"--string-slice-option", "z",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "baz",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"x", "y", "z"},
},
},
}),
Entry("when setting env variables without a config file", &testOptionsTableInput{
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "bar",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c"},
},
},
}),
Entry("when setting flags without a config file", &testOptionsTableInput{
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
args: []string{
"--string-option", "baz",
"--string-slice-option", "a,b,c,d,e",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "baz",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c", "d", "e"},
},
},
}),
Entry("when setting flags without a config file", &testOptionsTableInput{
env: map[string]string{
"OAUTH2_PROXY_STRING_OPTION": "bar",
"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
},
args: []string{
"--string-option", "baz",
"--string-slice-option", "a,b,c,d,e",
},
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "baz",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b", "c", "d", "e"},
},
},
}),
Entry("when nothing is set it should use flag defaults", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedOutput: &TestOptions{
StringOption: "default",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b"},
},
},
}),
Entry("with an invalid config file", &testOptionsTableInput{
configFile: []byte(`slice_option = foo`),
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedErr: fmt.Errorf("unable to load config file: While parsing config: (1, 16): never reached"),
expectedOutput: &TestOptions{},
}),
Entry("with an invalid flagset", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet {
// Missing a flag
f := pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
f.String("string-option", "default", "")
return f
},
expectedErr: fmt.Errorf("unable to register flags: field \"string-slice-option\" does not have a registered flag"),
expectedOutput: &TestOptions{},
}),
Entry("with an struct is missing the squash tag", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedErr: fmt.Errorf("unable to register flags: field \".Sub\" does not have required cfg tag: `,squash`"),
input: &MissingSquashTestOptions{},
expectedOutput: &MissingSquashTestOptions{},
}),
Entry("with a field is missing the cfg tag", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedErr: fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
input: &MissingCfgTestOptions{},
expectedOutput: &MissingCfgTestOptions{},
}),
Entry("with a field is missing the flag tag", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedErr: fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
input: &MissingFlagTestOptions{},
expectedOutput: &MissingFlagTestOptions{},
}),
Entry("with existing unexported fields", &testOptionsTableInput{
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
input: &TestOptions{
unexported: "unexported",
},
expectedOutput: &TestOptions{
StringOption: "default",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b"},
},
unexported: "unexported",
},
}),
Entry("with an unknown option in the config file", &testOptionsTableInput{
configFile: []byte(`unknown_option="foo"`),
flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
expectedErr: fmt.Errorf("error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_option"),
// Viper will unmarshal before returning the error, so this is the default output
expectedOutput: &TestOptions{
StringOption: "default",
Sub: TestOptionSubStruct{
StringSliceOption: []string{"a", "b"},
},
},
}),
)
})
})

View File

@ -5,30 +5,26 @@ import "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
// SessionOptions contains configuration options for the SessionStore providers.
type SessionOptions struct {
Type string `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
Cipher *encryption.Cipher
CookieStoreOptions
RedisStoreOptions
Cipher *encryption.Cipher `cfg:",internal"`
Redis RedisStoreOptions `cfg:",squash"`
}
// CookieSessionStoreType is used to indicate the CookieSessionStore should be
// used for storing sessions.
var CookieSessionStoreType = "cookie"
// CookieStoreOptions contains configuration options for the CookieSessionStore.
type CookieStoreOptions struct{}
// RedisSessionStoreType is used to indicate the RedisSessionStore should be
// used for storing sessions.
var RedisSessionStoreType = "redis"
// RedisStoreOptions contains configuration options for the RedisSessionStore.
type RedisStoreOptions struct {
RedisConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url" env:"OAUTH2_PROXY_REDIS_CONNECTION_URL"`
UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel" env:"OAUTH2_PROXY_REDIS_USE_SENTINEL"`
SentinelMasterName string `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name" env:"OAUTH2_PROXY_REDIS_SENTINEL_MASTER_NAME"`
SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls" env:"OAUTH2_PROXY_REDIS_SENTINEL_CONNECTION_URLS"`
UseCluster bool `flag:"redis-use-cluster" cfg:"redis_use_cluster" env:"OAUTH2_PROXY_REDIS_USE_CLUSTER"`
ClusterConnectionURLs []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls" env:"OAUTH2_PROXY_REDIS_CLUSTER_CONNECTION_URLS"`
RedisCAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
RedisInsecureTLS bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path" env:"OAUTH2_PROXY_REDIS_CA_PATH"`
InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify" env:"OAUTH2_PROXY_REDIS_INSECURE_SKIP_TLS_VERIFY"`
}

View File

@ -38,19 +38,19 @@ func MakeCookie(req *http.Request, name string, value string, path string, domai
// MakeCookieFromOptions constructs a cookie based on the given *options.CookieOptions,
// value and creation time
func MakeCookieFromOptions(req *http.Request, name string, value string, opts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
domain := GetCookieDomain(req, opts.CookieDomains)
func MakeCookieFromOptions(req *http.Request, name string, value string, cookieOpts *options.CookieOptions, expiration time.Duration, now time.Time) *http.Cookie {
domain := GetCookieDomain(req, cookieOpts.Domains)
if domain != "" {
return MakeCookie(req, name, value, opts.CookiePath, domain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
return MakeCookie(req, name, value, cookieOpts.Path, domain, cookieOpts.HTTPOnly, cookieOpts.Secure, expiration, now, ParseSameSite(cookieOpts.SameSite))
}
// If nothing matches, create the cookie with the shortest domain
logger.Printf("Warning: request host %q did not match any of the specific cookie domains of %q", GetRequestHost(req), strings.Join(opts.CookieDomains, ","))
logger.Printf("Warning: request host %q did not match any of the specific cookie domains of %q", GetRequestHost(req), strings.Join(cookieOpts.Domains, ","))
defaultDomain := ""
if len(opts.CookieDomains) > 0 {
defaultDomain = opts.CookieDomains[len(opts.CookieDomains)-1]
if len(cookieOpts.Domains) > 0 {
defaultDomain = cookieOpts.Domains[len(cookieOpts.Domains)-1]
}
return MakeCookie(req, name, value, opts.CookiePath, defaultDomain, opts.CookieHTTPOnly, opts.CookieSecure, expiration, now, ParseSameSite(opts.CookieSameSite))
return MakeCookie(req, name, value, cookieOpts.Path, defaultDomain, cookieOpts.HTTPOnly, cookieOpts.Secure, expiration, now, ParseSameSite(cookieOpts.SameSite))
}
// GetCookieDomain returns the correct cookie domain given a list of domains

View File

@ -49,12 +49,12 @@ func (s *SessionStore) Save(rw http.ResponseWriter, req *http.Request, ss *sessi
// Load reads sessions.SessionState information from Cookies within the
// HTTP request object
func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
c, err := loadCookie(req, s.CookieOptions.CookieName)
c, err := loadCookie(req, s.CookieOptions.Name)
if err != nil {
// always http.ErrNoCookie
return nil, fmt.Errorf("cookie %q not present", s.CookieOptions.CookieName)
return nil, fmt.Errorf("cookie %q not present", s.CookieOptions.Name)
}
val, _, ok := encryption.Validate(c, s.CookieOptions.CookieSecret, s.CookieOptions.CookieExpire)
val, _, ok := encryption.Validate(c, s.CookieOptions.Secret, s.CookieOptions.Expire)
if !ok {
return nil, errors.New("cookie signature not valid")
}
@ -70,7 +70,7 @@ func (s *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
// clear the session
func (s *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) error {
// matches CookieName, CookieName_<number>
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieOptions.CookieName))
var cookieNameRegex = regexp.MustCompile(fmt.Sprintf("^%s(_\\d+)?$", s.CookieOptions.Name))
for _, c := range req.Cookies() {
if cookieNameRegex.MatchString(c.Name) {
@ -94,10 +94,10 @@ func (s *SessionStore) setSessionCookie(rw http.ResponseWriter, req *http.Reques
// authentication details
func (s *SessionStore) makeSessionCookie(req *http.Request, value string, now time.Time) []*http.Cookie {
if value != "" {
value = encryption.SignedValue(s.CookieOptions.CookieSecret, s.CookieOptions.CookieName, value, now)
value = encryption.SignedValue(s.CookieOptions.Secret, s.CookieOptions.Name, value, now)
}
c := s.makeCookie(req, s.CookieOptions.CookieName, value, s.CookieOptions.CookieExpire, now)
if len(c.Value) > 4096-len(s.CookieOptions.CookieName) {
c := s.makeCookie(req, s.CookieOptions.Name, value, s.CookieOptions.Expire, now)
if len(c.Value) > 4096-len(s.CookieOptions.Name) {
return splitCookie(c)
}
return []*http.Cookie{c}

View File

@ -40,7 +40,7 @@ type SessionStore struct {
// NewRedisSessionStore initialises a new instance of the SessionStore from
// the configuration given
func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.CookieOptions) (sessions.SessionStore, error) {
client, err := newRedisCmdable(opts.RedisStoreOptions)
client, err := newRedisCmdable(opts.Redis)
if err != nil {
return nil, fmt.Errorf("error constructing redis client: %v", err)
}
@ -74,16 +74,16 @@ func newRedisCmdable(opts options.RedisStoreOptions) (Client, error) {
return newClusterClient(client), nil
}
opt, err := redis.ParseURL(opts.RedisConnectionURL)
opt, err := redis.ParseURL(opts.ConnectionURL)
if err != nil {
return nil, fmt.Errorf("unable to parse redis url: %s", err)
}
if opts.RedisInsecureTLS {
if opts.InsecureSkipTLSVerify {
opt.TLSConfig.InsecureSkipVerify = true
}
if opts.RedisCAPath != "" {
if opts.CAPath != "" {
rootCAs, err := x509.SystemCertPool()
if err != nil {
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) {
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
certs, err := ioutil.ReadFile(opts.RedisCAPath)
certs, err := ioutil.ReadFile(opts.CAPath)
if err != nil {
return nil, fmt.Errorf("failed to load %q, %v", opts.RedisCAPath, err)
return nil, fmt.Errorf("failed to load %q, %v", opts.CAPath, err)
}
// Append our cert to the system pool
@ -117,13 +117,13 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
// Old sessions that we are refreshing would have a request cookie
// New sessions don't, so we ignore the error. storeValue will check requestCookie
requestCookie, _ := req.Cookie(store.CookieOptions.CookieName)
requestCookie, _ := req.Cookie(store.CookieOptions.Name)
value, err := s.EncodeSessionState(store.CookieCipher)
if err != nil {
return err
}
ctx := req.Context()
ticketString, err := store.storeValue(ctx, value, store.CookieOptions.CookieExpire, requestCookie)
ticketString, err := store.storeValue(ctx, value, store.CookieOptions.Expire, requestCookie)
if err != nil {
return err
}
@ -131,7 +131,7 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
ticketCookie := store.makeCookie(
req,
ticketString,
store.CookieOptions.CookieExpire,
store.CookieOptions.Expire,
s.CreatedAt,
)
@ -142,12 +142,12 @@ func (store *SessionStore) Save(rw http.ResponseWriter, req *http.Request, s *se
// Load reads sessions.SessionState information from a ticket
// cookie within the HTTP request object
func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, error) {
requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
requestCookie, err := req.Cookie(store.CookieOptions.Name)
if err != nil {
return nil, fmt.Errorf("error loading session: %s", err)
}
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
if !ok {
return nil, fmt.Errorf("cookie signature not valid")
}
@ -161,12 +161,12 @@ func (store *SessionStore) Load(req *http.Request) (*sessions.SessionState, erro
// loadSessionFromString loads the session based on the ticket value
func (store *SessionStore) loadSessionFromString(ctx context.Context, value string) (*sessions.SessionState, error) {
ticket, err := decodeTicket(store.CookieOptions.CookieName, value)
ticket, err := decodeTicket(store.CookieOptions.Name, value)
if err != nil {
return nil, err
}
resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.CookieOptions.CookieName))
resultBytes, err := store.Client.Get(ctx, ticket.asHandle(store.CookieOptions.Name))
if err != nil {
return nil, err
}
@ -199,7 +199,7 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
http.SetCookie(rw, clearCookie)
// If there was an existing cookie we should clear the session in redis
requestCookie, err := req.Cookie(store.CookieOptions.CookieName)
requestCookie, err := req.Cookie(store.CookieOptions.Name)
if err != nil && err == http.ErrNoCookie {
// No existing cookie so can't clear redis
return nil
@ -207,17 +207,17 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
return fmt.Errorf("error retrieving cookie: %v", err)
}
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
if !ok {
return fmt.Errorf("cookie signature not valid")
}
// We only return an error if we had an issue with redis
// If there's an issue decoding the ticket, ignore it
ticket, _ := decodeTicket(store.CookieOptions.CookieName, val)
ticket, _ := decodeTicket(store.CookieOptions.Name, val)
if ticket != nil {
ctx := req.Context()
err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.CookieName))
err := store.Client.Del(ctx, ticket.asHandle(store.CookieOptions.Name))
if err != nil {
return fmt.Errorf("error clearing cookie from redis: %s", err)
}
@ -228,11 +228,11 @@ func (store *SessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
// makeCookie makes a cookie, signing the value if present
func (store *SessionStore) makeCookie(req *http.Request, value string, expires time.Duration, now time.Time) *http.Cookie {
if value != "" {
value = encryption.SignedValue(store.CookieOptions.CookieSecret, store.CookieOptions.CookieName, value, now)
value = encryption.SignedValue(store.CookieOptions.Secret, store.CookieOptions.Name, value, now)
}
return cookies.MakeCookieFromOptions(
req,
store.CookieOptions.CookieName,
store.CookieOptions.Name,
value,
store.CookieOptions,
expires,
@ -256,12 +256,12 @@ func (store *SessionStore) storeValue(ctx context.Context, value string, expirat
stream := cipher.NewCFBEncrypter(block, ticket.Secret)
stream.XORKeyStream(ciphertext, []byte(value))
handle := ticket.asHandle(store.CookieOptions.CookieName)
handle := ticket.asHandle(store.CookieOptions.Name)
err = store.Client.Set(ctx, handle, ciphertext, expiration)
if err != nil {
return "", err
}
return ticket.encodeTicket(store.CookieOptions.CookieName), nil
return ticket.encodeTicket(store.CookieOptions.Name), nil
}
// getTicket retrieves an existing ticket from the cookie if present,
@ -272,14 +272,14 @@ func (store *SessionStore) getTicket(requestCookie *http.Cookie) (*TicketData, e
}
// An existing cookie exists, try to retrieve the ticket
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.CookieSecret, store.CookieOptions.CookieExpire)
val, _, ok := encryption.Validate(requestCookie, store.CookieOptions.Secret, store.CookieOptions.Expire)
if !ok {
// Cookie is invalid, create a new ticket
return newTicket()
}
// Valid cookie, decode the ticket
ticket, err := decodeTicket(store.CookieOptions.CookieName, val)
ticket, err := decodeTicket(store.CookieOptions.Name, val)
if err != nil {
// If we can't decode the ticket we have to create a new one
return newTicket()

View File

@ -47,25 +47,25 @@ var _ = Describe("NewSessionStore", func() {
It("have the correct name set", func() {
if len(cookies) == 1 {
Expect(cookies[0].Name).To(Equal(cookieOpts.CookieName))
Expect(cookies[0].Name).To(Equal(cookieOpts.Name))
} else {
for _, cookie := range cookies {
Expect(cookie.Name).To(ContainSubstring(cookieOpts.CookieName))
Expect(cookie.Name).To(ContainSubstring(cookieOpts.Name))
}
}
})
It("have the correct path set", func() {
for _, cookie := range cookies {
Expect(cookie.Path).To(Equal(cookieOpts.CookiePath))
Expect(cookie.Path).To(Equal(cookieOpts.Path))
}
})
It("have the correct domain set", func() {
for _, cookie := range cookies {
specifiedDomain := ""
if len(cookieOpts.CookieDomains) > 0 {
specifiedDomain = cookieOpts.CookieDomains[0]
if len(cookieOpts.Domains) > 0 {
specifiedDomain = cookieOpts.Domains[0]
}
Expect(cookie.Domain).To(Equal(specifiedDomain))
}
@ -73,19 +73,19 @@ var _ = Describe("NewSessionStore", func() {
It("have the correct HTTPOnly set", func() {
for _, cookie := range cookies {
Expect(cookie.HttpOnly).To(Equal(cookieOpts.CookieHTTPOnly))
Expect(cookie.HttpOnly).To(Equal(cookieOpts.HTTPOnly))
}
})
It("have the correct secure set", func() {
for _, cookie := range cookies {
Expect(cookie.Secure).To(Equal(cookieOpts.CookieSecure))
Expect(cookie.Secure).To(Equal(cookieOpts.Secure))
}
})
It("have the correct SameSite set", func() {
for _, cookie := range cookies {
Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.CookieSameSite)))
Expect(cookie.SameSite).To(Equal(cookiesapi.ParseSameSite(cookieOpts.SameSite)))
}
})
@ -168,8 +168,8 @@ var _ = Describe("NewSessionStore", func() {
BeforeEach(func() {
By("Using a valid cookie with a different providers session encoding")
broken := "BrokenSessionFromADifferentSessionImplementation"
value := encryption.SignedValue(cookieOpts.CookieSecret, cookieOpts.CookieName, broken, time.Now())
cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.CookieName, value, cookieOpts, cookieOpts.CookieExpire, time.Now())
value := encryption.SignedValue(cookieOpts.Secret, cookieOpts.Name, broken, time.Now())
cookie := cookiesapi.MakeCookieFromOptions(request, cookieOpts.Name, value, cookieOpts, cookieOpts.Expire, time.Now())
request.AddCookie(cookie)
err := ss.Save(response, request, session)
@ -245,7 +245,7 @@ var _ = Describe("NewSessionStore", func() {
})
It("loads a session equal to the original session", func() {
if cookieOpts.CookieSecret == "" {
if cookieOpts.Secret == "" {
// Only Email and User stored in session when encrypted
Expect(loadedSession.Email).To(Equal(session.Email))
Expect(loadedSession.User).To(Equal(session.User))
@ -290,7 +290,7 @@ var _ = Describe("NewSessionStore", func() {
BeforeEach(func() {
switch ss.(type) {
case *redis.SessionStore:
mr.FastForward(cookieOpts.CookieRefresh + time.Minute)
mr.FastForward(cookieOpts.Refresh + time.Minute)
}
})
@ -304,7 +304,7 @@ var _ = Describe("NewSessionStore", func() {
BeforeEach(func() {
switch ss.(type) {
case *redis.SessionStore:
mr.FastForward(cookieOpts.CookieExpire + time.Minute)
mr.FastForward(cookieOpts.Expire + time.Minute)
}
loadedSession, err = ss.Load(request)
@ -341,14 +341,14 @@ var _ = Describe("NewSessionStore", func() {
Context("with non-default options", func() {
BeforeEach(func() {
cookieOpts = &options.CookieOptions{
CookieName: "_cookie_name",
CookiePath: "/path",
CookieExpire: time.Duration(72) * time.Hour,
CookieRefresh: time.Duration(2) * time.Hour,
CookieSecure: false,
CookieHTTPOnly: false,
CookieDomains: []string{"example.com"},
CookieSameSite: "strict",
Name: "_cookie_name",
Path: "/path",
Expire: time.Duration(72) * time.Hour,
Refresh: time.Duration(2) * time.Hour,
Secure: false,
HTTPOnly: false,
Domains: []string{"example.com"},
SameSite: "strict",
}
var err error
@ -364,8 +364,8 @@ var _ = Describe("NewSessionStore", func() {
secret := make([]byte, 32)
_, err := rand.Read(secret)
Expect(err).ToNot(HaveOccurred())
cookieOpts.CookieSecret = base64.URLEncoding.EncodeToString(secret)
cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.CookieSecret))
cookieOpts.Secret = base64.URLEncoding.EncodeToString(secret)
cipher, err := encryption.NewCipher(utils.SecretBytes(cookieOpts.Secret))
Expect(err).ToNot(HaveOccurred())
Expect(cipher).ToNot(BeNil())
opts.Cipher = cipher
@ -384,13 +384,13 @@ var _ = Describe("NewSessionStore", func() {
// Set default options in CookieOptions
cookieOpts = &options.CookieOptions{
CookieName: "_oauth2_proxy",
CookiePath: "/",
CookieExpire: time.Duration(168) * time.Hour,
CookieRefresh: time.Duration(1) * time.Hour,
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: "",
Name: "_oauth2_proxy",
Path: "/",
Expire: time.Duration(168) * time.Hour,
Refresh: time.Duration(1) * time.Hour,
Secure: true,
HTTPOnly: true,
SameSite: "",
}
session = &sessionsapi.SessionState{
@ -428,7 +428,7 @@ var _ = Describe("NewSessionStore", func() {
mr, err = miniredis.Run()
Expect(err).ToNot(HaveOccurred())
opts.Type = options.RedisSessionStoreType
opts.RedisConnectionURL = "redis://" + mr.Addr()
opts.Redis.ConnectionURL = "redis://" + mr.Addr()
})
AfterEach(func() {