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:
tuunit 2024-05-04 16:41:54 +02:00 committed by Jan Larwig
parent 8afb047e01
commit 31a4c34726
No known key found for this signature in database
GPG Key ID: C2172BFA220A037A
29 changed files with 269 additions and 233 deletions

View File

@ -37,7 +37,7 @@ linters:
- linters:
- revive
path: _test\.go
text: 'dot-imports:'
text: "dot-imports:"
# # If we have tests in shared test folders, these can be less strictly linted
- linters:
- bodyclose

View File

@ -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. |
| `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
(**Appears on:** [Provider](#provider))
@ -275,7 +265,7 @@ make up the header value
| Field | Type | Description |
| ----- | ---- | ----------- |
| `value` | _[]byte_ | Value expects a base64 encoded string value. |
| `value` | _string_ | Value expects a base64 encoded string value. |
| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
| `claim` | _string_ | Claim is the name of the claim in the session that the value should be<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 |
| ----- | ---- | ----------- |
| `value` | _[]byte_ | Value expects a base64 encoded string value. |
| `value` | _string_ | Value expects a base64 encoded string value. |
| `fromEnv` | _string_ | FromEnv expects the name of an environment variable. |
| `fromFile` | _string_ | FromFile expects a path to a file containing the secret value. |
@ -547,10 +537,10 @@ Requests will be proxied to this upstream if the path matches the request path.
| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.<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. |
| `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. |
| `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. |
### UpstreamConfig

35
main.go
View File

@ -67,12 +67,17 @@ func main() {
// loadConfiguration will load in the user's configuration.
// It will either load the alpha configuration (if alphaConfig is given)
// or the legacy configuration.
func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
if alphaConfig != "" {
logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
return loadAlphaOptions(config, alphaConfig, extraFlags, args)
func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadLegacyOptions(config, extraFlags, args)
if err != nil {
return nil, err
}
return loadLegacyOptions(config, extraFlags, args)
if yamlConfig != "" {
logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
return loadYamlOptions(yamlConfig, config, extraFlags, args)
}
return opts, err
}
// loadLegacyOptions loads the old toml options using the legacy flagset
@ -97,17 +102,17 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string)
return opts, nil
}
// loadAlphaOptions loads the old style config excluding options converted to
// loadYamlOptions loads the old style config excluding options converted to
// the new alpha format, then merges the alpha options, loaded from YAML,
// into the core configuration.
func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
func loadYamlOptions(yamlConfig, config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadOptions(config, extraFlags, args)
if err != nil {
return nil, fmt.Errorf("failed to load core options: %v", err)
}
alphaOpts := &options.AlphaOptions{}
if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
alphaOpts := options.NewAlphaOptions(opts)
if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
return nil, fmt.Errorf("failed to load alpha options: %v", err)
}
@ -137,10 +142,16 @@ func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*opti
// printConvertedConfig extracts alpha options from the loaded configuration
// and renders these to stdout in YAML format.
func printConvertedConfig(opts *options.Options) error {
alphaConfig := &options.AlphaOptions{}
alphaConfig.ExtractFrom(opts)
alphaConfig := options.NewAlphaOptions(opts)
data, err := yaml.Marshal(alphaConfig)
// Generic interface for loading arbitrary yaml structure
var buffer map[string]interface{}
if err := options.Decode(alphaConfig, &buffer); err != nil {
return fmt.Errorf("unable to decode alpha config into interface: %w", err)
}
data, err := yaml.Marshal(buffer)
if err != nil {
return fmt.Errorf("unable to marshal config: %v", err)
}

View File

@ -43,29 +43,35 @@ upstreamConfig:
injectRequestHeaders:
- name: Authorization
values:
- claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- claimSource:
claim: user
prefix: "Basic "
basicAuthPassword:
value: super-secret-password
- name: X-Forwarded-Groups
values:
- claim: groups
- claimSource:
claim: groups
- name: X-Forwarded-User
values:
- claim: user
- claimSource:
claim: user
- name: X-Forwarded-Email
values:
- claim: email
- claimSource:
claim: email
- name: X-Forwarded-Preferred-Username
values:
- claim: preferred_username
- claimSource:
claim: preferred_username
injectResponseHeaders:
- name: Authorization
values:
- claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- claimSource:
claim: user
prefix: "Basic "
basicAuthPassword:
value: super-secret-password
server:
bindAddress: "127.0.0.1:4180"
providers:
@ -100,9 +106,8 @@ redirect_url="http://localhost:4180/oauth2/callback"
return &b
}
durationPtr := func(d time.Duration) *options.Duration {
du := options.Duration(d)
return &du
durationPtr := func(d time.Duration) *time.Duration {
return &d
}
testExpectedOptions := func() *options.Options {
@ -136,7 +141,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
Claim: "user",
Prefix: "Basic ",
BasicAuthPassword: &options.SecretSource{
Value: []byte("super-secret-password"),
Value: "super-secret-password",
},
},
},
@ -226,7 +231,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}

View File

@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) {
ClaimSource: &options.ClaimSource{
Claim: "email",
BasicAuthPassword: &options.SecretSource{
Value: []byte(basicAuthPassword),
Value: basicAuthPassword,
},
},
},
@ -1282,7 +1282,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte("This is a secure password"),
Value: "This is a secure password",
},
},
},
@ -2044,7 +2044,7 @@ func baseTestOptions() *options.Options {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
Value: base64.StdEncoding.EncodeToString([]byte("This is a secure password")),
},
},
},

View File

@ -47,15 +47,11 @@ type AlphaOptions struct {
Providers Providers `json:"providers,omitempty"`
}
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
// Initialize alpha options with default values and settings of the core options
func NewAlphaOptions(opts *Options) *AlphaOptions {
aOpts := &AlphaOptions{}
aOpts.ExtractFrom(opts)
return aOpts
}
// ExtractFrom populates the fields in the AlphaOptions with the values from
@ -68,3 +64,14 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) {
a.MetricsServer = opts.MetricsServer
a.Providers = opts.Providers
}
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -21,10 +21,10 @@ type Header struct {
// make up the header value
type HeaderValue struct {
// Allow users to load the value from a secret source
*SecretSource `json:",omitempty"`
*SecretSource `json:"secretSource,omitempty"`
// Allow users to load the value from a session claim
*ClaimSource `json:",omitempty"`
*ClaimSource `json:"claimSource,omitempty"`
}
// ClaimSource allows loading a header value from a claim within the session

View File

@ -136,8 +136,8 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
u.Path = "/"
}
flushInterval := Duration(l.FlushInterval)
timeout := Duration(l.Timeout)
flushInterval := l.FlushInterval
timeout := l.Timeout
upstream := Upstream{
ID: u.Path,
Path: u.Path,
@ -294,7 +294,7 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
Claim: claim,
Prefix: "Basic ",
BasicAuthPassword: &SecretSource{
Value: []byte(basicAuthPassword),
Value: basicAuthPassword,
},
},
},

View File

@ -15,8 +15,8 @@ var _ = Describe("Legacy Options", func() {
legacyOpts := NewLegacyOptions()
// Set upstreams and related options to test their conversion
flushInterval := Duration(5 * time.Second)
timeout := Duration(5 * time.Second)
flushInterval := 5 * time.Second
timeout := 5 * time.Second
legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
legacyOpts.LegacyUpstreams.PassHostHeader = true
@ -147,8 +147,8 @@ var _ = Describe("Legacy Options", func() {
skipVerify := true
passHostHeader := false
proxyWebSockets := true
flushInterval := Duration(5 * time.Second)
timeout := Duration(5 * time.Second)
flushInterval := 5 * time.Second
timeout := 5 * time.Second
disableKeepAlives := true
// Test cases and expected outcomes
@ -369,7 +369,7 @@ var _ = Describe("Legacy Options", func() {
Claim: "user",
Prefix: "Basic ",
BasicAuthPassword: &SecretSource{
Value: []byte(basicAuthSecret),
Value: basicAuthSecret,
},
},
},
@ -409,7 +409,7 @@ var _ = Describe("Legacy Options", func() {
Claim: "email",
Prefix: "Basic ",
BasicAuthPassword: &SecretSource{
Value: []byte(basicAuthSecret),
Value: basicAuthSecret,
},
},
},

View File

@ -55,6 +55,76 @@ func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error
return nil
}
// LoadYAML will load a YAML based configuration file into the options interface provided.
func LoadYAML(configFileName string, opts interface{}) error {
buffer, err := loadAndSubstituteEnvs(configFileName)
if err != nil {
return err
}
// Generic interface for loading arbitrary yaml structure
var intermediate map[string]interface{}
if err := yaml.Unmarshal(buffer, &intermediate); err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
return Decode(intermediate, opts)
}
func Decode(input interface{}, result interface{}) error {
// Using mapstructure to decode arbitrary yaml structure into options and
// merge with existing values instead of overwriting everything. This is especially
// important as we have a lot of default values for boolean which are supposed to be
// true by default. Normally by just parsing through yaml all booleans that aren't
// referenced in the config file would be parsed as false and we cannot identify after
// the fact if they have been explicitly set to false or have not been referenced.
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()),
Metadata: nil, // Don't track any metadata
Result: result, // Decode the result into the prefilled options
TagName: "json", // Parse all fields that use the yaml tag
ZeroFields: false, // Don't clean the default values from the result map (options)
ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields
IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml
})
if err != nil {
return fmt.Errorf("error creating decoder for config: %w", err)
}
if err := decoder.Decode(input); err != nil {
return fmt.Errorf("error decoding config: %w", err)
}
return nil
}
// loadAndSubstituteEnvs reads the yaml config into a generic byte buffer and
// substitute env references
func loadAndSubstituteEnvs(configFileName string) ([]byte, error) {
if configFileName == "" {
return nil, errors.New("no configuration file provided")
}
unparsedBuffer, err := os.ReadFile(configFileName)
if err != nil {
return nil, fmt.Errorf("unable to load config file: %w", err)
}
modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
if err != nil {
return nil, fmt.Errorf("error normalizing substitution string : %w", err)
}
buffer, err := envsubst.Bytes(modifiedBuffer)
if err != nil {
return nil, fmt.Errorf("error in substituting env variables : %w", err)
}
return buffer, nil
}
// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
// to the fields in the options interface provided.
// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
@ -140,47 +210,6 @@ func isUnexported(name string) bool {
return first == strings.ToLower(first)
}
// LoadYAML will load a YAML based configuration file into the options interface provided.
func LoadYAML(configFileName string, into interface{}) error {
buffer, err := loadAndParseYaml(configFileName)
if err != nil {
return err
}
// UnmarshalStrict will return an error if the config includes options that are
// not mapped to fields of the into struct
if err := yaml.UnmarshalStrict(buffer, into, yaml.DisallowUnknownFields); err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
return nil
}
// loadAndParseYaml reads the config from the filesystem and
// execute the environment variable substitution
func loadAndParseYaml(configFileName string) ([]byte, error) {
if configFileName == "" {
return nil, errors.New("no configuration file provided")
}
unparsedBuffer, err := os.ReadFile(configFileName)
if err != nil {
return nil, fmt.Errorf("unable to load config file: %w", err)
}
modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
if err != nil {
return nil, fmt.Errorf("error normalizing substitution string : %w", err)
}
buffer, err := envsubst.Bytes(modifiedBuffer)
if err != nil {
return nil, fmt.Errorf("error in substituting env variables : %w", err)
}
return buffer, nil
}
// normalizeSubstitution normalizes dollar signs ($) with numerals like
// $1 or $2 properly by correctly escaping them
func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {

View File

@ -355,15 +355,15 @@ var _ = Describe("Load", func() {
var _ = Describe("LoadYAML", func() {
Context("with a testOptions structure", func() {
type TestOptionSubStruct struct {
StringSliceOption []string `yaml:"stringSliceOption,omitempty"`
StringSliceOption []string `json:"stringSliceOption,omitempty"`
}
type TestOptions struct {
StringOption string `yaml:"stringOption,omitempty"`
Sub TestOptionSubStruct `yaml:"sub,omitempty"`
StringOption string `json:"stringOption,omitempty"`
Sub TestOptionSubStruct `json:"sub,omitempty"`
// Check that embedded fields can be unmarshalled
TestOptionSubStruct `yaml:",inline,squash"`
TestOptionSubStruct `json:",inline,squash"`
}
var testOptionsConfigBytesFull = []byte(`
@ -416,7 +416,7 @@ sub:
err := LoadYAML(configFileName, input)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}
@ -459,13 +459,13 @@ sub:
StringSliceOption: []string{"a", "b", "c"},
},
},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
expectedErr: errors.New("has invalid keys: foo"),
}),
Entry("with an incorrect type for a string field", loadYAMLTableInput{
configFile: []byte(`stringOption: ["a", "b"]`),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field TestOptions.StringOption of type string"),
expectedErr: errors.New("'stringOption' expected type 'string', got unconvertible type"),
}),
Entry("with an incorrect type for an array field", loadYAMLTableInput{
configFile: []byte(`stringSliceOption: "a"`),
@ -526,11 +526,13 @@ upstreamConfig:
injectRequestHeaders:
- name: X-Forwarded-User
values:
- claim: user
- claimSource:
claim: user
injectResponseHeaders:
- name: X-Secret
values:
- value: c2VjcmV0
- secretSource:
value: secret
`)
By("Creating a config file")
@ -548,7 +550,7 @@ injectResponseHeaders:
into := &AlphaOptions{}
Expect(LoadYAML(configFileName, into)).To(Succeed())
flushInterval := Duration(500 * time.Millisecond)
flushInterval := 500 * time.Millisecond
Expect(into).To(Equal(&AlphaOptions{
UpstreamConfig: UpstreamConfig{
@ -579,7 +581,7 @@ injectResponseHeaders:
Values: []HeaderValue{
{
SecretSource: &SecretSource{
Value: []byte("secret"),
Value: "secret",
},
},
},

View File

@ -238,16 +238,16 @@ type OIDCOptions struct {
IssuerURL string `json:"issuerURL,omitempty"`
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
// default set to 'false'
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail"`
// InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
// default set to 'false'
InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"`
InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification"`
// InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
// the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
// after the initial OAuth redeem & subsequent token refreshes.
// default set to 'true'
// Warning: In a future release, this will change to 'false' by default for enhanced security.
InsecureSkipNonce bool `json:"insecureSkipNonce,omitempty"`
InsecureSkipNonce bool `json:"insecureSkipNonce"`
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
// default set to 'false'
SkipDiscovery bool `json:"skipDiscovery,omitempty"`

View File

@ -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"`
}

View File

@ -79,7 +79,7 @@ type Upstream struct {
// FlushInterval is the period between flushing the response buffer when
// streaming response from the upstream.
// Defaults to 1 second.
FlushInterval *Duration `json:"flushInterval,omitempty"`
FlushInterval *time.Duration `json:"flushInterval,omitempty"`
// PassHostHeader determines whether the request host header should be proxied
// to the upstream server.
@ -92,7 +92,7 @@ type Upstream struct {
// Timeout is the maximum duration the server will wait for a response from the upstream server.
// Defaults to 30 seconds.
Timeout *Duration `json:"timeout,omitempty"`
Timeout *time.Duration `json:"timeout,omitempty"`
// DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
// Defaults to false.

View File

@ -11,7 +11,7 @@ import (
func GetSecretValue(source *options.SecretSource) ([]byte, error) {
switch {
case len(source.Value) > 0 && source.FromEnv == "" && source.FromFile == "":
return source.Value, nil
return []byte(source.Value), nil
case len(source.Value) == 0 && source.FromEnv != "" && source.FromFile == "":
return []byte(os.Getenv(source.FromEnv)), nil
case len(source.Value) == 0 && source.FromEnv == "" && source.FromFile != "":

View File

@ -31,7 +31,7 @@ var _ = Describe("GetSecretValue", func() {
It("returns the correct value from the string value", func() {
value, err := GetSecretValue(&options.SecretSource{
Value: []byte("secret-value-1"),
Value: "secret-value-1",
})
Expect(err).ToNot(HaveOccurred())
Expect(string(value)).To(Equal("secret-value-1"))

View File

@ -55,7 +55,7 @@ var _ = Describe("Injector Suite", func() {
Values: []options.HeaderValue{
{
SecretSource: &options.SecretSource{
Value: []byte("super-secret"),
Value: "super-secret",
},
},
},
@ -199,7 +199,7 @@ var _ = Describe("Injector Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte("basic-password"),
Value: "basic-password",
},
},
},
@ -227,7 +227,7 @@ var _ = Describe("Injector Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
},
},
},
@ -322,7 +322,7 @@ var _ = Describe("Injector Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
FromEnv: "SECRET_ENV",
},
},
@ -348,7 +348,7 @@ var _ = Describe("Injector Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte("basic-password"),
Value: "basic-password",
},
},
},
@ -379,17 +379,17 @@ var _ = Describe("Injector Suite", func() {
Values: []options.HeaderValue{
{
SecretSource: &options.SecretSource{
Value: []byte("major=1"),
Value: "major=1",
},
},
{
SecretSource: &options.SecretSource{
Value: []byte("minor=2"),
Value: "minor=2",
},
},
{
SecretSource: &options.SecretSource{
Value: []byte("patch=3"),
Value: "patch=3",
},
},
},

View File

@ -48,10 +48,10 @@ var _ = BeforeSuite(func() {
certOut := new(bytes.Buffer)
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
ipv4CertDataSource.Value = certOut.Bytes()
ipv4CertDataSource.Value = certOut.String()
keyOut := new(bytes.Buffer)
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
ipv4KeyDataSource.Value = keyOut.Bytes()
ipv4KeyDataSource.Value = keyOut.String()
})
By("Generating a ipv6 self-signed cert for TLS tests", func() {
@ -61,16 +61,16 @@ var _ = BeforeSuite(func() {
certOut := new(bytes.Buffer)
Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
ipv6CertDataSource.Value = certOut.Bytes()
ipv6CertDataSource.Value = certOut.String()
keyOut := new(bytes.Buffer)
Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
ipv6KeyDataSource.Value = keyOut.Bytes()
ipv6KeyDataSource.Value = keyOut.String()
})
By("Setting up a http client", func() {
ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value)
ipv4cert, err := tls.X509KeyPair([]byte(ipv4CertDataSource.Value), []byte(ipv4KeyDataSource.Value))
Expect(err).ToNot(HaveOccurred())
ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value)
ipv6cert, err := tls.X509KeyPair([]byte(ipv6CertDataSource.Value), []byte(ipv6KeyDataSource.Value))
Expect(err).ToNot(HaveOccurred())
ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])

View File

@ -234,7 +234,7 @@ var _ = Describe("Server", func() {
SecureBindAddress: "127.0.0.1:0",
TLS: &options.TLS{
Key: &options.SecretSource{
Value: []byte("invalid"),
Value: "invalid",
},
Cert: &ipv4CertDataSource,
},
@ -250,7 +250,7 @@ var _ = Describe("Server", func() {
TLS: &options.TLS{
Key: &ipv4KeyDataSource,
Cert: &options.SecretSource{
Value: []byte("invalid"),
Value: "invalid",
},
},
},
@ -506,7 +506,7 @@ var _ = Describe("Server", func() {
SecureBindAddress: "[::1]:0",
TLS: &options.TLS{
Key: &options.SecretSource{
Value: []byte("invalid"),
Value: "invalid",
},
Cert: &ipv6CertDataSource,
},
@ -523,7 +523,7 @@ var _ = Describe("Server", func() {
TLS: &options.TLS{
Key: &ipv6KeyDataSource,
Cert: &options.SecretSource{
Value: []byte("invalid"),
Value: "invalid",
},
},
},

View File

@ -188,7 +188,7 @@ var _ = Describe("Headers Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
FromEnv: "SECRET_ENV",
},
},
@ -260,7 +260,7 @@ var _ = Describe("Headers Suite", func() {
Values: []options.HeaderValue{
{
SecretSource: &options.SecretSource{
Value: []byte("_oauth2_proxy=ey123123123"),
Value: "_oauth2_proxy=ey123123123",
},
},
},
@ -270,7 +270,7 @@ var _ = Describe("Headers Suite", func() {
Values: []options.HeaderValue{
{
SecretSource: &options.SecretSource{
Value: []byte("oauth_user"),
Value: "oauth_user",
},
},
},
@ -416,7 +416,7 @@ var _ = Describe("Headers Suite", func() {
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("basic-password"))),
Value: base64.StdEncoding.EncodeToString([]byte("basic-password")),
FromEnv: "SECRET_ENV",
},
},

View File

@ -137,12 +137,12 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
// Change default duration for waiting for an upstream response
if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
transport.ResponseHeaderTimeout = *upstream.Timeout
}
// Configure options on the SingleHostReverseProxy
if upstream.FlushInterval != nil {
proxy.FlushInterval = upstream.FlushInterval.Duration()
proxy.FlushInterval = *upstream.FlushInterval
} else {
proxy.FlushInterval = options.DefaultUpstreamFlushInterval
}

View File

@ -21,8 +21,8 @@ import (
)
var _ = Describe("HTTP Upstream Suite", func() {
defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval)
defaultTimeout := options.Duration(options.DefaultUpstreamTimeout)
defaultFlushInterval := options.DefaultUpstreamFlushInterval
defaultTimeout := options.DefaultUpstreamTimeout
truth := true
falsum := false
@ -57,9 +57,9 @@ var _ = Describe("HTTP Upstream Suite", func() {
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
rw := httptest.NewRecorder()
flush := options.Duration(1 * time.Second)
flush := 1 * time.Second
timeout := options.Duration(options.DefaultUpstreamTimeout)
timeout := options.DefaultUpstreamTimeout
upstream := options.Upstream{
ID: in.id,
@ -373,11 +373,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
type newUpstreamTableInput struct {
proxyWebSockets bool
flushInterval options.Duration
flushInterval time.Duration
skipVerify bool
sigData *options.SignatureData
errorHandler func(http.ResponseWriter, *http.Request, error)
timeout options.Duration
timeout time.Duration
disableKeepAlives bool
}
@ -406,10 +406,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
Expect(ok).To(BeTrue())
Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
transport, ok := proxy.Transport.(*http.Transport)
Expect(ok).To(BeTrue())
Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout.Duration()))
Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout))
Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
if in.skipVerify {
Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true))
@ -428,7 +428,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
}),
Entry("with a non standard flush interval", &newUpstreamTableInput{
proxyWebSockets: false,
flushInterval: options.Duration(5 * time.Second),
flushInterval: 5 * time.Second,
skipVerify: false,
sigData: nil,
errorHandler: nil,
@ -466,7 +466,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
skipVerify: false,
sigData: nil,
errorHandler: nil,
timeout: options.Duration(5 * time.Second),
timeout: 5 * time.Second,
}),
Entry("with a DisableKeepAlives", &newUpstreamTableInput{
proxyWebSockets: false,
@ -483,8 +483,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
var proxyServer *httptest.Server
BeforeEach(func() {
flush := options.Duration(1 * time.Second)
timeout := options.Duration(options.DefaultUpstreamTimeout)
flush := 1 * time.Second
timeout := options.DefaultUpstreamTimeout
upstream := options.Upstream{
ID: "websocketProxy",
PassHostHeader: &truth,

View File

@ -9,12 +9,12 @@ import (
)
var _ = Describe("Common", func() {
var validSecretSourceValue []byte
var validSecretSourceValue string
const validSecretSourceEnv = "OAUTH2_PROXY_TEST_SECRET_SOURCE_ENV"
var validSecretSourceFile string
BeforeEach(func() {
validSecretSourceValue = []byte("This is a secret source value")
validSecretSourceValue = "This is a secret source value"
Expect(os.Setenv(validSecretSourceEnv, "This is a secret source env")).To(Succeed())
tmp, err := os.CreateTemp("", "oauth2-proxy-secret-source-test")
Expect(err).ToNot(HaveOccurred())

View File

@ -51,11 +51,9 @@ func validateHeaderValue(_ string, value options.HeaderValue) []string {
func validateHeaderValueClaimSource(claim options.ClaimSource) []string {
msgs := []string{}
if claim.Claim == "" {
msgs = append(msgs, "claim should not be empty")
}
if claim.BasicAuthPassword != nil {
msgs = append(msgs, prefixValues("invalid basicAuthPassword: ", validateSecretSource(*claim.BasicAuthPassword))...)
}

View File

@ -30,7 +30,7 @@ var _ = Describe("Headers", func() {
Values: []options.HeaderValue{
{
SecretSource: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
Value: base64.StdEncoding.EncodeToString([]byte("secret")),
},
},
},
@ -43,7 +43,7 @@ var _ = Describe("Headers", func() {
ClaimSource: &options.ClaimSource{
Claim: "email",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("secret"))),
Value: base64.StdEncoding.EncodeToString([]byte("secret")),
},
},
},

View File

@ -69,7 +69,7 @@ func validateStaticUpstream(upstream options.Upstream) []string {
if upstream.InsecureSkipTLSVerify {
msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval {
msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.PassHostHeader != nil {

View File

@ -14,7 +14,7 @@ var _ = Describe("Upstreams", func() {
errStrings []string
}
flushInterval := options.Duration(5 * time.Second)
flushInterval := 5 * time.Second
staticCode200 := 200
truth := true