From c5be09ca483dd2cd8f23675c754ff12bb5f27704 Mon Sep 17 00:00:00 2001
From: Joel Speed <joel.speed@hotmail.co.uk>
Date: Mon, 13 Apr 2020 11:34:25 +0100
Subject: [PATCH] Replace options loading with viper

---
 env_options.go               | 54 ------------------------------
 env_options_test.go          | 64 ------------------------------------
 go.mod                       |  2 --
 go.sum                       |  7 ++--
 main.go                      | 24 +++++++-------
 oauthproxy.go                |  4 +--
 options.go                   | 11 +++----
 options_test.go              |  2 +-
 pkg/apis/options/sessions.go |  6 ++--
 9 files changed, 27 insertions(+), 147 deletions(-)
 delete mode 100644 env_options.go
 delete mode 100644 env_options_test.go

diff --git a/env_options.go b/env_options.go
deleted file mode 100644
index 51a888f..0000000
--- a/env_options.go
+++ /dev/null
@@ -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 {
-			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
-		}
-	}
-}
diff --git a/env_options_test.go b/env_options_test.go
deleted file mode 100644
index c8ee75c..0000000
--- a/env_options_test.go
+++ /dev/null
@@ -1,64 +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
-	Named EnvTestNamed
-}
-
-type EnvTestEmbed struct {
-	TestFieldEmbed string `cfg:"target_field_embed" env:"TEST_ENV_FIELD_EMBED"`
-}
-
-type EnvTestNamed struct {
-	TestFieldNamed string `cfg:"target_field_named" env:"TEST_ENV_FIELD_NAMED"`
-}
-
-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")
-}
-
-func TestLoadEnvForStructWithNamedStructFields(t *testing.T) {
-	cfg := make(proxy.EnvOptions)
-	cfg.LoadEnvForStruct(&EnvTest{})
-
-	_, ok := cfg["target_field_named"]
-	assert.Equal(t, ok, false)
-
-	os.Setenv("TEST_ENV_FIELD_NAMED", "1234abcd")
-	cfg.LoadEnvForStruct(&EnvTest{})
-	v := cfg["target_field_named"]
-	assert.Equal(t, v, "1234abcd")
-}
diff --git a/go.mod b/go.mod
index 21bd59f..618c7c7 100644
--- a/go.mod
+++ b/go.mod
@@ -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
@@ -14,7 +13,6 @@ require (
 	github.com/kr/pretty v0.2.0 // indirect
 	github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
 	github.com/mitchellh/mapstructure v1.1.2
-	github.com/mreiferson/go-options v1.0.0
 	github.com/onsi/ginkgo v1.12.0
 	github.com/onsi/gomega v1.9.0
 	github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
diff --git a/go.sum b/go.sum
index 5e85e76..45e2be1 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
 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=
@@ -74,6 +75,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
 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=
@@ -89,6 +91,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 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=
@@ -111,8 +114,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 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/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/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=
@@ -142,7 +143,9 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
 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=
diff --git a/main.go b/main.go
index 91111ea..698c64a 100644
--- a/main.go
+++ b/main.go
@@ -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)
-		if err != nil {
-			logger.Fatalf("ERROR: failed to load config file %s - %s", *config, err)
-		}
+	err := options.Load(*config, pflagSet, opts)
+	if err != nil {
+		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)
diff --git a/oauthproxy.go b/oauthproxy.go
index 516340c..587215f 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -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)
 	}
 
@@ -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,
diff --git a/options.go b/options.go
index ca8c14d..b59c2b1 100644
--- a/options.go
+++ b/options.go
@@ -64,9 +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"`
 
-	Cookie options.CookieOptions
-
-	Session 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"`
@@ -132,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
@@ -370,12 +369,12 @@ 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)
 
diff --git a/options_test.go b/options_test.go
index 42a9689..b14c9e4 100644
--- a/options_test.go
+++ b/options_test.go
@@ -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)
diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go
index a12be7f..fd69d60 100644
--- a/pkg/apis/options/sessions.go
+++ b/pkg/apis/options/sessions.go
@@ -4,9 +4,9 @@ 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
-	Redis  RedisStoreOptions
+	Type   string             `flag:"session-store-type" cfg:"session_store_type" env:"OAUTH2_PROXY_SESSION_STORE_TYPE"`
+	Cipher *encryption.Cipher `cfg:",internal"`
+	Redis  RedisStoreOptions  `cfg:",squash"`
 }
 
 // CookieSessionStoreType is used to indicate the CookieSessionStore should be
-- 
GitLab