diff --git a/main.go b/main.go index a9def2f6..7142ae0b 100644 --- a/main.go +++ b/main.go @@ -70,7 +70,7 @@ func main() { func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { opts, err := loadLegacyOptions(config, extraFlags, args) if err != nil { - return nil, fmt.Errorf("couldn't load legacy options: %w", err) + return nil, fmt.Errorf("failed to load legacy options: %w", err) } if yamlConfig != "" { diff --git a/main_test.go b/main_test.go index 60b7eafd..709c6857 100644 --- a/main_test.go +++ b/main_test.go @@ -22,7 +22,7 @@ var _ = Describe("Configuration Loading Suite", func() { http_address="127.0.0.1:4180" upstreams="http://httpbin" set_basic_auth="true" -basic_auth_password="super-secret-password" +basic_auth_password="c3VwZXItc2VjcmV0LXBhc3N3b3Jk" client_id="oauth2-proxy" client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" ` @@ -45,7 +45,7 @@ injectRequestHeaders: claim: user prefix: "Basic " basicAuthPassword: - value: super-secret-password + value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk - name: X-Forwarded-Groups values: - claimSource: @@ -69,12 +69,12 @@ injectResponseHeaders: claim: user prefix: "Basic " basicAuthPassword: - value: super-secret-password + value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk server: bindAddress: "127.0.0.1:4180" providers: -- provider: google - ID: google=oauth2-proxy +- id: google=oauth2-proxy + provider: google clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK clientID: oauth2-proxy azureConfig: @@ -139,7 +139,7 @@ redirect_url="http://localhost:4180/oauth2/callback" Claim: "user", Prefix: "Basic ", BasicAuthPassword: &options.SecretSource{ - Value: []byte("super-secret-password"), + Value: []byte("c3VwZXItc2VjcmV0LXBhc3N3b3Jk"), }, }, }, @@ -248,7 +248,7 @@ redirect_url="http://localhost:4180/oauth2/callback" 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: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), + expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), }), Entry("with bad alpha configuration", loadConfigurationTableInput{ configContent: testCoreConfig, @@ -260,7 +260,7 @@ redirect_url="http://localhost:4180/oauth2/callback" 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: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), + expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), }), ) }) diff --git a/pkg/apis/options/header.go b/pkg/apis/options/header.go index b585fceb..976e4f0a 100644 --- a/pkg/apis/options/header.go +++ b/pkg/apis/options/header.go @@ -11,7 +11,7 @@ type Header struct { // should be preserved for the request to the upstream server. // This option only applies to injected request headers. // Defaults to false (headers that match this header will be stripped). - PreserveRequestValue bool `yaml:"preserveRequestValue,omitempty"` + PreserveRequestValue bool `yaml:"preserveRequestValue"` // Values contains the desired values for this header Values []HeaderValue `yaml:"values,omitempty"` diff --git a/pkg/apis/options/duration.go b/pkg/apis/options/hooks.go similarity index 78% rename from pkg/apis/options/duration.go rename to pkg/apis/options/hooks.go index 15f8776d..79653ecb 100644 --- a/pkg/apis/options/duration.go +++ b/pkg/apis/options/hooks.go @@ -41,3 +41,17 @@ func toDurationHookFunc() mapstructure.DecodeHookFunc { } } } + +// StringToBytesHookFunc returns a DecodeHookFunc that converts string to []byte. +func stringToBytesHookFunc() mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() == reflect.String && t == reflect.TypeOf([]byte{}) { + return []byte(data.(string)), nil + } + return data, nil + } +} diff --git a/pkg/apis/options/duration_test.go b/pkg/apis/options/hooks_test.go similarity index 83% rename from pkg/apis/options/duration_test.go rename to pkg/apis/options/hooks_test.go index 63f203a8..99514a7c 100644 --- a/pkg/apis/options/duration_test.go +++ b/pkg/apis/options/hooks_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func TestDecode(t *testing.T) { +func TestToDurationHook(t *testing.T) { type result struct { Duration time.Duration `yaml:"duration"` } @@ -80,3 +80,17 @@ func TestDecode(t *testing.T) { }) } } + +func TestStringToBytesHook(t *testing.T) { + var result struct { + Value []byte `yaml:"value"` + } + + if err := Decode(map[string]interface{}{"value": "hello-world"}, &result); err != nil { + t.Fatal(err) + } + + if string(result.Value) != "hello-world" { + t.Errorf("expected %q, got %q", "hello-world", string(result.Value)) + } +} diff --git a/pkg/apis/options/load.go b/pkg/apis/options/load.go index af7f76d9..fdd20c7a 100644 --- a/pkg/apis/options/load.go +++ b/pkg/apis/options/load.go @@ -89,7 +89,10 @@ func LoadYAML(configFileName string, opts interface{}) error { // - An error if decoding fails or if there are unmapped keys. func Decode(input interface{}, result interface{}) error { decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()), + DecodeHook: mapstructure.ComposeDecodeHookFunc( + toDurationHookFunc(), + stringToBytesHookFunc(), + ), Metadata: nil, // Don't track any metadata Result: result, // Decode the result into the prefilled options TagName: "yaml", // Parse all fields that use the json tag diff --git a/pkg/apis/options/load_test.go b/pkg/apis/options/load_test.go index 29d6511e..42083f76 100644 --- a/pkg/apis/options/load_test.go +++ b/pkg/apis/options/load_test.go @@ -155,7 +155,7 @@ var _ = Describe("Load", func() { } err := Load(configFileName, flagSet, input) if o.expectedErr != nil { - Expect(err).To(MatchError(o.expectedErr.Error())) + Expect(err).To(MatchError(ContainSubstring(o.expectedErr.Error()))) } else { Expect(err).ToNot(HaveOccurred()) } @@ -471,7 +471,7 @@ sub: 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.TestOptionSubStruct.StringSliceOption of type []string"), + expectedErr: errors.New("error decoding config: decoding failed due to the following error(s):\n\n'stringSliceOption' source data must be an array or slice, got string"), }), Entry("with a config file containing environment variable references", loadYAMLTableInput{ configFile: []byte("stringOption: ${TESTUSER}"), diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go index f431f48c..904fd0ac 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -67,7 +67,7 @@ type Provider struct { CAFiles []string `yaml:"caFiles,omitempty"` // UseSystemTrustStore determines if your custom CA files and the system trust store are used // If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files. - UseSystemTrustStore bool `yaml:"useSystemTrustStore,omitempty"` + UseSystemTrustStore bool `yaml:"useSystemTrustStore"` // LoginURL is the authentication endpoint LoginURL string `yaml:"loginURL,omitempty"` // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL @@ -80,7 +80,7 @@ type Provider struct { ProfileURL string `yaml:"profileURL,omitempty"` // SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token // default set to 'false' - SkipClaimsFromProfileURL bool `yaml:"skipClaimsFromProfileURL,omitempty"` + SkipClaimsFromProfileURL bool `yaml:"skipClaimsFromProfileURL"` // ProtectedResource is the resource that is protected (Azure AD and ADFS only) ProtectedResource string `yaml:"resource,omitempty"` // ValidateURL is the access token validation endpoint @@ -181,13 +181,13 @@ type MicrosoftEntraIDOptions struct { // FederatedTokenAuth enable oAuth2 client authentication with federated token projected // by Entra Workload Identity plugin, instead of client secret. - FederatedTokenAuth bool `yaml:"federatedTokenAuth,omitempty"` + FederatedTokenAuth bool `yaml:"federatedTokenAuth"` } type ADFSOptions struct { // Skip adding the scope parameter in login request // Default value is 'false' - SkipScope bool `yaml:"skipScope,omitempty"` + SkipScope bool `yaml:"skipScope"` } type BitbucketOptions struct { @@ -227,7 +227,7 @@ type GoogleOptions struct { // ServiceAccountJSON is the path to the service account json credentials ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"` // UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON - UseApplicationDefaultCredentials bool `yaml:"useApplicationDefaultCredentials,omitempty"` + UseApplicationDefaultCredentials bool `yaml:"useApplicationDefaultCredentials"` // TargetPrincipal is the Google Service Account used for Application Default Credentials TargetPrincipal string `yaml:"targetPrincipal,omitempty"` } @@ -250,7 +250,7 @@ type OIDCOptions struct { InsecureSkipNonce bool `yaml:"insecureSkipNonce"` // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints // default set to 'false' - SkipDiscovery bool `yaml:"skipDiscovery,omitempty"` + SkipDiscovery bool `yaml:"skipDiscovery"` // JwksURL is the OpenID Connect JWKS URL // eg: https://www.googleapis.com/oauth2/v3/certs JwksURL string `yaml:"jwksURL,omitempty"` diff --git a/pkg/apis/options/upstreams.go b/pkg/apis/options/upstreams.go index b32cc27d..2b90e632 100644 --- a/pkg/apis/options/upstreams.go +++ b/pkg/apis/options/upstreams.go @@ -14,7 +14,7 @@ const ( type UpstreamConfig struct { // ProxyRawPath will pass the raw url path to upstream allowing for urls // like: "/%2F/" which would otherwise be redirected to "/" - ProxyRawPath bool `yaml:"proxyRawPath,omitempty"` + ProxyRawPath bool `yaml:"proxyRawPath"` // Upstreams represents the configuration for the upstream servers. // Requests will be proxied to this upstream if the path matches the request path. @@ -64,13 +64,13 @@ type Upstream struct { // This option is insecure and will allow potential Man-In-The-Middle attacks // between OAuth2 Proxy and the upstream server. // Defaults to false. - InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"` + InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify"` // Static will make all requests to this upstream have a static response. // The response will have a body of "Authenticated" and a response code // matching StaticCode. // If StaticCode is not set, the response will return a 200 response. - Static bool `yaml:"static,omitempty"` + Static bool `yaml:"static"` // StaticCode determines the response code for the Static response. // This option can only be used with Static enabled. @@ -84,11 +84,11 @@ type Upstream struct { // PassHostHeader determines whether the request host header should be proxied // to the upstream server. // Defaults to true. - PassHostHeader *bool `yaml:"passHostHeader,omitempty"` + PassHostHeader *bool `yaml:"passHostHeader"` // ProxyWebSockets enables proxying of websockets to upstream servers // Defaults to true. - ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"` + ProxyWebSockets *bool `yaml:"proxyWebSockets"` // Timeout is the maximum duration the server will wait for a response from the upstream server. // Defaults to 30 seconds.