git.iko.soy/typeable/oauth2-proxy
on master help sign in
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 Signed-off-by: Jan Larwig <jan@larwig.com>

commit7c20001045b63a3cc506ce6dbc50bff252efac02 authortuunit <jan@larwig.com> dateSat 4 May 2024, 16:41 (2 years ago) parent7cf69b2
29 files changed+268−232
.golangci.yml+1−1 docs/docs/configuration/alpha_config.md+4−14 main.go+22−11 main_test.go+22−17 oauthproxy_test.go+3−3 pkg/apis/options/alpha_options.go+16−9 pkg/apis/options/common.go−63 pkg/apis/options/duration.go+43 pkg/apis/options/header.go+2−2 pkg/apis/options/legacy_options.go+3−3 pkg/apis/options/legacy_options_test.go+6−6 pkg/apis/options/load.go+70−41 pkg/apis/options/load_test.go+13−11 pkg/apis/options/providers.go+3−3 pkg/apis/options/secret_source.go+14 pkg/apis/options/upstreams.go+2−2 pkg/apis/options/util/util.go+1−1 pkg/apis/options/util/util_test.go+1−1 pkg/header/injector_test.go+8−8 pkg/http/http_suite_test.go+6−6 pkg/http/server_test.go+4−4 pkg/middleware/headers_test.go+4−4 pkg/upstream/http.go+2−2 pkg/upstream/http_test.go+12−12 pkg/validation/common_test.go+2−2 pkg/validation/header.go−2 pkg/validation/header_test.go+2−2 pkg/validation/upstreams.go+1−1 pkg/validation/upstreams_test.go+1−1
.golangci.yml+1−1
@@ -37,7 +37,7 @@ linters:
3737 - linters:
3838 - revive
3939 path: _test\.go
40− text: 'dot-imports:'
40+ text: "dot-imports:"
4141 # # If we have tests in shared test folders, these can be less strictly linted
4242 - linters:
4343 - bodyclose
docs/docs/configuration/alpha_config.md+4−14
@@ -204,16 +204,6 @@ ClaimSource allows loading a header value from a claim within the session
204204| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the<br/>claim if it is non-empty. |
205205| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.<br/>Note the value of claim will become the basic auth username and the<br/>basicAuthPassword will be used as the password value. |
206206
207−### Duration
208−#### (`string` alias)
209−
210−(**Appears on:** [Upstream](#upstream))
211−
212−Duration is as string representation of a period of time.
213−A duration string is a is a possibly signed sequence of decimal numbers,
214−each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
215−Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
216−
217207### GitHubOptions
218208
219209(**Appears on:** [Provider](#provider))
@@ -278,7 +268,7 @@ make up the header value
278268
279269| Field | Type | Description |
280270| ----- | ---- | ----------- |
281−| `value` | _[]byte_ | Value expects a base64 encoded string value. |
271+| `value` | _string_ | Value expects a base64 encoded string value. |
282272| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
283273| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
284274| `claim` | _string_ | Claim is the name of the claim in the session that the value should be<br/>loaded from. Available claims: `access_token` `id_token` `created_at`<br/>`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. |
@@ -490,7 +480,7 @@ Only one source within the struct should be defined at any time.
490480
491481| Field | Type | Description |
492482| ----- | ---- | ----------- |
493−| `value` | _[]byte_ | Value expects a base64 encoded string value. |
483+| `value` | _string_ | Value expects a base64 encoded string value. |
494484| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
495485| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
496486
@@ -550,10 +540,10 @@ Requests will be proxied to this upstream if the path matches the request path.
550540| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.<br/>This option is insecure and will allow potential Man-In-The-Middle attacks<br/>between OAuth2 Proxy and the upstream server.<br/>Defaults to false. |
551541| `static` | _bool_ | Static will make all requests to this upstream have a static response.<br/>The response will have a body of "Authenticated" and a response code<br/>matching StaticCode.<br/>If StaticCode is not set, the response will return a 200 response. |
552542| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
553−| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
543+| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
554544| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
555545| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
556−| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
546+| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
557547| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
558548
559549### UpstreamConfig
main.go+22−11
@@ -67,12 +67,17 @@ func main() {
6767// loadConfiguration will load in the user's configuration.
6868// It will either load the alpha configuration (if alphaConfig is given)
6969// or the legacy configuration.
70−func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
71− if alphaConfig != "" {
70+func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
71+ opts, err := loadLegacyOptions(config, extraFlags, args)
72+ if err != nil {
73+ return nil, err
74+ }
75+
76+ if yamlConfig != "" {
7277 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.")
73− return loadAlphaOptions(config, alphaConfig, extraFlags, args)
78+ return loadYamlOptions(yamlConfig, config, extraFlags, args)
7479 }
75− return loadLegacyOptions(config, extraFlags, args)
80+ return opts, err
7681}
7782
7883// loadLegacyOptions loads the old toml options using the legacy flagset
@@ -97,17 +102,17 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string)
97102 return opts, nil
98103}
99104
100−// loadAlphaOptions loads the old style config excluding options converted to
105+// loadYamlOptions loads the old style config excluding options converted to
101106// the new alpha format, then merges the alpha options, loaded from YAML,
102107// into the core configuration.
103−func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
108+func loadYamlOptions(yamlConfig, config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
104109 opts, err := loadOptions(config, extraFlags, args)
105110 if err != nil {
106111 return nil, fmt.Errorf("failed to load core options: %v", err)
107112 }
108113
109− alphaOpts := &options.AlphaOptions{}
110− if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
114+ alphaOpts := options.NewAlphaOptions(opts)
115+ if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
111116 return nil, fmt.Errorf("failed to load alpha options: %v", err)
112117 }
113118
@@ -137,10 +142,16 @@ func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*opti
137142// printConvertedConfig extracts alpha options from the loaded configuration
138143// and renders these to stdout in YAML format.
139144func printConvertedConfig(opts *options.Options) error {
140− alphaConfig := &options.AlphaOptions{}
141− alphaConfig.ExtractFrom(opts)
145+ alphaConfig := options.NewAlphaOptions(opts)
146+
147+ // Generic interface for loading arbitrary yaml structure
148+ var buffer map[string]interface{}
149+
150+ if err := options.Decode(alphaConfig, &buffer); err != nil {
151+ return fmt.Errorf("unable to decode alpha config into interface: %w", err)
152+ }
142153
143− data, err := yaml.Marshal(alphaConfig)
154+ data, err := yaml.Marshal(buffer)
144155 if err != nil {
145156 return fmt.Errorf("unable to marshal config: %v", err)
146157 }
main_test.go+22−17
@@ -43,29 +43,35 @@ upstreamConfig:
4343injectRequestHeaders:
4444- name: Authorization
4545 values:
46− - claim: user
47− prefix: "Basic "
48− basicAuthPassword:
49− value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
46+ - claimSource:
47+ claim: user
48+ prefix: "Basic "
49+ basicAuthPassword:
50+ value: super-secret-password
5051- name: X-Forwarded-Groups
5152 values:
52− - claim: groups
53+ - claimSource:
54+ claim: groups
5355- name: X-Forwarded-User
5456 values:
55− - claim: user
57+ - claimSource:
58+ claim: user
5659- name: X-Forwarded-Email
5760 values:
58− - claim: email
61+ - claimSource:
62+ claim: email
5963- name: X-Forwarded-Preferred-Username
6064 values:
61− - claim: preferred_username
65+ - claimSource:
66+ claim: preferred_username
6267injectResponseHeaders:
6368- name: Authorization
6469 values:
65− - claim: user
66− prefix: "Basic "
67− basicAuthPassword:
68− value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
70+ - claimSource:
71+ claim: user
72+ prefix: "Basic "
73+ basicAuthPassword:
74+ value: super-secret-password
6975server:
7076 bindAddress: "127.0.0.1:4180"
7177providers:
@@ -100,9 +106,8 @@ redirect_url="http://localhost:4180/oauth2/callback"
100106 return &b
101107 }
102108
103− durationPtr := func(d time.Duration) *options.Duration {
104− du := options.Duration(d)
105− return &du
109+ durationPtr := func(d time.Duration) *time.Duration {
110+ return &d
106111 }
107112
108113 testExpectedOptions := func() *options.Options {
@@ -136,7 +141,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
136141 Claim: "user",
137142 Prefix: "Basic ",
138143 BasicAuthPassword: &options.SecretSource{
139− Value: []byte("super-secret-password"),
144+ Value: "super-secret-password",
140145 },
141146 },
142147 },
@@ -226,7 +231,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
226231
227232 opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
228233 if in.expectedErr != nil {
229− Expect(err).To(MatchError(in.expectedErr.Error()))
234+ Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
230235 } else {
231236 Expect(err).ToNot(HaveOccurred())
232237 }
oauthproxy_test.go+3−3
@@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) {
215215 ClaimSource: &options.ClaimSource{
216216 Claim: "email",
217217 BasicAuthPassword: &options.SecretSource{
218− Value: []byte(basicAuthPassword),
218+ Value: basicAuthPassword,
219219 },
220220 },
221221 },
@@ -1282,7 +1282,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
12821282 ClaimSource: &options.ClaimSource{
12831283 Claim: "user",
12841284 BasicAuthPassword: &options.SecretSource{
1285− Value: []byte("This is a secure password"),
1285+ Value: "This is a secure password",
12861286 },
12871287 },
12881288 },
@@ -2044,7 +2044,7 @@ func baseTestOptions() *options.Options {
20442044 ClaimSource: &options.ClaimSource{
20452045 Claim: "user",
20462046 BasicAuthPassword: &options.SecretSource{
2047− Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
2047+ Value: base64.StdEncoding.EncodeToString([]byte("This is a secure password")),
20482048 },
20492049 },
20502050 },
pkg/apis/options/alpha_options.go+16−9
@@ -47,15 +47,11 @@ type AlphaOptions struct {
4747 Providers Providers `json:"providers,omitempty"`
4848}
4949
50−// MergeInto replaces alpha options in the Options struct with the values
51−// from the AlphaOptions
52−func (a *AlphaOptions) MergeInto(opts *Options) {
53− opts.UpstreamServers = a.UpstreamConfig
54− opts.InjectRequestHeaders = a.InjectRequestHeaders
55− opts.InjectResponseHeaders = a.InjectResponseHeaders
56− opts.Server = a.Server
57− opts.MetricsServer = a.MetricsServer
58− opts.Providers = a.Providers
50+// Initialize alpha options with default values and settings of the core options
51+func NewAlphaOptions(opts *Options) *AlphaOptions {
52+ aOpts := &AlphaOptions{}
53+ aOpts.ExtractFrom(opts)
54+ return aOpts
5955}
6056
6157// ExtractFrom populates the fields in the AlphaOptions with the values from
@@ -68,3 +64,14 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) {
6864 a.MetricsServer = opts.MetricsServer
6965 a.Providers = opts.Providers
7066}
67+
68+// MergeInto replaces alpha options in the Options struct with the values
69+// from the AlphaOptions
70+func (a *AlphaOptions) MergeInto(opts *Options) {
71+ opts.UpstreamServers = a.UpstreamConfig
72+ opts.InjectRequestHeaders = a.InjectRequestHeaders
73+ opts.InjectResponseHeaders = a.InjectResponseHeaders
74+ opts.Server = a.Server
75+ opts.MetricsServer = a.MetricsServer
76+ opts.Providers = a.Providers
77+}
pkg/apis/options/common.godeleted−63
@@ -1,63 +0,0 @@
1−package options
2−
3−import (
4− "fmt"
5− "strconv"
6− "time"
7−)
8−
9−// SecretSource references an individual secret value.
10−// Only one source within the struct should be defined at any time.
11−type SecretSource struct {
12− // Value expects a base64 encoded string value.
13− Value []byte `json:"value,omitempty"`
14−
15− // FromEnv expects the name of an environment variable.
16− FromEnv string `json:"fromEnv,omitempty"`
17−
18− // FromFile expects a path to a file containing the secret value.
19− FromFile string `json:"fromFile,omitempty"`
20−}
21−
22−// Duration is an alias for time.Duration so that we can ensure the marshalling
23−// and unmarshalling of string durations is done as users expect.
24−// Intentional blank line below to keep this first part of the comment out of
25−// any generated references.
26−
27−// Duration is as string representation of a period of time.
28−// A duration string is a is a possibly signed sequence of decimal numbers,
29−// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
30−// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
31−// +reference-gen:alias-name=string
32−type Duration time.Duration
33−
34−// UnmarshalJSON parses the duration string and sets the value of duration
35−// to the value of the duration string.
36−func (d *Duration) UnmarshalJSON(data []byte) error {
37− input := string(data)
38− if unquoted, err := strconv.Unquote(input); err == nil {
39− input = unquoted
40− }
41−
42− du, err := time.ParseDuration(input)
43− if err != nil {
44− return err
45− }
46− *d = Duration(du)
47− return nil
48−}
49−
50−// MarshalJSON ensures that when the string is marshalled to JSON as a human
51−// readable string.
52−func (d *Duration) MarshalJSON() ([]byte, error) {
53− dStr := fmt.Sprintf("%q", d.Duration().String())
54− return []byte(dStr), nil
55−}
56−
57−// Duration returns the time.Duration version of this Duration
58−func (d *Duration) Duration() time.Duration {
59− if d == nil {
60− return time.Duration(0)
61− }
62− return time.Duration(*d)
63−}
pkg/apis/options/duration.gonew file+43
@@ -0,0 +1,43 @@
1+package options
2+
3+import (
4+ "reflect"
5+ "time"
6+
7+ "github.com/mitchellh/mapstructure"
8+)
9+
10+// Duration is an alias for time.Duration so that we can ensure the marshalling
11+// and unmarshalling of string durations is done as users expect.
12+// Intentional blank line below to keep this first part of the comment out of
13+// any generated references.
14+
15+// Duration is as string representation of a period of time.
16+// A duration string is a is a possibly signed sequence of decimal numbers,
17+// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
18+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
19+
20+// Conversion from string or floating point to golang duration type
21+// This way floating points will be converted to seconds and strings
22+// of type 3s or 5m will be parsed with time.ParseDuration
23+func toDurationHookFunc() mapstructure.DecodeHookFunc {
24+ return func(
25+ f reflect.Type,
26+ t reflect.Type,
27+ data interface{}) (interface{}, error) {
28+ if t != reflect.TypeOf(time.Duration(0)) {
29+ return data, nil
30+ }
31+
32+ switch f.Kind() {
33+ case reflect.String:
34+ return time.ParseDuration(data.(string))
35+ case reflect.Float64:
36+ return time.Duration(data.(float64) * float64(time.Second)), nil
37+ case reflect.Int64:
38+ return time.Duration(data.(int64)), nil
39+ default:
40+ return data, nil
41+ }
42+ }
43+}
pkg/apis/options/header.go+2−2
@@ -31,10 +31,10 @@ type Header struct {
3131// make up the header value
3232type HeaderValue struct {
3333 // Allow users to load the value from a secret source
34− *SecretSource `json:",omitempty"`
34+ *SecretSource `json:"secretSource,omitempty"`
3535
3636 // Allow users to load the value from a session claim
37− *ClaimSource `json:",omitempty"`
37+ *ClaimSource `json:"claimSource,omitempty"`
3838}
3939
4040// ClaimSource allows loading a header value from a claim within the session
pkg/apis/options/legacy_options.go+3−3
@@ -136,8 +136,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
136136 u.Path = "/"
137137 }
138138
139− flushInterval := Duration(l.FlushInterval)
140− timeout := Duration(l.Timeout)
139+ flushInterval := l.FlushInterval
140+ timeout := l.Timeout
141141 upstream := Upstream{
142142 ID: u.Path,
143143 Path: u.Path,
@@ -294,7 +294,7 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
294294 Claim: claim,
295295 Prefix: "Basic ",
296296 BasicAuthPassword: &SecretSource{
297− Value: []byte(basicAuthPassword),
297+ Value: basicAuthPassword,
298298 },
299299 },
300300 },
pkg/apis/options/legacy_options_test.go+6−6
@@ -15,8 +15,8 @@ var _ = Describe("Legacy Options", func() {
1515 legacyOpts := NewLegacyOptions()
1616
1717 // Set upstreams and related options to test their conversion
18− flushInterval := Duration(5 * time.Second)
19− timeout := Duration(5 * time.Second)
18+ flushInterval := 5 * time.Second
19+ timeout := 5 * time.Second
2020 legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
2121 legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
2222 legacyOpts.LegacyUpstreams.PassHostHeader = true
@@ -147,8 +147,8 @@ var _ = Describe("Legacy Options", func() {
147147 skipVerify := true
148148 passHostHeader := false
149149 proxyWebSockets := true
150− flushInterval := Duration(5 * time.Second)
151− timeout := Duration(5 * time.Second)
150+ flushInterval := 5 * time.Second
151+ timeout := 5 * time.Second
152152 disableKeepAlives := true
153153
154154 // Test cases and expected outcomes
@@ -369,7 +369,7 @@ var _ = Describe("Legacy Options", func() {
369369 Claim: "user",
370370 Prefix: "Basic ",
371371 BasicAuthPassword: &SecretSource{
372− Value: []byte(basicAuthSecret),
372+ Value: basicAuthSecret,
373373 },
374374 },
375375 },
@@ -409,7 +409,7 @@ var _ = Describe("Legacy Options", func() {
409409 Claim: "email",
410410 Prefix: "Basic ",
411411 BasicAuthPassword: &SecretSource{
412− Value: []byte(basicAuthSecret),
412+ Value: basicAuthSecret,
413413 },
414414 },
415415 },
pkg/apis/options/load.go+70−41
@@ -55,6 +55,76 @@ func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error
5555 return nil
5656}
5757
58+// LoadYAML will load a YAML based configuration file into the options interface provided.
59+func LoadYAML(configFileName string, opts interface{}) error {
60+ buffer, err := loadAndSubstituteEnvs(configFileName)
61+ if err != nil {
62+ return err
63+ }
64+
65+ // Generic interface for loading arbitrary yaml structure
66+ var intermediate map[string]interface{}
67+
68+ if err := yaml.Unmarshal(buffer, &intermediate); err != nil {
69+ return fmt.Errorf("error unmarshalling config: %w", err)
70+ }
71+
72+ return Decode(intermediate, opts)
73+}
74+
75+func Decode(input interface{}, result interface{}) error {
76+ // Using mapstructure to decode arbitrary yaml structure into options and
77+ // merge with existing values instead of overwriting everything. This is especially
78+ // important as we have a lot of default values for boolean which are supposed to be
79+ // true by default. Normally by just parsing through yaml all booleans that aren't
80+ // referenced in the config file would be parsed as false and we cannot identify after
81+ // the fact if they have been explicitly set to false or have not been referenced.
82+ decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
83+ DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()),
84+ Metadata: nil, // Don't track any metadata
85+ Result: result, // Decode the result into the prefilled options
86+ TagName: "json", // Parse all fields that use the yaml tag
87+ ZeroFields: false, // Don't clean the default values from the result map (options)
88+ ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields
89+ IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml
90+ })
91+
92+ if err != nil {
93+ return fmt.Errorf("error creating decoder for config: %w", err)
94+ }
95+
96+ if err := decoder.Decode(input); err != nil {
97+ return fmt.Errorf("error decoding config: %w", err)
98+ }
99+
100+ return nil
101+}
102+
103+// loadAndSubstituteEnvs reads the yaml config into a generic byte buffer and
104+// substitute env references
105+func loadAndSubstituteEnvs(configFileName string) ([]byte, error) {
106+ if configFileName == "" {
107+ return nil, errors.New("no configuration file provided")
108+ }
109+
110+ unparsedBuffer, err := os.ReadFile(configFileName)
111+ if err != nil {
112+ return nil, fmt.Errorf("unable to load config file: %w", err)
113+ }
114+
115+ modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
116+ if err != nil {
117+ return nil, fmt.Errorf("error normalizing substitution string : %w", err)
118+ }
119+
120+ buffer, err := envsubst.Bytes(modifiedBuffer)
121+ if err != nil {
122+ return nil, fmt.Errorf("error in substituting env variables : %w", err)
123+ }
124+
125+ return buffer, nil
126+}
127+
58128// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
59129// to the fields in the options interface provided.
60130// 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 {
140210 return first == strings.ToLower(first)
141211}
142212
143−// LoadYAML will load a YAML based configuration file into the options interface provided.
144−func LoadYAML(configFileName string, into interface{}) error {
145− buffer, err := loadAndParseYaml(configFileName)
146− if err != nil {
147− return err
148− }
149−
150− // UnmarshalStrict will return an error if the config includes options that are
151− // not mapped to fields of the into struct
152− if err := yaml.UnmarshalStrict(buffer, into, yaml.DisallowUnknownFields); err != nil {
153− return fmt.Errorf("error unmarshalling config: %w", err)
154− }
155−
156− return nil
157−}
158−
159−// loadAndParseYaml reads the config from the filesystem and
160−// execute the environment variable substitution
161−func loadAndParseYaml(configFileName string) ([]byte, error) {
162− if configFileName == "" {
163− return nil, errors.New("no configuration file provided")
164− }
165−
166− unparsedBuffer, err := os.ReadFile(configFileName)
167− if err != nil {
168− return nil, fmt.Errorf("unable to load config file: %w", err)
169− }
170−
171− modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
172− if err != nil {
173− return nil, fmt.Errorf("error normalizing substitution string : %w", err)
174− }
175−
176− buffer, err := envsubst.Bytes(modifiedBuffer)
177− if err != nil {
178− return nil, fmt.Errorf("error in substituting env variables : %w", err)
179− }
180−
181− return buffer, nil
182−}
183−
184213// normalizeSubstitution normalizes dollar signs ($) with numerals like
185214// $1 or $2 properly by correctly escaping them
186215func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {
pkg/apis/options/load_test.go+13−11
@@ -355,15 +355,15 @@ var _ = Describe("Load", func() {
355355var _ = Describe("LoadYAML", func() {
356356 Context("with a testOptions structure", func() {
357357 type TestOptionSubStruct struct {
358− StringSliceOption []string `yaml:"stringSliceOption,omitempty"`
358+ StringSliceOption []string `json:"stringSliceOption,omitempty"`
359359 }
360360
361361 type TestOptions struct {
362− StringOption string `yaml:"stringOption,omitempty"`
363− Sub TestOptionSubStruct `yaml:"sub,omitempty"`
362+ StringOption string `json:"stringOption,omitempty"`
363+ Sub TestOptionSubStruct `json:"sub,omitempty"`
364364
365365 // Check that embedded fields can be unmarshalled
366− TestOptionSubStruct `yaml:",inline,squash"`
366+ TestOptionSubStruct `json:",inline,squash"`
367367 }
368368
369369 var testOptionsConfigBytesFull = []byte(`
@@ -416,7 +416,7 @@ sub:
416416
417417 err := LoadYAML(configFileName, input)
418418 if in.expectedErr != nil {
419− Expect(err).To(MatchError(in.expectedErr.Error()))
419+ Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
420420 } else {
421421 Expect(err).ToNot(HaveOccurred())
422422 }
@@ -459,13 +459,13 @@ sub:
459459 StringSliceOption: []string{"a", "b", "c"},
460460 },
461461 },
462− expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
462+ expectedErr: errors.New("has invalid keys: foo"),
463463 }),
464464 Entry("with an incorrect type for a string field", loadYAMLTableInput{
465465 configFile: []byte(`stringOption: ["a", "b"]`),
466466 input: &TestOptions{},
467467 expectedOutput: &TestOptions{},
468− 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"),
468+ expectedErr: errors.New("'stringOption' expected type 'string', got unconvertible type"),
469469 }),
470470 Entry("with an incorrect type for an array field", loadYAMLTableInput{
471471 configFile: []byte(`stringSliceOption: "a"`),
@@ -526,11 +526,13 @@ upstreamConfig:
526526injectRequestHeaders:
527527- name: X-Forwarded-User
528528 values:
529− - claim: user
529+ - claimSource:
530+ claim: user
530531injectResponseHeaders:
531532- name: X-Secret
532533 values:
533− - value: c2VjcmV0
534+ - secretSource:
535+ value: secret
534536`)
535537
536538 By("Creating a config file")
@@ -548,7 +550,7 @@ injectResponseHeaders:
548550 into := &AlphaOptions{}
549551 Expect(LoadYAML(configFileName, into)).To(Succeed())
550552
551− flushInterval := Duration(500 * time.Millisecond)
553+ flushInterval := 500 * time.Millisecond
552554
553555 Expect(into).To(Equal(&AlphaOptions{
554556 UpstreamConfig: UpstreamConfig{
@@ -579,7 +581,7 @@ injectResponseHeaders:
579581 Values: []HeaderValue{
580582 {
581583 SecretSource: &SecretSource{
582− Value: []byte("secret"),
584+ Value: "secret",
583585 },
584586 },
585587 },
pkg/apis/options/providers.go+3−3
@@ -242,16 +242,16 @@ type OIDCOptions struct {
242242 IssuerURL string `json:"issuerURL,omitempty"`
243243 // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
244244 // default set to 'false'
245− InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
245+ InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail"`
246246 // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
247247 // default set to 'false'
248− InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"`
248+ InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification"`
249249 // InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
250250 // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
251251 // after the initial OAuth redeem & subsequent token refreshes.
252252 // default set to 'true'
253253 // Warning: In a future release, this will change to 'false' by default for enhanced security.
254− InsecureSkipNonce bool `json:"insecureSkipNonce,omitempty"`
254+ InsecureSkipNonce bool `json:"insecureSkipNonce"`
255255 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
256256 // default set to 'false'
257257 SkipDiscovery bool `json:"skipDiscovery,omitempty"`
pkg/apis/options/secret_source.gonew file+14
@@ -0,0 +1,14 @@
1+package options
2+
3+// SecretSource references an individual secret value.
4+// Only one source within the struct should be defined at any time.
5+type SecretSource struct {
6+ // Value expects a base64 encoded string value.
7+ Value string `json:"value,omitempty"`
8+
9+ // FromEnv expects the name of an environment variable.
10+ FromEnv string `json:"fromEnv,omitempty"`
11+
12+ // FromFile expects a path to a file containing the secret value.
13+ FromFile string `json:"fromFile,omitempty"`
14+}
pkg/apis/options/upstreams.go+2−2
@@ -79,7 +79,7 @@ type Upstream struct {
7979 // FlushInterval is the period between flushing the response buffer when
8080 // streaming response from the upstream.
8181 // Defaults to 1 second.
82− FlushInterval *Duration `json:"flushInterval,omitempty"`
82+ FlushInterval *time.Duration `json:"flushInterval,omitempty"`
8383
8484 // PassHostHeader determines whether the request host header should be proxied
8585 // to the upstream server.
@@ -92,7 +92,7 @@ type Upstream struct {
9292
9393 // Timeout is the maximum duration the server will wait for a response from the upstream server.
9494 // Defaults to 30 seconds.
95− Timeout *Duration `json:"timeout,omitempty"`
95+ Timeout *time.Duration `json:"timeout,omitempty"`
9696
9797 // DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
9898 // Defaults to false.
pkg/apis/options/util/util.go+1−1
@@ -11,7 +11,7 @@ import (
1111func GetSecretValue(source *options.SecretSource) ([]byte, error) {
1212 switch {
1313 case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
14− return source.Value, nil
14+ return []byte(source.Value), nil
1515 case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
1616 return []byte(os.Getenv(source.FromEnv)), nil
1717 case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
pkg/apis/options/util/util_test.go+1−1
@@ -31,7 +31,7 @@ var _ = Describe("GetSecretValue", func() {
3131
3232 It("returns the correct value from the string value", func() {
3333 value, err := GetSecretValue(&options.SecretSource{
34− Value: []byte("secret-value-1"),
34+ Value: "secret-value-1",
3535 })
3636 Expect(err).ToNot(HaveOccurred())
3737 Expect(string(value)).To(Equal("secret-value-1"))
pkg/header/injector_test.go+8−8
@@ -55,7 +55,7 @@ var _ = Describe("Injector Suite", func() {
5555 Values: []options.HeaderValue{
5656 {
5757 SecretSource: &options.SecretSource{
58− Value: []byte("super-secret"),
58+ Value: "super-secret",
5959 },
6060 },
6161 },
@@ -199,7 +199,7 @@ var _ = Describe("Injector Suite", func() {
199199 ClaimSource: &options.ClaimSource{
200200 Claim: "user",
201201 BasicAuthPassword: &options.SecretSource{
202− Value: []byte("basic-password"),
202+ Value: "basic-password",
203203 },
204204 },
205205 },
@@ -227,7 +227,7 @@ var _ = Describe("Injector Suite", func() {
227227 ClaimSource: &options.ClaimSource{
228228 Claim: "user",
229229 BasicAuthPassword: &options.SecretSource{
230− Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
230+ Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
231231 },
232232 },
233233 },
@@ -322,7 +322,7 @@ var _ = Describe("Injector Suite", func() {
322322 ClaimSource: &options.ClaimSource{
323323 Claim: "user",
324324 BasicAuthPassword: &options.SecretSource{
325− Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
325+ Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
326326 FromEnv: "SECRET_ENV",
327327 },
328328 },
@@ -348,7 +348,7 @@ var _ = Describe("Injector Suite", func() {
348348 ClaimSource: &options.ClaimSource{
349349 Claim: "user",
350350 BasicAuthPassword: &options.SecretSource{
351− Value: []byte("basic-password"),
351+ Value: "basic-password",
352352 },
353353 },
354354 },
@@ -379,17 +379,17 @@ var _ = Describe("Injector Suite", func() {
379379 Values: []options.HeaderValue{
380380 {
381381 SecretSource: &options.SecretSource{
382− Value: []byte("major=1"),
382+ Value: "major=1",
383383 },
384384 },
385385 {
386386 SecretSource: &options.SecretSource{
387− Value: []byte("minor=2"),
387+ Value: "minor=2",
388388 },
389389 },
390390 {
391391 SecretSource: &options.SecretSource{
392− Value: []byte("patch=3"),
392+ Value: "patch=3",
393393 },
394394 },
395395 },
pkg/http/http_suite_test.go+6−6
@@ -48,10 +48,10 @@ var _ = BeforeSuite(func() {
4848
4949 certOut := new(bytes.Buffer)
5050 Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
51− ipv4CertDataSource.Value = certOut.Bytes()
51+ ipv4CertDataSource.Value = certOut.String()
5252 keyOut := new(bytes.Buffer)
5353 Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
54− ipv4KeyDataSource.Value = keyOut.Bytes()
54+ ipv4KeyDataSource.Value = keyOut.String()
5555 })
5656
5757 By("Generating a ipv6 self-signed cert for TLS tests", func() {
@@ -61,16 +61,16 @@ var _ = BeforeSuite(func() {
6161
6262 certOut := new(bytes.Buffer)
6363 Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
64− ipv6CertDataSource.Value = certOut.Bytes()
64+ ipv6CertDataSource.Value = certOut.String()
6565 keyOut := new(bytes.Buffer)
6666 Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
67− ipv6KeyDataSource.Value = keyOut.Bytes()
67+ ipv6KeyDataSource.Value = keyOut.String()
6868 })
6969
7070 By("Setting up a http client", func() {
71− ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value)
71+ ipv4cert, err := tls.X509KeyPair([]byte(ipv4CertDataSource.Value), []byte(ipv4KeyDataSource.Value))
7272 Expect(err).ToNot(HaveOccurred())
73− ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value)
73+ ipv6cert, err := tls.X509KeyPair([]byte(ipv6CertDataSource.Value), []byte(ipv6KeyDataSource.Value))
7474 Expect(err).ToNot(HaveOccurred())
7575
7676 ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])
pkg/http/server_test.go+4−4
@@ -234,7 +234,7 @@ var _ = Describe("Server", func() {
234234 SecureBindAddress: "127.0.0.1:0",
235235 TLS: &options.TLS{
236236 Key: &options.SecretSource{
237− Value: []byte("invalid"),
237+ Value: "invalid",
238238 },
239239 Cert: &ipv4CertDataSource,
240240 },
@@ -250,7 +250,7 @@ var _ = Describe("Server", func() {
250250 TLS: &options.TLS{
251251 Key: &ipv4KeyDataSource,
252252 Cert: &options.SecretSource{
253− Value: []byte("invalid"),
253+ Value: "invalid",
254254 },
255255 },
256256 },
@@ -506,7 +506,7 @@ var _ = Describe("Server", func() {
506506 SecureBindAddress: "[::1]:0",
507507 TLS: &options.TLS{
508508 Key: &options.SecretSource{
509− Value: []byte("invalid"),
509+ Value: "invalid",
510510 },
511511 Cert: &ipv6CertDataSource,
512512 },
@@ -523,7 +523,7 @@ var _ = Describe("Server", func() {
523523 TLS: &options.TLS{
524524 Key: &ipv6KeyDataSource,
525525 Cert: &options.SecretSource{
526− Value: []byte("invalid"),
526+ Value: "invalid",
527527 },
528528 },
529529 },
pkg/middleware/headers_test.go+4−4
@@ -188,7 +188,7 @@ var _ = Describe("Headers Suite", func() {
188188 ClaimSource: &options.ClaimSource{
189189 Claim: "user",
190190 BasicAuthPassword: &options.SecretSource{
191− Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
191+ Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
192192 FromEnv: "SECRET_ENV",
193193 },
194194 },
@@ -304,7 +304,7 @@ var _ = Describe("Headers Suite", func() {
304304 Values: []options.HeaderValue{
305305 {
306306 SecretSource: &options.SecretSource{
307− Value: []byte("_oauth2_proxy=ey123123123"),
307+ Value: "_oauth2_proxy=ey123123123",
308308 },
309309 },
310310 },
@@ -314,7 +314,7 @@ var _ = Describe("Headers Suite", func() {
314314 Values: []options.HeaderValue{
315315 {
316316 SecretSource: &options.SecretSource{
317− Value: []byte("oauth_user"),
317+ Value: "oauth_user",
318318 },
319319 },
320320 },
@@ -460,7 +460,7 @@ var _ = Describe("Headers Suite", func() {
460460 ClaimSource: &options.ClaimSource{
461461 Claim: "user",
462462 BasicAuthPassword: &options.SecretSource{
463− Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
463+ Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
464464 FromEnv: "SECRET_ENV",
465465 },
466466 },
pkg/upstream/http.go+2−2
@@ -137,12 +137,12 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
137137
138138 // Change default duration for waiting for an upstream response
139139 if upstream.Timeout != nil {
140− transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
140+ transport.ResponseHeaderTimeout = *upstream.Timeout
141141 }
142142
143143 // Configure options on the SingleHostReverseProxy
144144 if upstream.FlushInterval != nil {
145− proxy.FlushInterval = upstream.FlushInterval.Duration()
145+ proxy.FlushInterval = *upstream.FlushInterval
146146 } else {
147147 proxy.FlushInterval = options.DefaultUpstreamFlushInterval
148148 }
pkg/upstream/http_test.go+12−12
@@ -21,8 +21,8 @@ import (
2121)
2222
2323var _ = Describe("HTTP Upstream Suite", func() {
24− defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval)
25− defaultTimeout := options.Duration(options.DefaultUpstreamTimeout)
24+ defaultFlushInterval := options.DefaultUpstreamFlushInterval
25+ defaultTimeout := options.DefaultUpstreamTimeout
2626 truth := true
2727 falsum := false
2828
@@ -57,9 +57,9 @@ var _ = Describe("HTTP Upstream Suite", func() {
5757 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
5858 rw := httptest.NewRecorder()
5959
60− flush := options.Duration(1 * time.Second)
60+ flush := 1 * time.Second
6161
62− timeout := options.Duration(options.DefaultUpstreamTimeout)
62+ timeout := options.DefaultUpstreamTimeout
6363
6464 upstream := options.Upstream{
6565 ID: in.id,
@@ -373,11 +373,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
373373
374374 type newUpstreamTableInput struct {
375375 proxyWebSockets bool
376− flushInterval options.Duration
376+ flushInterval time.Duration
377377 skipVerify bool
378378 sigData *options.SignatureData
379379 errorHandler func(http.ResponseWriter, *http.Request, error)
380− timeout options.Duration
380+ timeout time.Duration
381381 disableKeepAlives bool
382382 }
383383
@@ -406,10 +406,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
406406
407407 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
408408 Expect(ok).To(BeTrue())
409− Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
409+ Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
410410 transport, ok := proxy.Transport.(*http.Transport)
411411 Expect(ok).To(BeTrue())
412− Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout.Duration()))
412+ Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout))
413413 Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
414414 if in.skipVerify {
415415 Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true))
@@ -428,7 +428,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
428428 }),
429429 Entry("with a non standard flush interval", &newUpstreamTableInput{
430430 proxyWebSockets: false,
431− flushInterval: options.Duration(5 * time.Second),
431+ flushInterval: 5 * time.Second,
432432 skipVerify: false,
433433 sigData: nil,
434434 errorHandler: nil,
@@ -466,7 +466,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
466466 skipVerify: false,
467467 sigData: nil,
468468 errorHandler: nil,
469− timeout: options.Duration(5 * time.Second),
469+ timeout: 5 * time.Second,
470470 }),
471471 Entry("with a DisableKeepAlives", &newUpstreamTableInput{
472472 proxyWebSockets: false,
@@ -483,8 +483,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
483483 var proxyServer *httptest.Server
484484
485485 BeforeEach(func() {
486− flush := options.Duration(1 * time.Second)
487− timeout := options.Duration(options.DefaultUpstreamTimeout)
486+ flush := 1 * time.Second
487+ timeout := options.DefaultUpstreamTimeout
488488 upstream := options.Upstream{
489489 ID: "websocketProxy",
490490 PassHostHeader: &truth,
pkg/validation/common_test.go+2−2
@@ -9,12 +9,12 @@ import (
99)
1010
1111var _ = Describe("Common", func() {
12− var validSecretSourceValue []byte
12+ var validSecretSourceValue string
1313 const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
1414 var validSecretSourceFile string
1515
1616 BeforeEach(func() {
17− validSecretSourceValue = []byte("This is a secret source value")
17+ validSecretSourceValue = "This is a secret source value"
1818 Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed())
1919 tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test")
2020 Expect(err).ToNot(HaveOccurred())
pkg/validation/header.go−2
@@ -51,11 +51,9 @@ func validateHeaderValue(_ string, value options.HeaderValue) []string {
5151
5252func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
5353 msgs := []string{}
54−
5554 if claim.Claim == "" {
5655 msgs = append(msgs, "claim should not be empty")
5756 }
58−
5957 if claim.BasicAuthPassword != nil {
6058 msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
6159 }
pkg/validation/header_test.go+2−2
@@ -30,7 +30,7 @@ var _ = Describe("Headers", func() {
3030 Values: []options.HeaderValue{
3131 {
3232 SecretSource: &options.SecretSource{
33− Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
33+ Value: base64.StdEncoding.EncodeToString([]byte("secret")),
3434 },
3535 },
3636 },
@@ -43,7 +43,7 @@ var _ = Describe("Headers", func() {
4343 ClaimSource: &options.ClaimSource{
4444 Claim: "email",
4545 BasicAuthPassword: &options.SecretSource{
46− Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
46+ Value: base64.StdEncoding.EncodeToString([]byte("secret")),
4747 },
4848 },
4949 },
pkg/validation/upstreams.go+1−1
@@ -69,7 +69,7 @@ func validateStaticUpstream(upstream options.Upstream) []string {
6969 if upstream.InsecureSkipTLSVerify {
7070 msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
7171 }
72− if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
72+ if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval {
7373 msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
7474 }
7575 if upstream.PassHostHeader != nil {
pkg/validation/upstreams_test.go+1−1
@@ -14,7 +14,7 @@ var _ = Describe("Upstreams", func() {
1414 errStrings []string
1515 }
1616
17− flushInterval := options.Duration(5 * time.Second)
17+ flushInterval := 5 * time.Second
1818 staticCode200 := 200
1919 truth := true
2020