Skip to content

Commit

Permalink
introduce mapstructure decoder for yaml parsing
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tuunit committed Feb 1, 2025
1 parent fafb47e commit 629ac24
Show file tree
Hide file tree
Showing 29 changed files with 268 additions and 229 deletions.
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ linters:
disable-all: true
issues:
exclude-rules:
- linters:
- staticcheck
text: "SA5008: unknown JSON option \"squash\""
- path: _test\.go
linters:
- scopelint
Expand Down
18 changes: 4 additions & 14 deletions docs/docs/configuration/alpha_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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`. |
Expand Down Expand Up @@ -482,7 +472,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. |
Expand Down Expand Up @@ -542,10 +532,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. |
### UpstreamConfig
Expand Down
33 changes: 22 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
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
}

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 loadAlphaOptions(config, alphaConfig, extraFlags, args)
return loadYamlOptions(yamlConfig, config, extraFlags, args)
}
return loadLegacyOptions(config, extraFlags, args)
return opts, err
}

// loadLegacyOptions loads the old toml options using the legacy flagset
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)

// 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(alphaConfig)
data, err := yaml.Marshal(buffer)
if err != nil {
return fmt.Errorf("unable to marshal config: %v", err)
}
Expand Down
41 changes: 23 additions & 18 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
},
},
},
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -257,7 +262,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
configContent: testCoreConfig + "unknown_field=\"something\"",
alphaConfigContent: testAlphaConfig,
expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load core options: failed to load config: error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_field"),
expectedErr: errors.New("has invalid keys: unknown_field"),
}),
)
})
6 changes: 3 additions & 3 deletions oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func TestBasicAuthPassword(t *testing.T) {
ClaimSource: &options.ClaimSource{
Claim: "email",
BasicAuthPassword: &options.SecretSource{
Value: []byte(basicAuthPassword),
Value: basicAuthPassword,
},
},
},
Expand Down Expand Up @@ -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",
},
},
},
Expand Down Expand Up @@ -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")),
},
},
},
Expand Down
25 changes: 16 additions & 9 deletions pkg/apis/options/alpha_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,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
Expand All @@ -66,3 +62,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
}
63 changes: 0 additions & 63 deletions pkg/apis/options/common.go

This file was deleted.

43 changes: 43 additions & 0 deletions pkg/apis/options/duration.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
4 changes: 2 additions & 2 deletions pkg/apis/options/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 629ac24

Please sign in to comment.