fix merge problems and test cases

Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
Jan Larwig 2025-07-25 15:08:50 +02:00
parent 1d73f140bf
commit fa2587ac09
No known key found for this signature in database
GPG Key ID: C2172BFA220A037A
9 changed files with 56 additions and 25 deletions

View File

@ -70,7 +70,7 @@ func main() {
func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadLegacyOptions(config, extraFlags, args) opts, err := loadLegacyOptions(config, extraFlags, args)
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't load legacy options: %w", err) return nil, fmt.Errorf("failed to load legacy options: %w", err)
} }
if yamlConfig != "" { if yamlConfig != "" {

View File

@ -22,7 +22,7 @@ var _ = Describe("Configuration Loading Suite", func() {
http_address="127.0.0.1:4180" http_address="127.0.0.1:4180"
upstreams="http://httpbin" upstreams="http://httpbin"
set_basic_auth="true" set_basic_auth="true"
basic_auth_password="super-secret-password" basic_auth_password="c3VwZXItc2VjcmV0LXBhc3N3b3Jk"
client_id="oauth2-proxy" client_id="oauth2-proxy"
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
` `
@ -45,7 +45,7 @@ injectRequestHeaders:
claim: user claim: user
prefix: "Basic " prefix: "Basic "
basicAuthPassword: basicAuthPassword:
value: super-secret-password value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- name: X-Forwarded-Groups - name: X-Forwarded-Groups
values: values:
- claimSource: - claimSource:
@ -69,12 +69,12 @@ injectResponseHeaders:
claim: user claim: user
prefix: "Basic " prefix: "Basic "
basicAuthPassword: basicAuthPassword:
value: super-secret-password value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
server: server:
bindAddress: "127.0.0.1:4180" bindAddress: "127.0.0.1:4180"
providers: providers:
- provider: google - id: google=oauth2-proxy
ID: google=oauth2-proxy provider: google
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
clientID: oauth2-proxy clientID: oauth2-proxy
azureConfig: azureConfig:
@ -139,7 +139,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: []byte("c3VwZXItc2VjcmV0LXBhc3N3b3Jk"),
}, },
}, },
}, },
@ -248,7 +248,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
Entry("with bad legacy configuration", loadConfigurationTableInput{ Entry("with bad legacy configuration", loadConfigurationTableInput{
configContent: testCoreConfig + "unknown_field=\"something\"", configContent: testCoreConfig + "unknown_field=\"something\"",
expectedOptions: func() *options.Options { return nil }, expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
}), }),
Entry("with bad alpha configuration", loadConfigurationTableInput{ Entry("with bad alpha configuration", loadConfigurationTableInput{
configContent: testCoreConfig, configContent: testCoreConfig,
@ -260,7 +260,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
configContent: testCoreConfig + "unknown_field=\"something\"", configContent: testCoreConfig + "unknown_field=\"something\"",
alphaConfigContent: testAlphaConfig, alphaConfigContent: testAlphaConfig,
expectedOptions: func() *options.Options { return nil }, expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load core options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
}), }),
) )
}) })

View File

@ -11,7 +11,7 @@ type Header struct {
// should be preserved for the request to the upstream server. // should be preserved for the request to the upstream server.
// This option only applies to injected request headers. // This option only applies to injected request headers.
// Defaults to false (headers that match this header will be stripped). // Defaults to false (headers that match this header will be stripped).
PreserveRequestValue bool `yaml:"preserveRequestValue,omitempty"` PreserveRequestValue bool `yaml:"preserveRequestValue"`
// Values contains the desired values for this header // Values contains the desired values for this header
Values []HeaderValue `yaml:"values,omitempty"` Values []HeaderValue `yaml:"values,omitempty"`

View File

@ -41,3 +41,17 @@ func toDurationHookFunc() mapstructure.DecodeHookFunc {
} }
} }
} }
// StringToBytesHookFunc returns a DecodeHookFunc that converts string to []byte.
func stringToBytesHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() == reflect.String && t == reflect.TypeOf([]byte{}) {
return []byte(data.(string)), nil
}
return data, nil
}
}

View File

