Introduce alpha configuration loading
This commit is contained in:
		
							parent
							
								
									5b003a5657
								
							
						
					
					
						commit
						f36dfbb494
					
				
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -11,6 +11,7 @@ require ( | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| 	github.com/frankban/quicktest v1.10.0 // indirect | 	github.com/frankban/quicktest v1.10.0 // indirect | ||||||
| 	github.com/fsnotify/fsnotify v1.4.9 | 	github.com/fsnotify/fsnotify v1.4.9 | ||||||
|  | 	github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 | ||||||
| 	github.com/go-redis/redis/v8 v8.2.3 | 	github.com/go-redis/redis/v8 v8.2.3 | ||||||
| 	github.com/justinas/alice v1.2.0 | 	github.com/justinas/alice v1.2.0 | ||||||
| 	github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa | 	github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										3
									
								
								go.sum
								
								
								
								
							|  | @ -64,7 +64,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo | ||||||
| github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= | 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/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||||
| github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||||
|  | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | ||||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||||
|  | github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= | ||||||
|  | github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= | ||||||
| github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | 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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||||
|  |  | ||||||
							
								
								
									
										96
									
								
								main.go
								
								
								
								
							
							
						
						
									
										96
									
								
								main.go
								
								
								
								
							|  | @ -12,36 +12,26 @@ import ( | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	logger.SetFlags(logger.Lshortfile) | 	logger.SetFlags(logger.Lshortfile) | ||||||
| 	flagSet := options.NewFlagSet() |  | ||||||
| 
 | 
 | ||||||
| 	config := flagSet.String("config", "", "path to config file") | 	configFlagSet := pflag.NewFlagSet("oauth2-proxy", pflag.ContinueOnError) | ||||||
| 	showVersion := flagSet.Bool("version", false, "print version string") | 	config := configFlagSet.String("config", "", "path to config file") | ||||||
| 
 | 	alphaConfig := configFlagSet.String("alpha-config", "", "path to alpha config file (use at your own risk - the structure in this config file may change between minor releases)") | ||||||
| 	err := flagSet.Parse(os.Args[1:]) | 	showVersion := configFlagSet.Bool("version", false, "print version string") | ||||||
| 	if err != nil { | 	configFlagSet.Parse(os.Args[1:]) | ||||||
| 		logger.Printf("ERROR: Failed to parse flags: %v", err) |  | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if *showVersion { | 	if *showVersion { | ||||||
| 		fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version()) | 		fmt.Printf("oauth2-proxy %s (built with %s)\n", VERSION, runtime.Version()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	legacyOpts := options.NewLegacyOptions() | 	opts, err := loadConfiguration(*config, *alphaConfig, configFlagSet, os.Args[1:]) | ||||||
| 	err = options.Load(*config, flagSet, legacyOpts) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		logger.Errorf("ERROR: Failed to load config: %v", err) | 		logger.Printf("ERROR: %v", err) | ||||||
| 		os.Exit(1) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	opts, err := legacyOpts.ToOptions() |  | ||||||
| 	if err != nil { |  | ||||||
| 		logger.Errorf("ERROR: Failed to convert config: %v", err) |  | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -74,3 +64,73 @@ func main() { | ||||||
| 	}() | 	}() | ||||||
| 	s.ListenAndServe() | 	s.ListenAndServe() | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // loadConfiguration will load in the user's configuration.
 | ||||||
|  | // It will either load the alpha configuration (if alphaConfig is given)
 | ||||||
|  | // or the legacy configuration.
 | ||||||
|  | func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||||
|  | 	if alphaConfig != "" { | ||||||
|  | 		logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.") | ||||||
|  | 		return loadAlphaOptions(config, alphaConfig, extraFlags, args) | ||||||
|  | 	} | ||||||
|  | 	return loadLegacyOptions(config, extraFlags, args) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // loadLegacyOptions loads the old toml options using the legacy flagset
 | ||||||
|  | // and legacy options struct.
 | ||||||
|  | func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||||
|  | 	optionsFlagSet := options.NewLegacyFlagSet() | ||||||
|  | 	optionsFlagSet.AddFlagSet(extraFlags) | ||||||
|  | 	if err := optionsFlagSet.Parse(args); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to parse flags: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	legacyOpts := options.NewLegacyOptions() | ||||||
|  | 	if err := options.Load(config, optionsFlagSet, legacyOpts); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to load config: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	opts, err := legacyOpts.ToOptions() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to convert config: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return opts, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // loadAlphaOptions loads the old style config excluding options converted to
 | ||||||
|  | // the new alpha format, then merges the alpha options, loaded from YAML,
 | ||||||
|  | // into the core configuration.
 | ||||||
|  | func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||||
|  | 	opts, err := loadOptions(config, extraFlags, args) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to load core options: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	alphaOpts := &options.AlphaOptions{} | ||||||
|  | 	if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to load alpha options: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	alphaOpts.MergeInto(opts) | ||||||
|  | 	return opts, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // loadOptions loads the configuration using the old style format into the
 | ||||||
|  | // core options.Options struct.
 | ||||||
|  | // This means that none of the options that have been converted to alpha config
 | ||||||
|  | // will be loaded using this method.
 | ||||||
|  | func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { | ||||||
|  | 	optionsFlagSet := options.NewFlagSet() | ||||||
|  | 	optionsFlagSet.AddFlagSet(extraFlags) | ||||||
|  | 	if err := optionsFlagSet.Parse(args); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to parse flags: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	opts := options.NewOptions() | ||||||
|  | 	if err := options.Load(config, optionsFlagSet, opts); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to load config: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return opts, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||||
|  | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestMainSuite(t *testing.T) { | ||||||
|  | 	logger.SetOutput(GinkgoWriter) | ||||||
|  | 
 | ||||||
|  | 	RegisterFailHandler(Fail) | ||||||
|  | 	RunSpecs(t, "Main Suite") | ||||||
|  | } | ||||||
|  | @ -0,0 +1,215 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" | ||||||
|  | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/ginkgo/extensions/table" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
|  | 	"github.com/spf13/pflag" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var _ = Describe("Configuration Loading Suite", func() { | ||||||
|  | 	const testLegacyConfig = ` | ||||||
|  | upstreams="http://httpbin" | ||||||
|  | set_basic_auth="true" | ||||||
|  | basic_auth_password="super-secret-password" | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | 	const testAlphaConfig = ` | ||||||
|  | upstreams: | ||||||
|  |   - id: / | ||||||
|  |     path: / | ||||||
|  |     uri: http://httpbin
 | ||||||
|  |     flushInterval: 1s | ||||||
|  |     passHostHeader: true | ||||||
|  |     proxyWebSockets: true | ||||||
|  | injectRequestHeaders: | ||||||
|  | - name: Authorization | ||||||
|  |   values: | ||||||
|  |   - claim: user | ||||||
|  |     prefix: "Basic " | ||||||
|  |     basicAuthPassword: | ||||||
|  |       value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk | ||||||
|  | - name: X-Forwarded-Groups | ||||||
|  |   values: | ||||||
|  |   - claim: groups | ||||||
|  | - name: X-Forwarded-User | ||||||
|  |   values: | ||||||
|  |   - claim: user | ||||||
|  | - name: X-Forwarded-Email | ||||||
|  |   values: | ||||||
|  |   - claim: email | ||||||
|  | - name: X-Forwarded-Preferred-Username | ||||||
|  |   values: | ||||||
|  |   - claim: preferred_username | ||||||
|  | injectResponseHeaders: | ||||||
|  | - name: Authorization | ||||||
|  |   values: | ||||||
|  |   - claim: user | ||||||
|  |     prefix: "Basic " | ||||||
|  |     basicAuthPassword: | ||||||
|  |       value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | 	const testCoreConfig = ` | ||||||
|  | http_address="0.0.0.0:4180" | ||||||
|  | cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" | ||||||
|  | provider="oidc" | ||||||
|  | email_domains="example.com" | ||||||
|  | oidc_issuer_url="http://dex.localhost:4190/dex" | ||||||
|  | client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" | ||||||
|  | client_id="oauth2-proxy" | ||||||
|  | cookie_secure="false" | ||||||
|  | 
 | ||||||
|  | redirect_url="http://localhost:4180/oauth2/callback" | ||||||
|  | ` | ||||||
|  | 
 | ||||||
|  | 	boolPtr := func(b bool) *bool { | ||||||
|  | 		return &b | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	durationPtr := func(d time.Duration) *options.Duration { | ||||||
|  | 		du := options.Duration(d) | ||||||
|  | 		return &du | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	testExpectedOptions := func() *options.Options { | ||||||
|  | 		opts, err := options.NewLegacyOptions().ToOptions() | ||||||
|  | 		Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 
 | ||||||
|  | 		opts.HTTPAddress = "0.0.0.0:4180" | ||||||
|  | 		opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" | ||||||
|  | 		opts.ProviderType = "oidc" | ||||||
|  | 		opts.EmailDomains = []string{"example.com"} | ||||||
|  | 		opts.OIDCIssuerURL = "http://dex.localhost:4190/dex" | ||||||
|  | 		opts.ClientSecret = "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" | ||||||
|  | 		opts.ClientID = "oauth2-proxy" | ||||||
|  | 		opts.Cookie.Secure = false | ||||||
|  | 		opts.RawRedirectURL = "http://localhost:4180/oauth2/callback" | ||||||
|  | 
 | ||||||
|  | 		opts.UpstreamServers = options.Upstreams{ | ||||||
|  | 			{ | ||||||
|  | 				ID:              "/", | ||||||
|  | 				Path:            "/", | ||||||
|  | 				URI:             "http://httpbin", | ||||||
|  | 				FlushInterval:   durationPtr(options.DefaultUpstreamFlushInterval), | ||||||
|  | 				PassHostHeader:  boolPtr(true), | ||||||
|  | 				ProxyWebSockets: boolPtr(true), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		authHeader := options.Header{ | ||||||
|  | 			Name: "Authorization", | ||||||
|  | 			Values: []options.HeaderValue{ | ||||||
|  | 				{ | ||||||
|  | 					ClaimSource: &options.ClaimSource{ | ||||||
|  | 						Claim:  "user", | ||||||
|  | 						Prefix: "Basic ", | ||||||
|  | 						BasicAuthPassword: &options.SecretSource{ | ||||||
|  | 							Value: []byte("super-secret-password"), | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		opts.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...) | ||||||
|  | 		opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader) | ||||||
|  | 		return opts | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	type loadConfigurationTableInput struct { | ||||||
|  | 		configContent      string | ||||||
|  | 		alphaConfigContent string | ||||||
|  | 		args               []string | ||||||
|  | 		extraFlags         func() *pflag.FlagSet | ||||||
|  | 		expectedOptions    func() *options.Options | ||||||
|  | 		expectedErr        error | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	DescribeTable("LoadConfiguration", | ||||||
|  | 		func(in loadConfigurationTableInput) { | ||||||
|  | 			var configFileName, alphaConfigFileName string | ||||||
|  | 
 | ||||||
|  | 			defer func() { | ||||||
|  | 				if configFileName != "" { | ||||||
|  | 					Expect(os.Remove(configFileName)).To(Succeed()) | ||||||
|  | 				} | ||||||
|  | 				if alphaConfigFileName != "" { | ||||||
|  | 					Expect(os.Remove(alphaConfigFileName)).To(Succeed()) | ||||||
|  | 				} | ||||||
|  | 			}() | ||||||
|  | 
 | ||||||
|  | 			if in.configContent != "" { | ||||||
|  | 				By("Writing the config to a temporary file", func() { | ||||||
|  | 					file, err := ioutil.TempFile("", "oauth2-proxy-test-config-XXXX.cfg") | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 					defer file.Close() | ||||||
|  | 
 | ||||||
|  | 					configFileName = file.Name() | ||||||
|  | 
 | ||||||
|  | 					_, err = file.WriteString(in.configContent) | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if in.alphaConfigContent != "" { | ||||||
|  | 				By("Writing the config to a temporary file", func() { | ||||||
|  | 					file, err := ioutil.TempFile("", "oauth2-proxy-test-alpha-config-XXXX.yaml") | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 					defer file.Close() | ||||||
|  | 
 | ||||||
|  | 					alphaConfigFileName = file.Name() | ||||||
|  | 
 | ||||||
|  | 					_, err = file.WriteString(in.alphaConfigContent) | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			extraFlags := pflag.NewFlagSet("test-flagset", pflag.ExitOnError) | ||||||
|  | 			if in.extraFlags != nil { | ||||||
|  | 				extraFlags = in.extraFlags() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args) | ||||||
|  | 			if in.expectedErr != nil { | ||||||
|  | 				Expect(err).To(MatchError(in.expectedErr.Error())) | ||||||
|  | 			} else { | ||||||
|  | 				Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 			} | ||||||
|  | 			Expect(in.expectedOptions).ToNot(BeNil()) | ||||||
|  | 			Expect(opts).To(Equal(in.expectedOptions())) | ||||||
|  | 		}, | ||||||
|  | 		Entry("with legacy configuration", loadConfigurationTableInput{ | ||||||
|  | 			configContent:   testCoreConfig + testLegacyConfig, | ||||||
|  | 			expectedOptions: testExpectedOptions, | ||||||
|  | 		}), | ||||||
|  | 		Entry("with alpha configuration", loadConfigurationTableInput{ | ||||||
|  | 			configContent:      testCoreConfig, | ||||||
|  | 			alphaConfigContent: testAlphaConfig, | ||||||
|  | 			expectedOptions:    testExpectedOptions, | ||||||
|  | 		}), | ||||||
|  | 		Entry("with bad legacy configuration", loadConfigurationTableInput{ | ||||||
|  | 			configContent:   testCoreConfig + "unknown_field=\"something\"", | ||||||
|  | 			expectedOptions: func() *options.Options { return nil }, | ||||||
|  | 			expectedErr:     errors.New("failed to load config: error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_field"), | ||||||
|  | 		}), | ||||||
|  | 		Entry("with bad alpha configuration", loadConfigurationTableInput{ | ||||||
|  | 			configContent:      testCoreConfig, | ||||||
|  | 			alphaConfigContent: testAlphaConfig + ":", | ||||||
|  | 			expectedOptions:    func() *options.Options { return nil }, | ||||||
|  | 			expectedErr:        errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 34: did not find expected key"), | ||||||
|  | 		}), | ||||||
|  | 		Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{ | ||||||
|  | 			configContent:      testCoreConfig + "unknown_field=\"something\"", | ||||||
|  | 			alphaConfigContent: testAlphaConfig, | ||||||
|  | 			expectedOptions:    func() *options.Options { return nil }, | ||||||
|  | 			expectedErr:        errors.New("failed to load core options: failed to load config: error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_field"), | ||||||
|  | 		}), | ||||||
|  | 	) | ||||||
|  | }) | ||||||
|  | @ -29,3 +29,11 @@ type AlphaOptions struct { | ||||||
| 	// or from a static secret value.
 | 	// or from a static secret value.
 | ||||||
| 	InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"` | 	InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // MergeInto replaces alpha options in the Options struct with the values
 | ||||||
|  | // from the AlphaOptions
 | ||||||
|  | func (a *AlphaOptions) MergeInto(opts *Options) { | ||||||
|  | 	opts.UpstreamServers = a.Upstreams | ||||||
|  | 	opts.InjectRequestHeaders = a.InjectRequestHeaders | ||||||
|  | 	opts.InjectResponseHeaders = a.InjectResponseHeaders | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -39,6 +39,15 @@ func NewLegacyOptions() *LegacyOptions { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func NewLegacyFlagSet() *pflag.FlagSet { | ||||||
|  | 	flagSet := NewFlagSet() | ||||||
|  | 
 | ||||||
|  | 	flagSet.AddFlagSet(legacyUpstreamsFlagSet()) | ||||||
|  | 	flagSet.AddFlagSet(legacyHeadersFlagSet()) | ||||||
|  | 
 | ||||||
|  | 	return flagSet | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (l *LegacyOptions) ToOptions() (*Options, error) { | func (l *LegacyOptions) ToOptions() (*Options, error) { | ||||||
| 	upstreams, err := l.LegacyUpstreams.convert() | 	upstreams, err := l.LegacyUpstreams.convert() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -1,10 +1,13 @@ | ||||||
| package options | package options | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/ghodss/yaml" | ||||||
| 	"github.com/mitchellh/mapstructure" | 	"github.com/mitchellh/mapstructure" | ||||||
| 	"github.com/spf13/pflag" | 	"github.com/spf13/pflag" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
|  | @ -132,3 +135,28 @@ func isUnexported(name string) bool { | ||||||
| 	first := string(name[0]) | 	first := string(name[0]) | ||||||
| 	return first == strings.ToLower(first) | 	return first == strings.ToLower(first) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // LoadYAML will load a YAML based configuration file into the options interface provided.
 | ||||||
|  | func LoadYAML(configFileName string, into interface{}) error { | ||||||
|  | 	v := viper.New() | ||||||
|  | 	v.SetConfigFile(configFileName) | ||||||
|  | 	v.SetConfigType("yaml") | ||||||
|  | 	v.SetTypeByDefaultValue(true) | ||||||
|  | 
 | ||||||
|  | 	if configFileName == "" { | ||||||
|  | 		return errors.New("no configuration file provided") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data, err := ioutil.ReadFile(configFileName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("unable to load config file: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// UnmarshalStrict will return an error if the config includes options that are
 | ||||||
|  | 	// not mapped to felds of the into struct
 | ||||||
|  | 	if err := yaml.UnmarshalStrict(data, into, yaml.DisallowUnknownFields); err != nil { | ||||||
|  | 		return fmt.Errorf("error unmarshalling config: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| package options | package options | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/ginkgo/extensions/table" | 	. "github.com/onsi/ginkgo/extensions/table" | ||||||
|  | @ -295,10 +297,199 @@ var _ = Describe("Load", func() { | ||||||
| 				expectedOutput: NewOptions(), | 				expectedOutput: NewOptions(), | ||||||
| 			}), | 			}), | ||||||
| 			Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{ | 			Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{ | ||||||
| 				flagSet:        NewFlagSet, | 				flagSet:        NewLegacyFlagSet, | ||||||
| 				input:          &LegacyOptions{}, | 				input:          &LegacyOptions{}, | ||||||
| 				expectedOutput: NewLegacyOptions(), | 				expectedOutput: NewLegacyOptions(), | ||||||
| 			}), | 			}), | ||||||
| 		) | 		) | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
|  | 
 | ||||||
|  | var _ = Describe("LoadYAML", func() { | ||||||
|  | 	Context("with a testOptions structure", func() { | ||||||
|  | 		type TestOptionSubStruct struct { | ||||||
|  | 			StringSliceOption []string `yaml:"stringSliceOption,omitempty"` | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		type TestOptions struct { | ||||||
|  | 			StringOption string              `yaml:"stringOption,omitempty"` | ||||||
|  | 			Sub          TestOptionSubStruct `yaml:"sub,omitempty"` | ||||||
|  | 
 | ||||||
|  | 			// Check that embedded fields can be unmarshalled
 | ||||||
|  | 			TestOptionSubStruct `yaml:",inline,squash"` | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var testOptionsConfigBytesFull = []byte(` | ||||||
|  | stringOption: foo | ||||||
|  | stringSliceOption: | ||||||
|  | - a | ||||||
|  | - b | ||||||
|  | - c | ||||||
|  | sub: | ||||||
|  |   stringSliceOption: | ||||||
|  |   - d | ||||||
|  |   - e | ||||||
|  | `) | ||||||
|  | 
 | ||||||
|  | 		type loadYAMLTableInput struct { | ||||||
|  | 			configFile     []byte | ||||||
|  | 			input          interface{} | ||||||
|  | 			expectedErr    error | ||||||
|  | 			expectedOutput interface{} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		DescribeTable("LoadYAML", | ||||||
|  | 			func(in loadYAMLTableInput) { | ||||||
|  | 				var configFileName string | ||||||
|  | 
 | ||||||
|  | 				if in.configFile != nil { | ||||||
|  | 					By("Creating a config file") | ||||||
|  | 					configFile, err := ioutil.TempFile("", "oauth2-proxy-test-config-file") | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 					defer configFile.Close() | ||||||
|  | 
 | ||||||
|  | 					_, err = configFile.Write(in.configFile) | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 					defer os.Remove(configFile.Name()) | ||||||
|  | 
 | ||||||
|  | 					configFileName = configFile.Name() | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var input interface{} | ||||||
|  | 				if in.input != nil { | ||||||
|  | 					input = in.input | ||||||
|  | 				} else { | ||||||
|  | 					input = &TestOptions{} | ||||||
|  | 				} | ||||||
|  | 				err := LoadYAML(configFileName, input) | ||||||
|  | 				if in.expectedErr != nil { | ||||||
|  | 					Expect(err).To(MatchError(in.expectedErr.Error())) | ||||||
|  | 				} else { | ||||||
|  | 					Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 				} | ||||||
|  | 				Expect(input).To(Equal(in.expectedOutput)) | ||||||
|  | 			}, | ||||||
|  | 			Entry("with a valid input", loadYAMLTableInput{ | ||||||
|  | 				configFile: testOptionsConfigBytesFull, | ||||||
|  | 				input:      &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{ | ||||||
|  | 					StringOption: "foo", | ||||||
|  | 					Sub: TestOptionSubStruct{ | ||||||
|  | 						StringSliceOption: []string{"d", "e"}, | ||||||
|  | 					}, | ||||||
|  | 					TestOptionSubStruct: TestOptionSubStruct{ | ||||||
|  | 						StringSliceOption: []string{"a", "b", "c"}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 			Entry("with no config file", loadYAMLTableInput{ | ||||||
|  | 				configFile:     nil, | ||||||
|  | 				input:          &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{}, | ||||||
|  | 				expectedErr:    errors.New("no configuration file provided"), | ||||||
|  | 			}), | ||||||
|  | 			Entry("with invalid YAML", loadYAMLTableInput{ | ||||||
|  | 				configFile:     []byte("\tfoo: bar"), | ||||||
|  | 				input:          &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{}, | ||||||
|  | 				expectedErr:    errors.New("error unmarshalling config: error converting YAML to JSON: yaml: found character that cannot start any token"), | ||||||
|  | 			}), | ||||||
|  | 			Entry("with extra fields in the YAML", loadYAMLTableInput{ | ||||||
|  | 				configFile: append(testOptionsConfigBytesFull, []byte("foo: bar\n")...), | ||||||
|  | 				input:      &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{ | ||||||
|  | 					StringOption: "foo", | ||||||
|  | 					Sub: TestOptionSubStruct{ | ||||||
|  | 						StringSliceOption: []string{"d", "e"}, | ||||||
|  | 					}, | ||||||
|  | 					TestOptionSubStruct: TestOptionSubStruct{ | ||||||
|  | 						StringSliceOption: []string{"a", "b", "c"}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""), | ||||||
|  | 			}), | ||||||
|  | 			Entry("with an incorrect type for a string field", loadYAMLTableInput{ | ||||||
|  | 				configFile:     []byte(`stringOption: ["a", "b"]`), | ||||||
|  | 				input:          &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{}, | ||||||
|  | 				expectedErr:    errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field TestOptions.StringOption of type string"), | ||||||
|  | 			}), | ||||||
|  | 			Entry("with an incorrect type for an array field", loadYAMLTableInput{ | ||||||
|  | 				configFile:     []byte(`stringSliceOption: "a"`), | ||||||
|  | 				input:          &TestOptions{}, | ||||||
|  | 				expectedOutput: &TestOptions{}, | ||||||
|  | 				expectedErr:    errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go struct field TestOptions.StringSliceOption of type []string"), | ||||||
|  | 			}), | ||||||
|  | 		) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	It("should load a full example AlphaOptions", func() { | ||||||
|  | 		config := []byte(` | ||||||
|  | upstreams: | ||||||
|  | - id: httpbin | ||||||
|  |   path: / | ||||||
|  |   uri: http://httpbin
 | ||||||
|  |   flushInterval: 500ms | ||||||
|  | injectRequestHeaders: | ||||||
|  | - name: X-Forwarded-User | ||||||
|  |   values: | ||||||
|  |   - claim: user | ||||||
|  | injectResponseHeaders: | ||||||
|  | - name: X-Secret | ||||||
|  |   values: | ||||||
|  |   - value: c2VjcmV0 | ||||||
|  | `) | ||||||
|  | 
 | ||||||
|  | 		By("Creating a config file") | ||||||
|  | 		configFile, err := ioutil.TempFile("", "oauth2-proxy-test-alpha-config-file") | ||||||
|  | 		Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 		defer configFile.Close() | ||||||
|  | 
 | ||||||
|  | 		_, err = configFile.Write(config) | ||||||
|  | 		Expect(err).ToNot(HaveOccurred()) | ||||||
|  | 		defer os.Remove(configFile.Name()) | ||||||
|  | 
 | ||||||
|  | 		configFileName := configFile.Name() | ||||||
|  | 
 | ||||||
|  | 		By("Loading the example config") | ||||||
|  | 		into := &AlphaOptions{} | ||||||
|  | 		Expect(LoadYAML(configFileName, into)).To(Succeed()) | ||||||
|  | 
 | ||||||
|  | 		flushInterval := Duration(500 * time.Millisecond) | ||||||
|  | 
 | ||||||
|  | 		Expect(into).To(Equal(&AlphaOptions{ | ||||||
|  | 			Upstreams: []Upstream{ | ||||||
|  | 				{ | ||||||
|  | 					ID:            "httpbin", | ||||||
|  | 					Path:          "/", | ||||||
|  | 					URI:           "http://httpbin", | ||||||
|  | 					FlushInterval: &flushInterval, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			InjectRequestHeaders: []Header{ | ||||||
|  | 				{ | ||||||
|  | 					Name: "X-Forwarded-User", | ||||||
|  | 					Values: []HeaderValue{ | ||||||
|  | 						{ | ||||||
|  | 							ClaimSource: &ClaimSource{ | ||||||
|  | 								Claim: "user", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			InjectResponseHeaders: []Header{ | ||||||
|  | 				{ | ||||||
|  | 					Name: "X-Secret", | ||||||
|  | 					Values: []HeaderValue{ | ||||||
|  | 						{ | ||||||
|  | 							SecretSource: &SecretSource{ | ||||||
|  | 								Value: []byte("secret"), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		})) | ||||||
|  | 	}) | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | @ -246,8 +246,6 @@ func NewFlagSet() *pflag.FlagSet { | ||||||
| 
 | 
 | ||||||
| 	flagSet.AddFlagSet(cookieFlagSet()) | 	flagSet.AddFlagSet(cookieFlagSet()) | ||||||
| 	flagSet.AddFlagSet(loggingFlagSet()) | 	flagSet.AddFlagSet(loggingFlagSet()) | ||||||
| 	flagSet.AddFlagSet(legacyUpstreamsFlagSet()) |  | ||||||
| 	flagSet.AddFlagSet(legacyHeadersFlagSet()) |  | ||||||
| 
 | 
 | ||||||
| 	return flagSet | 	return flagSet | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue