From 31a4c34726da114c9185afa60f65107263dd208f Mon Sep 17 00:00:00 2001 From: tuunit Date: Sat, 4 May 2024 16:41:54 +0200 Subject: [PATCH 01/12] introduce mapstructure decoder for yaml parsing remove color output in tests for better readability in github actions bugfix: remove google as default provider for alpha options fix conversion flow for toml to yaml revert ginkgo color deactivation revert claim- and secret source back to pointers regenerate alpha config --- .golangci.yml | 2 +- docs/docs/configuration/alpha_config.md | 18 +--- main.go | 35 +++++--- main_test.go | 39 +++++---- oauthproxy_test.go | 6 +- pkg/apis/options/alpha_options.go | 25 ++++-- pkg/apis/options/common.go | 63 -------------- pkg/apis/options/duration.go | 43 +++++++++ pkg/apis/options/header.go | 4 +- pkg/apis/options/legacy_options.go | 6 +- pkg/apis/options/legacy_options_test.go | 12 +-- pkg/apis/options/load.go | 111 +++++++++++++++--------- pkg/apis/options/load_test.go | 24 ++--- pkg/apis/options/providers.go | 6 +- pkg/apis/options/secret_source.go | 14 +++ pkg/apis/options/upstreams.go | 4 +- pkg/apis/options/util/util.go | 2 +- pkg/apis/options/util/util_test.go | 2 +- pkg/header/injector_test.go | 16 ++-- pkg/http/http_suite_test.go | 12 +-- pkg/http/server_test.go | 8 +- pkg/middleware/headers_test.go | 8 +- pkg/upstream/http.go | 4 +- pkg/upstream/http_test.go | 24 ++--- pkg/validation/common_test.go | 4 +- pkg/validation/header.go | 2 - pkg/validation/header_test.go | 4 +- pkg/validation/upstreams.go | 2 +- pkg/validation/upstreams_test.go | 2 +- 29 files changed, 269 insertions(+), 233 deletions(-) delete mode 100644 pkg/apis/options/common.go create mode 100644 pkg/apis/options/duration.go create mode 100644 pkg/apis/options/secret_source.go diff --git a/.golangci.yml b/.golangci.yml index edab12d0..62e1df8e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,7 +37,7 @@ linters: - linters: - revive path: _test\.go - text: 'dot-imports:' + text: "dot-imports:" # # If we have tests in shared test folders, these can be less strictly linted - linters: - bodyclose diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 28645ceb..dcfb9648 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -204,16 +204,6 @@ ClaimSource allows loading a header value from a claim within the session | `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | | `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | -### Duration -#### (`string` alias) - -(**Appears on:** [Upstream](#upstream)) - -Duration is as string representation of a period of time. -A duration string is a is a possibly signed sequence of decimal numbers, -each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". -Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". - ### GitHubOptions (**Appears on:** [Provider](#provider)) @@ -275,7 +265,7 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _[]byte_ | Value expects a base64 encoded string value. | +| `value` | _string_ | Value expects a base64 encoded string value. | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | | `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | @@ -487,7 +477,7 @@ Only one source within the struct should be defined at any time. | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _[]byte_ | Value expects a base64 encoded string value. | +| `value` | _string_ | Value expects a base64 encoded string value. | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | @@ -547,10 +537,10 @@ Requests will be proxied to this upstream if the path matches the request path. | `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
This option is insecure and will allow potential Man-In-The-Middle attacks
between OAuth2 Proxy and the upstream server.
Defaults to false. | | `static` | _bool_ | 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. | | `staticCode` | _int_ | StaticCode determines the response code for the Static response.
This option can only be used with Static enabled. | -| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | +| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | | `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | | `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | -| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | +| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | | `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | ### UpstreamConfig diff --git a/main.go b/main.go index cf7e964c..d9fc406b 100644 --- a/main.go +++ b/main.go @@ -67,12 +67,17 @@ func main() { // 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) +func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { + opts, err := loadLegacyOptions(config, extraFlags, args) + if err != nil { + return nil, err } - return loadLegacyOptions(config, extraFlags, args) + + if yamlConfig != "" { + 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 loadYamlOptions(yamlConfig, config, extraFlags, args) + } + return opts, err } // loadLegacyOptions loads the old toml options using the legacy flagset @@ -97,17 +102,17 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string) return opts, nil } -// loadAlphaOptions loads the old style config excluding options converted to +// loadYamlOptions 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) { +func loadYamlOptions(yamlConfig, config 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 { + alphaOpts := options.NewAlphaOptions(opts) + if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil { return nil, fmt.Errorf("failed to load alpha options: %v", err) } @@ -137,10 +142,16 @@ func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*opti // printConvertedConfig extracts alpha options from the loaded configuration // and renders these to stdout in YAML format. func printConvertedConfig(opts *options.Options) error { - alphaConfig := &options.AlphaOptions{} - alphaConfig.ExtractFrom(opts) + alphaConfig := options.NewAlphaOptions(opts) - data, err := yaml.Marshal(alphaConfig) + // Generic interface for loading arbitrary yaml structure + var buffer map[string]interface{} + + if err := options.Decode(alphaConfig, &buffer); err != nil { + return fmt.Errorf("unable to decode alpha config into interface: %w", err) + } + + data, err := yaml.Marshal(buffer) if err != nil { return fmt.Errorf("unable to marshal config: %v", err) } diff --git a/main_test.go b/main_test.go index 8e40fe7f..a6ea83c2 100644 --- a/main_test.go +++ b/main_test.go @@ -43,29 +43,35 @@ upstreamConfig: injectRequestHeaders: - name: Authorization values: - - claim: user - prefix: "Basic " - basicAuthPassword: - value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk + - claimSource: + claim: user + prefix: "Basic " + basicAuthPassword: + value: super-secret-password - name: X-Forwarded-Groups values: - - claim: groups + - claimSource: + claim: groups - name: X-Forwarded-User values: - - claim: user + - claimSource: + claim: user - name: X-Forwarded-Email values: - - claim: email + - claimSource: + claim: email - name: X-Forwarded-Preferred-Username values: - - claim: preferred_username + - claimSource: + claim: preferred_username injectResponseHeaders: - name: Authorization values: - - claim: user - prefix: "Basic " - basicAuthPassword: - value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk + - claimSource: + claim: user + prefix: "Basic " + basicAuthPassword: + value: super-secret-password server: bindAddress: "127.0.0.1:4180" providers: @@ -100,9 +106,8 @@ redirect_url="http://localhost:4180/oauth2/callback" return &b } - durationPtr := func(d time.Duration) *options.Duration { - du := options.Duration(d) - return &du + durationPtr := func(d time.Duration) *time.Duration { + return &d } testExpectedOptions := func() *options.Options { @@ -136,7 +141,7 @@ redirect_url="http://localhost:4180/oauth2/callback" Claim: "user", Prefix: "Basic ", BasicAuthPassword: &options.SecretSource{ - Value: []byte("super-secret-password"), + Value: "super-secret-password", }, }, }, @@ -226,7 +231,7 @@ redirect_url="http://localhost:4180/oauth2/callback" opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args) if in.expectedErr != nil { - Expect(err).To(MatchError(in.expectedErr.Error())) + Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error()))) } else { Expect(err).ToNot(HaveOccurred()) } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 488b8cea..7b495bff 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) { ClaimSource: &options.ClaimSource{ Claim: "email", BasicAuthPassword: &options.SecretSource{ - Value: []byte(basicAuthPassword), + Value: basicAuthPassword, }, }, }, @@ -1282,7 +1282,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte("This is a secure password"), + Value: "This is a secure password", }, }, }, @@ -2044,7 +2044,7 @@ func baseTestOptions() *options.Options { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))), + Value: base64.StdEncoding.EncodeToString([]byte("This is a secure password")), }, }, }, diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index a438518c..278db401 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -47,15 +47,11 @@ type AlphaOptions struct { Providers Providers `json:"providers,omitempty"` } -// MergeInto replaces alpha options in the Options struct with the values -// from the AlphaOptions -func (a *AlphaOptions) MergeInto(opts *Options) { - opts.UpstreamServers = a.UpstreamConfig - opts.InjectRequestHeaders = a.InjectRequestHeaders - opts.InjectResponseHeaders = a.InjectResponseHeaders - opts.Server = a.Server - opts.MetricsServer = a.MetricsServer - opts.Providers = a.Providers +// Initialize alpha options with default values and settings of the core options +func NewAlphaOptions(opts *Options) *AlphaOptions { + aOpts := &AlphaOptions{} + aOpts.ExtractFrom(opts) + return aOpts } // ExtractFrom populates the fields in the AlphaOptions with the values from @@ -68,3 +64,14 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) { a.MetricsServer = opts.MetricsServer a.Providers = opts.Providers } + +// MergeInto replaces alpha options in the Options struct with the values +// from the AlphaOptions +func (a *AlphaOptions) MergeInto(opts *Options) { + opts.UpstreamServers = a.UpstreamConfig + opts.InjectRequestHeaders = a.InjectRequestHeaders + opts.InjectResponseHeaders = a.InjectResponseHeaders + opts.Server = a.Server + opts.MetricsServer = a.MetricsServer + opts.Providers = a.Providers +} diff --git a/pkg/apis/options/common.go b/pkg/apis/options/common.go deleted file mode 100644 index 88d24d82..00000000 --- a/pkg/apis/options/common.go +++ /dev/null @@ -1,63 +0,0 @@ -package options - -import ( - "fmt" - "strconv" - "time" -) - -// SecretSource references an individual secret value. -// Only one source within the struct should be defined at any time. -type SecretSource struct { - // Value expects a base64 encoded string value. - Value []byte `json:"value,omitempty"` - - // FromEnv expects the name of an environment variable. - FromEnv string `json:"fromEnv,omitempty"` - - // FromFile expects a path to a file containing the secret value. - FromFile string `json:"fromFile,omitempty"` -} - -// Duration is an alias for time.Duration so that we can ensure the marshalling -// and unmarshalling of string durations is done as users expect. -// Intentional blank line below to keep this first part of the comment out of -// any generated references. - -// Duration is as string representation of a period of time. -// A duration string is a is a possibly signed sequence of decimal numbers, -// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". -// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -// +reference-gen:alias-name=string -type Duration time.Duration - -// UnmarshalJSON parses the duration string and sets the value of duration -// to the value of the duration string. -func (d *Duration) UnmarshalJSON(data []byte) error { - input := string(data) - if unquoted, err := strconv.Unquote(input); err == nil { - input = unquoted - } - - du, err := time.ParseDuration(input) - if err != nil { - return err - } - *d = Duration(du) - return nil -} - -// MarshalJSON ensures that when the string is marshalled to JSON as a human -// readable string. -func (d *Duration) MarshalJSON() ([]byte, error) { - dStr := fmt.Sprintf("%q", d.Duration().String()) - return []byte(dStr), nil -} - -// Duration returns the time.Duration version of this Duration -func (d *Duration) Duration() time.Duration { - if d == nil { - return time.Duration(0) - } - return time.Duration(*d) -} diff --git a/pkg/apis/options/duration.go b/pkg/apis/options/duration.go new file mode 100644 index 00000000..da13d96e --- /dev/null +++ b/pkg/apis/options/duration.go @@ -0,0 +1,43 @@ +package options + +import ( + "reflect" + "time" + + "github.com/mitchellh/mapstructure" +) + +// Duration is an alias for time.Duration so that we can ensure the marshalling +// and unmarshalling of string durations is done as users expect. +// Intentional blank line below to keep this first part of the comment out of +// any generated references. + +// Duration is as string representation of a period of time. +// A duration string is a is a possibly signed sequence of decimal numbers, +// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + +// Conversion from string or floating point to golang duration type +// This way floating points will be converted to seconds and strings +// of type 3s or 5m will be parsed with time.ParseDuration +func toDurationHookFunc() mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if t != reflect.TypeOf(time.Duration(0)) { + return data, nil + } + + switch f.Kind() { + case reflect.String: + return time.ParseDuration(data.(string)) + case reflect.Float64: + return time.Duration(data.(float64) * float64(time.Second)), nil + case reflect.Int64: + return time.Duration(data.(int64)), nil + default: + return data, nil + } + } +} diff --git a/pkg/apis/options/header.go b/pkg/apis/options/header.go index 90e6445c..a7e10c44 100644 --- a/pkg/apis/options/header.go +++ b/pkg/apis/options/header.go @@ -21,10 +21,10 @@ type Header struct { // make up the header value type HeaderValue struct { // Allow users to load the value from a secret source - *SecretSource `json:",omitempty"` + *SecretSource `json:"secretSource,omitempty"` // Allow users to load the value from a session claim - *ClaimSource `json:",omitempty"` + *ClaimSource `json:"claimSource,omitempty"` } // ClaimSource allows loading a header value from a claim within the session diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index 12975225..db73f910 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -136,8 +136,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { u.Path = "/" } - flushInterval := Duration(l.FlushInterval) - timeout := Duration(l.Timeout) + flushInterval := l.FlushInterval + timeout := l.Timeout upstream := Upstream{ ID: u.Path, Path: u.Path, @@ -294,7 +294,7 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header Claim: claim, Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: []byte(basicAuthPassword), + Value: basicAuthPassword, }, }, }, diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go index 9481cf95..c5f0e4da 100644 --- a/pkg/apis/options/legacy_options_test.go +++ b/pkg/apis/options/legacy_options_test.go @@ -15,8 +15,8 @@ var _ = Describe("Legacy Options", func() { legacyOpts := NewLegacyOptions() // Set upstreams and related options to test their conversion - flushInterval := Duration(5 * time.Second) - timeout := Duration(5 * time.Second) + flushInterval := 5 * time.Second + timeout := 5 * time.Second legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval) legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout) legacyOpts.LegacyUpstreams.PassHostHeader = true @@ -147,8 +147,8 @@ var _ = Describe("Legacy Options", func() { skipVerify := true passHostHeader := false proxyWebSockets := true - flushInterval := Duration(5 * time.Second) - timeout := Duration(5 * time.Second) + flushInterval := 5 * time.Second + timeout := 5 * time.Second disableKeepAlives := true // Test cases and expected outcomes @@ -369,7 +369,7 @@ var _ = Describe("Legacy Options", func() { Claim: "user", Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: []byte(basicAuthSecret), + Value: basicAuthSecret, }, }, }, @@ -409,7 +409,7 @@ var _ = Describe("Legacy Options", func() { Claim: "email", Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: []byte(basicAuthSecret), + Value: basicAuthSecret, }, }, }, diff --git a/pkg/apis/options/load.go b/pkg/apis/options/load.go index c302e8e7..22e775a8 100644 --- a/pkg/apis/options/load.go +++ b/pkg/apis/options/load.go @@ -55,6 +55,76 @@ func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error return nil } +// LoadYAML will load a YAML based configuration file into the options interface provided. +func LoadYAML(configFileName string, opts interface{}) error { + buffer, err := loadAndSubstituteEnvs(configFileName) + if err != nil { + return err + } + + // Generic interface for loading arbitrary yaml structure + var intermediate map[string]interface{} + + if err := yaml.Unmarshal(buffer, &intermediate); err != nil { + return fmt.Errorf("error unmarshalling config: %w", err) + } + + return Decode(intermediate, opts) +} + +func Decode(input interface{}, result interface{}) error { + // Using mapstructure to decode arbitrary yaml structure into options and + // merge with existing values instead of overwriting everything. This is especially + // important as we have a lot of default values for boolean which are supposed to be + // true by default. Normally by just parsing through yaml all booleans that aren't + // referenced in the config file would be parsed as false and we cannot identify after + // the fact if they have been explicitly set to false or have not been referenced. + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()), + Metadata: nil, // Don't track any metadata + Result: result, // Decode the result into the prefilled options + TagName: "json", // Parse all fields that use the yaml tag + ZeroFields: false, // Don't clean the default values from the result map (options) + ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields + IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml + }) + + if err != nil { + return fmt.Errorf("error creating decoder for config: %w", err) + } + + if err := decoder.Decode(input); err != nil { + return fmt.Errorf("error decoding config: %w", err) + } + + return nil +} + +// loadAndSubstituteEnvs reads the yaml config into a generic byte buffer and +// substitute env references +func loadAndSubstituteEnvs(configFileName string) ([]byte, error) { + if configFileName == "" { + return nil, errors.New("no configuration file provided") + } + + unparsedBuffer, err := os.ReadFile(configFileName) + if err != nil { + return nil, fmt.Errorf("unable to load config file: %w", err) + } + + modifiedBuffer, err := normalizeSubstitution(unparsedBuffer) + if err != nil { + return nil, fmt.Errorf("error normalizing substitution string : %w", err) + } + + buffer, err := envsubst.Bytes(modifiedBuffer) + if err != nil { + return nil, fmt.Errorf("error in substituting env variables : %w", err) + } + + return buffer, 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. @@ -140,47 +210,6 @@ func isUnexported(name string) bool { 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 { - buffer, err := loadAndParseYaml(configFileName) - if err != nil { - return err - } - - // UnmarshalStrict will return an error if the config includes options that are - // not mapped to fields of the into struct - if err := yaml.UnmarshalStrict(buffer, into, yaml.DisallowUnknownFields); err != nil { - return fmt.Errorf("error unmarshalling config: %w", err) - } - - return nil -} - -// loadAndParseYaml reads the config from the filesystem and -// execute the environment variable substitution -func loadAndParseYaml(configFileName string) ([]byte, error) { - if configFileName == "" { - return nil, errors.New("no configuration file provided") - } - - unparsedBuffer, err := os.ReadFile(configFileName) - if err != nil { - return nil, fmt.Errorf("unable to load config file: %w", err) - } - - modifiedBuffer, err := normalizeSubstitution(unparsedBuffer) - if err != nil { - return nil, fmt.Errorf("error normalizing substitution string : %w", err) - } - - buffer, err := envsubst.Bytes(modifiedBuffer) - if err != nil { - return nil, fmt.Errorf("error in substituting env variables : %w", err) - } - - return buffer, nil -} - // normalizeSubstitution normalizes dollar signs ($) with numerals like // $1 or $2 properly by correctly escaping them func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) { diff --git a/pkg/apis/options/load_test.go b/pkg/apis/options/load_test.go index 06123c37..6f327a22 100644 --- a/pkg/apis/options/load_test.go +++ b/pkg/apis/options/load_test.go @@ -355,15 +355,15 @@ var _ = Describe("Load", func() { var _ = Describe("LoadYAML", func() { Context("with a testOptions structure", func() { type TestOptionSubStruct struct { - StringSliceOption []string `yaml:"stringSliceOption,omitempty"` + StringSliceOption []string `json:"stringSliceOption,omitempty"` } type TestOptions struct { - StringOption string `yaml:"stringOption,omitempty"` - Sub TestOptionSubStruct `yaml:"sub,omitempty"` + StringOption string `json:"stringOption,omitempty"` + Sub TestOptionSubStruct `json:"sub,omitempty"` // Check that embedded fields can be unmarshalled - TestOptionSubStruct `yaml:",inline,squash"` + TestOptionSubStruct `json:",inline,squash"` } var testOptionsConfigBytesFull = []byte(` @@ -416,7 +416,7 @@ sub: err := LoadYAML(configFileName, input) if in.expectedErr != nil { - Expect(err).To(MatchError(in.expectedErr.Error())) + Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error()))) } else { Expect(err).ToNot(HaveOccurred()) } @@ -459,13 +459,13 @@ sub: StringSliceOption: []string{"a", "b", "c"}, }, }, - expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""), + expectedErr: errors.New("has invalid keys: 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"), + expectedErr: errors.New("'stringOption' expected type 'string', got unconvertible type"), }), Entry("with an incorrect type for an array field", loadYAMLTableInput{ configFile: []byte(`stringSliceOption: "a"`), @@ -526,11 +526,13 @@ upstreamConfig: injectRequestHeaders: - name: X-Forwarded-User values: - - claim: user + - claimSource: + claim: user injectResponseHeaders: - name: X-Secret values: - - value: c2VjcmV0 + - secretSource: + value: secret `) By("Creating a config file") @@ -548,7 +550,7 @@ injectResponseHeaders: into := &AlphaOptions{} Expect(LoadYAML(configFileName, into)).To(Succeed()) - flushInterval := Duration(500 * time.Millisecond) + flushInterval := 500 * time.Millisecond Expect(into).To(Equal(&AlphaOptions{ UpstreamConfig: UpstreamConfig{ @@ -579,7 +581,7 @@ injectResponseHeaders: Values: []HeaderValue{ { SecretSource: &SecretSource{ - Value: []byte("secret"), + Value: "secret", }, }, }, diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go index 0f254575..c94b6b92 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -238,16 +238,16 @@ type OIDCOptions struct { IssuerURL string `json:"issuerURL,omitempty"` // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified // default set to 'false' - InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"` + InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail"` // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL // default set to 'false' - InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"` + InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification"` // InsecureSkipNonce skips verifying the ID Token's nonce claim that must match // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked // after the initial OAuth redeem & subsequent token refreshes. // default set to 'true' // Warning: In a future release, this will change to 'false' by default for enhanced security. - InsecureSkipNonce bool `json:"insecureSkipNonce,omitempty"` + InsecureSkipNonce bool `json:"insecureSkipNonce"` // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints // default set to 'false' SkipDiscovery bool `json:"skipDiscovery,omitempty"` diff --git a/pkg/apis/options/secret_source.go b/pkg/apis/options/secret_source.go new file mode 100644 index 00000000..2be4d890 --- /dev/null +++ b/pkg/apis/options/secret_source.go @@ -0,0 +1,14 @@ +package options + +// SecretSource references an individual secret value. +// Only one source within the struct should be defined at any time. +type SecretSource struct { + // Value expects a base64 encoded string value. + Value string `json:"value,omitempty"` + + // FromEnv expects the name of an environment variable. + FromEnv string `json:"fromEnv,omitempty"` + + // FromFile expects a path to a file containing the secret value. + FromFile string `json:"fromFile,omitempty"` +} diff --git a/pkg/apis/options/upstreams.go b/pkg/apis/options/upstreams.go index b3c7195f..1002ae07 100644 --- a/pkg/apis/options/upstreams.go +++ b/pkg/apis/options/upstreams.go @@ -79,7 +79,7 @@ type Upstream struct { // FlushInterval is the period between flushing the response buffer when // streaming response from the upstream. // Defaults to 1 second. - FlushInterval *Duration `json:"flushInterval,omitempty"` + FlushInterval *time.Duration `json:"flushInterval,omitempty"` // PassHostHeader determines whether the request host header should be proxied // to the upstream server. @@ -92,7 +92,7 @@ type Upstream struct { // Timeout is the maximum duration the server will wait for a response from the upstream server. // Defaults to 30 seconds. - Timeout *Duration `json:"timeout,omitempty"` + Timeout *time.Duration `json:"timeout,omitempty"` // DisableKeepAlives disables HTTP keep-alive connections to the upstream server. // Defaults to false. diff --git a/pkg/apis/options/util/util.go b/pkg/apis/options/util/util.go index 03f0a134..794a6e91 100644 --- a/pkg/apis/options/util/util.go +++ b/pkg/apis/options/util/util.go @@ -11,7 +11,7 @@ import ( func GetSecretValue(source *options.SecretSource) ([]byte, error) { switch { case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "": - return source.Value, nil + return []byte(source.Value), nil case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "": return []byte(os.Getenv(source.FromEnv)), nil case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "": diff --git a/pkg/apis/options/util/util_test.go b/pkg/apis/options/util/util_test.go index e84db1ec..5c4bfa6d 100644 --- a/pkg/apis/options/util/util_test.go +++ b/pkg/apis/options/util/util_test.go @@ -31,7 +31,7 @@ var _ = Describe("GetSecretValue", func() { It("returns the correct value from the string value", func() { value, err := GetSecretValue(&options.SecretSource{ - Value: []byte("secret-value-1"), + Value: "secret-value-1", }) Expect(err).ToNot(HaveOccurred()) Expect(string(value)).To(Equal("secret-value-1")) diff --git a/pkg/header/injector_test.go b/pkg/header/injector_test.go index 25c276dc..bb37261d 100644 --- a/pkg/header/injector_test.go +++ b/pkg/header/injector_test.go @@ -55,7 +55,7 @@ var _ = Describe("Injector Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: []byte("super-secret"), + Value: "super-secret", }, }, }, @@ -199,7 +199,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte("basic-password"), + Value: "basic-password", }, }, }, @@ -227,7 +227,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), + Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), }, }, }, @@ -322,7 +322,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), + Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), FromEnv: "SECRET_ENV", }, }, @@ -348,7 +348,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte("basic-password"), + Value: "basic-password", }, }, }, @@ -379,17 +379,17 @@ var _ = Describe("Injector Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: []byte("major=1"), + Value: "major=1", }, }, { SecretSource: &options.SecretSource{ - Value: []byte("minor=2"), + Value: "minor=2", }, }, { SecretSource: &options.SecretSource{ - Value: []byte("patch=3"), + Value: "patch=3", }, }, }, diff --git a/pkg/http/http_suite_test.go b/pkg/http/http_suite_test.go index 19d4d3ff..219f26ea 100644 --- a/pkg/http/http_suite_test.go +++ b/pkg/http/http_suite_test.go @@ -48,10 +48,10 @@ var _ = BeforeSuite(func() { certOut := new(bytes.Buffer) Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed()) - ipv4CertDataSource.Value = certOut.Bytes() + ipv4CertDataSource.Value = certOut.String() keyOut := new(bytes.Buffer) Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed()) - ipv4KeyDataSource.Value = keyOut.Bytes() + ipv4KeyDataSource.Value = keyOut.String() }) By("Generating a ipv6 self-signed cert for TLS tests", func() { @@ -61,16 +61,16 @@ var _ = BeforeSuite(func() { certOut := new(bytes.Buffer) Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed()) - ipv6CertDataSource.Value = certOut.Bytes() + ipv6CertDataSource.Value = certOut.String() keyOut := new(bytes.Buffer) Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed()) - ipv6KeyDataSource.Value = keyOut.Bytes() + ipv6KeyDataSource.Value = keyOut.String() }) By("Setting up a http client", func() { - ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value) + ipv4cert, err := tls.X509KeyPair([]byte(ipv4CertDataSource.Value), []byte(ipv4KeyDataSource.Value)) Expect(err).ToNot(HaveOccurred()) - ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value) + ipv6cert, err := tls.X509KeyPair([]byte(ipv6CertDataSource.Value), []byte(ipv6KeyDataSource.Value)) Expect(err).ToNot(HaveOccurred()) ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0]) diff --git a/pkg/http/server_test.go b/pkg/http/server_test.go index 8dfa13af..6584b757 100644 --- a/pkg/http/server_test.go +++ b/pkg/http/server_test.go @@ -234,7 +234,7 @@ var _ = Describe("Server", func() { SecureBindAddress: "127.0.0.1:0", TLS: &options.TLS{ Key: &options.SecretSource{ - Value: []byte("invalid"), + Value: "invalid", }, Cert: &ipv4CertDataSource, }, @@ -250,7 +250,7 @@ var _ = Describe("Server", func() { TLS: &options.TLS{ Key: &ipv4KeyDataSource, Cert: &options.SecretSource{ - Value: []byte("invalid"), + Value: "invalid", }, }, }, @@ -506,7 +506,7 @@ var _ = Describe("Server", func() { SecureBindAddress: "[::1]:0", TLS: &options.TLS{ Key: &options.SecretSource{ - Value: []byte("invalid"), + Value: "invalid", }, Cert: &ipv6CertDataSource, }, @@ -523,7 +523,7 @@ var _ = Describe("Server", func() { TLS: &options.TLS{ Key: &ipv6KeyDataSource, Cert: &options.SecretSource{ - Value: []byte("invalid"), + Value: "invalid", }, }, }, diff --git a/pkg/middleware/headers_test.go b/pkg/middleware/headers_test.go index 06440eea..b1848ae0 100644 --- a/pkg/middleware/headers_test.go +++ b/pkg/middleware/headers_test.go @@ -188,7 +188,7 @@ var _ = Describe("Headers Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), + Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), FromEnv: "SECRET_ENV", }, }, @@ -260,7 +260,7 @@ var _ = Describe("Headers Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: []byte("_oauth2_proxy=ey123123123"), + Value: "_oauth2_proxy=ey123123123", }, }, }, @@ -270,7 +270,7 @@ var _ = Describe("Headers Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: []byte("oauth_user"), + Value: "oauth_user", }, }, }, @@ -416,7 +416,7 @@ var _ = Describe("Headers Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), + Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), FromEnv: "SECRET_ENV", }, }, diff --git a/pkg/upstream/http.go b/pkg/upstream/http.go index 7a0e6e84..d9d8f152 100644 --- a/pkg/upstream/http.go +++ b/pkg/upstream/http.go @@ -137,12 +137,12 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr // Change default duration for waiting for an upstream response if upstream.Timeout != nil { - transport.ResponseHeaderTimeout = upstream.Timeout.Duration() + transport.ResponseHeaderTimeout = *upstream.Timeout } // Configure options on the SingleHostReverseProxy if upstream.FlushInterval != nil { - proxy.FlushInterval = upstream.FlushInterval.Duration() + proxy.FlushInterval = *upstream.FlushInterval } else { proxy.FlushInterval = options.DefaultUpstreamFlushInterval } diff --git a/pkg/upstream/http_test.go b/pkg/upstream/http_test.go index 31476df0..df264c33 100644 --- a/pkg/upstream/http_test.go +++ b/pkg/upstream/http_test.go @@ -21,8 +21,8 @@ import ( ) var _ = Describe("HTTP Upstream Suite", func() { - defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval) - defaultTimeout := options.Duration(options.DefaultUpstreamTimeout) + defaultFlushInterval := options.DefaultUpstreamFlushInterval + defaultTimeout := options.DefaultUpstreamTimeout truth := true falsum := false @@ -57,9 +57,9 @@ var _ = Describe("HTTP Upstream Suite", func() { req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{}) rw := httptest.NewRecorder() - flush := options.Duration(1 * time.Second) + flush := 1 * time.Second - timeout := options.Duration(options.DefaultUpstreamTimeout) + timeout := options.DefaultUpstreamTimeout upstream := options.Upstream{ ID: in.id, @@ -373,11 +373,11 @@ var _ = Describe("HTTP Upstream Suite", func() { type newUpstreamTableInput struct { proxyWebSockets bool - flushInterval options.Duration + flushInterval time.Duration skipVerify bool sigData *options.SignatureData errorHandler func(http.ResponseWriter, *http.Request, error) - timeout options.Duration + timeout time.Duration disableKeepAlives bool } @@ -406,10 +406,10 @@ var _ = Describe("HTTP Upstream Suite", func() { proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy) Expect(ok).To(BeTrue()) - Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration())) + Expect(proxy.FlushInterval).To(Equal(in.flushInterval)) transport, ok := proxy.Transport.(*http.Transport) Expect(ok).To(BeTrue()) - Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout.Duration())) + Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout)) Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil)) if in.skipVerify { Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true)) @@ -428,7 +428,7 @@ var _ = Describe("HTTP Upstream Suite", func() { }), Entry("with a non standard flush interval", &newUpstreamTableInput{ proxyWebSockets: false, - flushInterval: options.Duration(5 * time.Second), + flushInterval: 5 * time.Second, skipVerify: false, sigData: nil, errorHandler: nil, @@ -466,7 +466,7 @@ var _ = Describe("HTTP Upstream Suite", func() { skipVerify: false, sigData: nil, errorHandler: nil, - timeout: options.Duration(5 * time.Second), + timeout: 5 * time.Second, }), Entry("with a DisableKeepAlives", &newUpstreamTableInput{ proxyWebSockets: false, @@ -483,8 +483,8 @@ var _ = Describe("HTTP Upstream Suite", func() { var proxyServer *httptest.Server BeforeEach(func() { - flush := options.Duration(1 * time.Second) - timeout := options.Duration(options.DefaultUpstreamTimeout) + flush := 1 * time.Second + timeout := options.DefaultUpstreamTimeout upstream := options.Upstream{ ID: "websocketProxy", PassHostHeader: &truth, diff --git a/pkg/validation/common_test.go b/pkg/validation/common_test.go index 9e873c35..bb7c2dd6 100644 --- a/pkg/validation/common_test.go +++ b/pkg/validation/common_test.go @@ -9,12 +9,12 @@ import ( ) var _ = Describe("Common", func() { - var validSecretSourceValue []byte + var validSecretSourceValue string const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV" var validSecretSourceFile string BeforeEach(func() { - validSecretSourceValue = []byte("This is a secret source value") + validSecretSourceValue = "This is a secret source value" Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed()) tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test") Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/validation/header.go b/pkg/validation/header.go index b1258144..713113f4 100644 --- a/pkg/validation/header.go +++ b/pkg/validation/header.go @@ -51,11 +51,9 @@ func validateHeaderValue(_ string, value options.HeaderValue) []string { func validateHeaderValueClaimSource(claim options.ClaimSource) []string { msgs := []string{} - if claim.Claim == "" { msgs = append(msgs, "claim should not be empty") } - if claim.BasicAuthPassword != nil { msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...) } diff --git a/pkg/validation/header_test.go b/pkg/validation/header_test.go index 2d9ef6dd..88849ea1 100644 --- a/pkg/validation/header_test.go +++ b/pkg/validation/header_test.go @@ -30,7 +30,7 @@ var _ = Describe("Headers", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))), + Value: base64.StdEncoding.EncodeToString([]byte("secret")), }, }, }, @@ -43,7 +43,7 @@ var _ = Describe("Headers", func() { ClaimSource: &options.ClaimSource{ Claim: "email", BasicAuthPassword: &options.SecretSource{ - Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))), + Value: base64.StdEncoding.EncodeToString([]byte("secret")), }, }, }, diff --git a/pkg/validation/upstreams.go b/pkg/validation/upstreams.go index bafed628..52facb4d 100644 --- a/pkg/validation/upstreams.go +++ b/pkg/validation/upstreams.go @@ -69,7 +69,7 @@ func validateStaticUpstream(upstream options.Upstream) []string { if upstream.InsecureSkipTLSVerify { msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID)) } - if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval { + if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval { msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID)) } if upstream.PassHostHeader != nil { diff --git a/pkg/validation/upstreams_test.go b/pkg/validation/upstreams_test.go index fe431d27..67991b76 100644 --- a/pkg/validation/upstreams_test.go +++ b/pkg/validation/upstreams_test.go @@ -14,7 +14,7 @@ var _ = Describe("Upstreams", func() { errStrings []string } - flushInterval := options.Duration(5 * time.Second) + flushInterval := 5 * time.Second staticCode200 := 200 truth := true From 202de3a05ee6f1a5a63d5c88ef7be7d1ac11a39b Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Sat, 1 Feb 2025 15:57:23 +0100 Subject: [PATCH 02/12] apply review suggestions --- main.go | 2 +- pkg/apis/options/load.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index d9fc406b..2a09b3ce 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, err + return nil, fmt.Errorf("couldn't load legacy options: %w", err) } if yamlConfig != "" { diff --git a/pkg/apis/options/load.go b/pkg/apis/options/load.go index 22e775a8..878199ed 100644 --- a/pkg/apis/options/load.go +++ b/pkg/apis/options/load.go @@ -83,12 +83,11 @@ func Decode(input interface{}, result interface{}) error { DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()), Metadata: nil, // Don't track any metadata Result: result, // Decode the result into the prefilled options - TagName: "json", // Parse all fields that use the yaml tag + TagName: "json", // Parse all fields that use the json tag ZeroFields: false, // Don't clean the default values from the result map (options) ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields - IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml + IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with json }) - if err != nil { return fmt.Errorf("error creating decoder for config: %w", err) } From 11f1b9eacd64427c8937c3540aa50e4690ec82b2 Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 9 Feb 2025 16:44:07 +0100 Subject: [PATCH 03/12] add duration test --- pkg/apis/options/common_test.go | 87 ------------------------------- pkg/apis/options/duration_test.go | 82 +++++++++++++++++++++++++++++ pkg/apis/options/load.go | 17 ++++-- 3 files changed, 95 insertions(+), 91 deletions(-) delete mode 100644 pkg/apis/options/common_test.go create mode 100644 pkg/apis/options/duration_test.go diff --git a/pkg/apis/options/common_test.go b/pkg/apis/options/common_test.go deleted file mode 100644 index db33a58b..00000000 --- a/pkg/apis/options/common_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package options - -import ( - "encoding/json" - "errors" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Common", func() { - Context("Duration", func() { - type marshalJSONTableInput struct { - duration Duration - expectedJSON string - } - - DescribeTable("MarshalJSON", - func(in marshalJSONTableInput) { - data, err := in.duration.MarshalJSON() - Expect(err).ToNot(HaveOccurred()) - Expect(string(data)).To(Equal(in.expectedJSON)) - - var d Duration - Expect(json.Unmarshal(data, &d)).To(Succeed()) - Expect(d).To(Equal(in.duration)) - }, - Entry("30 seconds", marshalJSONTableInput{ - duration: Duration(30 * time.Second), - expectedJSON: "\"30s\"", - }), - Entry("1 minute", marshalJSONTableInput{ - duration: Duration(1 * time.Minute), - expectedJSON: "\"1m0s\"", - }), - Entry("1 hour 15 minutes", marshalJSONTableInput{ - duration: Duration(75 * time.Minute), - expectedJSON: "\"1h15m0s\"", - }), - Entry("A zero Duration", marshalJSONTableInput{ - duration: Duration(0), - expectedJSON: "\"0s\"", - }), - ) - - type unmarshalJSONTableInput struct { - json string - expectedErr error - expectedDuration Duration - } - - DescribeTable("UnmarshalJSON", - func(in unmarshalJSONTableInput) { - // A duration must be initialised pointer before UnmarshalJSON will work. - zero := Duration(0) - d := &zero - - err := d.UnmarshalJSON([]byte(in.json)) - if in.expectedErr != nil { - Expect(err).To(MatchError(in.expectedErr.Error())) - } else { - Expect(err).ToNot(HaveOccurred()) - } - Expect(d).ToNot(BeNil()) - Expect(*d).To(Equal(in.expectedDuration)) - }, - Entry("1m", unmarshalJSONTableInput{ - json: "\"1m\"", - expectedDuration: Duration(1 * time.Minute), - }), - Entry("30s", unmarshalJSONTableInput{ - json: "\"30s\"", - expectedDuration: Duration(30 * time.Second), - }), - Entry("1h15m", unmarshalJSONTableInput{ - json: "\"1h15m\"", - expectedDuration: Duration(75 * time.Minute), - }), - Entry("am", unmarshalJSONTableInput{ - json: "\"am\"", - expectedErr: errors.New("time: invalid duration \"am\""), - expectedDuration: Duration(0), - }), - ) - }) -}) diff --git a/pkg/apis/options/duration_test.go b/pkg/apis/options/duration_test.go new file mode 100644 index 00000000..fc1e77e3 --- /dev/null +++ b/pkg/apis/options/duration_test.go @@ -0,0 +1,82 @@ +package options + +import ( + "testing" + "time" +) + +func TestDecode(t *testing.T) { + type result struct { + Duration time.Duration `json:"duration"` + } + + tests := []struct { + name string + input map[string]interface{} + out result + expected time.Duration + expectedErr bool + }{ + { + name: "Valid String Duration with single unit", + input: map[string]interface{}{"duration": "3s"}, + out: result{}, + expected: 3 * time.Second, + expectedErr: false, + }, + { + name: "Valid String Duration with multiple units", + input: map[string]interface{}{"duration": "1h20m30s"}, + out: result{}, + expected: 1*time.Hour + 20*time.Minute + 30*time.Second, + expectedErr: false, + }, + { + name: "Valid Float Duration", + input: map[string]interface{}{"duration": 2.5}, + out: result{}, + expected: 2500 * time.Millisecond, + expectedErr: false, + }, + { + name: "Valid Int64 Duration", + input: map[string]interface{}{"duration": int64(5000000000)}, + out: result{}, + expected: 5 * time.Second, + expectedErr: false, + }, + { + name: "Invalid String", + input: map[string]interface{}{"duration": "invalid"}, + out: result{}, + expected: 0, + expectedErr: true, + }, + { + name: "Unsupported Type", + input: map[string]interface{}{"duration": true}, + out: result{}, + expected: 0, + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result struct { + Duration time.Duration `json:"duration"` + } + + err := Decode(tt.input, &result) + if (err != nil) != tt.expectedErr { + t.Errorf("expected error: %v, got: %v", tt.expectedErr, err) + } + + if !tt.expectedErr { + if result.Duration != tt.expected { + t.Errorf("expected: %v, got: %v", tt.expected, result.Duration) + } + } + }) + } +} diff --git a/pkg/apis/options/load.go b/pkg/apis/options/load.go index 878199ed..2905af5f 100644 --- a/pkg/apis/options/load.go +++ b/pkg/apis/options/load.go @@ -69,16 +69,25 @@ func LoadYAML(configFileName string, opts interface{}) error { return fmt.Errorf("error unmarshalling config: %w", err) } - return Decode(intermediate, opts) -} - -func Decode(input interface{}, result interface{}) error { // Using mapstructure to decode arbitrary yaml structure into options and // merge with existing values instead of overwriting everything. This is especially // important as we have a lot of default values for boolean which are supposed to be // true by default. Normally by just parsing through yaml all booleans that aren't // referenced in the config file would be parsed as false and we cannot identify after // the fact if they have been explicitly set to false or have not been referenced. + return Decode(intermediate, opts) +} + +// Decode processes an input map and decodes it into a given struct while preserving default values. +// It ensures proper conversion of duration values from strings, floats, and int64 into time.Duration. +// +// Parameters: +// - input: A map[string]interface{} representing the input data. +// - result: A pointer to a struct where the decoded values will be stored. +// +// Returns: +// - 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()), Metadata: nil, // Don't track any metadata From 37019fc4cc98d3aaf55e208b403bbc793f8c608b Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 9 Feb 2025 17:23:03 +0100 Subject: [PATCH 04/12] use official upstream yaml library v3 --- docs/docs/configuration/alpha_config.md | 210 +++++++++++------------ go.mod | 14 +- go.sum | 91 ++-------- main.go | 2 +- main_test.go | 4 +- pkg/apis/options/alpha_options.go | 12 +- pkg/apis/options/duration_test.go | 4 +- pkg/apis/options/header.go | 16 +- pkg/apis/options/load.go | 4 +- pkg/apis/options/load_test.go | 10 +- pkg/apis/options/login_url_parameters.go | 10 +- pkg/apis/options/providers.go | 132 +++++++------- pkg/apis/options/secret_source.go | 6 +- pkg/apis/options/server.go | 14 +- pkg/apis/options/upstreams.go | 28 +-- pkg/requests/result_test.go | 4 +- 16 files changed, 246 insertions(+), 315 deletions(-) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index dcfb9648..617114b9 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -148,7 +148,7 @@ You must remove these options before starting OAuth2 Proxy with `--alpha-config` | Field | Type | Description | | ----- | ---- | ----------- | -| `skipScope` | _bool_ | Skip adding the scope parameter in login request
Default value is 'false' | +| `SkipScope` | _bool_ | Skip adding the scope parameter in login request
Default value is 'false' | ### AlphaOptions @@ -163,12 +163,12 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `upstreamConfig` | _[UpstreamConfig](#upstreamconfig)_ | UpstreamConfig is used to configure upstream servers.
Once a user is authenticated, requests to the server will be proxied to
these upstream servers based on the path mappings defined in this list. | -| `injectRequestHeaders` | _[[]Header](#header)_ | InjectRequestHeaders is used to configure headers that should be added
to requests to upstream servers.
Headers may source values from either the authenticated user's session
or from a static secret value. | -| `injectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added
to responses from the proxy.
This is typically used when using the proxy as an external authentication
provider in conjunction with another proxy such as NGINX and its
auth_request module.
Headers may source values from either the authenticated user's session
or from a static secret value. | -| `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | -| `metricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | -| `providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | +| `UpstreamConfig` | _[UpstreamConfig](#upstreamconfig)_ | UpstreamConfig is used to configure upstream servers.
Once a user is authenticated, requests to the server will be proxied to
these upstream servers based on the path mappings defined in this list. | +| `InjectRequestHeaders` | _[[]Header](#header)_ | InjectRequestHeaders is used to configure headers that should be added
to requests to upstream servers.
Headers may source values from either the authenticated user's session
or from a static secret value. | +| `InjectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added
to responses from the proxy.
This is typically used when using the proxy as an external authentication
provider in conjunction with another proxy such as NGINX and its
auth_request module.
Headers may source values from either the authenticated user's session
or from a static secret value. | +| `Server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | +| `MetricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | +| `Providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | ### AzureOptions @@ -178,8 +178,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `tenant` | _string_ | Tenant directs to a tenant-specific or common (tenant-independent) endpoint
Default value is 'common' | -| `graphGroupField` | _string_ | GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
Default value is 'id' | +| `Tenant` | _string_ | Tenant directs to a tenant-specific or common (tenant-independent) endpoint
Default value is 'common' | +| `GraphGroupField` | _string_ | GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
Default value is 'id' | ### BitbucketOptions @@ -189,8 +189,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `team` | _string_ | Team sets restrict logins to members of this team | -| `repository` | _string_ | Repository sets restrict logins to user with access to this repository | +| `Team` | _string_ | Team sets restrict logins to members of this team | +| `Repository` | _string_ | Repository sets restrict logins to user with access to this repository | ### ClaimSource @@ -200,9 +200,9 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | -| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | -| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | +| `Claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | +| `Prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | +| `BasicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | ### GitHubOptions @@ -212,11 +212,11 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `org` | _string_ | Org sets restrict logins to members of this organisation | -| `team` | _string_ | Team sets restrict logins to members of this team | -| `repo` | _string_ | Repo sets restrict logins to collaborators of this repository | -| `token` | _string_ | Token is the token to use when verifying repository collaborators
it must have push access to the repository | -| `users` | _[]string_ | Users allows users with these usernames to login
even if they do not belong to the specified org and team or collaborators | +| `Org` | _string_ | Org sets restrict logins to members of this organisation | +| `Team` | _string_ | Team sets restrict logins to members of this team | +| `Repo` | _string_ | Repo sets restrict logins to collaborators of this repository | +| `Token` | _string_ | Token is the token to use when verifying repository collaborators
it must have push access to the repository | +| `Users` | _[]string_ | Users allows users with these usernames to login
even if they do not belong to the specified org and team or collaborators | ### GitLabOptions @@ -226,8 +226,8 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `group` | _[]string_ | Group sets restrict logins to members of this group | -| `projects` | _[]string_ | Projects restricts logins to members of these projects | +| `Group` | _[]string_ | Group sets restrict logins to members of this group | +| `Projects` | _[]string_ | Projects restricts logins to members of these projects | ### GoogleOptions @@ -237,11 +237,11 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `group` | _[]string_ | Groups sets restrict logins to members of this Google group | -| `adminEmail` | _string_ | AdminEmail is the Google admin to impersonate for api calls | -| `serviceAccountJson` | _string_ | ServiceAccountJSON is the path to the service account json credentials | -| `useApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | -| `targetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | +| `Groups` | _[]string_ | Groups sets restrict logins to members of this Google group | +| `AdminEmail` | _string_ | AdminEmail is the Google admin to impersonate for api calls | +| `ServiceAccountJSON` | _string_ | ServiceAccountJSON is the path to the service account json credentials | +| `UseApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | +| `TargetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | ### Header @@ -252,9 +252,9 @@ response header. | Field | Type | Description | | ----- | ---- | ----------- | -| `name` | _string_ | Name is the header name to be used for this set of values.
Names should be unique within a list of Headers. | -| `preserveRequestValue` | _bool_ | PreserveRequestValue determines whether any values for this header
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). | -| `values` | _[[]HeaderValue](#headervalue)_ | Values contains the desired values for this header | +| `Name` | _string_ | Name is the header name to be used for this set of values.
Names should be unique within a list of Headers. | +| `PreserveRequestValue` | _bool_ | PreserveRequestValue determines whether any values for this header
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). | +| `Values` | _[[]HeaderValue](#headervalue)_ | Values contains the desired values for this header | ### HeaderValue @@ -265,12 +265,12 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _string_ | Value expects a base64 encoded string value. | -| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | -| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | -| `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | -| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | -| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | +| `Value` | _string_ | Value expects a base64 encoded string value. | +| `FromEnv` | _string_ | FromEnv expects the name of an environment variable. | +| `FromFile` | _string_ | FromFile expects a path to a file containing the secret value. | +| `Claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | +| `Prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | +| `BasicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | ### KeycloakOptions @@ -280,8 +280,8 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `groups` | _[]string_ | Group enables to restrict login to members of indicated group | -| `roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) | +| `Groups` | _[]string_ | Group enables to restrict login to members of indicated group | +| `Roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) | ### LoginGovOptions @@ -291,9 +291,9 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `jwtKey` | _string_ | JWTKey is a private key in PEM format used to sign JWT, | -| `jwtKeyFile` | _string_ | JWTKeyFile is a path to the private key file in PEM format used to sign the JWT | -| `pubjwkURL` | _string_ | PubJWKURL is the JWK pubkey access endpoint | +| `JWTKey` | _string_ | JWTKey is a private key in PEM format used to sign JWT, | +| `JWTKeyFile` | _string_ | JWTKeyFile is a path to the private key file in PEM format used to sign the JWT | +| `PubJWKURL` | _string_ | PubJWKURL is the JWK pubkey access endpoint | ### LoginURLParameter @@ -371,9 +371,9 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `name` | _string_ | Name specifies the name of the query parameter. | -| `default` | _[]string_ | _(Optional)_ Default specifies a default value or values that will be
passed to the IdP if not overridden. | -| `allow` | _[[]URLParameterRule](#urlparameterrule)_ | _(Optional)_ Allow specifies rules about how the default (if any) may be
overridden via the query string to `/oauth2/start`. Only
values that match one or more of the allow rules will be
forwarded to the IdP. | +| `Name` | _string_ | Name specifies the name of the query parameter. | +| `Default` | _[]string_ | _(Optional)_ Default specifies a default value or values that will be
passed to the IdP if not overridden. | +| `Allow` | _[[]URLParameterRule](#urlparameterrule)_ | _(Optional)_ Allow specifies rules about how the default (if any) may be
overridden via the query string to `/oauth2/start`. Only
values that match one or more of the allow rules will be
forwarded to the IdP. | ### MicrosoftEntraIDOptions @@ -383,8 +383,8 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `allowedTenants` | _[]string_ | AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are
issued by different issuers and OIDC issuer verification needs to be disabled.
When not specified, all tenants are allowed. Redundant for single-tenant apps
(regular ID token validation matches the issuer). | -| `federatedTokenAuth` | _bool_ | FederatedTokenAuth enable oAuth2 client authentication with federated token projected
by Entra Workload Identity plugin, instead of client secret. | +| `AllowedTenants` | _[]string_ | AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are
issued by different issuers and OIDC issuer verification needs to be disabled.
When not specified, all tenants are allowed. Redundant for single-tenant apps
(regular ID token validation matches the issuer). | +| `FederatedTokenAuth` | _bool_ | FederatedTokenAuth enable oAuth2 client authentication with federated token projected
by Entra Workload Identity plugin, instead of client secret. | ### OIDCOptions @@ -394,18 +394,18 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `issuerURL` | _string_ | IssuerURL is the OpenID Connect issuer URL
eg: https://accounts.google.com | -| `insecureAllowUnverifiedEmail` | _bool_ | InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
default set to 'false' | -| `insecureSkipIssuerVerification` | _bool_ | InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
default set to 'false' | -| `insecureSkipNonce` | _bool_ | InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
after the initial OAuth redeem & subsequent token refreshes.
default set to 'true'
Warning: In a future release, this will change to 'false' by default for enhanced security. | -| `skipDiscovery` | _bool_ | SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
default set to 'false' | -| `jwksURL` | _string_ | JwksURL is the OpenID Connect JWKS URL
eg: https://www.googleapis.com/oauth2/v3/certs | -| `publicKeyFiles` | _[]string_ | PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
for verifying JWT tokens | -| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,
default set to 'email' | -| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups
default set to 'groups' | -| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID
default set to 'email' | -| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id
By default `aud` claim is used for verification. | -| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed
to pass verification in addition to the client id. | +| `IssuerURL` | _string_ | IssuerURL is the OpenID Connect issuer URL
eg: https://accounts.google.com | +| `InsecureAllowUnverifiedEmail` | _bool_ | InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
default set to 'false' | +| `InsecureSkipIssuerVerification` | _bool_ | InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
default set to 'false' | +| `InsecureSkipNonce` | _bool_ | InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
after the initial OAuth redeem & subsequent token refreshes.
default set to 'true'
Warning: In a future release, this will change to 'false' by default for enhanced security. | +| `SkipDiscovery` | _bool_ | SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
default set to 'false' | +| `JwksURL` | _string_ | JwksURL is the OpenID Connect JWKS URL
eg: https://www.googleapis.com/oauth2/v3/certs | +| `PublicKeyFiles` | _[]string_ | PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
for verifying JWT tokens | +| `EmailClaim` | _string_ | EmailClaim indicates which claim contains the user email,
default set to 'email' | +| `GroupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups
default set to 'groups' | +| `UserIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID
default set to 'email' | +| `AudienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id
By default `aud` claim is used for verification. | +| `ExtraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed
to pass verification in addition to the client id. | ### Provider @@ -415,36 +415,36 @@ Provider holds all configuration for a single provider | Field | Type | Description | | ----- | ---- | ----------- | -| `clientID` | _string_ | ClientID is the OAuth Client ID that is defined in the provider
This value is required for all providers. | -| `clientSecret` | _string_ | ClientSecret is the OAuth Client Secret that is defined in the provider
This value is required for all providers. | -| `clientSecretFile` | _string_ | ClientSecretFile is the name of the file
containing the OAuth Client Secret, it will be used if ClientSecret is not set. | -| `keycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. | -| `azureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. | -| `microsoftEntraIDConfig` | _[MicrosoftEntraIDOptions](#microsoftentraidoptions)_ | MicrosoftEntraIDConfig holds all configurations for Entra ID provider. | +| `ClientID` | _string_ | ClientID is the OAuth Client ID that is defined in the provider
This value is required for all providers. | +| `ClientSecret` | _string_ | ClientSecret is the OAuth Client Secret that is defined in the provider
This value is required for all providers. | +| `ClientSecretFile` | _string_ | ClientSecretFile is the name of the file
containing the OAuth Client Secret, it will be used if ClientSecret is not set. | +| `KeycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. | +| `AzureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. | +| `MicrosoftEntraIDConfig` | _[MicrosoftEntraIDOptions](#microsoftentraidoptions)_ | MicrosoftEntraIDConfig holds all configurations for Entra ID provider. | | `ADFSConfig` | _[ADFSOptions](#adfsoptions)_ | ADFSConfig holds all configurations for ADFS provider. | -| `bitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. | -| `githubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. | -| `gitlabConfig` | _[GitLabOptions](#gitlaboptions)_ | GitLabConfig holds all configurations for GitLab provider. | -| `googleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. | -| `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider
or providers utilize OIDC configurations. | -| `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | -| `id` | _string_ | ID should be a unique identifier for the provider.
This value is required for all providers. | -| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider
must be set from the supported providers group,
otherwise 'Google' is set as default | -| `name` | _string_ | Name is the providers display name
if set, it will be shown to the users in the login page. | -| `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
If not specified, the default Go trust sources are used instead | -| `useSystemTrustStore` | _bool_ | 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. | -| `loginURL` | _string_ | LoginURL is the authentication endpoint | -| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL | -| `authRequestResponseMode` | _string_ | AuthRequestResponseMode defines the response mode to request during authorization request | -| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint | -| `profileURL` | _string_ | ProfileURL is the profile access endpoint | -| `skipClaimsFromProfileURL` | _bool_ | SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
default set to 'false' | -| `resource` | _string_ | ProtectedResource is the resource that is protected (Azure AD and ADFS only) | -| `validateURL` | _string_ | ValidateURL is the access token validation endpoint | -| `scope` | _string_ | Scope is the OAuth scope specification | -| `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | -| `code_challenge_method` | _string_ | The code challenge method | -| `backendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session | +| `BitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. | +| `GitHubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. | +| `GitLabConfig` | _[GitLabOptions](#gitlaboptions)_ | GitLabConfig holds all configurations for GitLab provider. | +| `GoogleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. | +| `OIDCConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider
or providers utilize OIDC configurations. | +| `LoginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | +| `ID` | _string_ | ID should be a unique identifier for the provider.
This value is required for all providers. | +| `Type` | _[ProviderType](#providertype)_ | Type is the OAuth provider
must be set from the supported providers group,
otherwise 'Google' is set as default | +| `Name` | _string_ | Name is the providers display name
if set, it will be shown to the users in the login page. | +| `CAFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
If not specified, the default Go trust sources are used instead | +| `UseSystemTrustStore` | _bool_ | 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. | +| `LoginURL` | _string_ | LoginURL is the authentication endpoint | +| `LoginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL | +| `AuthRequestResponseMode` | _string_ | AuthRequestResponseMode defines the response mode to request during authorization request | +| `RedeemURL` | _string_ | RedeemURL is the token redemption endpoint | +| `ProfileURL` | _string_ | ProfileURL is the profile access endpoint | +| `SkipClaimsFromProfileURL` | _bool_ | SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
default set to 'false' | +| `ProtectedResource` | _string_ | ProtectedResource is the resource that is protected (Azure AD and ADFS only) | +| `ValidateURL` | _string_ | ValidateURL is the access token validation endpoint | +| `Scope` | _string_ | Scope is the OAuth scope specification | +| `AllowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | +| `CodeChallengeMethod` | _string_ | The code challenge method | +| `BackendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session | ### ProviderType #### (`string` alias) @@ -477,9 +477,9 @@ Only one source within the struct should be defined at any time. | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _string_ | Value expects a base64 encoded string value. | -| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | -| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | +| `Value` | _string_ | Value expects a base64 encoded string value. | +| `FromEnv` | _string_ | FromEnv expects the name of an environment variable. | +| `FromFile` | _string_ | FromFile expects a path to a file containing the secret value. | ### Server @@ -518,8 +518,8 @@ login URL. Either Value or Pattern should be supplied, not both. | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _string_ | A Value rule matches just this specific value | -| `pattern` | _string_ | A Pattern rule gives a regular expression that must be matched by
some substring of the value. The expression is _not_ automatically
anchored to the start and end of the value, if you _want_ to restrict
the whole parameter value you must anchor it yourself with `^` and `$`. | +| `Value` | _string_ | A Value rule matches just this specific value | +| `Pattern` | _string_ | A Pattern rule gives a regular expression that must be matched by
some substring of the value. The expression is _not_ automatically
anchored to the start and end of the value, if you _want_ to restrict
the whole parameter value you must anchor it yourself with `^` and `$`. | ### Upstream @@ -530,18 +530,18 @@ Requests will be proxied to this upstream if the path matches the request path. | Field | Type | Description | | ----- | ---- | ----------- | -| `id` | _string_ | ID should be a unique identifier for the upstream.
This value is required for all upstreams. | -| `path` | _string_ | Path is used to map requests to the upstream server.
The closest match will take precedence and all Paths must be unique.
Path can also take a pattern when used with RewriteTarget.
Path segments can be captured and matched using regular experessions.
Eg:
- `^/foo$`: Match only the explicit path `/foo`
- `^/bar/$`: Match any path prefixed with `/bar/`
- `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget | -| `rewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to
the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
(for a `file:` upstream).
Use the Path to capture segments for reuse within the rewrite target.
Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite
the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
upstream server. Or if the upstream were `file:///app`, a request for
`/baz/info.html` would return the contents of the file `/app/foo/info.html`. | -| `uri` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File
based URL. It may include a path, in which case all requests will be served
under that path.
Eg:
- http://localhost:8080
- https://service.localhost
- https://service.localhost/path
- file://host/path
If the URI's path is "/base" and the incoming request was for "/dir",
the upstream request will be for "/base/dir". | -| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
This option is insecure and will allow potential Man-In-The-Middle attacks
between OAuth2 Proxy and the upstream server.
Defaults to false. | -| `static` | _bool_ | 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. | -| `staticCode` | _int_ | StaticCode determines the response code for the Static response.
This option can only be used with Static enabled. | -| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | -| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | -| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | -| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | -| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | +| `ID` | _string_ | ID should be a unique identifier for the upstream.
This value is required for all upstreams. | +| `Path` | _string_ | Path is used to map requests to the upstream server.
The closest match will take precedence and all Paths must be unique.
Path can also take a pattern when used with RewriteTarget.
Path segments can be captured and matched using regular experessions.
Eg:
- `^/foo$`: Match only the explicit path `/foo`
- `^/bar/$`: Match any path prefixed with `/bar/`
- `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget | +| `RewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to
the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
(for a `file:` upstream).
Use the Path to capture segments for reuse within the rewrite target.
Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite
the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
upstream server. Or if the upstream were `file:///app`, a request for
`/baz/info.html` would return the contents of the file `/app/foo/info.html`. | +| `URI` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File
based URL. It may include a path, in which case all requests will be served
under that path.
Eg:
- http://localhost:8080
- https://service.localhost
- https://service.localhost/path
- file://host/path
If the URI's path is "/base" and the incoming request was for "/dir",
the upstream request will be for "/base/dir". | +| `InsecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
This option is insecure and will allow potential Man-In-The-Middle attacks
between OAuth2 Proxy and the upstream server.
Defaults to false. | +| `Static` | _bool_ | 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. | +| `StaticCode` | _int_ | StaticCode determines the response code for the Static response.
This option can only be used with Static enabled. | +| `FlushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | +| `PassHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | +| `ProxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | +| `Timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | +| `DisableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | ### UpstreamConfig @@ -551,5 +551,5 @@ UpstreamConfig is a collection of definitions for upstream servers. | Field | Type | Description | | ----- | ---- | ----------- | -| `proxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for urls
like: "/%2F/" which would otherwise be redirected to "/" | -| `upstreams` | _[[]Upstream](#upstream)_ | Upstreams represents the configuration for the upstream servers.
Requests will be proxied to this upstream if the path matches the request path. | +| `ProxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for urls
like: "/%2F/" which would otherwise be redirected to "/" | +| `Upstreams` | _[[]Upstream](#upstream)_ | Upstreams represents the configuration for the upstream servers.
Requests will be proxied to this upstream if the path matches the request path. | diff --git a/go.mod b/go.mod index 24f316e4..a34d4d50 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/fsnotify/fsnotify v1.9.0 - github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/go-jose/go-jose/v3 v3.0.4 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/golang-jwt/jwt/v5 v5.2.3 @@ -22,9 +21,10 @@ require ( github.com/gorilla/mux v1.8.1 github.com/justinas/alice v1.2.0 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa + github.com/mitchellh/mapstructure v1.5.0 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/onsi/ginkgo/v2 v2.23.4 - github.com/onsi/gomega v1.37.0 + github.com/onsi/gomega v1.38.0 github.com/pierrec/lz4/v4 v4.1.22 github.com/prometheus/client_golang v1.22.0 github.com/redis/go-redis/v9 v9.11.0 @@ -37,13 +37,14 @@ require ( golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.16.0 - google.golang.org/api v0.242.0 + google.golang.org/api v0.243.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.33.3 ) require ( - cloud.google.com/go/auth v0.16.2 // indirect + cloud.google.com/go/auth v0.16.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect @@ -83,9 +84,8 @@ require ( golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.35.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/grpc v1.73.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect + google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index caa8e2a0..c653cee5 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,11 @@ -cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= -cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= +cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= +cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw= github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss= -github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc= github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY= github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU= @@ -20,12 +19,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= -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/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= -github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw= github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -44,12 +38,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4= -github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= @@ -61,17 +51,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -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/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -85,34 +68,27 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= -github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk= github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= +github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -120,8 +96,6 @@ github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -132,7 +106,6 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= @@ -142,8 +115,6 @@ github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= @@ -165,18 +136,12 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -186,8 +151,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -197,8 +160,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= @@ -206,8 +167,6 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -219,8 +178,6 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -234,53 +191,29 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.240.0 h1:PxG3AA2UIqT1ofIzWV2COM3j3JagKTKSwy7L6RHNXNU= -google.golang.org/api v0.240.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE= -google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= -google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/api v0.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ= +google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= diff --git a/main.go b/main.go index 2a09b3ce..a9def2f6 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,12 @@ import ( "os" "runtime" - "github.com/ghodss/yaml" "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/validation" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/version" "github.com/spf13/pflag" + "gopkg.in/yaml.v3" ) func main() { diff --git a/main_test.go b/main_test.go index a6ea83c2..7de1ac72 100644 --- a/main_test.go +++ b/main_test.go @@ -2,9 +2,7 @@ package main import ( "errors" - "fmt" "os" - "strings" "time" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" @@ -256,7 +254,7 @@ redirect_url="http://localhost:4180/oauth2/callback" configContent: testCoreConfig, alphaConfigContent: testAlphaConfig + ":", expectedOptions: func() *options.Options { return nil }, - expectedErr: fmt.Errorf("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line %d: did not find expected key", strings.Count(testAlphaConfig, "\n")), + expectedErr: errors.New("failed to load alpha options: error unmarshalling config: yaml: line 1: did not find expected key"), }), Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{ configContent: testCoreConfig + "unknown_field=\"something\"", diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index 278db401..0c78359a 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -12,13 +12,13 @@ type AlphaOptions struct { // UpstreamConfig is used to configure upstream servers. // Once a user is authenticated, requests to the server will be proxied to // these upstream servers based on the path mappings defined in this list. - UpstreamConfig UpstreamConfig `json:"upstreamConfig,omitempty"` + UpstreamConfig UpstreamConfig `yaml:"upstreamConfig,omitempty"` // InjectRequestHeaders is used to configure headers that should be added // to requests to upstream servers. // Headers may source values from either the authenticated user's session // or from a static secret value. - InjectRequestHeaders []Header `json:"injectRequestHeaders,omitempty"` + InjectRequestHeaders []Header `yaml:"injectRequestHeaders,omitempty"` // InjectResponseHeaders is used to configure headers that should be added // to responses from the proxy. @@ -27,24 +27,24 @@ type AlphaOptions struct { // auth_request module. // Headers may source values from either the authenticated user's session // or from a static secret value. - InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"` + InjectResponseHeaders []Header `yaml:"injectResponseHeaders,omitempty"` // Server is used to configure the HTTP(S) server for the proxy application. // You may choose to run both HTTP and HTTPS servers simultaneously. // This can be done by setting the BindAddress and the SecureBindAddress simultaneously. // To use the secure server you must configure a TLS certificate and key. - Server Server `json:"server,omitempty"` + Server Server `yaml:"server,omitempty"` // MetricsServer is used to configure the HTTP(S) server for metrics. // You may choose to run both HTTP and HTTPS servers simultaneously. // This can be done by setting the BindAddress and the SecureBindAddress simultaneously. // To use the secure server you must configure a TLS certificate and key. - MetricsServer Server `json:"metricsServer,omitempty"` + MetricsServer Server `yaml:"metricsServer,omitempty"` // Providers is used to configure your provider. **Multiple-providers is not // yet working.** [This feature is tracked in // #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) - Providers Providers `json:"providers,omitempty"` + Providers Providers `yaml:"providers,omitempty"` } // Initialize alpha options with default values and settings of the core options diff --git a/pkg/apis/options/duration_test.go b/pkg/apis/options/duration_test.go index fc1e77e3..63f203a8 100644 --- a/pkg/apis/options/duration_test.go +++ b/pkg/apis/options/duration_test.go @@ -7,7 +7,7 @@ import ( func TestDecode(t *testing.T) { type result struct { - Duration time.Duration `json:"duration"` + Duration time.Duration `yaml:"duration"` } tests := []struct { @@ -64,7 +64,7 @@ func TestDecode(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var result struct { - Duration time.Duration `json:"duration"` + Duration time.Duration `yaml:"duration"` } err := Decode(tt.input, &result) diff --git a/pkg/apis/options/header.go b/pkg/apis/options/header.go index a7e10c44..b585fceb 100644 --- a/pkg/apis/options/header.go +++ b/pkg/apis/options/header.go @@ -5,26 +5,26 @@ package options type Header struct { // Name is the header name to be used for this set of values. // Names should be unique within a list of Headers. - Name string `json:"name,omitempty"` + Name string `yaml:"name,omitempty"` // PreserveRequestValue determines whether any values for this header // 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 `json:"preserveRequestValue,omitempty"` + PreserveRequestValue bool `yaml:"preserveRequestValue,omitempty"` // Values contains the desired values for this header - Values []HeaderValue `json:"values,omitempty"` + Values []HeaderValue `yaml:"values,omitempty"` } // HeaderValue represents a single header value and the sources that can // make up the header value type HeaderValue struct { // Allow users to load the value from a secret source - *SecretSource `json:"secretSource,omitempty"` + *SecretSource `yaml:"secretSource,omitempty"` // Allow users to load the value from a session claim - *ClaimSource `json:"claimSource,omitempty"` + *ClaimSource `yaml:"claimSource,omitempty"` } // ClaimSource allows loading a header value from a claim within the session @@ -32,14 +32,14 @@ type ClaimSource struct { // Claim is the name of the claim in the session that the value should be // loaded from. Available claims: `access_token` `id_token` `created_at` // `expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. - Claim string `json:"claim,omitempty"` + Claim string `yaml:"claim,omitempty"` // Prefix is an optional prefix that will be prepended to the value of the // claim if it is non-empty. - Prefix string `json:"prefix,omitempty"` + Prefix string `yaml:"prefix,omitempty"` // BasicAuthPassword converts this claim into a basic auth header. // Note the value of claim will become the basic auth username and the // basicAuthPassword will be used as the password value. - BasicAuthPassword *SecretSource `json:"basicAuthPassword,omitempty"` + BasicAuthPassword *SecretSource `yaml:"basicAuthPassword,omitempty"` } diff --git a/pkg/apis/options/load.go b/pkg/apis/options/load.go index 2905af5f..af7f76d9 100644 --- a/pkg/apis/options/load.go +++ b/pkg/apis/options/load.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/a8m/envsubst" - "github.com/ghodss/yaml" "github.com/go-viper/mapstructure/v2" "github.com/spf13/pflag" "github.com/spf13/viper" + "gopkg.in/yaml.v3" ) // Load reads in the config file at the path given, then merges in environment @@ -92,7 +92,7 @@ func Decode(input interface{}, result interface{}) error { DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()), Metadata: nil, // Don't track any metadata Result: result, // Decode the result into the prefilled options - TagName: "json", // Parse all fields that use the json tag + TagName: "yaml", // Parse all fields that use the json tag ZeroFields: false, // Don't clean the default values from the result map (options) ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with json diff --git a/pkg/apis/options/load_test.go b/pkg/apis/options/load_test.go index 6f327a22..a0079267 100644 --- a/pkg/apis/options/load_test.go +++ b/pkg/apis/options/load_test.go @@ -355,15 +355,15 @@ var _ = Describe("Load", func() { var _ = Describe("LoadYAML", func() { Context("with a testOptions structure", func() { type TestOptionSubStruct struct { - StringSliceOption []string `json:"stringSliceOption,omitempty"` + StringSliceOption []string `yaml:"stringSliceOption,omitempty"` } type TestOptions struct { - StringOption string `json:"stringOption,omitempty"` - Sub TestOptionSubStruct `json:"sub,omitempty"` + StringOption string `yaml:"stringOption,omitempty"` + Sub TestOptionSubStruct `yaml:"sub,omitempty"` // Check that embedded fields can be unmarshalled - TestOptionSubStruct `json:",inline,squash"` + TestOptionSubStruct `yaml:",inline,squash"` } var testOptionsConfigBytesFull = []byte(` @@ -445,7 +445,7 @@ sub: 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"), + expectedErr: errors.New("error unmarshalling config: yaml: found character that cannot start any token"), }), Entry("with extra fields in the YAML", loadYAMLTableInput{ configFile: append(testOptionsConfigBytesFull, []byte("foo: bar\n")...), diff --git a/pkg/apis/options/login_url_parameters.go b/pkg/apis/options/login_url_parameters.go index 1cb763b9..02729760 100644 --- a/pkg/apis/options/login_url_parameters.go +++ b/pkg/apis/options/login_url_parameters.go @@ -71,19 +71,19 @@ package options // character. type LoginURLParameter struct { // Name specifies the name of the query parameter. - Name string `json:"name"` + Name string `yaml:"name"` // Default specifies a default value or values that will be // passed to the IdP if not overridden. //+optional - Default []string `json:"default,omitempty"` + Default []string `yaml:"default,omitempty"` // Allow specifies rules about how the default (if any) may be // overridden via the query string to `/oauth2/start`. Only // values that match one or more of the allow rules will be // forwarded to the IdP. //+optional - Allow []URLParameterRule `json:"allow,omitempty"` + Allow []URLParameterRule `yaml:"allow,omitempty"` } // URLParameterRule represents a rule by which query parameters @@ -92,11 +92,11 @@ type LoginURLParameter struct { // login URL. Either Value or Pattern should be supplied, not both. type URLParameterRule struct { // A Value rule matches just this specific value - Value *string `json:"value,omitempty"` + Value *string `yaml:"value,omitempty"` // A Pattern rule gives a regular expression that must be matched by // some substring of the value. The expression is _not_ automatically // anchored to the start and end of the value, if you _want_ to restrict // the whole parameter value you must anchor it yourself with `^` and `$`. - Pattern *string `json:"pattern,omitempty"` + Pattern *string `yaml:"pattern,omitempty"` } diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go index c94b6b92..f431f48c 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -22,78 +22,78 @@ type Providers []Provider type Provider struct { // ClientID is the OAuth Client ID that is defined in the provider // This value is required for all providers. - ClientID string `json:"clientID,omitempty"` + ClientID string `yaml:"clientID,omitempty"` // ClientSecret is the OAuth Client Secret that is defined in the provider // This value is required for all providers. - ClientSecret string `json:"clientSecret,omitempty"` + ClientSecret string `yaml:"clientSecret,omitempty"` // ClientSecretFile is the name of the file // containing the OAuth Client Secret, it will be used if ClientSecret is not set. - ClientSecretFile string `json:"clientSecretFile,omitempty"` + ClientSecretFile string `yaml:"clientSecretFile,omitempty"` // KeycloakConfig holds all configurations for Keycloak provider. - KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"` + KeycloakConfig KeycloakOptions `yaml:"keycloakConfig,omitempty"` // AzureConfig holds all configurations for Azure provider. - AzureConfig AzureOptions `json:"azureConfig,omitempty"` + AzureConfig AzureOptions `yaml:"azureConfig,omitempty"` // MicrosoftEntraIDConfig holds all configurations for Entra ID provider. - MicrosoftEntraIDConfig MicrosoftEntraIDOptions `json:"microsoftEntraIDConfig,omitempty"` + MicrosoftEntraIDConfig MicrosoftEntraIDOptions `yaml:"microsoftEntraIDConfig,omitempty"` // ADFSConfig holds all configurations for ADFS provider. - ADFSConfig ADFSOptions `json:"ADFSConfig,omitempty"` + ADFSConfig ADFSOptions `yaml:"ADFSConfig,omitempty"` // BitbucketConfig holds all configurations for Bitbucket provider. - BitbucketConfig BitbucketOptions `json:"bitbucketConfig,omitempty"` + BitbucketConfig BitbucketOptions `yaml:"bitbucketConfig,omitempty"` // GitHubConfig holds all configurations for GitHubC provider. - GitHubConfig GitHubOptions `json:"githubConfig,omitempty"` + GitHubConfig GitHubOptions `yaml:"githubConfig,omitempty"` // GitLabConfig holds all configurations for GitLab provider. - GitLabConfig GitLabOptions `json:"gitlabConfig,omitempty"` + GitLabConfig GitLabOptions `yaml:"gitlabConfig,omitempty"` // GoogleConfig holds all configurations for Google provider. - GoogleConfig GoogleOptions `json:"googleConfig,omitempty"` + GoogleConfig GoogleOptions `yaml:"googleConfig,omitempty"` // OIDCConfig holds all configurations for OIDC provider // or providers utilize OIDC configurations. - OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"` + OIDCConfig OIDCOptions `yaml:"oidcConfig,omitempty"` // LoginGovConfig holds all configurations for LoginGov provider. - LoginGovConfig LoginGovOptions `json:"loginGovConfig,omitempty"` + LoginGovConfig LoginGovOptions `yaml:"loginGovConfig,omitempty"` // ID should be a unique identifier for the provider. // This value is required for all providers. - ID string `json:"id,omitempty"` + ID string `yaml:"id,omitempty"` // Type is the OAuth provider // must be set from the supported providers group, // otherwise 'Google' is set as default - Type ProviderType `json:"provider,omitempty"` + Type ProviderType `yaml:"provider,omitempty"` // Name is the providers display name // if set, it will be shown to the users in the login page. - Name string `json:"name,omitempty"` + Name string `yaml:"name,omitempty"` // CAFiles is a list of paths to CA certificates that should be used when connecting to the provider. // If not specified, the default Go trust sources are used instead - CAFiles []string `json:"caFiles,omitempty"` + 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 `json:"useSystemTrustStore,omitempty"` + UseSystemTrustStore bool `yaml:"useSystemTrustStore,omitempty"` // LoginURL is the authentication endpoint - LoginURL string `json:"loginURL,omitempty"` + LoginURL string `yaml:"loginURL,omitempty"` // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL - LoginURLParameters []LoginURLParameter `json:"loginURLParameters,omitempty"` + LoginURLParameters []LoginURLParameter `yaml:"loginURLParameters,omitempty"` // AuthRequestResponseMode defines the response mode to request during authorization request - AuthRequestResponseMode string `json:"authRequestResponseMode,omitempty"` + AuthRequestResponseMode string `yaml:"authRequestResponseMode,omitempty"` // RedeemURL is the token redemption endpoint - RedeemURL string `json:"redeemURL,omitempty"` + RedeemURL string `yaml:"redeemURL,omitempty"` // ProfileURL is the profile access endpoint - ProfileURL string `json:"profileURL,omitempty"` + 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 `json:"skipClaimsFromProfileURL,omitempty"` + SkipClaimsFromProfileURL bool `yaml:"skipClaimsFromProfileURL,omitempty"` // ProtectedResource is the resource that is protected (Azure AD and ADFS only) - ProtectedResource string `json:"resource,omitempty"` + ProtectedResource string `yaml:"resource,omitempty"` // ValidateURL is the access token validation endpoint - ValidateURL string `json:"validateURL,omitempty"` + ValidateURL string `yaml:"validateURL,omitempty"` // Scope is the OAuth scope specification - Scope string `json:"scope,omitempty"` + Scope string `yaml:"scope,omitempty"` // AllowedGroups is a list of restrict logins to members of this group - AllowedGroups []string `json:"allowedGroups,omitempty"` + AllowedGroups []string `yaml:"allowedGroups,omitempty"` // The code challenge method - CodeChallengeMethod string `json:"code_challenge_method,omitempty"` + CodeChallengeMethod string `yaml:"code_challenge_method,omitempty"` // URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session - BackendLogoutURL string `json:"backendLogoutURL"` + BackendLogoutURL string `yaml:"backendLogoutURL"` } // ProviderType is used to enumerate the different provider type options @@ -157,19 +157,19 @@ const ( type KeycloakOptions struct { // Group enables to restrict login to members of indicated group - Groups []string `json:"groups,omitempty"` + Groups []string `yaml:"groups,omitempty"` // Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) - Roles []string `json:"roles,omitempty"` + Roles []string `yaml:"roles,omitempty"` } type AzureOptions struct { // Tenant directs to a tenant-specific or common (tenant-independent) endpoint // Default value is 'common' - Tenant string `json:"tenant,omitempty"` + Tenant string `yaml:"tenant,omitempty"` // GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph // Default value is 'id' - GraphGroupField string `json:"graphGroupField,omitempty"` + GraphGroupField string `yaml:"graphGroupField,omitempty"` } type MicrosoftEntraIDOptions struct { @@ -177,110 +177,110 @@ type MicrosoftEntraIDOptions struct { // issued by different issuers and OIDC issuer verification needs to be disabled. // When not specified, all tenants are allowed. Redundant for single-tenant apps // (regular ID token validation matches the issuer). - AllowedTenants []string `json:"allowedTenants,omitempty"` + AllowedTenants []string `yaml:"allowedTenants,omitempty"` // FederatedTokenAuth enable oAuth2 client authentication with federated token projected // by Entra Workload Identity plugin, instead of client secret. - FederatedTokenAuth bool `json:"federatedTokenAuth,omitempty"` + FederatedTokenAuth bool `yaml:"federatedTokenAuth,omitempty"` } type ADFSOptions struct { // Skip adding the scope parameter in login request // Default value is 'false' - SkipScope bool `json:"skipScope,omitempty"` + SkipScope bool `yaml:"skipScope,omitempty"` } type BitbucketOptions struct { // Team sets restrict logins to members of this team - Team string `json:"team,omitempty"` + Team string `yaml:"team,omitempty"` // Repository sets restrict logins to user with access to this repository - Repository string `json:"repository,omitempty"` + Repository string `yaml:"repository,omitempty"` } type GitHubOptions struct { // Org sets restrict logins to members of this organisation - Org string `json:"org,omitempty"` + Org string `yaml:"org,omitempty"` // Team sets restrict logins to members of this team - Team string `json:"team,omitempty"` + Team string `yaml:"team,omitempty"` // Repo sets restrict logins to collaborators of this repository - Repo string `json:"repo,omitempty"` + Repo string `yaml:"repo,omitempty"` // Token is the token to use when verifying repository collaborators // it must have push access to the repository - Token string `json:"token,omitempty"` + Token string `yaml:"token,omitempty"` // Users allows users with these usernames to login // even if they do not belong to the specified org and team or collaborators - Users []string `json:"users,omitempty"` + Users []string `yaml:"users,omitempty"` } type GitLabOptions struct { // Group sets restrict logins to members of this group - Group []string `json:"group,omitempty"` + Group []string `yaml:"group,omitempty"` // Projects restricts logins to members of these projects - Projects []string `json:"projects,omitempty"` + Projects []string `yaml:"projects,omitempty"` } type GoogleOptions struct { // Groups sets restrict logins to members of this Google group - Groups []string `json:"group,omitempty"` + Groups []string `yaml:"group,omitempty"` // AdminEmail is the Google admin to impersonate for api calls - AdminEmail string `json:"adminEmail,omitempty"` + AdminEmail string `yaml:"adminEmail,omitempty"` // ServiceAccountJSON is the path to the service account json credentials - ServiceAccountJSON string `json:"serviceAccountJson,omitempty"` + ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"` // UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON - UseApplicationDefaultCredentials bool `json:"useApplicationDefaultCredentials,omitempty"` + UseApplicationDefaultCredentials bool `yaml:"useApplicationDefaultCredentials,omitempty"` // TargetPrincipal is the Google Service Account used for Application Default Credentials - TargetPrincipal string `json:"targetPrincipal,omitempty"` + TargetPrincipal string `yaml:"targetPrincipal,omitempty"` } type OIDCOptions struct { // IssuerURL is the OpenID Connect issuer URL // eg: https://accounts.google.com - IssuerURL string `json:"issuerURL,omitempty"` + IssuerURL string `yaml:"issuerURL,omitempty"` // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified // default set to 'false' - InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail"` + InsecureAllowUnverifiedEmail bool `yaml:"insecureAllowUnverifiedEmail"` // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL // default set to 'false' - InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification"` + InsecureSkipIssuerVerification bool `yaml:"insecureSkipIssuerVerification"` // InsecureSkipNonce skips verifying the ID Token's nonce claim that must match // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked // after the initial OAuth redeem & subsequent token refreshes. // default set to 'true' // Warning: In a future release, this will change to 'false' by default for enhanced security. - InsecureSkipNonce bool `json:"insecureSkipNonce"` + InsecureSkipNonce bool `yaml:"insecureSkipNonce"` // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints // default set to 'false' - SkipDiscovery bool `json:"skipDiscovery,omitempty"` + SkipDiscovery bool `yaml:"skipDiscovery,omitempty"` // JwksURL is the OpenID Connect JWKS URL // eg: https://www.googleapis.com/oauth2/v3/certs - JwksURL string `json:"jwksURL,omitempty"` + JwksURL string `yaml:"jwksURL,omitempty"` // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use // for verifying JWT tokens - PublicKeyFiles []string `json:"publicKeyFiles,omitempty"` + PublicKeyFiles []string `yaml:"publicKeyFiles,omitempty"` // EmailClaim indicates which claim contains the user email, // default set to 'email' - EmailClaim string `json:"emailClaim,omitempty"` + EmailClaim string `yaml:"emailClaim,omitempty"` // GroupsClaim indicates which claim contains the user groups // default set to 'groups' - GroupsClaim string `json:"groupsClaim,omitempty"` + GroupsClaim string `yaml:"groupsClaim,omitempty"` // UserIDClaim indicates which claim contains the user ID // default set to 'email' - UserIDClaim string `json:"userIDClaim,omitempty"` + UserIDClaim string `yaml:"userIDClaim,omitempty"` // AudienceClaim allows to define any claim that is verified against the client id // By default `aud` claim is used for verification. - AudienceClaims []string `json:"audienceClaims,omitempty"` + AudienceClaims []string `yaml:"audienceClaims,omitempty"` // ExtraAudiences is a list of additional audiences that are allowed // to pass verification in addition to the client id. - ExtraAudiences []string `json:"extraAudiences,omitempty"` + ExtraAudiences []string `yaml:"extraAudiences,omitempty"` } type LoginGovOptions struct { // JWTKey is a private key in PEM format used to sign JWT, - JWTKey string `json:"jwtKey,omitempty"` + JWTKey string `yaml:"jwtKey,omitempty"` // JWTKeyFile is a path to the private key file in PEM format used to sign the JWT - JWTKeyFile string `json:"jwtKeyFile,omitempty"` + JWTKeyFile string `yaml:"jwtKeyFile,omitempty"` // PubJWKURL is the JWK pubkey access endpoint - PubJWKURL string `json:"pubjwkURL,omitempty"` + PubJWKURL string `yaml:"pubjwkURL,omitempty"` } func providerDefaults() Providers { diff --git a/pkg/apis/options/secret_source.go b/pkg/apis/options/secret_source.go index 2be4d890..9d82c605 100644 --- a/pkg/apis/options/secret_source.go +++ b/pkg/apis/options/secret_source.go @@ -4,11 +4,11 @@ package options // Only one source within the struct should be defined at any time. type SecretSource struct { // Value expects a base64 encoded string value. - Value string `json:"value,omitempty"` + Value string `yaml:"value,omitempty"` // FromEnv expects the name of an environment variable. - FromEnv string `json:"fromEnv,omitempty"` + FromEnv string `yaml:"fromEnv,omitempty"` // FromFile expects a path to a file containing the secret value. - FromFile string `json:"fromFile,omitempty"` + FromFile string `yaml:"fromFile,omitempty"` } diff --git a/pkg/apis/options/server.go b/pkg/apis/options/server.go index f423ef2c..8fa41af8 100644 --- a/pkg/apis/options/server.go +++ b/pkg/apis/options/server.go @@ -4,15 +4,15 @@ package options type Server struct { // BindAddress is the address on which to serve traffic. // Leave blank or set to "-" to disable. - BindAddress string + BindAddress string `yaml:"bindAddress,omitempty"` // SecureBindAddress is the address on which to serve secure traffic. // Leave blank or set to "-" to disable. - SecureBindAddress string + SecureBindAddress string `yaml:"secureBindAddress,omitempty"` // TLS contains the information for loading the certificate and key for the // secure traffic and further configuration for the TLS server. - TLS *TLS + TLS *TLS `yaml:"tls,omitempty"` } // TLS contains the information for loading a TLS certificate and key @@ -20,15 +20,15 @@ type Server struct { type TLS struct { // Key is the TLS key data to use. // Typically this will come from a file. - Key *SecretSource + Key *SecretSource `yaml:"key,omitempty"` // Cert is the TLS certificate data to use. // Typically this will come from a file. - Cert *SecretSource + Cert *SecretSource `yaml:"cert,omitempty"` // MinVersion is the minimal TLS version that is acceptable. // E.g. Set to "TLS1.3" to select TLS version 1.3 - MinVersion string + MinVersion string `yaml:"minVersion,omitempty"` // CipherSuites is a list of TLS cipher suites that are allowed. // E.g.: @@ -36,5 +36,5 @@ type TLS struct { // - TLS_RSA_WITH_AES_256_GCM_SHA384 // If not specified, the default Go safe cipher list is used. // List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants). - CipherSuites []string + CipherSuites []string `yaml:"cipherSuites,omitempty"` } diff --git a/pkg/apis/options/upstreams.go b/pkg/apis/options/upstreams.go index 1002ae07..b32cc27d 100644 --- a/pkg/apis/options/upstreams.go +++ b/pkg/apis/options/upstreams.go @@ -14,11 +14,11 @@ 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 `json:"proxyRawPath,omitempty"` + ProxyRawPath bool `yaml:"proxyRawPath,omitempty"` // Upstreams represents the configuration for the upstream servers. // Requests will be proxied to this upstream if the path matches the request path. - Upstreams []Upstream `json:"upstreams,omitempty"` + Upstreams []Upstream `yaml:"upstreams,omitempty"` } // Upstream represents the configuration for an upstream server. @@ -26,7 +26,7 @@ type UpstreamConfig struct { type Upstream struct { // ID should be a unique identifier for the upstream. // This value is required for all upstreams. - ID string `json:"id,omitempty"` + ID string `yaml:"id,omitempty"` // Path is used to map requests to the upstream server. // The closest match will take precedence and all Paths must be unique. @@ -36,7 +36,7 @@ type Upstream struct { // - `^/foo$`: Match only the explicit path `/foo` // - `^/bar/$`: Match any path prefixed with `/bar/` // - `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget - Path string `json:"path,omitempty"` + Path string `yaml:"path,omitempty"` // RewriteTarget allows users to rewrite the request path before it is sent to // the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem @@ -46,7 +46,7 @@ type Upstream struct { // the request `/baz/abc/123` to `/foo/abc/123` before proxying to the // upstream server. Or if the upstream were `file:///app`, a request for // `/baz/info.html` would return the contents of the file `/app/foo/info.html`. - RewriteTarget string `json:"rewriteTarget,omitempty"` + RewriteTarget string `yaml:"rewriteTarget,omitempty"` // The URI of the upstream server. This may be an HTTP(S) server of a File // based URL. It may include a path, in which case all requests will be served @@ -58,43 +58,43 @@ type Upstream struct { // - file://host/path // If the URI's path is "/base" and the incoming request was for "/dir", // the upstream request will be for "/base/dir". - URI string `json:"uri,omitempty"` + URI string `yaml:"uri,omitempty"` // InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts. // 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 `json:"insecureSkipTLSVerify,omitempty"` + InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"` // 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 `json:"static,omitempty"` + Static bool `yaml:"static,omitempty"` // StaticCode determines the response code for the Static response. // This option can only be used with Static enabled. - StaticCode *int `json:"staticCode,omitempty"` + StaticCode *int `yaml:"staticCode,omitempty"` // FlushInterval is the period between flushing the response buffer when // streaming response from the upstream. // Defaults to 1 second. - FlushInterval *time.Duration `json:"flushInterval,omitempty"` + FlushInterval *time.Duration `yaml:"flushInterval,omitempty"` // PassHostHeader determines whether the request host header should be proxied // to the upstream server. // Defaults to true. - PassHostHeader *bool `json:"passHostHeader,omitempty"` + PassHostHeader *bool `yaml:"passHostHeader,omitempty"` // ProxyWebSockets enables proxying of websockets to upstream servers // Defaults to true. - ProxyWebSockets *bool `json:"proxyWebSockets,omitempty"` + ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"` // Timeout is the maximum duration the server will wait for a response from the upstream server. // Defaults to 30 seconds. - Timeout *time.Duration `json:"timeout,omitempty"` + Timeout *time.Duration `yaml:"timeout,omitempty"` // DisableKeepAlives disables HTTP keep-alive connections to the upstream server. // Defaults to false. - DisableKeepAlives bool `json:"disableKeepAlives,omitempty"` + DisableKeepAlives bool `yaml:"disableKeepAlives,omitempty"` } diff --git a/pkg/requests/result_test.go b/pkg/requests/result_test.go index b6ecee74..76073975 100644 --- a/pkg/requests/result_test.go +++ b/pkg/requests/result_test.go @@ -104,8 +104,8 @@ var _ = Describe("Result suite", func() { Context("UnmarshalInto", func() { type testStruct struct { - A string `json:"a"` - B int `json:"b"` + A string `yaml:"a"` + B int `yaml:"b"` } type unmarshalIntoTableInput struct { From 8b3451a155aa05c5f0fc85f36e7406f703644f1f Mon Sep 17 00:00:00 2001 From: tuunit Date: Sun, 9 Feb 2025 19:29:06 +0100 Subject: [PATCH 05/12] fix alpha config example --- .../oauth2-proxy-alpha-config.cfg | 3 +- .../oauth2-proxy-alpha-config.yaml | 50 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/contrib/local-environment/oauth2-proxy-alpha-config.cfg b/contrib/local-environment/oauth2-proxy-alpha-config.cfg index 89e5a5b2..c913ec4f 100644 --- a/contrib/local-environment/oauth2-proxy-alpha-config.cfg +++ b/contrib/local-environment/oauth2-proxy-alpha-config.cfg @@ -1,5 +1,4 @@ -http_address="0.0.0.0:4180" cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" email_domains="example.com" cookie_secure="false" -redirect_url="http://localhost:4180/oauth2/callback" +redirect_url="http://oauth2-proxy.localtest.me:4180/oauth2/callback" diff --git a/contrib/local-environment/oauth2-proxy-alpha-config.yaml b/contrib/local-environment/oauth2-proxy-alpha-config.yaml index b2c9f6a8..41f07a03 100644 --- a/contrib/local-environment/oauth2-proxy-alpha-config.yaml +++ b/contrib/local-environment/oauth2-proxy-alpha-config.yaml @@ -1,23 +1,31 @@ -upstreams: - - id: httpbin - path: / - uri: http://httpbin +server: + bindAddress: "0.0.0.0:4180" +upstreamConfig: + upstreams: + - id: httpbin + path: / + uri: http://httpbin.localtest.me:8080 injectRequestHeaders: -- 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 + - name: X-Forwarded-Groups + values: + - claimSource: + claim: groups + - name: X-Forwarded-User + values: + - claimSource: + claim: user + - name: X-Forwarded-Email + values: + - claimSource: + claim: email + - name: X-Forwarded-Preferred-Username + values: + - claimSource: + claim: preferred_username providers: -- provider: oidc - clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK - clientID: oauth2-proxy - oidcConfig: - issuerURL: http://dex.localhost:5556/dex + - id: oidc + provider: oidc + clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK + clientID: oauth2-proxy + oidcConfig: + issuerURL: http://dex.localhost:5556/dex From 514296a7910e6396b290ee2e16c945f2652959f3 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Sat, 24 May 2025 17:05:33 +0200 Subject: [PATCH 06/12] resolve cipher deprecation and update mapstructures v2 Signed-off-by: Jan Larwig --- go.mod | 1 - go.sum | 2 -- pkg/apis/options/duration.go | 2 +- pkg/encryption/cipher.go | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index a34d4d50..2fbfed82 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/gorilla/mux v1.8.1 github.com/justinas/alice v1.2.0 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa - github.com/mitchellh/mapstructure v1.5.0 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/gomega v1.38.0 diff --git a/go.sum b/go.sum index c653cee5..8a0826d6 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk= diff --git a/pkg/apis/options/duration.go b/pkg/apis/options/duration.go index da13d96e..15f8776d 100644 --- a/pkg/apis/options/duration.go +++ b/pkg/apis/options/duration.go @@ -4,7 +4,7 @@ import ( "reflect" "time" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" ) // Duration is an alias for time.Duration so that we can ensure the marshalling diff --git a/pkg/encryption/cipher.go b/pkg/encryption/cipher.go index 300bba3a..ae5b50a3 100644 --- a/pkg/encryption/cipher.go +++ b/pkg/encryption/cipher.go @@ -79,7 +79,7 @@ func (c *cfbCipher) Decrypt(ciphertext []byte) ([]byte, error) { iv, ciphertext := ciphertext[:aes.BlockSize], ciphertext[aes.BlockSize:] plaintext := make([]byte, len(ciphertext)) - stream := cipher.NewCFBDecrypter(c.Block, iv) //nolint:staticcheck + stream := cipher.NewCFBEncrypter(c.Block, iv) //nolint:staticcheck stream.XORKeyStream(plaintext, ciphertext) return plaintext, nil From a646d9dea2cf64a1543014dcba4646574f2e4b89 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Sat, 24 May 2025 20:06:36 +0200 Subject: [PATCH 07/12] fix alpha config Signed-off-by: Jan Larwig --- docs/docs/configuration/alpha_config.md | 224 ++++++++++++------------ pkg/apis/options/doc.go | 2 +- pkg/encryption/cipher.go | 2 +- 3 files changed, 114 insertions(+), 114 deletions(-) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 617114b9..425dde8b 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -148,7 +148,7 @@ You must remove these options before starting OAuth2 Proxy with `--alpha-config` | Field | Type | Description | | ----- | ---- | ----------- | -| `SkipScope` | _bool_ | Skip adding the scope parameter in login request
Default value is 'false' | +| `skipScope` | _bool_ | Skip adding the scope parameter in login request
Default value is 'false' | ### AlphaOptions @@ -163,12 +163,12 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `UpstreamConfig` | _[UpstreamConfig](#upstreamconfig)_ | UpstreamConfig is used to configure upstream servers.
Once a user is authenticated, requests to the server will be proxied to
these upstream servers based on the path mappings defined in this list. | -| `InjectRequestHeaders` | _[[]Header](#header)_ | InjectRequestHeaders is used to configure headers that should be added
to requests to upstream servers.
Headers may source values from either the authenticated user's session
or from a static secret value. | -| `InjectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added
to responses from the proxy.
This is typically used when using the proxy as an external authentication
provider in conjunction with another proxy such as NGINX and its
auth_request module.
Headers may source values from either the authenticated user's session
or from a static secret value. | -| `Server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | -| `MetricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | -| `Providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | +| `upstreamConfig` | _[UpstreamConfig](#upstreamconfig)_ | UpstreamConfig is used to configure upstream servers.
Once a user is authenticated, requests to the server will be proxied to
these upstream servers based on the path mappings defined in this list. | +| `injectRequestHeaders` | _[[]Header](#header)_ | InjectRequestHeaders is used to configure headers that should be added
to requests to upstream servers.
Headers may source values from either the authenticated user's session
or from a static secret value. | +| `injectResponseHeaders` | _[[]Header](#header)_ | InjectResponseHeaders is used to configure headers that should be added
to responses from the proxy.
This is typically used when using the proxy as an external authentication
provider in conjunction with another proxy such as NGINX and its
auth_request module.
Headers may source values from either the authenticated user's session
or from a static secret value. | +| `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | +| `metricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | +| `providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | ### AzureOptions @@ -178,8 +178,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `Tenant` | _string_ | Tenant directs to a tenant-specific or common (tenant-independent) endpoint
Default value is 'common' | -| `GraphGroupField` | _string_ | GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
Default value is 'id' | +| `tenant` | _string_ | Tenant directs to a tenant-specific or common (tenant-independent) endpoint
Default value is 'common' | +| `graphGroupField` | _string_ | GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
Default value is 'id' | ### BitbucketOptions @@ -189,8 +189,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `Team` | _string_ | Team sets restrict logins to members of this team | -| `Repository` | _string_ | Repository sets restrict logins to user with access to this repository | +| `team` | _string_ | Team sets restrict logins to members of this team | +| `repository` | _string_ | Repository sets restrict logins to user with access to this repository | ### ClaimSource @@ -200,9 +200,9 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `Claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | -| `Prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | -| `BasicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | +| `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | +| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | +| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | ### GitHubOptions @@ -212,11 +212,11 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `Org` | _string_ | Org sets restrict logins to members of this organisation | -| `Team` | _string_ | Team sets restrict logins to members of this team | -| `Repo` | _string_ | Repo sets restrict logins to collaborators of this repository | -| `Token` | _string_ | Token is the token to use when verifying repository collaborators
it must have push access to the repository | -| `Users` | _[]string_ | Users allows users with these usernames to login
even if they do not belong to the specified org and team or collaborators | +| `org` | _string_ | Org sets restrict logins to members of this organisation | +| `team` | _string_ | Team sets restrict logins to members of this team | +| `repo` | _string_ | Repo sets restrict logins to collaborators of this repository | +| `token` | _string_ | Token is the token to use when verifying repository collaborators
it must have push access to the repository | +| `users` | _[]string_ | Users allows users with these usernames to login
even if they do not belong to the specified org and team or collaborators | ### GitLabOptions @@ -226,8 +226,8 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `Group` | _[]string_ | Group sets restrict logins to members of this group | -| `Projects` | _[]string_ | Projects restricts logins to members of these projects | +| `group` | _[]string_ | Group sets restrict logins to members of this group | +| `projects` | _[]string_ | Projects restricts logins to members of these projects | ### GoogleOptions @@ -237,11 +237,11 @@ ClaimSource allows loading a header value from a claim within the session | Field | Type | Description | | ----- | ---- | ----------- | -| `Groups` | _[]string_ | Groups sets restrict logins to members of this Google group | -| `AdminEmail` | _string_ | AdminEmail is the Google admin to impersonate for api calls | -| `ServiceAccountJSON` | _string_ | ServiceAccountJSON is the path to the service account json credentials | -| `UseApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | -| `TargetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | +| `group` | _[]string_ | Groups sets restrict logins to members of this Google group | +| `adminEmail` | _string_ | AdminEmail is the Google admin to impersonate for api calls | +| `serviceAccountJson` | _string_ | ServiceAccountJSON is the path to the service account json credentials | +| `useApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | +| `targetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | ### Header @@ -252,9 +252,9 @@ response header. | Field | Type | Description | | ----- | ---- | ----------- | -| `Name` | _string_ | Name is the header name to be used for this set of values.
Names should be unique within a list of Headers. | -| `PreserveRequestValue` | _bool_ | PreserveRequestValue determines whether any values for this header
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). | -| `Values` | _[[]HeaderValue](#headervalue)_ | Values contains the desired values for this header | +| `name` | _string_ | Name is the header name to be used for this set of values.
Names should be unique within a list of Headers. | +| `preserveRequestValue` | _bool_ | PreserveRequestValue determines whether any values for this header
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). | +| `values` | _[[]HeaderValue](#headervalue)_ | Values contains the desired values for this header | ### HeaderValue @@ -265,12 +265,12 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `Value` | _string_ | Value expects a base64 encoded string value. | -| `FromEnv` | _string_ | FromEnv expects the name of an environment variable. | -| `FromFile` | _string_ | FromFile expects a path to a file containing the secret value. | -| `Claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | -| `Prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | -| `BasicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | +| `value` | _string_ | Value expects a base64 encoded string value. | +| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | +| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | +| `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | +| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the
claim if it is non-empty. | +| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.
Note the value of claim will become the basic auth username and the
basicAuthPassword will be used as the password value. | ### KeycloakOptions @@ -280,8 +280,8 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `Groups` | _[]string_ | Group enables to restrict login to members of indicated group | -| `Roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) | +| `groups` | _[]string_ | Group enables to restrict login to members of indicated group | +| `roles` | _[]string_ | Role enables to restrict login to users with role (only available when using the keycloak-oidc provider) | ### LoginGovOptions @@ -291,9 +291,9 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `JWTKey` | _string_ | JWTKey is a private key in PEM format used to sign JWT, | -| `JWTKeyFile` | _string_ | JWTKeyFile is a path to the private key file in PEM format used to sign the JWT | -| `PubJWKURL` | _string_ | PubJWKURL is the JWK pubkey access endpoint | +| `jwtKey` | _string_ | JWTKey is a private key in PEM format used to sign JWT, | +| `jwtKeyFile` | _string_ | JWTKeyFile is a path to the private key file in PEM format used to sign the JWT | +| `pubjwkURL` | _string_ | PubJWKURL is the JWK pubkey access endpoint | ### LoginURLParameter @@ -371,9 +371,9 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `Name` | _string_ | Name specifies the name of the query parameter. | -| `Default` | _[]string_ | _(Optional)_ Default specifies a default value or values that will be
passed to the IdP if not overridden. | -| `Allow` | _[[]URLParameterRule](#urlparameterrule)_ | _(Optional)_ Allow specifies rules about how the default (if any) may be
overridden via the query string to `/oauth2/start`. Only
values that match one or more of the allow rules will be
forwarded to the IdP. | +| `name` | _string_ | Name specifies the name of the query parameter. | +| `default` | _[]string_ | _(Optional)_ Default specifies a default value or values that will be
passed to the IdP if not overridden. | +| `allow` | _[[]URLParameterRule](#urlparameterrule)_ | _(Optional)_ Allow specifies rules about how the default (if any) may be
overridden via the query string to `/oauth2/start`. Only
values that match one or more of the allow rules will be
forwarded to the IdP. | ### MicrosoftEntraIDOptions @@ -383,8 +383,8 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `AllowedTenants` | _[]string_ | AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are
issued by different issuers and OIDC issuer verification needs to be disabled.
When not specified, all tenants are allowed. Redundant for single-tenant apps
(regular ID token validation matches the issuer). | -| `FederatedTokenAuth` | _bool_ | FederatedTokenAuth enable oAuth2 client authentication with federated token projected
by Entra Workload Identity plugin, instead of client secret. | +| `allowedTenants` | _[]string_ | AllowedTenants is a list of allowed tenants. In case of multi-tenant apps, incoming tokens are
issued by different issuers and OIDC issuer verification needs to be disabled.
When not specified, all tenants are allowed. Redundant for single-tenant apps
(regular ID token validation matches the issuer). | +| `federatedTokenAuth` | _bool_ | FederatedTokenAuth enable oAuth2 client authentication with federated token projected
by Entra Workload Identity plugin, instead of client secret. | ### OIDCOptions @@ -394,18 +394,18 @@ character. | Field | Type | Description | | ----- | ---- | ----------- | -| `IssuerURL` | _string_ | IssuerURL is the OpenID Connect issuer URL
eg: https://accounts.google.com | -| `InsecureAllowUnverifiedEmail` | _bool_ | InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
default set to 'false' | -| `InsecureSkipIssuerVerification` | _bool_ | InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
default set to 'false' | -| `InsecureSkipNonce` | _bool_ | InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
after the initial OAuth redeem & subsequent token refreshes.
default set to 'true'
Warning: In a future release, this will change to 'false' by default for enhanced security. | -| `SkipDiscovery` | _bool_ | SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
default set to 'false' | -| `JwksURL` | _string_ | JwksURL is the OpenID Connect JWKS URL
eg: https://www.googleapis.com/oauth2/v3/certs | -| `PublicKeyFiles` | _[]string_ | PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
for verifying JWT tokens | -| `EmailClaim` | _string_ | EmailClaim indicates which claim contains the user email,
default set to 'email' | -| `GroupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups
default set to 'groups' | -| `UserIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID
default set to 'email' | -| `AudienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id
By default `aud` claim is used for verification. | -| `ExtraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed
to pass verification in addition to the client id. | +| `issuerURL` | _string_ | IssuerURL is the OpenID Connect issuer URL
eg: https://accounts.google.com | +| `insecureAllowUnverifiedEmail` | _bool_ | InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
default set to 'false' | +| `insecureSkipIssuerVerification` | _bool_ | InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
default set to 'false' | +| `insecureSkipNonce` | _bool_ | InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
after the initial OAuth redeem & subsequent token refreshes.
default set to 'true'
Warning: In a future release, this will change to 'false' by default for enhanced security. | +| `skipDiscovery` | _bool_ | SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
default set to 'false' | +| `jwksURL` | _string_ | JwksURL is the OpenID Connect JWKS URL
eg: https://www.googleapis.com/oauth2/v3/certs | +| `publicKeyFiles` | _[]string_ | PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
for verifying JWT tokens | +| `emailClaim` | _string_ | EmailClaim indicates which claim contains the user email,
default set to 'email' | +| `groupsClaim` | _string_ | GroupsClaim indicates which claim contains the user groups
default set to 'groups' | +| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID
default set to 'email' | +| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id
By default `aud` claim is used for verification. | +| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed
to pass verification in addition to the client id. | ### Provider @@ -415,36 +415,36 @@ Provider holds all configuration for a single provider | Field | Type | Description | | ----- | ---- | ----------- | -| `ClientID` | _string_ | ClientID is the OAuth Client ID that is defined in the provider
This value is required for all providers. | -| `ClientSecret` | _string_ | ClientSecret is the OAuth Client Secret that is defined in the provider
This value is required for all providers. | -| `ClientSecretFile` | _string_ | ClientSecretFile is the name of the file
containing the OAuth Client Secret, it will be used if ClientSecret is not set. | -| `KeycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. | -| `AzureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. | -| `MicrosoftEntraIDConfig` | _[MicrosoftEntraIDOptions](#microsoftentraidoptions)_ | MicrosoftEntraIDConfig holds all configurations for Entra ID provider. | +| `clientID` | _string_ | ClientID is the OAuth Client ID that is defined in the provider
This value is required for all providers. | +| `clientSecret` | _string_ | ClientSecret is the OAuth Client Secret that is defined in the provider
This value is required for all providers. | +| `clientSecretFile` | _string_ | ClientSecretFile is the name of the file
containing the OAuth Client Secret, it will be used if ClientSecret is not set. | +| `keycloakConfig` | _[KeycloakOptions](#keycloakoptions)_ | KeycloakConfig holds all configurations for Keycloak provider. | +| `azureConfig` | _[AzureOptions](#azureoptions)_ | AzureConfig holds all configurations for Azure provider. | +| `microsoftEntraIDConfig` | _[MicrosoftEntraIDOptions](#microsoftentraidoptions)_ | MicrosoftEntraIDConfig holds all configurations for Entra ID provider. | | `ADFSConfig` | _[ADFSOptions](#adfsoptions)_ | ADFSConfig holds all configurations for ADFS provider. | -| `BitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. | -| `GitHubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. | -| `GitLabConfig` | _[GitLabOptions](#gitlaboptions)_ | GitLabConfig holds all configurations for GitLab provider. | -| `GoogleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. | -| `OIDCConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider
or providers utilize OIDC configurations. | -| `LoginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | -| `ID` | _string_ | ID should be a unique identifier for the provider.
This value is required for all providers. | -| `Type` | _[ProviderType](#providertype)_ | Type is the OAuth provider
must be set from the supported providers group,
otherwise 'Google' is set as default | -| `Name` | _string_ | Name is the providers display name
if set, it will be shown to the users in the login page. | -| `CAFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
If not specified, the default Go trust sources are used instead | -| `UseSystemTrustStore` | _bool_ | 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. | -| `LoginURL` | _string_ | LoginURL is the authentication endpoint | -| `LoginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL | -| `AuthRequestResponseMode` | _string_ | AuthRequestResponseMode defines the response mode to request during authorization request | -| `RedeemURL` | _string_ | RedeemURL is the token redemption endpoint | -| `ProfileURL` | _string_ | ProfileURL is the profile access endpoint | -| `SkipClaimsFromProfileURL` | _bool_ | SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
default set to 'false' | -| `ProtectedResource` | _string_ | ProtectedResource is the resource that is protected (Azure AD and ADFS only) | -| `ValidateURL` | _string_ | ValidateURL is the access token validation endpoint | -| `Scope` | _string_ | Scope is the OAuth scope specification | -| `AllowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | -| `CodeChallengeMethod` | _string_ | The code challenge method | -| `BackendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session | +| `bitbucketConfig` | _[BitbucketOptions](#bitbucketoptions)_ | BitbucketConfig holds all configurations for Bitbucket provider. | +| `githubConfig` | _[GitHubOptions](#githuboptions)_ | GitHubConfig holds all configurations for GitHubC provider. | +| `gitlabConfig` | _[GitLabOptions](#gitlaboptions)_ | GitLabConfig holds all configurations for GitLab provider. | +| `googleConfig` | _[GoogleOptions](#googleoptions)_ | GoogleConfig holds all configurations for Google provider. | +| `oidcConfig` | _[OIDCOptions](#oidcoptions)_ | OIDCConfig holds all configurations for OIDC provider
or providers utilize OIDC configurations. | +| `loginGovConfig` | _[LoginGovOptions](#logingovoptions)_ | LoginGovConfig holds all configurations for LoginGov provider. | +| `id` | _string_ | ID should be a unique identifier for the provider.
This value is required for all providers. | +| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider
must be set from the supported providers group,
otherwise 'Google' is set as default | +| `name` | _string_ | Name is the providers display name
if set, it will be shown to the users in the login page. | +| `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
If not specified, the default Go trust sources are used instead | +| `useSystemTrustStore` | _bool_ | 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. | +| `loginURL` | _string_ | LoginURL is the authentication endpoint | +| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL | +| `authRequestResponseMode` | _string_ | AuthRequestResponseMode defines the response mode to request during authorization request | +| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint | +| `profileURL` | _string_ | ProfileURL is the profile access endpoint | +| `skipClaimsFromProfileURL` | _bool_ | SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
default set to 'false' | +| `resource` | _string_ | ProtectedResource is the resource that is protected (Azure AD and ADFS only) | +| `validateURL` | _string_ | ValidateURL is the access token validation endpoint | +| `scope` | _string_ | Scope is the OAuth scope specification | +| `allowedGroups` | _[]string_ | AllowedGroups is a list of restrict logins to members of this group | +| `code_challenge_method` | _string_ | The code challenge method | +| `backendLogoutURL` | _string_ | URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session | ### ProviderType #### (`string` alias) @@ -477,9 +477,9 @@ Only one source within the struct should be defined at any time. | Field | Type | Description | | ----- | ---- | ----------- | -| `Value` | _string_ | Value expects a base64 encoded string value. | -| `FromEnv` | _string_ | FromEnv expects the name of an environment variable. | -| `FromFile` | _string_ | FromFile expects a path to a file containing the secret value. | +| `value` | _string_ | Value expects a base64 encoded string value. | +| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | +| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | ### Server @@ -489,9 +489,9 @@ Server represents the configuration for an HTTP(S) server | Field | Type | Description | | ----- | ---- | ----------- | -| `BindAddress` | _string_ | BindAddress is the address on which to serve traffic.
Leave blank or set to "-" to disable. | -| `SecureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.
Leave blank or set to "-" to disable. | -| `TLS` | _[TLS](#tls)_ | TLS contains the information for loading the certificate and key for the
secure traffic and further configuration for the TLS server. | +| `bindAddress` | _string_ | BindAddress is the address on which to serve traffic.
Leave blank or set to "-" to disable. | +| `secureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.
Leave blank or set to "-" to disable. | +| `tls` | _[TLS](#tls)_ | TLS contains the information for loading the certificate and key for the
secure traffic and further configuration for the TLS server. | ### TLS @@ -502,10 +502,10 @@ as well as an optional minimal TLS version that is acceptable. | Field | Type | Description | | ----- | ---- | ----------- | -| `Key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.
Typically this will come from a file. | -| `Cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.
Typically this will come from a file. | -| `MinVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.
E.g. Set to "TLS1.3" to select TLS version 1.3 | -| `CipherSuites` | _[]string_ | CipherSuites is a list of TLS cipher suites that are allowed.
E.g.:
- TLS_RSA_WITH_RC4_128_SHA
- TLS_RSA_WITH_AES_256_GCM_SHA384
If not specified, the default Go safe cipher list is used.
List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants). | +| `key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.
Typically this will come from a file. | +| `cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.
Typically this will come from a file. | +| `minVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.
E.g. Set to "TLS1.3" to select TLS version 1.3 | +| `cipherSuites` | _[]string_ | CipherSuites is a list of TLS cipher suites that are allowed.
E.g.:
- TLS_RSA_WITH_RC4_128_SHA
- TLS_RSA_WITH_AES_256_GCM_SHA384
If not specified, the default Go safe cipher list is used.
List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants). | ### URLParameterRule @@ -518,8 +518,8 @@ login URL. Either Value or Pattern should be supplied, not both. | Field | Type | Description | | ----- | ---- | ----------- | -| `Value` | _string_ | A Value rule matches just this specific value | -| `Pattern` | _string_ | A Pattern rule gives a regular expression that must be matched by
some substring of the value. The expression is _not_ automatically
anchored to the start and end of the value, if you _want_ to restrict
the whole parameter value you must anchor it yourself with `^` and `$`. | +| `value` | _string_ | A Value rule matches just this specific value | +| `pattern` | _string_ | A Pattern rule gives a regular expression that must be matched by
some substring of the value. The expression is _not_ automatically
anchored to the start and end of the value, if you _want_ to restrict
the whole parameter value you must anchor it yourself with `^` and `$`. | ### Upstream @@ -530,18 +530,18 @@ Requests will be proxied to this upstream if the path matches the request path. | Field | Type | Description | | ----- | ---- | ----------- | -| `ID` | _string_ | ID should be a unique identifier for the upstream.
This value is required for all upstreams. | -| `Path` | _string_ | Path is used to map requests to the upstream server.
The closest match will take precedence and all Paths must be unique.
Path can also take a pattern when used with RewriteTarget.
Path segments can be captured and matched using regular experessions.
Eg:
- `^/foo$`: Match only the explicit path `/foo`
- `^/bar/$`: Match any path prefixed with `/bar/`
- `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget | -| `RewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to
the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
(for a `file:` upstream).
Use the Path to capture segments for reuse within the rewrite target.
Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite
the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
upstream server. Or if the upstream were `file:///app`, a request for
`/baz/info.html` would return the contents of the file `/app/foo/info.html`. | -| `URI` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File
based URL. It may include a path, in which case all requests will be served
under that path.
Eg:
- http://localhost:8080
- https://service.localhost
- https://service.localhost/path
- file://host/path
If the URI's path is "/base" and the incoming request was for "/dir",
the upstream request will be for "/base/dir". | -| `InsecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
This option is insecure and will allow potential Man-In-The-Middle attacks
between OAuth2 Proxy and the upstream server.
Defaults to false. | -| `Static` | _bool_ | 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. | -| `StaticCode` | _int_ | StaticCode determines the response code for the Static response.
This option can only be used with Static enabled. | -| `FlushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | -| `PassHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | -| `ProxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | -| `Timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | -| `DisableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | +| `id` | _string_ | ID should be a unique identifier for the upstream.
This value is required for all upstreams. | +| `path` | _string_ | Path is used to map requests to the upstream server.
The closest match will take precedence and all Paths must be unique.
Path can also take a pattern when used with RewriteTarget.
Path segments can be captured and matched using regular experessions.
Eg:
- `^/foo$`: Match only the explicit path `/foo`
- `^/bar/$`: Match any path prefixed with `/bar/`
- `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget | +| `rewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to
the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
(for a `file:` upstream).
Use the Path to capture segments for reuse within the rewrite target.
Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite
the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
upstream server. Or if the upstream were `file:///app`, a request for
`/baz/info.html` would return the contents of the file `/app/foo/info.html`. | +| `uri` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File
based URL. It may include a path, in which case all requests will be served
under that path.
Eg:
- http://localhost:8080
- https://service.localhost
- https://service.localhost/path
- file://host/path
If the URI's path is "/base" and the incoming request was for "/dir",
the upstream request will be for "/base/dir". | +| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
This option is insecure and will allow potential Man-In-The-Middle attacks
between OAuth2 Proxy and the upstream server.
Defaults to false. | +| `static` | _bool_ | 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. | +| `staticCode` | _int_ | StaticCode determines the response code for the Static response.
This option can only be used with Static enabled. | +| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when
streaming response from the upstream.
Defaults to 1 second. | +| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | +| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | +| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | +| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | ### UpstreamConfig @@ -551,5 +551,5 @@ UpstreamConfig is a collection of definitions for upstream servers. | Field | Type | Description | | ----- | ---- | ----------- | -| `ProxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for urls
like: "/%2F/" which would otherwise be redirected to "/" | -| `Upstreams` | _[[]Upstream](#upstream)_ | Upstreams represents the configuration for the upstream servers.
Requests will be proxied to this upstream if the path matches the request path. | +| `proxyRawPath` | _bool_ | ProxyRawPath will pass the raw url path to upstream allowing for urls
like: "/%2F/" which would otherwise be redirected to "/" | +| `upstreams` | _[[]Upstream](#upstream)_ | Upstreams represents the configuration for the upstream servers.
Requests will be proxied to this upstream if the path matches the request path. | diff --git a/pkg/apis/options/doc.go b/pkg/apis/options/doc.go index 8ef112dd..d4b8862d 100644 --- a/pkg/apis/options/doc.go +++ b/pkg/apis/options/doc.go @@ -1,3 +1,3 @@ -//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20220223111546-d3b50d1a591a +//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20250404153144-32055bc45bc3 //go:generate reference-gen --package github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options --types AlphaOptions --header-file ../../../docs/docs/configuration/alpha_config.md.tmpl --out-file ../../../docs/docs/configuration/alpha_config.md package options diff --git a/pkg/encryption/cipher.go b/pkg/encryption/cipher.go index ae5b50a3..300bba3a 100644 --- a/pkg/encryption/cipher.go +++ b/pkg/encryption/cipher.go @@ -79,7 +79,7 @@ func (c *cfbCipher) Decrypt(ciphertext []byte) ([]byte, error) { iv, ciphertext := ciphertext[:aes.BlockSize], ciphertext[aes.BlockSize:] plaintext := make([]byte, len(ciphertext)) - stream := cipher.NewCFBEncrypter(c.Block, iv) //nolint:staticcheck + stream := cipher.NewCFBDecrypter(c.Block, iv) //nolint:staticcheck stream.XORKeyStream(plaintext, ciphertext) return plaintext, nil From 1d73f140bf7b14634c196ada9e0926636f60f994 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Fri, 25 Jul 2025 13:29:22 +0200 Subject: [PATCH 08/12] revert: secrets as []byte instead of string Signed-off-by: Jan Larwig --- docs/docs/configuration/alpha_config.md | 4 ++-- main_test.go | 2 +- oauthproxy_test.go | 6 +++--- pkg/apis/options/legacy_options.go | 2 +- pkg/apis/options/legacy_options_test.go | 4 ++-- pkg/apis/options/load_test.go | 2 +- pkg/apis/options/secret_source.go | 4 ++-- pkg/apis/options/util/util.go | 2 +- pkg/apis/options/util/util_test.go | 2 +- pkg/header/injector_test.go | 16 ++++++++-------- pkg/http/http_suite_test.go | 12 ++++++------ pkg/http/server_test.go | 8 ++++---- pkg/middleware/headers_test.go | 8 ++++---- pkg/validation/common_test.go | 4 ++-- pkg/validation/header.go | 2 ++ pkg/validation/header_test.go | 4 ++-- 16 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 425dde8b..6e578bbb 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -265,7 +265,7 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _string_ | Value expects a base64 encoded string value. | +| `value` | _[]byte_ | Value expects a base64 encoded []byte | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | | `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | @@ -477,7 +477,7 @@ Only one source within the struct should be defined at any time. | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _string_ | Value expects a base64 encoded string value. | +| `value` | _[]byte_ | Value expects a base64 encoded []byte | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | diff --git a/main_test.go b/main_test.go index 7de1ac72..60b7eafd 100644 --- a/main_test.go +++ b/main_test.go @@ -139,7 +139,7 @@ redirect_url="http://localhost:4180/oauth2/callback" Claim: "user", Prefix: "Basic ", BasicAuthPassword: &options.SecretSource{ - Value: "super-secret-password", + Value: []byte("super-secret-password"), }, }, }, diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 7b495bff..488b8cea 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) { ClaimSource: &options.ClaimSource{ Claim: "email", BasicAuthPassword: &options.SecretSource{ - Value: basicAuthPassword, + Value: []byte(basicAuthPassword), }, }, }, @@ -1282,7 +1282,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: "This is a secure password", + Value: []byte("This is a secure password"), }, }, }, @@ -2044,7 +2044,7 @@ func baseTestOptions() *options.Options { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("This is a secure password")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))), }, }, }, diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index db73f910..99746553 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -294,7 +294,7 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header Claim: claim, Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: basicAuthPassword, + Value: []byte(basicAuthPassword), }, }, }, diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go index c5f0e4da..98a89601 100644 --- a/pkg/apis/options/legacy_options_test.go +++ b/pkg/apis/options/legacy_options_test.go @@ -369,7 +369,7 @@ var _ = Describe("Legacy Options", func() { Claim: "user", Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: basicAuthSecret, + Value: []byte(basicAuthSecret), }, }, }, @@ -409,7 +409,7 @@ var _ = Describe("Legacy Options", func() { Claim: "email", Prefix: "Basic ", BasicAuthPassword: &SecretSource{ - Value: basicAuthSecret, + Value: []byte(basicAuthSecret), }, }, }, diff --git a/pkg/apis/options/load_test.go b/pkg/apis/options/load_test.go index a0079267..29d6511e 100644 --- a/pkg/apis/options/load_test.go +++ b/pkg/apis/options/load_test.go @@ -581,7 +581,7 @@ injectResponseHeaders: Values: []HeaderValue{ { SecretSource: &SecretSource{ - Value: "secret", + Value: []byte("secret"), }, }, }, diff --git a/pkg/apis/options/secret_source.go b/pkg/apis/options/secret_source.go index 9d82c605..e73d019f 100644 --- a/pkg/apis/options/secret_source.go +++ b/pkg/apis/options/secret_source.go @@ -3,8 +3,8 @@ package options // SecretSource references an individual secret value. // Only one source within the struct should be defined at any time. type SecretSource struct { - // Value expects a base64 encoded string value. - Value string `yaml:"value,omitempty"` + // Value expects a base64 encoded []byte + Value []byte `yaml:"value,omitempty"` // FromEnv expects the name of an environment variable. FromEnv string `yaml:"fromEnv,omitempty"` diff --git a/pkg/apis/options/util/util.go b/pkg/apis/options/util/util.go index 794a6e91..03f0a134 100644 --- a/pkg/apis/options/util/util.go +++ b/pkg/apis/options/util/util.go @@ -11,7 +11,7 @@ import ( func GetSecretValue(source *options.SecretSource) ([]byte, error) { switch { case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "": - return []byte(source.Value), nil + return source.Value, nil case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "": return []byte(os.Getenv(source.FromEnv)), nil case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "": diff --git a/pkg/apis/options/util/util_test.go b/pkg/apis/options/util/util_test.go index 5c4bfa6d..e84db1ec 100644 --- a/pkg/apis/options/util/util_test.go +++ b/pkg/apis/options/util/util_test.go @@ -31,7 +31,7 @@ var _ = Describe("GetSecretValue", func() { It("returns the correct value from the string value", func() { value, err := GetSecretValue(&options.SecretSource{ - Value: "secret-value-1", + Value: []byte("secret-value-1"), }) Expect(err).ToNot(HaveOccurred()) Expect(string(value)).To(Equal("secret-value-1")) diff --git a/pkg/header/injector_test.go b/pkg/header/injector_test.go index bb37261d..25c276dc 100644 --- a/pkg/header/injector_test.go +++ b/pkg/header/injector_test.go @@ -55,7 +55,7 @@ var _ = Describe("Injector Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: "super-secret", + Value: []byte("super-secret"), }, }, }, @@ -199,7 +199,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: "basic-password", + Value: []byte("basic-password"), }, }, }, @@ -227,7 +227,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), }, }, }, @@ -322,7 +322,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), FromEnv: "SECRET_ENV", }, }, @@ -348,7 +348,7 @@ var _ = Describe("Injector Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: "basic-password", + Value: []byte("basic-password"), }, }, }, @@ -379,17 +379,17 @@ var _ = Describe("Injector Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: "major=1", + Value: []byte("major=1"), }, }, { SecretSource: &options.SecretSource{ - Value: "minor=2", + Value: []byte("minor=2"), }, }, { SecretSource: &options.SecretSource{ - Value: "patch=3", + Value: []byte("patch=3"), }, }, }, diff --git a/pkg/http/http_suite_test.go b/pkg/http/http_suite_test.go index 219f26ea..19d4d3ff 100644 --- a/pkg/http/http_suite_test.go +++ b/pkg/http/http_suite_test.go @@ -48,10 +48,10 @@ var _ = BeforeSuite(func() { certOut := new(bytes.Buffer) Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed()) - ipv4CertDataSource.Value = certOut.String() + ipv4CertDataSource.Value = certOut.Bytes() keyOut := new(bytes.Buffer) Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed()) - ipv4KeyDataSource.Value = keyOut.String() + ipv4KeyDataSource.Value = keyOut.Bytes() }) By("Generating a ipv6 self-signed cert for TLS tests", func() { @@ -61,16 +61,16 @@ var _ = BeforeSuite(func() { certOut := new(bytes.Buffer) Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed()) - ipv6CertDataSource.Value = certOut.String() + ipv6CertDataSource.Value = certOut.Bytes() keyOut := new(bytes.Buffer) Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed()) - ipv6KeyDataSource.Value = keyOut.String() + ipv6KeyDataSource.Value = keyOut.Bytes() }) By("Setting up a http client", func() { - ipv4cert, err := tls.X509KeyPair([]byte(ipv4CertDataSource.Value), []byte(ipv4KeyDataSource.Value)) + ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value) Expect(err).ToNot(HaveOccurred()) - ipv6cert, err := tls.X509KeyPair([]byte(ipv6CertDataSource.Value), []byte(ipv6KeyDataSource.Value)) + ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value) Expect(err).ToNot(HaveOccurred()) ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0]) diff --git a/pkg/http/server_test.go b/pkg/http/server_test.go index 6584b757..8dfa13af 100644 --- a/pkg/http/server_test.go +++ b/pkg/http/server_test.go @@ -234,7 +234,7 @@ var _ = Describe("Server", func() { SecureBindAddress: "127.0.0.1:0", TLS: &options.TLS{ Key: &options.SecretSource{ - Value: "invalid", + Value: []byte("invalid"), }, Cert: &ipv4CertDataSource, }, @@ -250,7 +250,7 @@ var _ = Describe("Server", func() { TLS: &options.TLS{ Key: &ipv4KeyDataSource, Cert: &options.SecretSource{ - Value: "invalid", + Value: []byte("invalid"), }, }, }, @@ -506,7 +506,7 @@ var _ = Describe("Server", func() { SecureBindAddress: "[::1]:0", TLS: &options.TLS{ Key: &options.SecretSource{ - Value: "invalid", + Value: []byte("invalid"), }, Cert: &ipv6CertDataSource, }, @@ -523,7 +523,7 @@ var _ = Describe("Server", func() { TLS: &options.TLS{ Key: &ipv6KeyDataSource, Cert: &options.SecretSource{ - Value: "invalid", + Value: []byte("invalid"), }, }, }, diff --git a/pkg/middleware/headers_test.go b/pkg/middleware/headers_test.go index b1848ae0..06440eea 100644 --- a/pkg/middleware/headers_test.go +++ b/pkg/middleware/headers_test.go @@ -188,7 +188,7 @@ var _ = Describe("Headers Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), FromEnv: "SECRET_ENV", }, }, @@ -260,7 +260,7 @@ var _ = Describe("Headers Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: "_oauth2_proxy=ey123123123", + Value: []byte("_oauth2_proxy=ey123123123"), }, }, }, @@ -270,7 +270,7 @@ var _ = Describe("Headers Suite", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: "oauth_user", + Value: []byte("oauth_user"), }, }, }, @@ -416,7 +416,7 @@ var _ = Describe("Headers Suite", func() { ClaimSource: &options.ClaimSource{ Claim: "user", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("basic-password")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))), FromEnv: "SECRET_ENV", }, }, diff --git a/pkg/validation/common_test.go b/pkg/validation/common_test.go index bb7c2dd6..9e873c35 100644 --- a/pkg/validation/common_test.go +++ b/pkg/validation/common_test.go @@ -9,12 +9,12 @@ import ( ) var _ = Describe("Common", func() { - var validSecretSourceValue string + var validSecretSourceValue []byte const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV" var validSecretSourceFile string BeforeEach(func() { - validSecretSourceValue = "This is a secret source value" + validSecretSourceValue = []byte("This is a secret source value") Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed()) tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test") Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/validation/header.go b/pkg/validation/header.go index 713113f4..b1258144 100644 --- a/pkg/validation/header.go +++ b/pkg/validation/header.go @@ -51,9 +51,11 @@ func validateHeaderValue(_ string, value options.HeaderValue) []string { func validateHeaderValueClaimSource(claim options.ClaimSource) []string { msgs := []string{} + if claim.Claim == "" { msgs = append(msgs, "claim should not be empty") } + if claim.BasicAuthPassword != nil { msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...) } diff --git a/pkg/validation/header_test.go b/pkg/validation/header_test.go index 88849ea1..2d9ef6dd 100644 --- a/pkg/validation/header_test.go +++ b/pkg/validation/header_test.go @@ -30,7 +30,7 @@ var _ = Describe("Headers", func() { Values: []options.HeaderValue{ { SecretSource: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("secret")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))), }, }, }, @@ -43,7 +43,7 @@ var _ = Describe("Headers", func() { ClaimSource: &options.ClaimSource{ Claim: "email", BasicAuthPassword: &options.SecretSource{ - Value: base64.StdEncoding.EncodeToString([]byte("secret")), + Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))), }, }, }, From fa2587ac09284bba31c25f6ab777f9945ab41bfd Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Fri, 25 Jul 2025 15:08:50 +0200 Subject: [PATCH 09/12] fix merge problems and test cases Signed-off-by: Jan Larwig --- main.go | 2 +- main_test.go | 16 ++++++++-------- pkg/apis/options/header.go | 2 +- pkg/apis/options/{duration.go => hooks.go} | 14 ++++++++++++++ .../options/{duration_test.go => hooks_test.go} | 16 +++++++++++++++- pkg/apis/options/load.go | 5 ++++- pkg/apis/options/load_test.go | 4 ++-- pkg/apis/options/providers.go | 12 ++++++------ pkg/apis/options/upstreams.go | 10 +++++----- 9 files changed, 56 insertions(+), 25 deletions(-) rename pkg/apis/options/{duration.go => hooks.go} (78%) rename pkg/apis/options/{duration_test.go => hooks_test.go} (83%) 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. From 7e070d1deea3dbdf5e67531bf80b725f1e45c9c0 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Sat, 26 Jul 2025 12:46:17 +0200 Subject: [PATCH 10/12] fix test setup and add local image build make target Signed-off-by: Jan Larwig --- Makefile | 4 ++ .../docker-compose-alpha-config.yaml | 49 ++++++++++++++++++- contrib/local-environment/docker-compose.yaml | 2 +- .../oauth2-proxy-alpha-config.yaml | 12 +---- docs/docs/configuration/alpha_config.md | 4 +- pkg/apis/options/secret_source.go | 2 +- 6 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 091ca726..1299a529 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,10 @@ DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --pus .PHONY: build-docker build-docker: build-distroless build-alpine ## Build multi architecture docker images in both flavours (distroless / alpine) +.PHONY: build-docker-local +build-docker-local: ## Build distroless docker image and locally load into docker images + $(DOCKER_BUILDX) --load -t $(REGISTRY)/$(REPOSITORY):${VERSION}-local . + .PHONY: build-distroless build-distroless: ## Build multi architecture distroless based docker image $(DOCKER_BUILDX_X_PLATFORM) -t $(REGISTRY)/$(REPOSITORY):latest -t $(REGISTRY)/$(REPOSITORY):${VERSION} . diff --git a/contrib/local-environment/docker-compose-alpha-config.yaml b/contrib/local-environment/docker-compose-alpha-config.yaml index a43dc457..42bb2d1f 100644 --- a/contrib/local-environment/docker-compose-alpha-config.yaml +++ b/contrib/local-environment/docker-compose-alpha-config.yaml @@ -10,11 +10,58 @@ # make alpha-config- (eg make nginx-up, make nginx-down) # # Access http://localhost:4180 to initiate a login cycle -version: '3.0' +version: "3.0" services: oauth2-proxy: + container_name: oauth2-proxy image: quay.io/oauth2-proxy/oauth2-proxy:v7.12.0 command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml + hostname: oauth2-proxy volumes: - "./oauth2-proxy-alpha-config.cfg:/oauth2-proxy.cfg" - "./oauth2-proxy-alpha-config.yaml:/oauth2-proxy-alpha-config.yaml" + restart: unless-stopped + ports: + - 4180:4180/tcp + networks: + dex: {} + httpbin: {} + depends_on: + - dex + - httpbin + dex: + container_name: dex + image: ghcr.io/dexidp/dex:v2.43.1 + command: dex serve /dex.yaml + hostname: dex + volumes: + - "./dex.yaml:/dex.yaml" + restart: unless-stopped + ports: + - 5556:5556/tcp + networks: + dex: + aliases: + - dex.localtest.me + etcd: {} + depends_on: + - etcd + httpbin: + container_name: httpbin + image: kennethreitz/httpbin + ports: [] + networks: + httpbin: {} + etcd: + container_name: etcd + image: gcr.io/etcd-development/etcd:v3.6.2 + entrypoint: /usr/local/bin/etcd + command: + - --listen-client-urls=http://0.0.0.0:2379 + - --advertise-client-urls=http://etcd:2379 + networks: + etcd: {} +networks: + dex: {} + etcd: {} + httpbin: {} diff --git a/contrib/local-environment/docker-compose.yaml b/contrib/local-environment/docker-compose.yaml index 6490ca8e..b787e9e0 100644 --- a/contrib/local-environment/docker-compose.yaml +++ b/contrib/local-environment/docker-compose.yaml @@ -9,7 +9,7 @@ # make (eg. make up, make down) # # Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle -version: '3.0' +version: "3.0" services: oauth2-proxy: container_name: oauth2-proxy diff --git a/contrib/local-environment/oauth2-proxy-alpha-config.yaml b/contrib/local-environment/oauth2-proxy-alpha-config.yaml index 41f07a03..e423db98 100644 --- a/contrib/local-environment/oauth2-proxy-alpha-config.yaml +++ b/contrib/local-environment/oauth2-proxy-alpha-config.yaml @@ -4,12 +4,8 @@ upstreamConfig: upstreams: - id: httpbin path: / - uri: http://httpbin.localtest.me:8080 + uri: http://httpbin injectRequestHeaders: - - name: X-Forwarded-Groups - values: - - claimSource: - claim: groups - name: X-Forwarded-User values: - claimSource: @@ -18,14 +14,10 @@ injectRequestHeaders: values: - claimSource: claim: email - - name: X-Forwarded-Preferred-Username - values: - - claimSource: - claim: preferred_username providers: - id: oidc provider: oidc clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK clientID: oauth2-proxy oidcConfig: - issuerURL: http://dex.localhost:5556/dex + issuerURL: http://dex.localtest.me:5556/dex diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 6e578bbb..2be241a7 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -265,7 +265,7 @@ make up the header value | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _[]byte_ | Value expects a base64 encoded []byte | +| `value` | _[]byte_ | Value expects a base64 encoded string value. | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | | `claim` | _string_ | Claim is the name of the claim in the session that the value should be
loaded from. Available claims: `access_token` `id_token` `created_at`
`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. | @@ -477,7 +477,7 @@ Only one source within the struct should be defined at any time. | Field | Type | Description | | ----- | ---- | ----------- | -| `value` | _[]byte_ | Value expects a base64 encoded []byte | +| `value` | _[]byte_ | Value expects a base64 encoded string value. | | `fromEnv` | _string_ | FromEnv expects the name of an environment variable. | | `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. | diff --git a/pkg/apis/options/secret_source.go b/pkg/apis/options/secret_source.go index e73d019f..848f1635 100644 --- a/pkg/apis/options/secret_source.go +++ b/pkg/apis/options/secret_source.go @@ -3,7 +3,7 @@ package options // SecretSource references an individual secret value. // Only one source within the struct should be defined at any time. type SecretSource struct { - // Value expects a base64 encoded []byte + // Value expects a base64 encoded string value. Value []byte `yaml:"value,omitempty"` // FromEnv expects the name of an environment variable. From 3a529608711f11f0da30dbad73a26107b7629eeb Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Tue, 19 Aug 2025 14:43:32 +0200 Subject: [PATCH 11/12] return nil directly Signed-off-by: Jan Larwig --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 7142ae0b..1525e94c 100644 --- a/main.go +++ b/main.go @@ -77,7 +77,8 @@ func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, arg 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 loadYamlOptions(yamlConfig, config, extraFlags, args) } - return opts, err + + return opts, nil } // loadLegacyOptions loads the old toml options using the legacy flagset From 9db77384d07b09b6a4cf4ef49330e06696cdf5b7 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Tue, 19 Aug 2025 16:27:53 +0200 Subject: [PATCH 12/12] feat: migrate all alpha config booleans to pointers Signed-off-by: Jan Larwig --- main_test.go | 73 +++++++++++++------- oauthproxy_test.go | 5 +- pkg/apis/options/header.go | 2 +- pkg/apis/options/legacy_options.go | 29 ++++---- pkg/apis/options/legacy_options_test.go | 88 ++++++++++++------------- pkg/apis/options/providers.go | 26 ++++---- pkg/apis/options/upstreams.go | 12 ++-- pkg/middleware/headers.go | 2 +- pkg/middleware/headers_test.go | 9 +-- pkg/upstream/http.go | 6 +- pkg/upstream/http_test.go | 23 ++++--- pkg/upstream/proxy.go | 4 +- pkg/upstream/proxy_test.go | 11 ++-- pkg/util/ptr/ptr.go | 14 ++++ pkg/util/ptr/ptr_test.go | 38 +++++++++++ pkg/validation/options.go | 2 +- pkg/validation/options_test.go | 5 +- pkg/validation/providers.go | 7 +- pkg/validation/upstreams.go | 10 +-- pkg/validation/upstreams_test.go | 12 ++-- providers/adfs.go | 2 +- providers/adfs_test.go | 3 +- providers/google.go | 4 +- providers/ms_entra_id.go | 2 +- providers/ms_entra_id_test.go | 7 +- providers/oidc.go | 2 +- providers/oidc_test.go | 2 +- providers/providers.go | 8 +-- providers/providers_test.go | 9 +-- 29 files changed, 252 insertions(+), 165 deletions(-) create mode 100644 pkg/util/ptr/ptr.go create mode 100644 pkg/util/ptr/ptr_test.go diff --git a/main_test.go b/main_test.go index 709c6857..b9ec39fb 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" . "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" @@ -25,11 +26,12 @@ set_basic_auth="true" basic_auth_password="c3VwZXItc2VjcmV0LXBhc3N3b3Jk" client_id="oauth2-proxy" client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" +google_admin_email="admin@example.com" +google_target_principal="principal" ` const testAlphaConfig = ` upstreamConfig: - proxyrawpath: false upstreams: - id: / path: / @@ -38,8 +40,11 @@ upstreamConfig: passHostHeader: true proxyWebSockets: true timeout: 30s + insecureSkipTLSVerify: false + disableKeepAlives: false injectRequestHeaders: - name: Authorization + preserveRequestValue: false values: - claimSource: claim: user @@ -47,18 +52,22 @@ injectRequestHeaders: basicAuthPassword: value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk - name: X-Forwarded-Groups + preserveRequestValue: false values: - claimSource: claim: groups - name: X-Forwarded-User + preserveRequestValue: false values: - claimSource: claim: user - name: X-Forwarded-Email + preserveRequestValue: false values: - claimSource: claim: email - name: X-Forwarded-Preferred-Username + preserveRequestValue: false values: - claimSource: claim: preferred_username @@ -77,12 +86,17 @@ providers: provider: google clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK clientID: oauth2-proxy - azureConfig: - tenant: common + useSystemTrustStore: false + skipClaimsFromProfileURL: false + googleConfig: + adminEmail: admin@example.com + targetPrincipal: principal + useApplicationDefaultCredentials: false oidcConfig: groupsClaim: groups emailClaim: email userIDClaim: email + insecureSkipIssuerVerification: false insecureSkipNonce: true audienceClaims: [aud] extraAudiences: [] @@ -100,10 +114,6 @@ cookie_secure="false" redirect_url="http://localhost:4180/oauth2/callback" ` - boolPtr := func(b bool) *bool { - return &b - } - durationPtr := func(d time.Duration) *time.Duration { return &d } @@ -120,13 +130,15 @@ redirect_url="http://localhost:4180/oauth2/callback" opts.UpstreamServers = options.UpstreamConfig{ Upstreams: []options.Upstream{ { - ID: "/", - Path: "/", - URI: "http://httpbin", - FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval), - PassHostHeader: boolPtr(true), - ProxyWebSockets: boolPtr(true), - Timeout: durationPtr(options.DefaultUpstreamTimeout), + ID: "/", + Path: "/", + URI: "http://httpbin", + FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval), + PassHostHeader: ptr.Ptr(true), + ProxyWebSockets: ptr.Ptr(true), + Timeout: durationPtr(options.DefaultUpstreamTimeout), + InsecureSkipTLSVerify: ptr.Ptr(false), + DisableKeepAlives: ptr.Ptr(false), }, }, } @@ -146,25 +158,38 @@ redirect_url="http://localhost:4180/oauth2/callback" }, } + authHeader.PreserveRequestValue = ptr.Ptr(false) opts.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...) + + authHeader.PreserveRequestValue = nil opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader) opts.Providers = options.Providers{ options.Provider{ - ID: "google=oauth2-proxy", - Type: "google", - ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK", - ClientID: "oauth2-proxy", + ID: "google=oauth2-proxy", + Type: "google", + ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK", + ClientID: "oauth2-proxy", + UseSystemTrustStore: ptr.Ptr(false), + SkipClaimsFromProfileURL: ptr.Ptr(false), + GoogleConfig: options.GoogleOptions{ + AdminEmail: "admin@example.com", + UseApplicationDefaultCredentials: ptr.Ptr(false), + TargetPrincipal: "principal", + }, AzureConfig: options.AzureOptions{ Tenant: "common", }, OIDCConfig: options.OIDCOptions{ - GroupsClaim: "groups", - EmailClaim: "email", - UserIDClaim: "email", - AudienceClaims: []string{"aud"}, - ExtraAudiences: []string{}, - InsecureSkipNonce: true, + GroupsClaim: "groups", + EmailClaim: "email", + UserIDClaim: "email", + AudienceClaims: []string{"aud"}, + ExtraAudiences: []string{}, + InsecureSkipNonce: ptr.Ptr(true), + InsecureAllowUnverifiedEmail: ptr.Ptr(false), + InsecureSkipIssuerVerification: ptr.Ptr(false), + SkipDiscovery: ptr.Ptr(false), }, LoginURLParameters: []options.LoginURLParameter{ {Name: "approval_prompt", Default: []string{"force"}}, diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 488b8cea..a156214c 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -23,6 +23,7 @@ import ( internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc" sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" "github.com/oauth2-proxy/oauth2-proxy/v7/providers" "github.com/stretchr/testify/assert" @@ -506,7 +507,7 @@ func TestStaticProxyUpstream(t *testing.T) { ProxyUpstream: options.Upstream{ ID: "static-proxy", Path: "/static-proxy", - Static: true, + Static: ptr.Ptr(true), }, }) if err != nil { @@ -2223,7 +2224,7 @@ func TestTrustedIPs(t *testing.T) { { ID: "static", Path: "/", - Static: true, + Static: ptr.Ptr(true), }, }, } diff --git a/pkg/apis/options/header.go b/pkg/apis/options/header.go index 976e4f0a..b50b9582 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"` + PreserveRequestValue *bool `yaml:"preserveRequestValue,omitempty"` // Values contains the desired values for this header Values []HeaderValue `yaml:"values,omitempty"` diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index 99746553..e70ae964 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -9,6 +9,7 @@ import ( "time" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" "github.com/spf13/pflag" ) @@ -142,12 +143,12 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { ID: u.Path, Path: u.Path, URI: upstreamString, - InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify, + InsecureSkipTLSVerify: &l.SSLUpstreamInsecureSkipVerify, PassHostHeader: &l.PassHostHeader, ProxyWebSockets: &l.ProxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, - DisableKeepAlives: l.DisableKeepAlives, + DisableKeepAlives: &l.DisableKeepAlives, } switch u.Scheme { @@ -164,7 +165,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { logger.Errorf("unable to convert %q to int, use default \"200\"", u.Host) responseCode = 200 } - upstream.Static = true + upstream.Static = ptr.Ptr(true) upstream.StaticCode = &responseCode // This is not allowed to be empty and must be unique @@ -175,12 +176,12 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { // Force defaults compatible with static responses upstream.URI = "" - upstream.InsecureSkipTLSVerify = false + upstream.InsecureSkipTLSVerify = ptr.Ptr(false) upstream.PassHostHeader = nil upstream.ProxyWebSockets = nil upstream.FlushInterval = nil upstream.Timeout = nil - upstream.DisableKeepAlives = false + upstream.DisableKeepAlives = ptr.Ptr(false) case "unix": upstream.Path = "/" } @@ -253,7 +254,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header { } for i := range requestHeaders { - requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders + requestHeaders[i].PreserveRequestValue = ptr.Ptr(!l.SkipAuthStripHeaders) } return requestHeaders @@ -680,11 +681,11 @@ func (l *LegacyProvider) convert() (Providers, error) { ClientSecretFile: l.ClientSecretFile, Type: ProviderType(l.ProviderType), CAFiles: l.ProviderCAFiles, - UseSystemTrustStore: l.UseSystemTrustStore, + UseSystemTrustStore: &l.UseSystemTrustStore, LoginURL: l.LoginURL, RedeemURL: l.RedeemURL, ProfileURL: l.ProfileURL, - SkipClaimsFromProfileURL: l.SkipClaimsFromProfileURL, + SkipClaimsFromProfileURL: &l.SkipClaimsFromProfileURL, ProtectedResource: l.ProtectedResource, ValidateURL: l.ValidateURL, Scope: l.Scope, @@ -697,10 +698,10 @@ func (l *LegacyProvider) convert() (Providers, error) { // This part is out of the switch section for all providers that support OIDC provider.OIDCConfig = OIDCOptions{ IssuerURL: l.OIDCIssuerURL, - InsecureAllowUnverifiedEmail: l.InsecureOIDCAllowUnverifiedEmail, - InsecureSkipIssuerVerification: l.InsecureOIDCSkipIssuerVerification, - InsecureSkipNonce: l.InsecureOIDCSkipNonce, - SkipDiscovery: l.SkipOIDCDiscovery, + InsecureAllowUnverifiedEmail: &l.InsecureOIDCAllowUnverifiedEmail, + InsecureSkipIssuerVerification: &l.InsecureOIDCSkipIssuerVerification, + InsecureSkipNonce: &l.InsecureOIDCSkipNonce, + SkipDiscovery: &l.SkipOIDCDiscovery, JwksURL: l.OIDCJwksURL, UserIDClaim: l.UserIDClaim, EmailClaim: l.OIDCEmailClaim, @@ -768,13 +769,13 @@ func (l *LegacyProvider) convert() (Providers, error) { Groups: l.GoogleGroups, AdminEmail: l.GoogleAdminEmail, ServiceAccountJSON: l.GoogleServiceAccountJSON, - UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials, + UseApplicationDefaultCredentials: &l.GoogleUseApplicationDefaultCredentials, TargetPrincipal: l.GoogleTargetPrincipal, } case "entra-id": provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{ AllowedTenants: l.EntraIDAllowedTenants, - FederatedTokenAuth: l.EntraIDFederatedTokenAuth, + FederatedTokenAuth: &l.EntraIDFederatedTokenAuth, } } diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go index 98a89601..4348021b 100644 --- a/pkg/apis/options/legacy_options_test.go +++ b/pkg/apis/options/legacy_options_test.go @@ -3,6 +3,7 @@ package options import ( "time" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -26,7 +27,6 @@ var _ = Describe("Legacy Options", func() { legacyOpts.LegacyProvider.ClientID = "oauth-proxy" legacyOpts.LegacyUpstreams.DisableKeepAlives = false - truth := true staticCode := 204 opts.UpstreamServers = UpstreamConfig{ Upstreams: []Upstream{ @@ -35,35 +35,35 @@ var _ = Describe("Legacy Options", func() { Path: "/baz", URI: "http://foo.bar/baz", FlushInterval: &flushInterval, - InsecureSkipTLSVerify: true, - PassHostHeader: &truth, - ProxyWebSockets: &truth, + InsecureSkipTLSVerify: ptr.Ptr(true), + PassHostHeader: ptr.Ptr(true), + ProxyWebSockets: ptr.Ptr(true), Timeout: &timeout, - DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, + DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives, }, { ID: "/bar", Path: "/bar", URI: "file:///var/lib/website", FlushInterval: &flushInterval, - InsecureSkipTLSVerify: true, - PassHostHeader: &truth, - ProxyWebSockets: &truth, + InsecureSkipTLSVerify: ptr.Ptr(true), + PassHostHeader: ptr.Ptr(true), + ProxyWebSockets: ptr.Ptr(true), Timeout: &timeout, - DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, + DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives, }, { ID: "static://204", Path: "/", URI: "", - Static: true, + Static: ptr.Ptr(true), StaticCode: &staticCode, FlushInterval: nil, - InsecureSkipTLSVerify: false, + InsecureSkipTLSVerify: ptr.Ptr(false), PassHostHeader: nil, ProxyWebSockets: nil, Timeout: nil, - DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, + DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives, }, }, } @@ -71,7 +71,7 @@ var _ = Describe("Legacy Options", func() { opts.InjectRequestHeaders = []Header{ { Name: "X-Forwarded-Groups", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -82,7 +82,7 @@ var _ = Describe("Legacy Options", func() { }, { Name: "X-Forwarded-User", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -93,7 +93,7 @@ var _ = Describe("Legacy Options", func() { }, { Name: "X-Forwarded-Email", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -104,7 +104,7 @@ var _ = Describe("Legacy Options", func() { }, { Name: "X-Forwarded-Preferred-Username", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -123,7 +123,7 @@ var _ = Describe("Legacy Options", func() { opts.Providers[0].ClientID = "oauth-proxy" opts.Providers[0].ID = "google=oauth-proxy" - opts.Providers[0].OIDCConfig.InsecureSkipNonce = true + opts.Providers[0].OIDCConfig.InsecureSkipNonce = ptr.Ptr(true) opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"} opts.Providers[0].OIDCConfig.ExtraAudiences = []string{} opts.Providers[0].LoginURLParameters = []LoginURLParameter{ @@ -157,12 +157,12 @@ var _ = Describe("Legacy Options", func() { ID: "/baz", Path: "/baz", URI: validHTTP, - InsecureSkipTLSVerify: skipVerify, + InsecureSkipTLSVerify: &skipVerify, PassHostHeader: &passHostHeader, ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, - DisableKeepAlives: disableKeepAlives, + DisableKeepAlives: &disableKeepAlives, } // Test cases and expected outcomes @@ -171,12 +171,12 @@ var _ = Describe("Legacy Options", func() { ID: "/", Path: "/", URI: emptyPathHTTP, - InsecureSkipTLSVerify: skipVerify, + InsecureSkipTLSVerify: &skipVerify, PassHostHeader: &passHostHeader, ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, - DisableKeepAlives: disableKeepAlives, + DisableKeepAlives: &disableKeepAlives, } validFileWithFragment := "file:///var/lib/website#/bar" @@ -184,12 +184,12 @@ var _ = Describe("Legacy Options", func() { ID: "/bar", Path: "/bar", URI: "file:///var/lib/website", - InsecureSkipTLSVerify: skipVerify, + InsecureSkipTLSVerify: &skipVerify, PassHostHeader: &passHostHeader, ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, - DisableKeepAlives: disableKeepAlives, + DisableKeepAlives: &disableKeepAlives, } validStatic := "static://204" @@ -198,14 +198,14 @@ var _ = Describe("Legacy Options", func() { ID: validStatic, Path: "/", URI: "", - Static: true, + Static: ptr.Ptr(true), StaticCode: &validStaticCode, - InsecureSkipTLSVerify: false, + InsecureSkipTLSVerify: ptr.Ptr(false), PassHostHeader: nil, ProxyWebSockets: nil, FlushInterval: nil, Timeout: nil, - DisableKeepAlives: false, + DisableKeepAlives: ptr.Ptr(false), } invalidStatic := "static://abc" @@ -214,14 +214,14 @@ var _ = Describe("Legacy Options", func() { ID: invalidStatic, Path: "/", URI: "", - Static: true, + Static: ptr.Ptr(true), StaticCode: &invalidStaticCode, - InsecureSkipTLSVerify: false, + InsecureSkipTLSVerify: ptr.Ptr(false), PassHostHeader: nil, ProxyWebSockets: nil, FlushInterval: nil, Timeout: nil, - DisableKeepAlives: false, + DisableKeepAlives: ptr.Ptr(false), } invalidHTTP := ":foo" @@ -308,13 +308,13 @@ var _ = Describe("Legacy Options", func() { } withPreserveRequestValue := func(h Header, preserve bool) Header { - h.PreserveRequestValue = preserve + h.PreserveRequestValue = &preserve return h } xForwardedUser := Header{ Name: "X-Forwarded-User", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -326,7 +326,7 @@ var _ = Describe("Legacy Options", func() { xForwardedEmail := Header{ Name: "X-Forwarded-Email", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -338,7 +338,7 @@ var _ = Describe("Legacy Options", func() { xForwardedGroups := Header{ Name: "X-Forwarded-Groups", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -350,7 +350,7 @@ var _ = Describe("Legacy Options", func() { xForwardedPreferredUsername := Header{ Name: "X-Forwarded-Preferred-Username", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -362,7 +362,7 @@ var _ = Describe("Legacy Options", func() { basicAuthHeader := Header{ Name: "Authorization", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -378,7 +378,7 @@ var _ = Describe("Legacy Options", func() { xForwardedUserWithEmail := Header{ Name: "X-Forwarded-User", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -390,7 +390,7 @@ var _ = Describe("Legacy Options", func() { xForwardedAccessToken := Header{ Name: "X-Forwarded-Access-Token", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -402,7 +402,7 @@ var _ = Describe("Legacy Options", func() { basicAuthHeaderWithEmail := Header{ Name: "Authorization", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -418,7 +418,7 @@ var _ = Describe("Legacy Options", func() { xAuthRequestUser := Header{ Name: "X-Auth-Request-User", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -430,7 +430,7 @@ var _ = Describe("Legacy Options", func() { xAuthRequestEmail := Header{ Name: "X-Auth-Request-Email", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -442,7 +442,7 @@ var _ = Describe("Legacy Options", func() { xAuthRequestGroups := Header{ Name: "X-Auth-Request-Groups", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -454,7 +454,7 @@ var _ = Describe("Legacy Options", func() { xAuthRequestPreferredUsername := Header{ Name: "X-Auth-Request-Preferred-Username", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -466,7 +466,7 @@ var _ = Describe("Legacy Options", func() { xAuthRequestAccessToken := Header{ Name: "X-Auth-Request-Access-Token", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ @@ -478,7 +478,7 @@ var _ = Describe("Legacy Options", func() { authorizationHeader := Header{ Name: "Authorization", - PreserveRequestValue: false, + PreserveRequestValue: ptr.Ptr(false), Values: []HeaderValue{ { ClaimSource: &ClaimSource{ diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go index 904fd0ac..21f622dd 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -1,5 +1,7 @@ package options +import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" + const ( // OIDCEmailClaim is the generic email claim used by the OIDC provider. OIDCEmailClaim = "email" @@ -67,7 +69,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"` + UseSystemTrustStore *bool `yaml:"useSystemTrustStore,omitempty"` // 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 +82,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"` + SkipClaimsFromProfileURL *bool `yaml:"skipClaimsFromProfileURL,omitempty"` // 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 +183,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"` + FederatedTokenAuth *bool `yaml:"federatedTokenAuth,omitempty"` } type ADFSOptions struct { // Skip adding the scope parameter in login request // Default value is 'false' - SkipScope bool `yaml:"skipScope"` + SkipScope *bool `yaml:"skipScope,omitempty"` } type BitbucketOptions struct { @@ -227,7 +229,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"` + UseApplicationDefaultCredentials *bool `yaml:"useApplicationDefaultCredentials,omitempty"` // TargetPrincipal is the Google Service Account used for Application Default Credentials TargetPrincipal string `yaml:"targetPrincipal,omitempty"` } @@ -238,19 +240,19 @@ type OIDCOptions struct { IssuerURL string `yaml:"issuerURL,omitempty"` // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified // default set to 'false' - InsecureAllowUnverifiedEmail bool `yaml:"insecureAllowUnverifiedEmail"` + InsecureAllowUnverifiedEmail *bool `yaml:"insecureAllowUnverifiedEmail,omitempty"` // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL // default set to 'false' - InsecureSkipIssuerVerification bool `yaml:"insecureSkipIssuerVerification"` + InsecureSkipIssuerVerification *bool `yaml:"insecureSkipIssuerVerification,omitempty"` // InsecureSkipNonce skips verifying the ID Token's nonce claim that must match // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked // after the initial OAuth redeem & subsequent token refreshes. // default set to 'true' // Warning: In a future release, this will change to 'false' by default for enhanced security. - InsecureSkipNonce bool `yaml:"insecureSkipNonce"` + InsecureSkipNonce *bool `yaml:"insecureSkipNonce,omitempty"` // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints // default set to 'false' - SkipDiscovery bool `yaml:"skipDiscovery"` + SkipDiscovery *bool `yaml:"skipDiscovery,omitempty"` // JwksURL is the OpenID Connect JWKS URL // eg: https://www.googleapis.com/oauth2/v3/certs JwksURL string `yaml:"jwksURL,omitempty"` @@ -291,9 +293,9 @@ func providerDefaults() Providers { Tenant: "common", }, OIDCConfig: OIDCOptions{ - InsecureAllowUnverifiedEmail: false, - InsecureSkipNonce: true, - SkipDiscovery: false, + InsecureAllowUnverifiedEmail: ptr.Ptr(false), + InsecureSkipNonce: ptr.Ptr(true), + SkipDiscovery: ptr.Ptr(false), UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim EmailClaim: OIDCEmailClaim, GroupsClaim: OIDCGroupsClaim, diff --git a/pkg/apis/options/upstreams.go b/pkg/apis/options/upstreams.go index 2b90e632..f12a4bf3 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"` + ProxyRawPath *bool `yaml:"proxyRawPath,omitempty"` // 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"` + InsecureSkipTLSVerify *bool `yaml:"insecureSkipTLSVerify,omitempty"` // 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"` + Static *bool `yaml:"static,omitempty"` // 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"` + PassHostHeader *bool `yaml:"passHostHeader,omitempty"` // ProxyWebSockets enables proxying of websockets to upstream servers // Defaults to true. - ProxyWebSockets *bool `yaml:"proxyWebSockets"` + ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"` // Timeout is the maximum duration the server will wait for a response from the upstream server. // Defaults to 30 seconds. @@ -96,5 +96,5 @@ type Upstream struct { // DisableKeepAlives disables HTTP keep-alive connections to the upstream server. // Defaults to false. - DisableKeepAlives bool `yaml:"disableKeepAlives,omitempty"` + DisableKeepAlives *bool `yaml:"disableKeepAlives,omitempty"` } diff --git a/pkg/middleware/headers.go b/pkg/middleware/headers.go index 8d2f8e3e..3fe56d42 100644 --- a/pkg/middleware/headers.go +++ b/pkg/middleware/headers.go @@ -27,7 +27,7 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro func newStripHeaders(headers []options.Header) alice.Constructor { headersToStrip := []string{} for _, header := range headers { - if !header.PreserveRequestValue { + if !(*header.PreserveRequestValue) { headersToStrip = append(headersToStrip, header.Name) } } diff --git a/pkg/middleware/headers_test.go b/pkg/middleware/headers_test.go index 06440eea..c05a732f 100644 --- a/pkg/middleware/headers_test.go +++ b/pkg/middleware/headers_test.go @@ -8,6 +8,7 @@ import ( middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -115,7 +116,7 @@ var _ = Describe("Headers Suite", func() { headers: []options.Header{ { Name: "Claim", - PreserveRequestValue: true, + PreserveRequestValue: ptr.Ptr(true), Values: []options.HeaderValue{ { ClaimSource: &options.ClaimSource{ @@ -160,7 +161,7 @@ var _ = Describe("Headers Suite", func() { headers: []options.Header{ { Name: "Claim", - PreserveRequestValue: true, + PreserveRequestValue: ptr.Ptr(true), Values: []options.HeaderValue{ { ClaimSource: &options.ClaimSource{ @@ -341,7 +342,7 @@ var _ = Describe("Headers Suite", func() { headers: []options.Header{ { Name: "Claim", - PreserveRequestValue: true, + PreserveRequestValue: ptr.Ptr(true), Values: []options.HeaderValue{ { ClaimSource: &options.ClaimSource{ @@ -388,7 +389,7 @@ var _ = Describe("Headers Suite", func() { headers: []options.Header{ { Name: "Claim", - PreserveRequestValue: true, + PreserveRequestValue: ptr.Ptr(true), Values: []options.HeaderValue{ { ClaimSource: &options.ClaimSource{ diff --git a/pkg/upstream/http.go b/pkg/upstream/http.go index d9d8f152..9f0d084c 100644 --- a/pkg/upstream/http.go +++ b/pkg/upstream/http.go @@ -54,7 +54,7 @@ func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *option // Set up a WebSocket proxy if required var wsProxy http.Handler if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets { - wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify) + wsProxy = newWebSocketReverseProxy(u, *upstream.InsecureSkipTLSVerify) } var auth hmacauth.HmacAuth @@ -149,7 +149,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr // InsecureSkipVerify is a configurable option we allow /* #nosec G402 */ - if upstream.InsecureSkipTLSVerify { + if *upstream.InsecureSkipTLSVerify { transport.TLSClientConfig.InsecureSkipVerify = true } @@ -168,7 +168,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr // Pass on DisableKeepAlives to the transport settings // to allow for disabling HTTP keep-alive connections - transport.DisableKeepAlives = upstream.DisableKeepAlives + transport.DisableKeepAlives = *upstream.DisableKeepAlives // Apply the customized transport to our proxy before returning it proxy.Transport = transport diff --git a/pkg/upstream/http_test.go b/pkg/upstream/http_test.go index df264c33..7c1831bf 100644 --- a/pkg/upstream/http_test.go +++ b/pkg/upstream/http_test.go @@ -15,6 +15,7 @@ import ( middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "golang.org/x/net/websocket" @@ -23,8 +24,6 @@ import ( var _ = Describe("HTTP Upstream Suite", func() { defaultFlushInterval := options.DefaultUpstreamFlushInterval defaultTimeout := options.DefaultUpstreamTimeout - truth := true - falsum := false type httpUpstreamTableInput struct { id string @@ -64,8 +63,8 @@ var _ = Describe("HTTP Upstream Suite", func() { upstream := options.Upstream{ ID: in.id, PassHostHeader: &in.passUpstreamHostHeader, - ProxyWebSockets: &falsum, - InsecureSkipTLSVerify: false, + ProxyWebSockets: ptr.Ptr(false), + InsecureSkipTLSVerify: ptr.Ptr(false), FlushInterval: &flush, Timeout: &timeout, } @@ -343,9 +342,9 @@ var _ = Describe("HTTP Upstream Suite", func() { upstream := options.Upstream{ ID: "noPassHost", - PassHostHeader: &falsum, - ProxyWebSockets: &falsum, - InsecureSkipTLSVerify: false, + PassHostHeader: ptr.Ptr(false), + ProxyWebSockets: ptr.Ptr(false), + InsecureSkipTLSVerify: ptr.Ptr(false), FlushInterval: &defaultFlushInterval, Timeout: &defaultTimeout, } @@ -389,10 +388,10 @@ var _ = Describe("HTTP Upstream Suite", func() { upstream := options.Upstream{ ID: "foo123", FlushInterval: &in.flushInterval, - InsecureSkipTLSVerify: in.skipVerify, + InsecureSkipTLSVerify: &in.skipVerify, ProxyWebSockets: &in.proxyWebSockets, Timeout: &in.timeout, - DisableKeepAlives: in.disableKeepAlives, + DisableKeepAlives: &in.disableKeepAlives, } handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler) @@ -487,9 +486,9 @@ var _ = Describe("HTTP Upstream Suite", func() { timeout := options.DefaultUpstreamTimeout upstream := options.Upstream{ ID: "websocketProxy", - PassHostHeader: &truth, - ProxyWebSockets: &truth, - InsecureSkipTLSVerify: false, + PassHostHeader: ptr.Ptr(true), + ProxyWebSockets: ptr.Ptr(true), + InsecureSkipTLSVerify: ptr.Ptr(false), FlushInterval: &flush, Timeout: &timeout, } diff --git a/pkg/upstream/proxy.go b/pkg/upstream/proxy.go index 74b0d02d..0d2286ea 100644 --- a/pkg/upstream/proxy.go +++ b/pkg/upstream/proxy.go @@ -27,12 +27,12 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData, serveMux: mux.NewRouter(), } - if upstreams.ProxyRawPath { + if *upstreams.ProxyRawPath { m.serveMux.UseEncodedPath() } for _, upstream := range sortByPathLongest(upstreams.Upstreams) { - if upstream.Static { + if *upstream.Static { if err := m.registerStaticResponseHandler(upstream, writer); err != nil { return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err) } diff --git a/pkg/upstream/proxy_test.go b/pkg/upstream/proxy_test.go index 5252bd42..87aae7fa 100644 --- a/pkg/upstream/proxy_test.go +++ b/pkg/upstream/proxy_test.go @@ -10,6 +10,7 @@ import ( middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -60,19 +61,19 @@ var _ = Describe("Proxy Suite", func() { { ID: "static-backend", Path: "/static/", - Static: true, + Static: ptr.Ptr(true), StaticCode: &ok, }, { ID: "static-backend-no-trailing-slash", Path: "/static", - Static: true, + Static: ptr.Ptr(true), StaticCode: &accepted, }, { ID: "static-backend-long", Path: "/static/long", - Static: true, + Static: ptr.Ptr(true), StaticCode: &accepted, }, { @@ -83,7 +84,7 @@ var _ = Describe("Proxy Suite", func() { { ID: "single-path-backend", Path: "/single-path", - Static: true, + Static: ptr.Ptr(true), StaticCode: &ok, }, { @@ -346,7 +347,7 @@ var _ = Describe("Proxy Suite", func() { upstream: "", }), Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{ - upstreams: options.UpstreamConfig{ProxyRawPath: true}, + upstreams: options.UpstreamConfig{ProxyRawPath: ptr.Ptr(true)}, target: "http://example.localhost/%2F/test1/%2F/test2", response: testHTTPResponse{ code: 404, diff --git a/pkg/util/ptr/ptr.go b/pkg/util/ptr/ptr.go new file mode 100644 index 00000000..9242773c --- /dev/null +++ b/pkg/util/ptr/ptr.go @@ -0,0 +1,14 @@ +package ptr + +// Ptr generically returns a pointer to the given value. +func Ptr[T any](v T) *T { + return &v +} + +// Deref returns the value of the pointer or def(ault) if nil. +func Deref[T any](p *T, def T) T { + if p == nil { + return def + } + return *p +} diff --git a/pkg/util/ptr/ptr_test.go b/pkg/util/ptr/ptr_test.go new file mode 100644 index 00000000..c4817a6b --- /dev/null +++ b/pkg/util/ptr/ptr_test.go @@ -0,0 +1,38 @@ +package ptr + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPtr(t *testing.T) { + p := Ptr(42) + assert.NotNil(t, p) + assert.Equal(t, 42, *p) + + s := Ptr("hello") + assert.NotNil(t, s) + assert.Equal(t, "hello", *s) + + b := Ptr(true) + assert.NotNil(t, b) + assert.True(t, *b) +} + +func TestDeref(t *testing.T) { + v := Deref(Ptr(99), 0) + assert.Equal(t, 99, v) + + v = Deref[int](nil, 123) + assert.Equal(t, 123, v) + + s := Deref[string](nil, "default") + assert.Equal(t, "default", s) + + b := Deref(Ptr(true), false) + assert.True(t, b) + + b = Deref[bool](nil, false) + assert.False(t, b) +} diff --git a/pkg/validation/options.go b/pkg/validation/options.go index c720f47e..cd51c40c 100644 --- a/pkg/validation/options.go +++ b/pkg/validation/options.go @@ -34,7 +34,7 @@ func Validate(o *options.Options) error { transport := requests.DefaultTransport.(*http.Transport) transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 -- InsecureSkipVerify is a configurable option we allow } else if len(o.Providers[0].CAFiles) > 0 { - pool, err := util.GetCertPool(o.Providers[0].CAFiles, o.Providers[0].UseSystemTrustStore) + pool, err := util.GetCertPool(o.Providers[0].CAFiles, *o.Providers[0].UseSystemTrustStore) if err == nil { transport := requests.DefaultTransport.(*http.Transport) transport.TLSClientConfig = &tls.Config{ diff --git a/pkg/validation/options_test.go b/pkg/validation/options_test.go index 5c283545..6657e847 100644 --- a/pkg/validation/options_test.go +++ b/pkg/validation/options_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" "github.com/stretchr/testify/assert" ) @@ -68,7 +69,7 @@ func TestGoogleGroupOptionsWithoutServiceAccountJSON(t *testing.T) { func TestGoogleGroupOptionsWithoutAdminEmail(t *testing.T) { o := testOptions() - o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true + o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.Ptr(true) err := Validate(o) assert.NotEqual(t, nil, err) @@ -81,7 +82,7 @@ func TestGoogleGroupOptionsWithoutGroups(t *testing.T) { o := testOptions() // Set admin email and application default credentials but no groups - should still require them o.Providers[0].GoogleConfig.AdminEmail = "admin@example.com" - o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true + o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.Ptr(true) err := Validate(o) // Should pass validation since google-group is now optional assert.Equal(t, nil, err) diff --git a/pkg/validation/providers.go b/pkg/validation/providers.go index 4527b841..3abf69d4 100644 --- a/pkg/validation/providers.go +++ b/pkg/validation/providers.go @@ -5,6 +5,7 @@ import ( "os" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" ) // validateProviders is the initial validation migration for multiple providrers @@ -64,7 +65,7 @@ func validateProvider(provider options.Provider, providerIDs map[string]struct{} // providerRequiresClientSecret checks if provider requires client secret to be set // or it can be omitted in favor of JWT token to authenticate oAuth client func providerRequiresClientSecret(provider options.Provider) bool { - if provider.Type == "entra-id" && provider.MicrosoftEntraIDConfig.FederatedTokenAuth { + if provider.Type == "entra-id" && *provider.MicrosoftEntraIDConfig.FederatedTokenAuth { return false } @@ -96,7 +97,7 @@ func validateGoogleConfig(provider options.Provider) []string { hasAdminEmail := provider.GoogleConfig.AdminEmail != "" hasSAJSON := provider.GoogleConfig.ServiceAccountJSON != "" - useADC := provider.GoogleConfig.UseApplicationDefaultCredentials + useADC := ptr.Deref(provider.GoogleConfig.UseApplicationDefaultCredentials, false) if !hasAdminEmail && !hasSAJSON && !useADC { return msgs @@ -123,7 +124,7 @@ func validateGoogleConfig(provider options.Provider) []string { func validateEntraConfig(provider options.Provider) []string { msgs := []string{} - if provider.MicrosoftEntraIDConfig.FederatedTokenAuth { + if *provider.MicrosoftEntraIDConfig.FederatedTokenAuth { federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") if federatedTokenPath == "" { diff --git a/pkg/validation/upstreams.go b/pkg/validation/upstreams.go index 52facb4d..05d441e3 100644 --- a/pkg/validation/upstreams.go +++ b/pkg/validation/upstreams.go @@ -54,19 +54,19 @@ func validateUpstream(upstream options.Upstream, ids, paths map[string]struct{}) func validateStaticUpstream(upstream options.Upstream) []string { msgs := []string{} - if !upstream.Static && upstream.StaticCode != nil { + if !*upstream.Static && upstream.StaticCode != nil { msgs = append(msgs, fmt.Sprintf("upstream %q has staticCode (%d), but is not a static upstream, set 'static' for a static response", upstream.ID, *upstream.StaticCode)) } // Checks after this only make sense when the upstream is static - if !upstream.Static { + if !*upstream.Static { return msgs } if upstream.URI != "" { msgs = append(msgs, fmt.Sprintf("upstream %q has uri, but is a static upstream, this will have no effect.", upstream.ID)) } - if upstream.InsecureSkipTLSVerify { + if *upstream.InsecureSkipTLSVerify { msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID)) } if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval { @@ -85,13 +85,13 @@ func validateStaticUpstream(upstream options.Upstream) []string { func validateUpstreamURI(upstream options.Upstream) []string { msgs := []string{} - if !upstream.Static && upstream.URI == "" { + if !*upstream.Static && upstream.URI == "" { msgs = append(msgs, fmt.Sprintf("upstream %q has empty uri: uris are required for all non-static upstreams", upstream.ID)) return msgs } // Checks after this only make sense the upstream is not static - if upstream.Static { + if *upstream.Static { return msgs } diff --git a/pkg/validation/upstreams_test.go b/pkg/validation/upstreams_test.go index 67991b76..580e2f29 100644 --- a/pkg/validation/upstreams_test.go +++ b/pkg/validation/upstreams_test.go @@ -4,6 +4,7 @@ import ( "time" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -16,7 +17,6 @@ var _ = Describe("Upstreams", func() { flushInterval := 5 * time.Second staticCode200 := 200 - truth := true validHTTPUpstream := options.Upstream{ ID: "validHTTPUpstream", @@ -26,7 +26,7 @@ var _ = Describe("Upstreams", func() { validStaticUpstream := options.Upstream{ ID: "validStaticUpstream", Path: "/validStaticUpstream", - Static: true, + Static: ptr.Ptr(true), } validFileUpstream := options.Upstream{ ID: "validFileUpstream", @@ -145,11 +145,11 @@ var _ = Describe("Upstreams", func() { ID: "foo", Path: "/foo", URI: "ftp://foo", - Static: true, + Static: ptr.Ptr(true), FlushInterval: &flushInterval, - PassHostHeader: &truth, - ProxyWebSockets: &truth, - InsecureSkipTLSVerify: true, + PassHostHeader: ptr.Ptr(true), + ProxyWebSockets: ptr.Ptr(true), + InsecureSkipTLSVerify: ptr.Ptr(true), }, }, }, diff --git a/providers/adfs.go b/providers/adfs.go index 0facfdcf..6615f38c 100644 --- a/providers/adfs.go +++ b/providers/adfs.go @@ -50,7 +50,7 @@ func NewADFSProvider(p *ProviderData, opts options.Provider) *ADFSProvider { return &ADFSProvider{ OIDCProvider: oidcProvider, - skipScope: opts.ADFSConfig.SkipScope, + skipScope: *opts.ADFSConfig.SkipScope, oidcEnrichFunc: oidcProvider.EnrichSession, oidcRefreshFunc: oidcProvider.RefreshSession, } diff --git a/providers/adfs_test.go b/providers/adfs_test.go index 0b730430..edcb9307 100755 --- a/providers/adfs_test.go +++ b/providers/adfs_test.go @@ -16,6 +16,7 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -172,7 +173,7 @@ var _ = Describe("ADFS Provider Tests", func() { ProtectedResource: resource, Scope: "", }, options.Provider{ - ADFSConfig: options.ADFSOptions{SkipScope: true}, + ADFSConfig: options.ADFSOptions{SkipScope: ptr.Ptr(true)}, }) result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "", url.Values{}) diff --git a/providers/google.go b/providers/google.go index 097e3567..7926c71b 100644 --- a/providers/google.go +++ b/providers/google.go @@ -102,7 +102,7 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv }, } - if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials { + if opts.ServiceAccountJSON != "" || *opts.UseApplicationDefaultCredentials { provider.configureGroups(opts) } @@ -259,7 +259,7 @@ var possibleScopesList = [...]string{ } func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource { - if opts.UseApplicationDefaultCredentials { + if *opts.UseApplicationDefaultCredentials { ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ TargetPrincipal: getTargetPrincipal(ctx, opts), Scopes: []string{scope}, diff --git a/providers/ms_entra_id.go b/providers/ms_entra_id.go index df1f38a4..752f9f44 100644 --- a/providers/ms_entra_id.go +++ b/providers/ms_entra_id.go @@ -51,7 +51,7 @@ func NewMicrosoftEntraIDProvider(p *ProviderData, opts options.Provider) *Micros OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig), multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants, - federatedTokenAuth: opts.MicrosoftEntraIDConfig.FederatedTokenAuth, + federatedTokenAuth: *opts.MicrosoftEntraIDConfig.FederatedTokenAuth, microsoftGraphURL: microsoftGraphURL, } } diff --git a/providers/ms_entra_id_test.go b/providers/ms_entra_id_test.go index dfd1ef99..7b720c91 100644 --- a/providers/ms_entra_id_test.go +++ b/providers/ms_entra_id_test.go @@ -13,6 +13,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/golang-jwt/jwt/v5" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" "github.com/stretchr/testify/assert" . "github.com/onsi/gomega" @@ -24,7 +25,7 @@ func TestAzureEntraOIDCProviderNewMultiTenant(t *testing.T) { provider := NewMicrosoftEntraIDProvider(&ProviderData{}, options.Provider{OIDCConfig: options.OIDCOptions{ IssuerURL: "https://login.microsoftonline.com/common/v2.0", - InsecureSkipIssuerVerification: true, + InsecureSkipIssuerVerification: ptr.Ptr(true), }}, ) g.Expect(provider.Data().ProviderName).To(Equal("Microsoft Entra ID")) @@ -90,8 +91,8 @@ func TestAzureEntraOIDCProviderValidateSessionAllowedTenants(t *testing.T) { options.Provider{ OIDCConfig: options.OIDCOptions{ IssuerURL: "https://login.microsoftonline.com/common/v2.0", - InsecureSkipIssuerVerification: true, - InsecureSkipNonce: true, + InsecureSkipIssuerVerification: ptr.Ptr(true), + InsecureSkipNonce: ptr.Ptr(true), }, MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{ AllowedTenants: []string{"85d7d600-7804-4d92-8d43-9c33c21c130c"}, diff --git a/providers/oidc.go b/providers/oidc.go index 15598aba..9a88b46c 100644 --- a/providers/oidc.go +++ b/providers/oidc.go @@ -50,7 +50,7 @@ func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider { return &OIDCProvider{ ProviderData: p, - SkipNonce: opts.InsecureSkipNonce, + SkipNonce: *opts.InsecureSkipNonce, } } diff --git a/providers/oidc_test.go b/providers/oidc_test.go index 81a70eb4..61f4762e 100644 --- a/providers/oidc_test.go +++ b/providers/oidc_test.go @@ -63,7 +63,7 @@ func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider { } p := NewOIDCProvider(providerData, options.OIDCOptions{ - InsecureSkipNonce: skipNonce, + InsecureSkipNonce: &skipNonce, }) return p diff --git a/providers/providers.go b/providers/providers.go index ec00f412..84f5ec91 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -98,8 +98,8 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData, IssuerURL: providerConfig.OIDCConfig.IssuerURL, JWKsURL: providerConfig.OIDCConfig.JwksURL, PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles, - SkipDiscovery: providerConfig.OIDCConfig.SkipDiscovery, - SkipIssuerVerification: providerConfig.OIDCConfig.InsecureSkipIssuerVerification, + SkipDiscovery: *providerConfig.OIDCConfig.SkipDiscovery, + SkipIssuerVerification: *providerConfig.OIDCConfig.InsecureSkipIssuerVerification, }) if err != nil { return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err) @@ -143,10 +143,10 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData, } // Make the OIDC options available to all providers that support it - p.AllowUnverifiedEmail = providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail + p.AllowUnverifiedEmail = *providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail p.EmailClaim = providerConfig.OIDCConfig.EmailClaim p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim - p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL + p.SkipClaimsFromProfileURL = *providerConfig.SkipClaimsFromProfileURL // Set PKCE enabled or disabled based on discovery and force options p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig) diff --git a/providers/providers_test.go b/providers/providers_test.go index 82961d84..9591dc65 100644 --- a/providers/providers_test.go +++ b/providers/providers_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" . "github.com/onsi/gomega" ) @@ -81,7 +82,7 @@ func TestSkipOIDCDiscovery(t *testing.T) { ClientSecretFile: clientSecret, OIDCConfig: options.OIDCOptions{ IssuerURL: msIssuerURL, - SkipDiscovery: true, + SkipDiscovery: ptr.Ptr(true), }, } @@ -108,7 +109,7 @@ func TestURLsCorrectlyParsed(t *testing.T) { RedeemURL: msTokenURL, OIDCConfig: options.OIDCOptions{ IssuerURL: msIssuerURL, - SkipDiscovery: true, + SkipDiscovery: ptr.Ptr(true), JwksURL: msKeysURL, }, } @@ -216,7 +217,7 @@ func TestScope(t *testing.T) { AllowedGroups: tc.allowedGroups, OIDCConfig: options.OIDCOptions{ IssuerURL: msIssuerURL, - SkipDiscovery: true, + SkipDiscovery: ptr.Ptr(true), JwksURL: msKeysURL, }, } @@ -297,7 +298,7 @@ func TestEmailClaimCorrectlySet(t *testing.T) { RedeemURL: msTokenURL, OIDCConfig: options.OIDCOptions{ IssuerURL: msIssuerURL, - SkipDiscovery: true, + SkipDiscovery: ptr.Ptr(true), JwksURL: msKeysURL, UserIDClaim: tc.userIDClaim, EmailClaim: tc.emailClaim,