@ -5,7 +5,7 @@ import (
"time" "time"
) )
func TestDecode(t *testing.T) { func TestToDurationHook(t *testing.T) {
type result struct { type result struct {
Duration time.Duration `yaml:"duration"` Duration time.Duration `yaml:"duration"`
} }
@ -80,3 +80,17 @@ func TestDecode(t *testing.T) {
}) })
} }
} }
func TestStringToBytesHook(t *testing.T) {
var result struct {
Value []byte `yaml:"value"`
}
if err := Decode(map[string]interface{}{"value": "hello-world"}, &result); err != nil {
t.Fatal(err)
}
if string(result.Value) != "hello-world" {
t.Errorf("expected %q, got %q", "hello-world", string(result.Value))
}
}

View File

@ -89,7 +89,10 @@ func LoadYAML(configFileName string, opts interface{}) error {
// - An error if decoding fails or if there are unmapped keys. // - An error if decoding fails or if there are unmapped keys.
func Decode(input interface{}, result interface{}) error { func Decode(input interface{}, result interface{}) error {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(toDurationHookFunc()), DecodeHook: mapstructure.ComposeDecodeHookFunc(
toDurationHookFunc(),
stringToBytesHookFunc(),
),
Metadata: nil, // Don't track any metadata Metadata: nil, // Don't track any metadata
Result: result, // Decode the result into the prefilled options Result: result, // Decode the result into the prefilled options
TagName: "yaml", // Parse all fields that use the json tag TagName: "yaml", // Parse all fields that use the json tag

View File

@ -155,7 +155,7 @@ var _ = Describe("Load", func() {
} }
err := Load(configFileName, flagSet, input) err := Load(configFileName, flagSet, input)
if o.expectedErr != nil { if o.expectedErr != nil {
Expect(err).To(MatchError(o.expectedErr.Error())) Expect(err).To(MatchError(ContainSubstring(o.expectedErr.Error())))
} else { } else {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
@ -471,7 +471,7 @@ sub:
configFile: []byte(`stringSliceOption: "a"`), configFile: []byte(`stringSliceOption: "a"`),
input: &TestOptions{}, input: &TestOptions{},
expectedOutput: &TestOptions{}, expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go struct field TestOptions.TestOptionSubStruct.StringSliceOption of type []string"), expectedErr: errors.New("error decoding config: decoding failed due to the following error(s):\n\n'stringSliceOption' source data must be an array or slice, got string"),
}), }),
Entry("with a config file containing environment variable references", loadYAMLTableInput{ Entry("with a config file containing environment variable references", loadYAMLTableInput{
configFile: []byte("stringOption: ${TESTUSER}"), configFile: []byte("stringOption: ${TESTUSER}"),

View File

@ -67,7 +67,7 @@ type Provider struct {
CAFiles []string `yaml:"caFiles,omitempty"` CAFiles []string `yaml:"caFiles,omitempty"`
// UseSystemTrustStore determines if your custom CA files and the system trust store are used // UseSystemTrustStore determines if your custom CA files and the system trust store are used
// If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files. // If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.
UseSystemTrustStore bool `yaml:"useSystemTrustStore,omitempty"` UseSystemTrustStore bool `yaml:"useSystemTrustStore"`
// LoginURL is the authentication endpoint // LoginURL is the authentication endpoint
LoginURL string `yaml:"loginURL,omitempty"` LoginURL string `yaml:"loginURL,omitempty"`
// LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL
@ -80,7 +80,7 @@ type Provider struct {
ProfileURL string `yaml:"profileURL,omitempty"` ProfileURL string `yaml:"profileURL,omitempty"`
// SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token // SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
// default set to 'false' // default set to 'false'
SkipClaimsFromProfileURL bool `yaml:"skipClaimsFromProfileURL,omitempty"` SkipClaimsFromProfileURL bool `yaml:"skipClaimsFromProfileURL"`
// ProtectedResource is the resource that is protected (Azure AD and ADFS only) // ProtectedResource is the resource that is protected (Azure AD and ADFS only)
ProtectedResource string `yaml:"resource,omitempty"` ProtectedResource string `yaml:"resource,omitempty"`
// ValidateURL is the access token validation endpoint // ValidateURL is the access token validation endpoint
@ -181,13 +181,13 @@ type MicrosoftEntraIDOptions struct {
// FederatedTokenAuth enable oAuth2 client authentication with federated token projected // FederatedTokenAuth enable oAuth2 client authentication with federated token projected
// by Entra Workload Identity plugin, instead of client secret. // by Entra Workload Identity plugin, instead of client secret.
FederatedTokenAuth bool `yaml:"federatedTokenAuth,omitempty"` FederatedTokenAuth bool `yaml:"federatedTokenAuth"`
} }
type ADFSOptions struct { type ADFSOptions struct {
// Skip adding the scope parameter in login request // Skip adding the scope parameter in login request
// Default value is 'false' // Default value is 'false'
SkipScope bool `yaml:"skipScope,omitempty"` SkipScope bool `yaml:"skipScope"`
} }
type BitbucketOptions struct { type BitbucketOptions struct {
@ -227,7 +227,7 @@ type GoogleOptions struct {
// ServiceAccountJSON is the path to the service account json credentials // ServiceAccountJSON is the path to the service account json credentials
ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"` ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"`
// UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON // UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON
UseApplicationDefaultCredentials bool `yaml:"useApplicationDefaultCredentials,omitempty"` UseApplicationDefaultCredentials bool `yaml:"useApplicationDefaultCredentials"`
// TargetPrincipal is the Google Service Account used for Application Default Credentials // TargetPrincipal is the Google Service Account used for Application Default Credentials
TargetPrincipal string `yaml:"targetPrincipal,omitempty"` TargetPrincipal string `yaml:"targetPrincipal,omitempty"`
} }
@ -250,7 +250,7 @@ type OIDCOptions struct {
InsecureSkipNonce bool `yaml:"insecureSkipNonce"` InsecureSkipNonce bool `yaml:"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 `yaml:"skipDiscovery,omitempty"` SkipDiscovery bool `yaml:"skipDiscovery"`
// JwksURL is the OpenID Connect JWKS URL // JwksURL is the OpenID Connect JWKS URL
// eg: https://www.googleapis.com/oauth2/v3/certs // eg: https://www.googleapis.com/oauth2/v3/certs
JwksURL string `yaml:"jwksURL,omitempty"` JwksURL string `yaml:"jwksURL,omitempty"`

View File

@ -14,7 +14,7 @@ const (
type UpstreamConfig struct { type UpstreamConfig struct {
// ProxyRawPath will pass the raw url path to upstream allowing for urls // ProxyRawPath will pass the raw url path to upstream allowing for urls
// like: "/%2F/" which would otherwise be redirected to "/" // like: "/%2F/" which would otherwise be redirected to "/"
ProxyRawPath bool `yaml:"proxyRawPath,omitempty"` ProxyRawPath bool `yaml:"proxyRawPath"`
// Upstreams represents the configuration for the upstream servers. // Upstreams represents the configuration for the upstream servers.
// Requests will be proxied to this upstream if the path matches the request path. // Requests will be proxied to this upstream if the path matches the request path.
@ -64,13 +64,13 @@ type Upstream struct {
// This option is insecure and will allow potential Man-In-The-Middle attacks // This option is insecure and will allow potential Man-In-The-Middle attacks
// between OAuth2 Proxy and the upstream server. // between OAuth2 Proxy and the upstream server.
// Defaults to false. // Defaults to false.
InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify,omitempty"` InsecureSkipTLSVerify bool `yaml:"insecureSkipTLSVerify"`
// Static will make all requests to this upstream have a static response. // Static will make all requests to this upstream have a static response.
// The response will have a body of "Authenticated" and a response code // The response will have a body of "Authenticated" and a response code
// matching StaticCode. // matching StaticCode.
// If StaticCode is not set, the response will return a 200 response. // If StaticCode is not set, the response will return a 200 response.
Static bool `yaml:"static,omitempty"` Static bool `yaml:"static"`
// StaticCode determines the response code for the Static response. // StaticCode determines the response code for the Static response.
// This option can only be used with Static enabled. // This option can only be used with Static enabled.
@ -84,11 +84,11 @@ type Upstream struct {
// PassHostHeader determines whether the request host header should be proxied // PassHostHeader determines whether the request host header should be proxied
// to the upstream server. // to the upstream server.
// Defaults to true. // Defaults to true.
PassHostHeader *bool `yaml:"passHostHeader,omitempty"` PassHostHeader *bool `yaml:"passHostHeader"`
// ProxyWebSockets enables proxying of websockets to upstream servers // ProxyWebSockets enables proxying of websockets to upstream servers
// Defaults to true. // Defaults to true.
ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"` ProxyWebSockets *bool `yaml:"proxyWebSockets"`
// Timeout is the maximum duration the server will wait for a response from the upstream server. // Timeout is the maximum duration the server will wait for a response from the upstream server.
// Defaults to 30 seconds. // Defaults to 30 seconds.