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
This commit is contained in:
parent
8afb047e01
commit
31a4c34726
|
|
@ -37,7 +37,7 @@ linters:
|
||||||
- linters:
|
- linters:
|
||||||
- revive
|
- revive
|
||||||
path: _test\.go
|
path: _test\.go
|
||||||
text: 'dot-imports:'
|
text: "dot-imports:"
|
||||||
# # If we have tests in shared test folders, these can be less strictly linted
|
# # If we have tests in shared test folders, these can be less strictly linted
|
||||||
- linters:
|
- linters:
|
||||||
- bodyclose
|
- bodyclose
|
||||||
|
|
|
||||||
|
|
@ -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<br/>claim if it is non-empty. |
|
| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the<br/>claim if it is non-empty. |
|
||||||
| `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. |
|
| `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. |
|
||||||
|
|
||||||
### 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
|
### GitHubOptions
|
||||||
|
|
||||||
(**Appears on:** [Provider](#provider))
|
(**Appears on:** [Provider](#provider))
|
||||||
|
|
@ -275,7 +265,7 @@ make up the header value
|
||||||
|
|
||||||
| Field | Type | Description |
|
| 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. |
|
| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
|
||||||
| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
|
| `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<br/>loaded from. Available claims: `access_token` `id_token` `created_at`<br/>`expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. |
|
| `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`. |
|
||||||
|
|
@ -487,7 +477,7 @@ Only one source within the struct should be defined at any time.
|
||||||
|
|
||||||
| Field | Type | Description |
|
| 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. |
|
| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
|
||||||
| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
|
| `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.<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. |
|
| `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. |
|
||||||
| `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. |
|
| `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. |
|
||||||
| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
|
| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
|
||||||
| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
|
| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
|
||||||
| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
|
| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
|
||||||
| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
|
| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
|
||||||
| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
|
| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
|
||||||
| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
|
| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
|
||||||
|
|
||||||
### UpstreamConfig
|
### UpstreamConfig
|
||||||
|
|
|
||||||
35
main.go
35
main.go
|
|
@ -67,12 +67,17 @@ func main() {
|
||||||
// loadConfiguration will load in the user's configuration.
|
// loadConfiguration will load in the user's configuration.
|
||||||
// It will either load the alpha configuration (if alphaConfig is given)
|
// It will either load the alpha configuration (if alphaConfig is given)
|
||||||
// or the legacy configuration.
|
// or the legacy configuration.
|
||||||
func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
|
func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
|
||||||
if alphaConfig != "" {
|
opts, err := loadLegacyOptions(config, extraFlags, args)
|
||||||
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.")
|
if err != nil {
|
||||||
return loadAlphaOptions(config, alphaConfig, extraFlags, args)
|
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
|
// 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
|
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,
|
// the new alpha format, then merges the alpha options, loaded from YAML,
|
||||||
// into the core configuration.
|
// 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)
|
opts, err := loadOptions(config, extraFlags, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load core options: %v", err)
|
return nil, fmt.Errorf("failed to load core options: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
alphaOpts := &options.AlphaOptions{}
|
alphaOpts := options.NewAlphaOptions(opts)
|
||||||
if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
|
if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load alpha options: %v", err)
|
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
|
// printConvertedConfig extracts alpha options from the loaded configuration
|
||||||
// and renders these to stdout in YAML format.
|
// and renders these to stdout in YAML format.
|
||||||
func printConvertedConfig(opts *options.Options) error {
|
func printConvertedConfig(opts *options.Options) error {
|
||||||
alphaConfig := &options.AlphaOptions{}
|
alphaConfig := options.NewAlphaOptions(opts)
|
||||||
alphaConfig.ExtractFrom(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 {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to marshal config: %v", err)
|
return fmt.Errorf("unable to marshal config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
main_test.go
31
main_test.go
|
|
@ -43,29 +43,35 @@ upstreamConfig:
|
||||||
injectRequestHeaders:
|
injectRequestHeaders:
|
||||||
- name: Authorization
|
- name: Authorization
|
||||||
values:
|
values:
|
||||||
- claim: user
|
- claimSource:
|
||||||
|
claim: user
|
||||||
prefix: "Basic "
|
prefix: "Basic "
|
||||||
basicAuthPassword:
|
basicAuthPassword:
|
||||||
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
|
value: super-secret-password
|
||||||
- name: X-Forwarded-Groups
|
- name: X-Forwarded-Groups
|
||||||
values:
|
values:
|
||||||
- claim: groups
|
- claimSource:
|
||||||
|
claim: groups
|
||||||
- name: X-Forwarded-User
|
- name: X-Forwarded-User
|
||||||
values:
|
values:
|
||||||
- claim: user
|
- claimSource:
|
||||||
|
claim: user
|
||||||
- name: X-Forwarded-Email
|
- name: X-Forwarded-Email
|
||||||
values:
|
values:
|
||||||
- claim: email
|
- claimSource:
|
||||||
|
claim: email
|
||||||
- name: X-Forwarded-Preferred-Username
|
- name: X-Forwarded-Preferred-Username
|
||||||
values:
|
values:
|
||||||
- claim: preferred_username
|
- claimSource:
|
||||||
|
claim: preferred_username
|
||||||
injectResponseHeaders:
|
injectResponseHeaders:
|
||||||
- name: Authorization
|
- name: Authorization
|
||||||
values:
|
values:
|
||||||
- claim: user
|
- claimSource:
|
||||||
|
claim: user
|
||||||
prefix: "Basic "
|
prefix: "Basic "
|
||||||
basicAuthPassword:
|
basicAuthPassword:
|
||||||
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
|
value: super-secret-password
|
||||||
server:
|
server:
|
||||||
bindAddress: "127.0.0.1:4180"
|
bindAddress: "127.0.0.1:4180"
|
||||||
providers:
|
providers:
|
||||||
|
|
@ -100,9 +106,8 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
||||||
durationPtr := func(d time.Duration) *options.Duration {
|
durationPtr := func(d time.Duration) *time.Duration {
|
||||||
du := options.Duration(d)
|
return &d
|
||||||
return &du
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testExpectedOptions := func() *options.Options {
|
testExpectedOptions := func() *options.Options {
|
||||||
|
|
@ -136,7 +141,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
Prefix: "Basic ",
|
Prefix: "Basic ",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
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)
|
opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
|
||||||
if in.expectedErr != nil {
|
if in.expectedErr != nil {
|
||||||
Expect(err).To(MatchError(in.expectedErr.Error()))
|
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
|
||||||
} else {
|
} else {
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "email",
|
Claim: "email",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(basicAuthPassword),
|
Value: basicAuthPassword,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1282,7 +1282,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
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{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
|
Value: base64.StdEncoding.EncodeToString([]byte("This is a secure password")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,11 @@ type AlphaOptions struct {
|
||||||
Providers Providers `json:"providers,omitempty"`
|
Providers Providers `json:"providers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeInto replaces alpha options in the Options struct with the values
|
// Initialize alpha options with default values and settings of the core options
|
||||||
// from the AlphaOptions
|
func NewAlphaOptions(opts *Options) *AlphaOptions {
|
||||||
func (a *AlphaOptions) MergeInto(opts *Options) {
|
aOpts := &AlphaOptions{}
|
||||||
opts.UpstreamServers = a.UpstreamConfig
|
aOpts.ExtractFrom(opts)
|
||||||
opts.InjectRequestHeaders = a.InjectRequestHeaders
|
return aOpts
|
||||||
opts.InjectResponseHeaders = a.InjectResponseHeaders
|
|
||||||
opts.Server = a.Server
|
|
||||||
opts.MetricsServer = a.MetricsServer
|
|
||||||
opts.Providers = a.Providers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractFrom populates the fields in the AlphaOptions with the values from
|
// 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.MetricsServer = opts.MetricsServer
|
||||||
a.Providers = opts.Providers
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,10 +21,10 @@ type Header struct {
|
||||||
// make up the header value
|
// make up the header value
|
||||||
type HeaderValue struct {
|
type HeaderValue struct {
|
||||||
// Allow users to load the value from a secret source
|
// 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
|
// 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
|
// ClaimSource allows loading a header value from a claim within the session
|
||||||
|
|
|
||||||
|
|
@ -136,8 +136,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
|
||||||
u.Path = "/"
|
u.Path = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
flushInterval := Duration(l.FlushInterval)
|
flushInterval := l.FlushInterval
|
||||||
timeout := Duration(l.Timeout)
|
timeout := l.Timeout
|
||||||
upstream := Upstream{
|
upstream := Upstream{
|
||||||
ID: u.Path,
|
ID: u.Path,
|
||||||
Path: u.Path,
|
Path: u.Path,
|
||||||
|
|
@ -294,7 +294,7 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
|
||||||
Claim: claim,
|
Claim: claim,
|
||||||
Prefix: "Basic ",
|
Prefix: "Basic ",
|
||||||
BasicAuthPassword: &SecretSource{
|
BasicAuthPassword: &SecretSource{
|
||||||
Value: []byte(basicAuthPassword),
|
Value: basicAuthPassword,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ var _ = Describe("Legacy Options", func() {
|
||||||
legacyOpts := NewLegacyOptions()
|
legacyOpts := NewLegacyOptions()
|
||||||
|
|
||||||
// Set upstreams and related options to test their conversion
|
// Set upstreams and related options to test their conversion
|
||||||
flushInterval := Duration(5 * time.Second)
|
flushInterval := 5 * time.Second
|
||||||
timeout := Duration(5 * time.Second)
|
timeout := 5 * time.Second
|
||||||
legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
|
legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
|
||||||
legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
|
legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
|
||||||
legacyOpts.LegacyUpstreams.PassHostHeader = true
|
legacyOpts.LegacyUpstreams.PassHostHeader = true
|
||||||
|
|
@ -147,8 +147,8 @@ var _ = Describe("Legacy Options", func() {
|
||||||
skipVerify := true
|
skipVerify := true
|
||||||
passHostHeader := false
|
passHostHeader := false
|
||||||
proxyWebSockets := true
|
proxyWebSockets := true
|
||||||
flushInterval := Duration(5 * time.Second)
|
flushInterval := 5 * time.Second
|
||||||
timeout := Duration(5 * time.Second)
|
timeout := 5 * time.Second
|
||||||
disableKeepAlives := true
|
disableKeepAlives := true
|
||||||
|
|
||||||
// Test cases and expected outcomes
|
// Test cases and expected outcomes
|
||||||
|
|
@ -369,7 +369,7 @@ var _ = Describe("Legacy Options", func() {
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
Prefix: "Basic ",
|
Prefix: "Basic ",
|
||||||
BasicAuthPassword: &SecretSource{
|
BasicAuthPassword: &SecretSource{
|
||||||
Value: []byte(basicAuthSecret),
|
Value: basicAuthSecret,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -409,7 +409,7 @@ var _ = Describe("Legacy Options", func() {
|
||||||
Claim: "email",
|
Claim: "email",
|
||||||
Prefix: "Basic ",
|
Prefix: "Basic ",
|
||||||
BasicAuthPassword: &SecretSource{
|
BasicAuthPassword: &SecretSource{
|
||||||
Value: []byte(basicAuthSecret),
|
Value: basicAuthSecret,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,76 @@ func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error
|
||||||
return nil
|
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
|
// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
|
||||||
// to the fields in the options interface provided.
|
// to the fields in the options interface provided.
|
||||||
// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
|
// 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)
|
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
|
// normalizeSubstitution normalizes dollar signs ($) with numerals like
|
||||||
// $1 or $2 properly by correctly escaping them
|
// $1 or $2 properly by correctly escaping them
|
||||||
func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {
|
func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {
|
||||||
|
|
|
||||||
|
|
@ -355,15 +355,15 @@ var _ = Describe("Load", func() {
|
||||||
var _ = Describe("LoadYAML", func() {
|
var _ = Describe("LoadYAML", func() {
|
||||||
Context("with a testOptions structure", func() {
|
Context("with a testOptions structure", func() {
|
||||||
type TestOptionSubStruct struct {
|
type TestOptionSubStruct struct {
|
||||||
StringSliceOption []string `yaml:"stringSliceOption,omitempty"`
|
StringSliceOption []string `json:"stringSliceOption,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestOptions struct {
|
type TestOptions struct {
|
||||||
StringOption string `yaml:"stringOption,omitempty"`
|
StringOption string `json:"stringOption,omitempty"`
|
||||||
Sub TestOptionSubStruct `yaml:"sub,omitempty"`
|
Sub TestOptionSubStruct `json:"sub,omitempty"`
|
||||||
|
|
||||||
// Check that embedded fields can be unmarshalled
|
// Check that embedded fields can be unmarshalled
|
||||||
TestOptionSubStruct `yaml:",inline,squash"`
|
TestOptionSubStruct `json:",inline,squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var testOptionsConfigBytesFull = []byte(`
|
var testOptionsConfigBytesFull = []byte(`
|
||||||
|
|
@ -416,7 +416,7 @@ sub:
|
||||||
|
|
||||||
err := LoadYAML(configFileName, input)
|
err := LoadYAML(configFileName, input)
|
||||||
if in.expectedErr != nil {
|
if in.expectedErr != nil {
|
||||||
Expect(err).To(MatchError(in.expectedErr.Error()))
|
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
|
||||||
} else {
|
} else {
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
|
@ -459,13 +459,13 @@ sub:
|
||||||
StringSliceOption: []string{"a", "b", "c"},
|
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{
|
Entry("with an incorrect type for a string field", loadYAMLTableInput{
|
||||||
configFile: []byte(`stringOption: ["a", "b"]`),
|
configFile: []byte(`stringOption: ["a", "b"]`),
|
||||||
input: &TestOptions{},
|
input: &TestOptions{},
|
||||||
expectedOutput: &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{
|
Entry("with an incorrect type for an array field", loadYAMLTableInput{
|
||||||
configFile: []byte(`stringSliceOption: "a"`),
|
configFile: []byte(`stringSliceOption: "a"`),
|
||||||
|
|
@ -526,11 +526,13 @@ upstreamConfig:
|
||||||
injectRequestHeaders:
|
injectRequestHeaders:
|
||||||
- name: X-Forwarded-User
|
- name: X-Forwarded-User
|
||||||
values:
|
values:
|
||||||
- claim: user
|
- claimSource:
|
||||||
|
claim: user
|
||||||
injectResponseHeaders:
|
injectResponseHeaders:
|
||||||
- name: X-Secret
|
- name: X-Secret
|
||||||
values:
|
values:
|
||||||
- value: c2VjcmV0
|
- secretSource:
|
||||||
|
value: secret
|
||||||
`)
|
`)
|
||||||
|
|
||||||
By("Creating a config file")
|
By("Creating a config file")
|
||||||
|
|
@ -548,7 +550,7 @@ injectResponseHeaders:
|
||||||
into := &AlphaOptions{}
|
into := &AlphaOptions{}
|
||||||
Expect(LoadYAML(configFileName, into)).To(Succeed())
|
Expect(LoadYAML(configFileName, into)).To(Succeed())
|
||||||
|
|
||||||
flushInterval := Duration(500 * time.Millisecond)
|
flushInterval := 500 * time.Millisecond
|
||||||
|
|
||||||
Expect(into).To(Equal(&AlphaOptions{
|
Expect(into).To(Equal(&AlphaOptions{
|
||||||
UpstreamConfig: UpstreamConfig{
|
UpstreamConfig: UpstreamConfig{
|
||||||
|
|
@ -579,7 +581,7 @@ injectResponseHeaders:
|
||||||
Values: []HeaderValue{
|
Values: []HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &SecretSource{
|
SecretSource: &SecretSource{
|
||||||
Value: []byte("secret"),
|
Value: "secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -238,16 +238,16 @@ type OIDCOptions struct {
|
||||||
IssuerURL string `json:"issuerURL,omitempty"`
|
IssuerURL string `json:"issuerURL,omitempty"`
|
||||||
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
|
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
|
||||||
// default set to 'false'
|
// 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
|
// InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
|
||||||
// default set to 'false'
|
// 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
|
// 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
|
// the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
|
||||||
// after the initial OAuth redeem & subsequent token refreshes.
|
// after the initial OAuth redeem & subsequent token refreshes.
|
||||||
// default set to 'true'
|
// default set to 'true'
|
||||||
// Warning: In a future release, this will change to 'false' by default for enhanced security.
|
// 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
|
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
|
||||||
// default set to 'false'
|
// default set to 'false'
|
||||||
SkipDiscovery bool `json:"skipDiscovery,omitempty"`
|
SkipDiscovery bool `json:"skipDiscovery,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ type Upstream struct {
|
||||||
// FlushInterval is the period between flushing the response buffer when
|
// FlushInterval is the period between flushing the response buffer when
|
||||||
// streaming response from the upstream.
|
// streaming response from the upstream.
|
||||||
// Defaults to 1 second.
|
// 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
|
// PassHostHeader determines whether the request host header should be proxied
|
||||||
// to the upstream server.
|
// 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.
|
// Timeout is the maximum duration the server will wait for a response from the upstream server.
|
||||||
// Defaults to 30 seconds.
|
// 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.
|
// DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
|
||||||
// Defaults to false.
|
// Defaults to false.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
func GetSecretValue(source *options.SecretSource) ([]byte, error) {
|
func GetSecretValue(source *options.SecretSource) ([]byte, error) {
|
||||||
switch {
|
switch {
|
||||||
case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
|
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 == "":
|
case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
|
||||||
return []byte(os.Getenv(source.FromEnv)), nil
|
return []byte(os.Getenv(source.FromEnv)), nil
|
||||||
case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
|
case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ var _ = Describe("GetSecretValue", func() {
|
||||||
|
|
||||||
It("returns the correct value from the string value", func() {
|
It("returns the correct value from the string value", func() {
|
||||||
value, err := GetSecretValue(&options.SecretSource{
|
value, err := GetSecretValue(&options.SecretSource{
|
||||||
Value: []byte("secret-value-1"),
|
Value: "secret-value-1",
|
||||||
})
|
})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(string(value)).To(Equal("secret-value-1"))
|
Expect(string(value)).To(Equal("secret-value-1"))
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ var _ = Describe("Injector Suite", func() {
|
||||||
Values: []options.HeaderValue{
|
Values: []options.HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("super-secret"),
|
Value: "super-secret",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -199,7 +199,7 @@ var _ = Describe("Injector Suite", func() {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte("basic-password"),
|
Value: "basic-password",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -227,7 +227,7 @@ var _ = Describe("Injector Suite", func() {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
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{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
|
||||||
FromEnv: "SECRET_ENV",
|
FromEnv: "SECRET_ENV",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -348,7 +348,7 @@ var _ = Describe("Injector Suite", func() {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte("basic-password"),
|
Value: "basic-password",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -379,17 +379,17 @@ var _ = Describe("Injector Suite", func() {
|
||||||
Values: []options.HeaderValue{
|
Values: []options.HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("major=1"),
|
Value: "major=1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("minor=2"),
|
Value: "minor=2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("patch=3"),
|
Value: "patch=3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
certOut := new(bytes.Buffer)
|
certOut := new(bytes.Buffer)
|
||||||
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
|
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
|
||||||
ipv4CertDataSource.Value = certOut.Bytes()
|
ipv4CertDataSource.Value = certOut.String()
|
||||||
keyOut := new(bytes.Buffer)
|
keyOut := new(bytes.Buffer)
|
||||||
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
|
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() {
|
By("Generating a ipv6 self-signed cert for TLS tests", func() {
|
||||||
|
|
@ -61,16 +61,16 @@ var _ = BeforeSuite(func() {
|
||||||
|
|
||||||
certOut := new(bytes.Buffer)
|
certOut := new(bytes.Buffer)
|
||||||
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
|
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
|
||||||
ipv6CertDataSource.Value = certOut.Bytes()
|
ipv6CertDataSource.Value = certOut.String()
|
||||||
keyOut := new(bytes.Buffer)
|
keyOut := new(bytes.Buffer)
|
||||||
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
|
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() {
|
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())
|
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())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])
|
ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ var _ = Describe("Server", func() {
|
||||||
SecureBindAddress: "127.0.0.1:0",
|
SecureBindAddress: "127.0.0.1:0",
|
||||||
TLS: &options.TLS{
|
TLS: &options.TLS{
|
||||||
Key: &options.SecretSource{
|
Key: &options.SecretSource{
|
||||||
Value: []byte("invalid"),
|
Value: "invalid",
|
||||||
},
|
},
|
||||||
Cert: &ipv4CertDataSource,
|
Cert: &ipv4CertDataSource,
|
||||||
},
|
},
|
||||||
|
|
@ -250,7 +250,7 @@ var _ = Describe("Server", func() {
|
||||||
TLS: &options.TLS{
|
TLS: &options.TLS{
|
||||||
Key: &ipv4KeyDataSource,
|
Key: &ipv4KeyDataSource,
|
||||||
Cert: &options.SecretSource{
|
Cert: &options.SecretSource{
|
||||||
Value: []byte("invalid"),
|
Value: "invalid",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -506,7 +506,7 @@ var _ = Describe("Server", func() {
|
||||||
SecureBindAddress: "[::1]:0",
|
SecureBindAddress: "[::1]:0",
|
||||||
TLS: &options.TLS{
|
TLS: &options.TLS{
|
||||||
Key: &options.SecretSource{
|
Key: &options.SecretSource{
|
||||||
Value: []byte("invalid"),
|
Value: "invalid",
|
||||||
},
|
},
|
||||||
Cert: &ipv6CertDataSource,
|
Cert: &ipv6CertDataSource,
|
||||||
},
|
},
|
||||||
|
|
@ -523,7 +523,7 @@ var _ = Describe("Server", func() {
|
||||||
TLS: &options.TLS{
|
TLS: &options.TLS{
|
||||||
Key: &ipv6KeyDataSource,
|
Key: &ipv6KeyDataSource,
|
||||||
Cert: &options.SecretSource{
|
Cert: &options.SecretSource{
|
||||||
Value: []byte("invalid"),
|
Value: "invalid",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ var _ = Describe("Headers Suite", func() {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
|
||||||
FromEnv: "SECRET_ENV",
|
FromEnv: "SECRET_ENV",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -260,7 +260,7 @@ var _ = Describe("Headers Suite", func() {
|
||||||
Values: []options.HeaderValue{
|
Values: []options.HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("_oauth2_proxy=ey123123123"),
|
Value: "_oauth2_proxy=ey123123123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -270,7 +270,7 @@ var _ = Describe("Headers Suite", func() {
|
||||||
Values: []options.HeaderValue{
|
Values: []options.HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
SecretSource: &options.SecretSource{
|
||||||
Value: []byte("oauth_user"),
|
Value: "oauth_user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -416,7 +416,7 @@ var _ = Describe("Headers Suite", func() {
|
||||||
ClaimSource: &options.ClaimSource{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "user",
|
Claim: "user",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
|
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
|
||||||
FromEnv: "SECRET_ENV",
|
FromEnv: "SECRET_ENV",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -137,12 +137,12 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
|
||||||
|
|
||||||
// Change default duration for waiting for an upstream response
|
// Change default duration for waiting for an upstream response
|
||||||
if upstream.Timeout != nil {
|
if upstream.Timeout != nil {
|
||||||
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
|
transport.ResponseHeaderTimeout = *upstream.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure options on the SingleHostReverseProxy
|
// Configure options on the SingleHostReverseProxy
|
||||||
if upstream.FlushInterval != nil {
|
if upstream.FlushInterval != nil {
|
||||||
proxy.FlushInterval = upstream.FlushInterval.Duration()
|
proxy.FlushInterval = *upstream.FlushInterval
|
||||||
} else {
|
} else {
|
||||||
proxy.FlushInterval = options.DefaultUpstreamFlushInterval
|
proxy.FlushInterval = options.DefaultUpstreamFlushInterval
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("HTTP Upstream Suite", func() {
|
var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval)
|
defaultFlushInterval := options.DefaultUpstreamFlushInterval
|
||||||
defaultTimeout := options.Duration(options.DefaultUpstreamTimeout)
|
defaultTimeout := options.DefaultUpstreamTimeout
|
||||||
truth := true
|
truth := true
|
||||||
falsum := false
|
falsum := false
|
||||||
|
|
||||||
|
|
@ -57,9 +57,9 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
|
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
flush := options.Duration(1 * time.Second)
|
flush := 1 * time.Second
|
||||||
|
|
||||||
timeout := options.Duration(options.DefaultUpstreamTimeout)
|
timeout := options.DefaultUpstreamTimeout
|
||||||
|
|
||||||
upstream := options.Upstream{
|
upstream := options.Upstream{
|
||||||
ID: in.id,
|
ID: in.id,
|
||||||
|
|
@ -373,11 +373,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
|
|
||||||
type newUpstreamTableInput struct {
|
type newUpstreamTableInput struct {
|
||||||
proxyWebSockets bool
|
proxyWebSockets bool
|
||||||
flushInterval options.Duration
|
flushInterval time.Duration
|
||||||
skipVerify bool
|
skipVerify bool
|
||||||
sigData *options.SignatureData
|
sigData *options.SignatureData
|
||||||
errorHandler func(http.ResponseWriter, *http.Request, error)
|
errorHandler func(http.ResponseWriter, *http.Request, error)
|
||||||
timeout options.Duration
|
timeout time.Duration
|
||||||
disableKeepAlives bool
|
disableKeepAlives bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,10 +406,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
|
|
||||||
proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
|
proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
|
||||||
Expect(ok).To(BeTrue())
|
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)
|
transport, ok := proxy.Transport.(*http.Transport)
|
||||||
Expect(ok).To(BeTrue())
|
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))
|
Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
|
||||||
if in.skipVerify {
|
if in.skipVerify {
|
||||||
Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true))
|
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{
|
Entry("with a non standard flush interval", &newUpstreamTableInput{
|
||||||
proxyWebSockets: false,
|
proxyWebSockets: false,
|
||||||
flushInterval: options.Duration(5 * time.Second),
|
flushInterval: 5 * time.Second,
|
||||||
skipVerify: false,
|
skipVerify: false,
|
||||||
sigData: nil,
|
sigData: nil,
|
||||||
errorHandler: nil,
|
errorHandler: nil,
|
||||||
|
|
@ -466,7 +466,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
skipVerify: false,
|
skipVerify: false,
|
||||||
sigData: nil,
|
sigData: nil,
|
||||||
errorHandler: nil,
|
errorHandler: nil,
|
||||||
timeout: options.Duration(5 * time.Second),
|
timeout: 5 * time.Second,
|
||||||
}),
|
}),
|
||||||
Entry("with a DisableKeepAlives", &newUpstreamTableInput{
|
Entry("with a DisableKeepAlives", &newUpstreamTableInput{
|
||||||
proxyWebSockets: false,
|
proxyWebSockets: false,
|
||||||
|
|
@ -483,8 +483,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
|
||||||
var proxyServer *httptest.Server
|
var proxyServer *httptest.Server
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
flush := options.Duration(1 * time.Second)
|
flush := 1 * time.Second
|
||||||
timeout := options.Duration(options.DefaultUpstreamTimeout)
|
timeout := options.DefaultUpstreamTimeout
|
||||||
upstream := options.Upstream{
|
upstream := options.Upstream{
|
||||||
ID: "websocketProxy",
|
ID: "websocketProxy",
|
||||||
PassHostHeader: &truth,
|
PassHostHeader: &truth,
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Common", func() {
|
var _ = Describe("Common", func() {
|
||||||
var validSecretSourceValue []byte
|
var validSecretSourceValue string
|
||||||
const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
|
const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
|
||||||
var validSecretSourceFile string
|
var validSecretSourceFile string
|
||||||
|
|
||||||
BeforeEach(func() {
|
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())
|
Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed())
|
||||||
tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test")
|
tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,9 @@ func validateHeaderValue(_ string, value options.HeaderValue) []string {
|
||||||
|
|
||||||
func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
|
func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
|
||||||
msgs := []string{}
|
msgs := []string{}
|
||||||
|
|
||||||
if claim.Claim == "" {
|
if claim.Claim == "" {
|
||||||
msgs = append(msgs, "claim should not be empty")
|
msgs = append(msgs, "claim should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if claim.BasicAuthPassword != nil {
|
if claim.BasicAuthPassword != nil {
|
||||||
msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
|
msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ var _ = Describe("Headers", func() {
|
||||||
Values: []options.HeaderValue{
|
Values: []options.HeaderValue{
|
||||||
{
|
{
|
||||||
SecretSource: &options.SecretSource{
|
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{
|
ClaimSource: &options.ClaimSource{
|
||||||
Claim: "email",
|
Claim: "email",
|
||||||
BasicAuthPassword: &options.SecretSource{
|
BasicAuthPassword: &options.SecretSource{
|
||||||
Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
|
Value: base64.StdEncoding.EncodeToString([]byte("secret")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func validateStaticUpstream(upstream options.Upstream) []string {
|
||||||
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))
|
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))
|
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 {
|
if upstream.PassHostHeader != nil {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ var _ = Describe("Upstreams", func() {
|
||||||
errStrings []string
|
errStrings []string
|
||||||
}
|
}
|
||||||
|
|
||||||
flushInterval := options.Duration(5 * time.Second)
|
flushInterval := 5 * time.Second
|
||||||
staticCode200 := 200
|
staticCode200 := 200
|
||||||
truth := true
|
truth := true
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue