git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #2628 from tuunit/use-mapstructures-for-parsing-and-merging

structured config #1: introduce mapstructure decoder for yaml parsing

commite27921ee8038bd353faa72fade4ef66d7201dab9 authorJan Larwig <jan@larwig.com> dateFri 28 Nov 2025, 18:14 (1 year ago) parent7cf69b2aee540a
57 files changed+1307−793
.golangci.yml+1−1 Makefile+4 contrib/local-environment/docker-compose-alpha-config.yaml+48−1 contrib/local-environment/docker-compose.yaml+1−1 contrib/local-environment/oauth2-proxy-alpha-config.cfg+1−2 contrib/local-environment/oauth2-proxy-alpha-config.yaml+21−21 docs/docs/configuration/alpha_config.md+9−19 go.mod+38−40 go.sum+110−93 main.go+31−14 main_test.go+85−54 oauthproxy_test.go+3−2 pkg/apis/middleware/session.go+5−1 pkg/apis/options/alpha_options.go+22−15 pkg/apis/options/common.go−63 pkg/apis/options/common_test.go−87 pkg/apis/options/doc.go+1−1 pkg/apis/options/header.go+43−9 pkg/apis/options/hooks.go+57 pkg/apis/options/hooks_test.go+96 pkg/apis/options/legacy_options.go+45−30 pkg/apis/options/legacy_options_test.go+110−75 pkg/apis/options/load.go+82−42 pkg/apis/options/load_test.go+11−9 pkg/apis/options/login_url_parameters.go+5−5 pkg/apis/options/options.go+20 pkg/apis/options/providers.go+187−73 pkg/apis/options/secret_source.go+19 pkg/apis/options/server.go+7−7 pkg/apis/options/upstreams.go+78−17 pkg/apis/sessions/session_state_test.go+4−7 pkg/middleware/headers.go+3−2 pkg/middleware/headers_test.go+6−5 pkg/middleware/jwt_session_test.go+2−2 pkg/requests/result_test.go+2−2 pkg/upstream/http.go+9−8 pkg/upstream/http_test.go+23−24 pkg/upstream/proxy.go+4−3 pkg/upstream/proxy_test.go+6−5 pkg/upstream/static.go+3−11 pkg/util/ptr/ptr.go+14 pkg/util/ptr/ptr_test.go+38 pkg/validation/options.go+2−1 pkg/validation/options_test.go+3−2 pkg/validation/providers.go+4−3 pkg/validation/sessions_test.go+2−2 pkg/validation/upstreams.go+7−6 pkg/validation/upstreams_test.go+7−7 providers/adfs.go+2−1 providers/adfs_test.go+2−1 providers/google.go+5−5 providers/ms_entra_id.go+2−1 providers/ms_entra_id_test.go+4−3 providers/oidc.go+2−1 providers/oidc_test.go+1−1 providers/providers.go+5−4 providers/providers_test.go+5−4
.golangci.yml+1−1
@@ -37,7 +37,7 @@ linters:
3737 - linters:
3838 - revive
3939 path: _test\.go
40− text: 'dot-imports:'
40+ text: "dot-imports:"
4141 # # If we have tests in shared test folders, these can be less strictly linted
4242 - linters:
4343 - bodyclose
Makefile+4
@@ -74,6 +74,10 @@ DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --pus
7474.PHONY: build-docker
7575build-docker: build-distroless build-alpine ## Build multi architecture docker images in both flavours (distroless / alpine)
7676
77+.PHONY: build-docker-local
78+build-docker-local: ## Build distroless docker image and locally load into docker images
79+ $(DOCKER_BUILDX) --load -t $(REGISTRY)/$(REPOSITORY):${VERSION}-local .
80+
7781.PHONY: build-distroless
7882build-distroless: ## Build multi architecture distroless based docker image
7983 $(DOCKER_BUILDX_X_PLATFORM) -t $(REGISTRY)/$(REPOSITORY):latest -t $(REGISTRY)/$(REPOSITORY):${VERSION} .
contrib/local-environment/docker-compose-alpha-config.yaml+48−1
@@ -10,11 +10,58 @@
1010# make alpha-config-<command> (eg make nginx-up, make nginx-down)
1111#
1212# Access http://localhost:4180 to initiate a login cycle
13−version: '3.0'
13+version: "3.0"
1414services:
1515 oauth2-proxy:
16+ container_name: oauth2-proxy
1617 image: quay.io/oauth2-proxy/oauth2-proxy:v7.13.0
1718 command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml
19+ hostname: oauth2-proxy
1820 volumes:
1921 - "./oauth2-proxy-alpha-config.cfg:/oauth2-proxy.cfg"
2022 - "./oauth2-proxy-alpha-config.yaml:/oauth2-proxy-alpha-config.yaml"
23+ restart: unless-stopped
24+ ports:
25+ - 4180:4180/tcp
26+ networks:
27+ dex: {}
28+ httpbin: {}
29+ depends_on:
30+ - dex
31+ - httpbin
32+ dex:
33+ container_name: dex
34+ image: ghcr.io/dexidp/dex:v2.43.1
35+ command: dex serve /dex.yaml
36+ hostname: dex
37+ volumes:
38+ - "./dex.yaml:/dex.yaml"
39+ restart: unless-stopped
40+ ports:
41+ - 5556:5556/tcp
42+ networks:
43+ dex:
44+ aliases:
45+ - dex.localtest.me
46+ etcd: {}
47+ depends_on:
48+ - etcd
49+ httpbin:
50+ container_name: httpbin
51+ image: kennethreitz/httpbin
52+ ports: []
53+ networks:
54+ httpbin: {}
55+ etcd:
56+ container_name: etcd
57+ image: gcr.io/etcd-development/etcd:v3.6.2
58+ entrypoint: /usr/local/bin/etcd
59+ command:
60+ - --listen-client-urls=http://0.0.0.0:2379
61+ - --advertise-client-urls=http://etcd:2379
62+ networks:
63+ etcd: {}
64+networks:
65+ dex: {}
66+ etcd: {}
67+ httpbin: {}
contrib/local-environment/docker-compose.yaml+1−1
@@ -9,7 +9,7 @@
99# make <command> (eg. make up, make down)
1010#
1111# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle
12−version: '3.0'
12+version: "3.0"
1313services:
1414 oauth2-proxy:
1515 container_name: oauth2-proxy
contrib/local-environment/oauth2-proxy-alpha-config.cfg+1−2
@@ -1,5 +1,4 @@
1−http_address="0.0.0.0:4180"
21cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
32email_domains="example.com"
43cookie_secure="false"
5−redirect_url="http://localhost:4180/oauth2/callback"
4+redirect_url="http://oauth2-proxy.localtest.me:4180/oauth2/callback"
contrib/local-environment/oauth2-proxy-alpha-config.yaml+21−21
@@ -1,23 +1,23 @@
1−upstreams:
2− - id: httpbin
3− path: /
4− uri: http://httpbin
1+server:
2+ bindAddress: "0.0.0.0:4180"
3+upstreamConfig:
4+ upstreams:
5+ - id: httpbin
6+ path: /
7+ uri: http://httpbin
58injectRequestHeaders:
6−- name: X-Forwarded-Groups
7− values:
8− - claim: groups
9−- name: X-Forwarded-User
10− values:
11− - claim: user
12−- name: X-Forwarded-Email
13− values:
14− - claim: email
15−- name: X-Forwarded-Preferred-Username
16− values:
17− - claim: preferred_username
9+ - name: X-Forwarded-User
10+ values:
11+ - claimSource:
12+ claim: user
13+ - name: X-Forwarded-Email
14+ values:
15+ - claimSource:
16+ claim: email
1817providers:
19−- provider: oidc
20− clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
21− clientID: oauth2-proxy
22− oidcConfig:
23− issuerURL: http://dex.localhost:5556/dex
18+ - id: oidc
19+ provider: oidc
20+ clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
21+ clientID: oauth2-proxy
22+ oidcConfig:
23+ issuerURL: http://dex.localtest.me:5556/dex
docs/docs/configuration/alpha_config.md+9−19
@@ -204,16 +204,6 @@ ClaimSource allows loading a header value from a claim within the session
204204| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the<br/>claim if it is non-empty. |
205205| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.<br/>Note the value of claim will become the basic auth username and the<br/>basicAuthPassword will be used as the password value. |
206206
207−### Duration
208−#### (`string` alias)
209−
210−(**Appears on:** [Upstream](#upstream))
211−
212−Duration is as string representation of a period of time.
213−A duration string is a is a possibly signed sequence of decimal numbers,
214−each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
215−Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
216−
217207### GitHubOptions
218208
219209(**Appears on:** [Provider](#provider))
@@ -502,9 +492,9 @@ Server represents the configuration for an HTTP(S) server
502492
503493| Field | Type | Description |
504494| ----- | ---- | ----------- |
505−| `BindAddress` | _string_ | BindAddress is the address on which to serve traffic.<br/>Leave blank or set to "-" to disable. |
506−| `SecureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.<br/>Leave blank or set to "-" to disable. |
507−| `TLS` | _[TLS](#tls)_ | TLS contains the information for loading the certificate and key for the<br/>secure traffic and further configuration for the TLS server. |
495+| `bindAddress` | _string_ | BindAddress is the address on which to serve traffic.<br/>Leave blank or set to "-" to disable. |
496+| `secureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.<br/>Leave blank or set to "-" to disable. |
497+| `tls` | _[TLS](#tls)_ | TLS contains the information for loading the certificate and key for the<br/>secure traffic and further configuration for the TLS server. |
508498
509499### TLS
510500
@@ -515,10 +505,10 @@ as well as an optional minimal TLS version that is acceptable.
515505
516506| Field | Type | Description |
517507| ----- | ---- | ----------- |
518−| `Key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.<br/>Typically this will come from a file. |
519−| `Cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.<br/>Typically this will come from a file. |
520−| `MinVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.<br/>E.g. Set to "TLS1.3" to select TLS version 1.3 |
521−| `CipherSuites` | _[]string_ | CipherSuites is a list of TLS cipher suites that are allowed.<br/>E.g.:<br/>- TLS_RSA_WITH_RC4_128_SHA<br/>- TLS_RSA_WITH_AES_256_GCM_SHA384<br/>If not specified, the default Go safe cipher list is used.<br/>List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants). |
508+| `key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.<br/>Typically this will come from a file. |
509+| `cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.<br/>Typically this will come from a file. |
510+| `minVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.<br/>E.g. Set to "TLS1.3" to select TLS version 1.3 |
511+| `cipherSuites` | _[]string_ | CipherSuites is a list of TLS cipher suites that are allowed.<br/>E.g.:<br/>- TLS_RSA_WITH_RC4_128_SHA<br/>- TLS_RSA_WITH_AES_256_GCM_SHA384<br/>If not specified, the default Go safe cipher list is used.<br/>List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants). |
522512
523513### URLParameterRule
524514
@@ -550,10 +540,10 @@ Requests will be proxied to this upstream if the path matches the request path.
550540| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.<br/>This option is insecure and will allow potential Man-In-The-Middle attacks<br/>between OAuth2 Proxy and the upstream server.<br/>Defaults to false. |
551541| `static` | _bool_ | Static will make all requests to this upstream have a static response.<br/>The response will have a body of "Authenticated" and a response code<br/>matching StaticCode.<br/>If StaticCode is not set, the response will return a 200 response. |
552542| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
553−| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
543+| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
554544| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
555545| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
556−| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
546+| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
557547| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
558548
559549### UpstreamConfig
go.mod+38−40
@@ -3,56 +3,57 @@ module github.com/oauth2-proxy/oauth2-proxy/v7
33go 1.25.3
44
55require (
6− cloud.google.com/go/compute/metadata v0.7.0
6+ cloud.google.com/go/compute/metadata v0.9.0
77 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
88 github.com/a8m/envsubst v1.4.3
99 github.com/alicebob/miniredis/v2 v2.35.0
1010 github.com/bitly/go-simplejson v0.5.1
1111 github.com/bsm/redislock v0.9.4
12− github.com/coreos/go-oidc/v3 v3.14.1
12+ github.com/coreos/go-oidc/v3 v3.16.0
1313 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
1414 github.com/fsnotify/fsnotify v1.9.0
15− github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
1615 github.com/go-jose/go-jose/v3 v3.0.4
1716 github.com/go-viper/mapstructure/v2 v2.4.0
18− github.com/golang-jwt/jwt/v5 v5.2.3
17+ github.com/golang-jwt/jwt/v5 v5.3.0
1918 github.com/google/go-cmp v0.7.0
2019 github.com/google/uuid v1.6.0
2120 github.com/gorilla/mux v1.8.1
2221 github.com/justinas/alice v1.2.0
2322 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
24− github.com/onsi/ginkgo/v2 v2.23.4
25− github.com/onsi/gomega v1.37.0
23+ github.com/onsi/ginkgo/v2 v2.27.2
24+ github.com/onsi/gomega v1.38.2
2625 github.com/pierrec/lz4/v4 v4.1.22
27− github.com/prometheus/client_golang v1.22.0
28− github.com/redis/go-redis/v9 v9.11.0
29− github.com/spf13/cast v1.9.2
30− github.com/spf13/pflag v1.0.7
31− github.com/spf13/viper v1.20.1
26+ github.com/prometheus/client_golang v1.23.2
27+ github.com/redis/go-redis/v9 v9.16.0
28+ github.com/spf13/cast v1.10.0
29+ github.com/spf13/pflag v1.0.10
30+ github.com/spf13/viper v1.21.0
3231 github.com/stretchr/testify v1.11.1
3332 github.com/vmihailenco/msgpack/v5 v5.4.1
34− golang.org/x/crypto v0.40.0
35− golang.org/x/net v0.42.0
36− golang.org/x/oauth2 v0.30.0
37− golang.org/x/sync v0.16.0
38− google.golang.org/api v0.242.0
33+ go.yaml.in/yaml/v3 v3.0.4
34+ golang.org/x/crypto v0.43.0
35+ golang.org/x/net v0.46.0
36+ golang.org/x/oauth2 v0.32.0
37+ golang.org/x/sync v0.17.0
38+ google.golang.org/api v0.254.0
3939 gopkg.in/natefinch/lumberjack.v2 v2.2.1
40− k8s.io/apimachinery v0.33.3
40+ k8s.io/apimachinery v0.34.1
4141)
4242
4343require (
44− cloud.google.com/go/auth v0.16.2 // indirect
44+ cloud.google.com/go/auth v0.17.0 // indirect
4545 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
46+ github.com/Masterminds/semver/v3 v3.4.0 // indirect
4647 github.com/beorn7/perks v1.0.1 // indirect
4748 github.com/cespare/xxhash/v2 v2.3.0 // indirect
4849 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
4950 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
5051 github.com/felixge/httpsnoop v1.0.4 // indirect
51− github.com/go-jose/go-jose/v4 v4.1.1 // indirect
52+ github.com/go-jose/go-jose/v4 v4.1.3 // indirect
5253 github.com/go-logr/logr v1.4.3 // indirect
5354 github.com/go-logr/stdr v1.2.2 // indirect
5455 github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
55− github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
56+ github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d // indirect
5657 github.com/google/s2a-go v0.1.9 // indirect
5758 github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
5859 github.com/googleapis/gax-go/v2 v2.15.0 // indirect
@@ -61,28 +62,25 @@ require (
6162 github.com/pelletier/go-toml/v2 v2.2.4 // indirect
6263 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
6364 github.com/prometheus/client_model v0.6.2 // indirect
64− github.com/prometheus/common v0.65.0 // indirect
65− github.com/prometheus/procfs v0.17.0 // indirect
66− github.com/rogpeppe/go-internal v1.14.1 // indirect
67− github.com/sagikazarmark/locafero v0.9.0 // indirect
68− github.com/sourcegraph/conc v0.3.0 // indirect
69− github.com/spf13/afero v1.14.0 // indirect
65+ github.com/prometheus/common v0.67.2 // indirect
66+ github.com/prometheus/procfs v0.19.1 // indirect
67+ github.com/sagikazarmark/locafero v0.12.0 // indirect
68+ github.com/spf13/afero v1.15.0 // indirect
7069 github.com/subosito/gotenv v1.6.0 // indirect
7170 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
7271 github.com/yuin/gopher-lua v1.1.1 // indirect
73− go.opentelemetry.io/auto/sdk v1.1.0 // indirect
74− go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
75− go.opentelemetry.io/otel v1.37.0 // indirect
76− go.opentelemetry.io/otel/metric v1.37.0 // indirect
77− go.opentelemetry.io/otel/trace v1.37.0 // indirect
78− go.uber.org/automaxprocs v1.6.0 // indirect
79− go.uber.org/multierr v1.11.0 // indirect
80− golang.org/x/sys v0.34.0 // indirect
81− golang.org/x/text v0.27.0 // indirect
82− golang.org/x/tools v0.35.0 // indirect
83− google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
84− google.golang.org/grpc v1.73.0 // indirect
85− google.golang.org/protobuf v1.36.6 // indirect
86− gopkg.in/yaml.v2 v2.4.0 // indirect
72+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
73+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
74+ go.opentelemetry.io/otel v1.38.0 // indirect
75+ go.opentelemetry.io/otel/metric v1.38.0 // indirect
76+ go.opentelemetry.io/otel/trace v1.38.0 // indirect
77+ go.yaml.in/yaml/v2 v2.4.3 // indirect
78+ golang.org/x/mod v0.29.0 // indirect
79+ golang.org/x/sys v0.37.0 // indirect
80+ golang.org/x/text v0.30.0 // indirect
81+ golang.org/x/tools v0.38.0 // indirect
82+ google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
83+ google.golang.org/grpc v1.76.0 // indirect
84+ google.golang.org/protobuf v1.36.10 // indirect
8785 gopkg.in/yaml.v3 v3.0.1 // indirect
8886)
go.sum+110−93
@@ -1,13 +1,15 @@
1−cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
2−cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
1+cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
2+cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
33cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
44cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
5−cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
6−cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
5+cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
6+cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
77github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw=
88github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
99github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
1010github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
11+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
12+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
1113github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
1214github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
1315github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
@@ -29,8 +31,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
2931github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
3032github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
3133github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
32−github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
33−github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
34+github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
35+github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
3436github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
3537github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
3638github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -44,12 +46,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
4446github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
4547github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
4648github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
47−github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
48−github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
49+github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
50+github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
51+github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
52+github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
53+github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
54+github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
4955github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
5056github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
51−github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
52−github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
57+github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
58+github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
5359github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
5460github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
5561github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -59,8 +65,10 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
5965github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
6066github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
6167github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
62−github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
63−github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
68+github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
69+github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
70+github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
71+github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
6472github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
6573github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
6674github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
@@ -68,8 +76,8 @@ github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85q
6876github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
6977github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
7078github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
71−github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
72−github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
79+github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d h1:KJIErDwbSHjnp/SGzE5ed8Aol7JsKiI5X7yWKAtzhM0=
80+github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
7381github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
7482github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
7583github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -81,6 +89,8 @@ github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81
8189github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
8290github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
8391github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
92+github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
93+github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
8494github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
8595github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
8696github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -91,18 +101,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
91101github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
92102github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
93103github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
104+github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
105+github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
94106github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
95107github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
96−github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
97−github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
108+github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
109+github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
98110github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
99111github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
100112github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
101113github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
102−github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
103−github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
104−github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
105−github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
114+github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
115+github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
116+github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
117+github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
106118github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
107119github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
108120github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
@@ -110,40 +122,42 @@ github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
110122github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
111123github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
112124github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
113−github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
114−github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
115−github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
116−github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
125+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
126+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
117127github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
118128github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
119−github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
120−github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
121−github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
122−github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
123−github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
124−github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
129+github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
130+github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
131+github.com/prometheus/procfs v0.19.1 h1:QVtROpTkphuXuNlnCv3m1ut3JytkXHtQ3xvck/YmzMM=
132+github.com/prometheus/procfs v0.19.1/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
133+github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
134+github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
125135github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
126136github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
127−github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
128−github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
129−github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
130−github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
131−github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
132−github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
133−github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
134−github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
135−github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
136−github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
137−github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
138−github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
137+github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
138+github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
139+github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
140+github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
141+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
142+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
143+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
144+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
145+github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
146+github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
139147github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
140148github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
141−github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
142−github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
143149github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
144150github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
145151github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
146152github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
153+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
154+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
155+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
156+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
157+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
158+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
159+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
160+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
147161github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
148162github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
149163github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
@@ -153,47 +167,51 @@ github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBU
153167github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
154168github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
155169github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
156−go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
157−go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
170+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
171+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
158172go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
159173go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
160−go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
161−go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
162−go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
163−go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
164−go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
165−go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
166−go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
167−go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
168−go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
169−go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
170−go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
171−go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
172−go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
173−go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
174−go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
175−go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
174+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
175+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
176+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
177+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
178+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
179+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
180+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
181+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
182+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
183+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
184+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
185+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
186+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
187+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
188+go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
189+go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
190+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
191+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
176192golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
177193golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
178194golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
179−golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
180−golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
195+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
196+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
181197golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
182198golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
199+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
200+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
183201golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
184202golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
185203golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
186204golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
187205golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
188−golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
189−golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
190−golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
191−golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
206+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
207+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
208+golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
209+golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
192210golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
193211golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
194212golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
195−golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
196−golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
213+golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
214+golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
197215golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
198216golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
199217golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -203,8 +221,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
203221golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
204222golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
205223golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
206−golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
207−golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
224+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
225+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
208226golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
209227golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
210228golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -216,39 +234,38 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
216234golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
217235golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
218236golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
219−golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
220−golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
221−golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
222−golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
237+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
238+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
239+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
240+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
223241golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
224242golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
225243golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
226244golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
227−golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
228−golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
245+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
246+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
229247golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
230−google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg=
231−google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
248+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
249+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
250+google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4=
251+google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ=
232252google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
233253google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
234−google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
235−google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
236−google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
237−google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
238−google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
239−google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
240−google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
241−google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
254+google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
255+google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
256+google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
257+google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
258+google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
259+google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
260+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
261+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
242262gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
243263gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
244264gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
245265gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
246266gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
247−gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
248−gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
249−gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
250267gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
251268gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
252269gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
253−k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
254−k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
270+k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
271+k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
main.go+31−14
@@ -5,12 +5,12 @@ import (
55 "os"
66 "runtime"
77
8− "github.com/ghodss/yaml"
98 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
109 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1110 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
1211 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/version"
1312 "github.com/spf13/pflag"
13+ "go.yaml.in/yaml/v3"
1414)
1515
1616func main() {
@@ -20,7 +20,7 @@ func main() {
2020
2121 // Because we parse early to determine alpha vs legacy config, we have to
2222 // ignore any unknown flags for now
23− configFlagSet.ParseErrorsWhitelist.UnknownFlags = true
23+ configFlagSet.ParseErrorsAllowlist.UnknownFlags = true
2424
2525 config := configFlagSet.String("config", "", "path to config file")
2626 alphaConfig := configFlagSet.String("alpha-config", "", "path to alpha config file (use at your own risk - the structure in this config file may change between minor releases)")
@@ -67,12 +67,23 @@ func main() {
6767// loadConfiguration will load in the user's configuration.
6868// It will either load the alpha configuration (if alphaConfig is given)
6969// or the legacy configuration.
70−func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
71− if alphaConfig != "" {
70+func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
71+ opts, err := loadLegacyOptions(config, extraFlags, args)
72+ if err != nil {
73+ return nil, fmt.Errorf("failed to load legacy options: %w", err)
74+ }
75+
76+ if yamlConfig != "" {
7277 logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
73− return loadAlphaOptions(config, alphaConfig, extraFlags, args)
78+ opts, err = loadYamlOptions(yamlConfig, config, extraFlags, args)
79+ if err != nil {
80+ return nil, fmt.Errorf("failed to load yaml options: %w", err)
81+ }
7482 }
75− return loadLegacyOptions(config, extraFlags, args)
83+
84+ // Ensure defaults after loading configuration
85+ opts.EnsureDefaults()
86+ return opts, nil
7687}
7788
7889// loadLegacyOptions loads the old toml options using the legacy flagset
@@ -97,21 +108,21 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string)
97108 return opts, nil
98109}
99110
100−// loadAlphaOptions loads the old style config excluding options converted to
111+// loadYamlOptions loads the old style config excluding options converted to
101112// the new alpha format, then merges the alpha options, loaded from YAML,
102113// into the core configuration.
103−func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
114+func loadYamlOptions(yamlConfig, config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
104115 opts, err := loadOptions(config, extraFlags, args)
105116 if err != nil {
106117 return nil, fmt.Errorf("failed to load core options: %v", err)
107118 }
108119
109− alphaOpts := &options.AlphaOptions{}
110− if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
120+ alphaOpts := options.NewAlphaOptions(opts)
121+ if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
111122 return nil, fmt.Errorf("failed to load alpha options: %v", err)
112123 }
113124
114− alphaOpts.MergeInto(opts)
125+ alphaOpts.MergeOptionsWithDefaults(opts)
115126 return opts, nil
116127}
117128
@@ -137,10 +148,16 @@ func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*opti
137148// printConvertedConfig extracts alpha options from the loaded configuration
138149// and renders these to stdout in YAML format.
139150func printConvertedConfig(opts *options.Options) error {
140− alphaConfig := &options.AlphaOptions{}
141− alphaConfig.ExtractFrom(opts)
151+ alphaConfig := options.NewAlphaOptions(opts)
152+
153+ // Generic interface for loading arbitrary yaml structure
154+ var buffer map[string]interface{}
155+
156+ if err := options.Decode(alphaConfig, &buffer); err != nil {
157+ return fmt.Errorf("unable to decode alpha config into interface: %w", err)
158+ }
142159
143− data, err := yaml.Marshal(alphaConfig)
160+ data, err := yaml.Marshal(buffer)
144161 if err != nil {
145162 return fmt.Errorf("unable to marshal config: %v", err)
146163 }
main_test.go+85−54
@@ -2,13 +2,11 @@ package main
22
33import (
44 "errors"
5− "fmt"
65 "os"
7− "strings"
8− "time"
96
107 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
118 . "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil"
9+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1210 . "github.com/onsi/ginkgo/v2"
1311 . "github.com/onsi/gomega"
1412 "github.com/onsi/gomega/format"
@@ -24,14 +22,15 @@ var _ = Describe("Configuration Loading Suite", func() {
2422http_address="127.0.0.1:4180"
2523upstreams="http://httpbin"
2624set_basic_auth="true"
27−basic_auth_password="super-secret-password"
25+basic_auth_password="c3VwZXItc2VjcmV0LXBhc3N3b3Jk"
2826client_id="oauth2-proxy"
2927client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
28+google_admin_email="admin@example.com"
29+google_target_principal="principal"
3030`
3131
3232 const testAlphaConfig = `
3333upstreamConfig:
34− proxyrawpath: false
3534 upstreams:
3635 - id: /
3736 path: /
@@ -40,45 +39,63 @@ upstreamConfig:
4039 passHostHeader: true
4140 proxyWebSockets: true
4241 timeout: 30s
42+ insecureSkipTLSVerify: false
43+ disableKeepAlives: false
4344injectRequestHeaders:
4445- name: Authorization
46+ preserveRequestValue: false
4547 values:
46− - claim: user
47− prefix: "Basic "
48− basicAuthPassword:
49− value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
48+ - claimSource:
49+ claim: user
50+ prefix: "Basic "
51+ basicAuthPassword:
52+ value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
5053- name: X-Forwarded-Groups
54+ preserveRequestValue: false
5155 values:
52− - claim: groups
56+ - claimSource:
57+ claim: groups
5358- name: X-Forwarded-User
59+ preserveRequestValue: false
5460 values:
55− - claim: user
61+ - claimSource:
62+ claim: user
5663- name: X-Forwarded-Email
64+ preserveRequestValue: false
5765 values:
58− - claim: email
66+ - claimSource:
67+ claim: email
5968- name: X-Forwarded-Preferred-Username
69+ preserveRequestValue: false
6070 values:
61− - claim: preferred_username
71+ - claimSource:
72+ claim: preferred_username
6273injectResponseHeaders:
6374- name: Authorization
6475 values:
65− - claim: user
66− prefix: "Basic "
67− basicAuthPassword:
68− value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
76+ - claimSource:
77+ claim: user
78+ prefix: "Basic "
79+ basicAuthPassword:
80+ value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
6981server:
7082 bindAddress: "127.0.0.1:4180"
7183providers:
72−- provider: google
73− ID: google=oauth2-proxy
84+- id: google=oauth2-proxy
85+ provider: google
7486 clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
7587 clientID: oauth2-proxy
76− azureConfig:
77− tenant: common
88+ useSystemTrustStore: false
89+ skipClaimsFromProfileURL: false
90+ googleConfig:
91+ adminEmail: admin@example.com
92+ targetPrincipal: principal
93+ useApplicationDefaultCredentials: false
7894 oidcConfig:
7995 groupsClaim: groups
8096 emailClaim: email
8197 userIDClaim: email
98+ insecureSkipIssuerVerification: false
8299 insecureSkipNonce: true
83100 audienceClaims: [aud]
84101 extraAudiences: []
@@ -96,15 +113,6 @@ cookie_secure="false"
96113redirect_url="http://localhost:4180/oauth2/callback"
97114`
98115
99− boolPtr := func(b bool) *bool {
100− return &b
101− }
102−
103− durationPtr := func(d time.Duration) *options.Duration {
104− du := options.Duration(d)
105− return &du
106− }
107−
108116 testExpectedOptions := func() *options.Options {
109117 opts, err := options.NewLegacyOptions().ToOptions()
110118 Expect(err).ToNot(HaveOccurred())
@@ -115,28 +123,33 @@ redirect_url="http://localhost:4180/oauth2/callback"
115123 opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
116124
117125 opts.UpstreamServers = options.UpstreamConfig{
126+ ProxyRawPath: ptr.To(false),
118127 Upstreams: []options.Upstream{
119128 {
120− ID: "/",
121− Path: "/",
122− URI: "http://httpbin",
123− FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
124− PassHostHeader: boolPtr(true),
125− ProxyWebSockets: boolPtr(true),
126− Timeout: durationPtr(options.DefaultUpstreamTimeout),
129+ ID: "/",
130+ Path: "/",
131+ URI: "http://httpbin",
132+ FlushInterval: ptr.To(options.DefaultUpstreamFlushInterval),
133+ PassHostHeader: ptr.To(true),
134+ ProxyWebSockets: ptr.To(true),
135+ Timeout: ptr.To(options.DefaultUpstreamTimeout),
136+ Static: ptr.To(false),
137+ InsecureSkipTLSVerify: ptr.To(false),
138+ DisableKeepAlives: ptr.To(false),
127139 },
128140 },
129141 }
130142
131143 authHeader := options.Header{
132− Name: "Authorization",
144+ Name: "Authorization",
145+ PreserveRequestValue: ptr.To(false),
133146 Values: []options.HeaderValue{
134147 {
135148 ClaimSource: &options.ClaimSource{
136149 Claim: "user",
137150 Prefix: "Basic ",
138151 BasicAuthPassword: &options.SecretSource{
139− Value: []byte("super-secret-password"),
152+ Value: []byte("c3VwZXItc2VjcmV0LXBhc3N3b3Jk"),
140153 },
141154 },
142155 },
@@ -148,20 +161,37 @@ redirect_url="http://localhost:4180/oauth2/callback"
148161
149162 opts.Providers = options.Providers{
150163 options.Provider{
151− ID: "google=oauth2-proxy",
152− Type: "google",
153− ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
154− ClientID: "oauth2-proxy",
164+ ID: "google=oauth2-proxy",
165+ Type: "google",
166+ ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
167+ ClientID: "oauth2-proxy",
168+ UseSystemTrustStore: ptr.To(false),
169+ SkipClaimsFromProfileURL: ptr.To(false),
170+ GoogleConfig: options.GoogleOptions{
171+ AdminEmail: "admin@example.com",
172+ TargetPrincipal: "principal",
173+ UseOrganizationID: ptr.To(false),
174+ UseApplicationDefaultCredentials: ptr.To(false),
175+ },
155176 AzureConfig: options.AzureOptions{
156177 Tenant: "common",
157178 },
158179 OIDCConfig: options.OIDCOptions{
159− GroupsClaim: "groups",
160− EmailClaim: "email",
161− UserIDClaim: "email",
162− AudienceClaims: []string{"aud"},
163− ExtraAudiences: []string{},
164− InsecureSkipNonce: true,
180+ GroupsClaim: "groups",
181+ EmailClaim: "email",
182+ UserIDClaim: "email",
183+ AudienceClaims: []string{"aud"},
184+ ExtraAudiences: []string{},
185+ InsecureSkipNonce: ptr.To(true),
186+ InsecureAllowUnverifiedEmail: ptr.To(false),
187+ InsecureSkipIssuerVerification: ptr.To(false),
188+ SkipDiscovery: ptr.To(false),
189+ },
190+ MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
191+ FederatedTokenAuth: ptr.To(false),
192+ },
193+ ADFSConfig: options.ADFSOptions{
194+ SkipScope: ptr.To(false),
165195 },
166196 LoginURLParameters: []options.LoginURLParameter{
167197 {Name: "approval_prompt", Default: []string{"force"}},
@@ -226,12 +256,13 @@ redirect_url="http://localhost:4180/oauth2/callback"
226256
227257 opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
228258 if in.expectedErr != nil {
229− Expect(err).To(MatchError(in.expectedErr.Error()))
259+ Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
230260 } else {
231261 Expect(err).ToNot(HaveOccurred())
232262 }
233263 Expect(in.expectedOptions).ToNot(BeNil())
234− Expect(opts).To(EqualOpts(in.expectedOptions()))
264+ expectedOpts := in.expectedOptions()
265+ Expect(opts).To(EqualOpts(expectedOpts))
235266 },
236267 Entry("with legacy configuration", loadConfigurationTableInput{
237268 configContent: testCoreConfig + testLegacyConfig,
@@ -245,19 +276,19 @@ redirect_url="http://localhost:4180/oauth2/callback"
245276 Entry("with bad legacy configuration", loadConfigurationTableInput{
246277 configContent: testCoreConfig + "unknown_field=\"something\"",
247278 expectedOptions: func() *options.Options { return nil },
248− 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"),
279+ 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"),
249280 }),
250281 Entry("with bad alpha configuration", loadConfigurationTableInput{
251282 configContent: testCoreConfig,
252283 alphaConfigContent: testAlphaConfig + ":",
253284 expectedOptions: func() *options.Options { return nil },
254− expectedErr: fmt.Errorf("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line %d: did not find expected key", strings.Count(testAlphaConfig, "\n")),
285+ expectedErr: errors.New("failed to load alpha options: error unmarshalling config: yaml: line 1: did not find expected key"),
255286 }),
256287 Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
257288 configContent: testCoreConfig + "unknown_field=\"something\"",
258289 alphaConfigContent: testAlphaConfig,
259290 expectedOptions: func() *options.Options { return nil },
260− 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"),
291+ 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"),
261292 }),
262293 )
263294})
oauthproxy_test.go+3−2
@@ -23,6 +23,7 @@ import (
2323 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
2424 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
2525 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
26+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
2627 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
2728 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
2829 "github.com/stretchr/testify/assert"
@@ -506,7 +507,7 @@ func TestStaticProxyUpstream(t *testing.T) {
506507 ProxyUpstream: options.Upstream{
507508 ID: "static-proxy",
508509 Path: "/static-proxy",
509− Static: true,
510+ Static: ptr.To(true),
510511 },
511512 })
512513 if err != nil {
@@ -2223,7 +2224,7 @@ func TestTrustedIPs(t *testing.T) {
22232224 {
22242225 ID: "static",
22252226 Path: "/",
2226− Static: true,
2227+ Static: ptr.To(true),
22272228 },
22282229 },
22292230 }
pkg/apis/middleware/session.go+5−1
@@ -6,6 +6,7 @@ import (
66
77 "github.com/coreos/go-oidc/v3/oidc"
88 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
9+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
910)
1011
1112// TokenToSessionFunc takes a raw ID Token and converts it into a SessionState.
@@ -40,7 +41,10 @@ func CreateTokenToSessionFunc(verify VerifyFunc) TokenToSessionFunc {
4041 claims.Email = claims.Subject
4142 }
4243
43− if claims.Verified != nil && !*claims.Verified {
44+ // Ensure email is verified
45+ // If the email is not verified, return an error
46+ // If the email_verified claim is missing, assume it is verified
47+ if !ptr.Deref(claims.Verified, true) {
4448 return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
4549 }
4650
pkg/apis/options/alpha_options.go+22−15
@@ -12,13 +12,13 @@ type AlphaOptions struct {
1212 // UpstreamConfig is used to configure upstream servers.
1313 // Once a user is authenticated, requests to the server will be proxied to
1414 // these upstream servers based on the path mappings defined in this list.
15− UpstreamConfig UpstreamConfig `json:"upstreamConfig,omitempty"`
15+ UpstreamConfig UpstreamConfig `yaml:"upstreamConfig,omitempty"`
1616
1717 // InjectRequestHeaders is used to configure headers that should be added
1818 // to requests to upstream servers.
1919 // Headers may source values from either the authenticated user's session
2020 // or from a static secret value.
21− InjectRequestHeaders []Header `json:"injectRequestHeaders,omitempty"`
21+ InjectRequestHeaders []Header `yaml:"injectRequestHeaders,omitempty"`
2222
2323 // InjectResponseHeaders is used to configure headers that should be added
2424 // to responses from the proxy.
@@ -27,35 +27,31 @@ type AlphaOptions struct {
2727 // auth_request module.
2828 // Headers may source values from either the authenticated user's session
2929 // or from a static secret value.
30− InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"`
30+ InjectResponseHeaders []Header `yaml:"injectResponseHeaders,omitempty"`
3131
3232 // Server is used to configure the HTTP(S) server for the proxy application.
3333 // You may choose to run both HTTP and HTTPS servers simultaneously.
3434 // This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
3535 // To use the secure server you must configure a TLS certificate and key.
36− Server Server `json:"server,omitempty"`
36+ Server Server `yaml:"server,omitempty"`
3737
3838 // MetricsServer is used to configure the HTTP(S) server for metrics.
3939 // You may choose to run both HTTP and HTTPS servers simultaneously.
4040 // This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
4141 // To use the secure server you must configure a TLS certificate and key.
42− MetricsServer Server `json:"metricsServer,omitempty"`
42+ MetricsServer Server `yaml:"metricsServer,omitempty"`
4343
4444 // Providers is used to configure your provider. **Multiple-providers is not
4545 // yet working.** [This feature is tracked in
4646 // #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926)
47− Providers Providers `json:"providers,omitempty"`
47+ Providers Providers `yaml:"providers,omitempty"`
4848}
4949
50−// MergeInto replaces alpha options in the Options struct with the values
51−// from the AlphaOptions
52−func (a *AlphaOptions) MergeInto(opts *Options) {
53− opts.UpstreamServers = a.UpstreamConfig
54− opts.InjectRequestHeaders = a.InjectRequestHeaders
55− opts.InjectResponseHeaders = a.InjectResponseHeaders
56− opts.Server = a.Server
57− opts.MetricsServer = a.MetricsServer
58− opts.Providers = a.Providers
50+// Initialize alpha options with default values and settings of the core options
51+func NewAlphaOptions(opts *Options) *AlphaOptions {
52+ aOpts := &AlphaOptions{}
53+ aOpts.ExtractFrom(opts)
54+ return aOpts
5955}
6056
6157// ExtractFrom populates the fields in the AlphaOptions with the values from
@@ -68,3 +64,14 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) {
6864 a.MetricsServer = opts.MetricsServer
6965 a.Providers = opts.Providers
7066}
67+
68+// MergeOptionsWithDefaults replaces alpha options in the Options struct
69+// with the values from the AlphaOptions and ensures the defaults
70+func (a *AlphaOptions) MergeOptionsWithDefaults(opts *Options) {
71+ opts.UpstreamServers = a.UpstreamConfig
72+ opts.InjectRequestHeaders = a.InjectRequestHeaders
73+ opts.InjectResponseHeaders = a.InjectResponseHeaders
74+ opts.Server = a.Server
75+ opts.MetricsServer = a.MetricsServer
76+ opts.Providers = a.Providers
77+}
pkg/apis/options/common.godeleted−63
@@ -1,63 +0,0 @@
1−package options
2−
3−import (
4− "fmt"
5− "strconv"
6− "time"
7−)
8−
9−// SecretSource references an individual secret value.
10−// Only one source within the struct should be defined at any time.
11−type SecretSource struct {
12− // Value expects a base64 encoded string value.
13− Value []byte `json:"value,omitempty"`
14−
15− // FromEnv expects the name of an environment variable.
16− FromEnv string `json:"fromEnv,omitempty"`
17−
18− // FromFile expects a path to a file containing the secret value.
19− FromFile string `json:"fromFile,omitempty"`
20−}
21−
22−// Duration is an alias for time.Duration so that we can ensure the marshalling
23−// and unmarshalling of string durations is done as users expect.
24−// Intentional blank line below to keep this first part of the comment out of
25−// any generated references.
26−
27−// Duration is as string representation of a period of time.
28−// A duration string is a is a possibly signed sequence of decimal numbers,
29−// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
30−// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
31−// +reference-gen:alias-name=string
32−type Duration time.Duration
33−
34−// UnmarshalJSON parses the duration string and sets the value of duration
35−// to the value of the duration string.
36−func (d *Duration) UnmarshalJSON(data []byte) error {
37− input := string(data)
38− if unquoted, err := strconv.Unquote(input); err == nil {
39− input = unquoted
40− }
41−
42− du, err := time.ParseDuration(input)
43− if err != nil {
44− return err
45− }
46− *d = Duration(du)
47− return nil
48−}
49−
50−// MarshalJSON ensures that when the string is marshalled to JSON as a human
51−// readable string.
52−func (d *Duration) MarshalJSON() ([]byte, error) {
53− dStr := fmt.Sprintf("%q", d.Duration().String())
54− return []byte(dStr), nil
55−}
56−
57−// Duration returns the time.Duration version of this Duration
58−func (d *Duration) Duration() time.Duration {
59− if d == nil {
60− return time.Duration(0)
61− }
62− return time.Duration(*d)
63−}
pkg/apis/options/common_test.godeleted−87
@@ -1,87 +0,0 @@
1−package options
2−
3−import (
4− "encoding/json"
5− "errors"
6− "time"
7−
8− . "github.com/onsi/ginkgo/v2"
9− . "github.com/onsi/gomega"
10−)
11−
12−var _ = Describe("Common", func() {
13− Context("Duration", func() {
14− type marshalJSONTableInput struct {
15− duration Duration
16− expectedJSON string
17− }
18−
19− DescribeTable("MarshalJSON",
20− func(in marshalJSONTableInput) {
21− data, err := in.duration.MarshalJSON()
22− Expect(err).ToNot(HaveOccurred())
23− Expect(string(data)).To(Equal(in.expectedJSON))
24−
25− var d Duration
26− Expect(json.Unmarshal(data, &d)).To(Succeed())
27− Expect(d).To(Equal(in.duration))
28− },
29− Entry("30 seconds", marshalJSONTableInput{
30− duration: Duration(30 * time.Second),
31− expectedJSON: "\"30s\"",
32− }),
33− Entry("1 minute", marshalJSONTableInput{
34− duration: Duration(1 * time.Minute),
35− expectedJSON: "\"1m0s\"",
36− }),
37− Entry("1 hour 15 minutes", marshalJSONTableInput{
38− duration: Duration(75 * time.Minute),
39− expectedJSON: "\"1h15m0s\"",
40− }),
41− Entry("A zero Duration", marshalJSONTableInput{
42− duration: Duration(0),
43− expectedJSON: "\"0s\"",
44− }),
45− )
46−
47− type unmarshalJSONTableInput struct {
48− json string
49− expectedErr error
50− expectedDuration Duration
51− }
52−
53− DescribeTable("UnmarshalJSON",
54− func(in unmarshalJSONTableInput) {
55− // A duration must be initialised pointer before UnmarshalJSON will work.
56− zero := Duration(0)
57− d := &zero
58−
59− err := d.UnmarshalJSON([]byte(in.json))
60− if in.expectedErr != nil {
61− Expect(err).To(MatchError(in.expectedErr.Error()))
62− } else {
63− Expect(err).ToNot(HaveOccurred())
64− }
65− Expect(d).ToNot(BeNil())
66− Expect(*d).To(Equal(in.expectedDuration))
67− },
68− Entry("1m", unmarshalJSONTableInput{
69− json: "\"1m\"",
70− expectedDuration: Duration(1 * time.Minute),
71− }),
72− Entry("30s", unmarshalJSONTableInput{
73− json: "\"30s\"",
74− expectedDuration: Duration(30 * time.Second),
75− }),
76− Entry("1h15m", unmarshalJSONTableInput{
77− json: "\"1h15m\"",
78− expectedDuration: Duration(75 * time.Minute),
79− }),
80− Entry("am", unmarshalJSONTableInput{
81− json: "\"am\"",
82− expectedErr: errors.New("time: invalid duration \"am\""),
83− expectedDuration: Duration(0),
84− }),
85− )
86− })
87−})
pkg/apis/options/doc.go+1−1
@@ -1,3 +1,3 @@
1−//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20220223111546-d3b50d1a591a
1+//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20250404153144-32055bc45bc3
22//go:generate reference-gen --package github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options --types AlphaOptions --header-file ../../../docs/docs/configuration/alpha_config.md.tmpl --out-file ../../../docs/docs/configuration/alpha_config.md
33package options
pkg/apis/options/header.go+43−9
@@ -1,17 +1,26 @@
11package options
22
3+import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
4+
5+const (
6+ // DefaultHeaderPreserveRequestValue is the default value for Header.PreserveRequestValue
7+ DefaultHeaderPreserveRequestValue bool = false
8+ // DefaultInsecureSkipHeaderNormalization is the default value for Header.InsecureSkipHeaderNormalization
9+ DefaultInsecureSkipHeaderNormalization bool = false
10+)
11+
312// Header represents an individual header that will be added to a request or
413// response header.
514type Header struct {
615 // Name is the header name to be used for this set of values.
716 // Names should be unique within a list of Headers.
8− Name string `json:"name,omitempty"`
17+ Name string `yaml:"name,omitempty"`
918
1019 // PreserveRequestValue determines whether any values for this header
1120 // should be preserved for the request to the upstream server.
1221 // This option only applies to injected request headers.
1322 // Defaults to false (headers that match this header will be stripped).
14− PreserveRequestValue bool `json:"preserveRequestValue,omitempty"`
23+ PreserveRequestValue *bool `yaml:"preserveRequestValue,omitempty"`
1524
1625 // InsecureSkipHeaderNormalization disables normalizing the header name
1726 // According to RFC 7230 Section 3.2 there aren't any rules about
@@ -21,20 +30,20 @@ type Header struct {
2130 // treated as the same header. Additionally underscores (_) in header names
2231 // will be converted to dashes (-) when normalizing.
2332 // Defaults to false (header names will be normalized).
24− InsecureSkipHeaderNormalization bool `json:"InsecureSkipHeaderNormalization,omitempty"`
33+ InsecureSkipHeaderNormalization *bool `yaml:"InsecureSkipHeaderNormalization,omitempty"`
2534
2635 // Values contains the desired values for this header
27− Values []HeaderValue `json:"values,omitempty"`
36+ Values []HeaderValue `yaml:"values,omitempty"`
2837}
2938
3039// HeaderValue represents a single header value and the sources that can
3140// make up the header value
3241type HeaderValue struct {
3342 // Allow users to load the value from a secret source
34− *SecretSource `json:",omitempty"`
43+ *SecretSource `yaml:"secretSource,omitempty"`
3544
3645 // Allow users to load the value from a session claim
37− *ClaimSource `json:",omitempty"`
46+ *ClaimSource `yaml:"claimSource,omitempty"`
3847}
3948
4049// ClaimSource allows loading a header value from a claim within the session
@@ -42,14 +51,39 @@ type ClaimSource struct {
4251 // Claim is the name of the claim in the session that the value should be
4352 // loaded from. Available claims: `access_token` `id_token` `created_at`
4453 // `expires_on` `refresh_token` `email` `user` `groups` `preferred_username`.
45− Claim string `json:"claim,omitempty"`
54+ Claim string `yaml:"claim,omitempty"`
4655
4756 // Prefix is an optional prefix that will be prepended to the value of the
4857 // claim if it is non-empty.
49− Prefix string `json:"prefix,omitempty"`
58+ Prefix string `yaml:"prefix,omitempty"`
5059
5160 // BasicAuthPassword converts this claim into a basic auth header.
5261 // Note the value of claim will become the basic auth username and the
5362 // basicAuthPassword will be used as the password value.
54− BasicAuthPassword *SecretSource `json:"basicAuthPassword,omitempty"`
63+ BasicAuthPassword *SecretSource `yaml:"basicAuthPassword,omitempty"`
64+}
65+
66+// EnsureDefaults sets any default values for Header fields.
67+func (h *Header) EnsureDefaults() {
68+ if h.PreserveRequestValue == nil {
69+ h.PreserveRequestValue = ptr.To(DefaultHeaderPreserveRequestValue)
70+ }
71+ for i := range h.Values {
72+ h.Values[i].EnsureDefaults()
73+ }
74+}
75+
76+// EnsureDefaults sets any default values for HeaderValue fields.
77+func (hv *HeaderValue) EnsureDefaults() {
78+ if hv.ClaimSource != nil {
79+ hv.ClaimSource.EnsureDefaults()
80+ }
81+ if hv.SecretSource != nil {
82+ hv.SecretSource.EnsureDefaults()
83+ }
84+}
85+
86+// EnsureDefaults sets any default values for ClaimSource fields.
87+func (hc *ClaimSource) EnsureDefaults() {
88+ // No defaults to set currently
5589}
pkg/apis/options/hooks.gonew file+57
@@ -0,0 +1,57 @@
1+package options
2+
3+import (
4+ "reflect"
5+ "time"
6+
7+ "github.com/go-viper/mapstructure/v2"
8+)
9+
10+// Duration is an alias for time.Duration so that we can ensure the marshalling
11+// and unmarshalling of string durations is done as users expect.
12+// Intentional blank line below to keep this first part of the comment out of
13+// any generated references.
14+
15+// Duration is as string representation of a period of time.
16+// A duration string is a is a possibly signed sequence of decimal numbers,
17+// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
18+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
19+
20+// Conversion from string or floating point to golang duration type
21+// This way floating points will be converted to seconds and strings
22+// of type 3s or 5m will be parsed with time.ParseDuration
23+func toDurationHookFunc() mapstructure.DecodeHookFunc {
24+ return func(
25+ f reflect.Type,
26+ t reflect.Type,
27+ data interface{}) (interface{}, error) {
28+ if t != reflect.TypeOf(time.Duration(0)) {
29+ return data, nil
30+ }
31+
32+ switch f.Kind() {
33+ case reflect.String:
34+ return time.ParseDuration(data.(string))
35+ case reflect.Float64:
36+ return time.Duration(data.(float64) * float64(time.Second)), nil
37+ case reflect.Int64:
38+ return time.Duration(data.(int64)), nil
39+ default:
40+ return data, nil
41+ }
42+ }
43+}
44+
45+// StringToBytesHookFunc returns a DecodeHookFunc that converts string to []byte.
46+func stringToBytesHookFunc() mapstructure.DecodeHookFunc {
47+ return func(
48+ f reflect.Type,
49+ t reflect.Type,
50+ data interface{},
51+ ) (interface{}, error) {
52+ if f.Kind() == reflect.String && t == reflect.TypeOf([]byte{}) {
53+ return []byte(data.(string)), nil
54+ }
55+ return data, nil
56+ }
57+}
pkg/apis/options/hooks_test.gonew file+96
@@ -0,0 +1,96 @@
1+package options
2+
3+import (
4+ "testing"
5+ "time"
6+)
7+
8+func TestToDurationHook(t *testing.T) {
9+ type result struct {
10+ Duration time.Duration `yaml:"duration"`
11+ }
12+
13+ tests := []struct {
14+ name string
15+ input map[string]interface{}
16+ out result
17+ expected time.Duration
18+ expectedErr bool
19+ }{
20+ {
21+ name: "Valid String Duration with single unit",
22+ input: map[string]interface{}{"duration": "3s"},
23+ out: result{},
24+ expected: 3 * time.Second,
25+ expectedErr: false,
26+ },
27+ {
28+ name: "Valid String Duration with multiple units",
29+ input: map[string]interface{}{"duration": "1h20m30s"},
30+ out: result{},
31+ expected: 1*time.Hour + 20*time.Minute + 30*time.Second,
32+ expectedErr: false,
33+ },
34+ {
35+ name: "Valid Float Duration",
36+ input: map[string]interface{}{"duration": 2.5},
37+ out: result{},
38+ expected: 2500 * time.Millisecond,
39+ expectedErr: false,
40+ },
41+ {
42+ name: "Valid Int64 Duration",
43+ input: map[string]interface{}{"duration": int64(5000000000)},
44+ out: result{},
45+ expected: 5 * time.Second,
46+ expectedErr: false,
47+ },
48+ {
49+ name: "Invalid String",
50+ input: map[string]interface{}{"duration": "invalid"},
51+ out: result{},
52+ expected: 0,
53+ expectedErr: true,
54+ },
55+ {
56+ name: "Unsupported Type",
57+ input: map[string]interface{}{"duration": true},
58+ out: result{},
59+ expected: 0,
60+ expectedErr: true,
61+ },
62+ }
63+
64+ for _, tt := range tests {
65+ t.Run(tt.name, func(t *testing.T) {
66+ var result struct {
67+ Duration time.Duration `yaml:"duration"`
68+ }
69+
70+ err := Decode(tt.input, &result)
71+ if (err != nil) != tt.expectedErr {
72+ t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
73+ }
74+
75+ if !tt.expectedErr {
76+ if result.Duration != tt.expected {
77+ t.Errorf("expected: %v, got: %v", tt.expected, result.Duration)
78+ }
79+ }
80+ })
81+ }
82+}
83+
84+func TestStringToBytesHook(t *testing.T) {
85+ var result struct {
86+ Value []byte `yaml:"value"`
87+ }
88+
89+ if err := Decode(map[string]interface{}{"value": "hello-world"}, &result); err != nil {
90+ t.Fatal(err)
91+ }
92+
93+ if string(result.Value) != "hello-world" {
94+ t.Errorf("expected %q, got %q", "hello-world", string(result.Value))
95+ }
96+}
pkg/apis/options/legacy_options.go+45−30
@@ -9,6 +9,7 @@ import (
99 "time"
1010
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1213 "github.com/spf13/pflag"
1314)
1415
@@ -95,6 +96,7 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
9596 return nil, fmt.Errorf("error converting provider: %v", err)
9697 }
9798 l.Options.Providers = providers
99+ l.Options.EnsureDefaults()
98100
99101 return &l.Options, nil
100102}
@@ -136,18 +138,18 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
136138 u.Path = "/"
137139 }
138140
139− flushInterval := Duration(l.FlushInterval)
140− timeout := Duration(l.Timeout)
141+ flushInterval := l.FlushInterval
142+ timeout := l.Timeout
141143 upstream := Upstream{
142144 ID: u.Path,
143145 Path: u.Path,
144146 URI: upstreamString,
145− InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify,
147+ InsecureSkipTLSVerify: &l.SSLUpstreamInsecureSkipVerify,
146148 PassHostHeader: &l.PassHostHeader,
147149 ProxyWebSockets: &l.ProxyWebSockets,
148150 FlushInterval: &flushInterval,
149151 Timeout: &timeout,
150− DisableKeepAlives: l.DisableKeepAlives,
152+ DisableKeepAlives: &l.DisableKeepAlives,
151153 }
152154
153155 switch u.Scheme {
@@ -164,7 +166,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
164166 logger.Errorf("unable to convert %q to int, use default \"200\"", u.Host)
165167 responseCode = 200
166168 }
167− upstream.Static = true
169+ upstream.Static = ptr.To(true)
168170 upstream.StaticCode = &responseCode
169171
170172 // This is not allowed to be empty and must be unique
@@ -175,12 +177,12 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
175177
176178 // Force defaults compatible with static responses
177179 upstream.URI = ""
178− upstream.InsecureSkipTLSVerify = false
180+ upstream.InsecureSkipTLSVerify = ptr.To(false)
181+ upstream.DisableKeepAlives = ptr.To(false)
179182 upstream.PassHostHeader = nil
180183 upstream.ProxyWebSockets = nil
181184 upstream.FlushInterval = nil
182185 upstream.Timeout = nil
183− upstream.DisableKeepAlives = false
184186 case "unix":
185187 upstream.Path = "/"
186188 }
@@ -253,7 +255,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
253255 }
254256
255257 for i := range requestHeaders {
256− requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
258+ requestHeaders[i].PreserveRequestValue = ptr.To(!l.SkipAuthStripHeaders)
257259 }
258260
259261 return requestHeaders
@@ -287,7 +289,8 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
287289 }
288290
289291 return Header{
290− Name: "Authorization",
292+ Name: "Authorization",
293+ PreserveRequestValue: ptr.To(false),
291294 Values: []HeaderValue{
292295 {
293296 ClaimSource: &ClaimSource{
@@ -305,7 +308,8 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
305308func getPassUserHeaders(preferEmailToUser bool) []Header {
306309 headers := []Header{
307310 {
308− Name: "X-Forwarded-Groups",
311+ Name: "X-Forwarded-Groups",
312+ PreserveRequestValue: ptr.To(false),
309313 Values: []HeaderValue{
310314 {
311315 ClaimSource: &ClaimSource{
@@ -319,7 +323,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
319323 if preferEmailToUser {
320324 return append(headers,
321325 Header{
322− Name: "X-Forwarded-User",
326+ Name: "X-Forwarded-User",
327+ PreserveRequestValue: ptr.To(false),
323328 Values: []HeaderValue{
324329 {
325330 ClaimSource: &ClaimSource{
@@ -333,7 +338,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
333338
334339 return append(headers,
335340 Header{
336− Name: "X-Forwarded-User",
341+ Name: "X-Forwarded-User",
342+ PreserveRequestValue: ptr.To(false),
337343 Values: []HeaderValue{
338344 {
339345 ClaimSource: &ClaimSource{
@@ -343,7 +349,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
343349 },
344350 },
345351 Header{
346− Name: "X-Forwarded-Email",
352+ Name: "X-Forwarded-Email",
353+ PreserveRequestValue: ptr.To(false),
347354 Values: []HeaderValue{
348355 {
349356 ClaimSource: &ClaimSource{
@@ -357,7 +364,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
357364
358365func getPassAccessTokenHeader() Header {
359366 return Header{
360− Name: "X-Forwarded-Access-Token",
367+ Name: "X-Forwarded-Access-Token",
368+ PreserveRequestValue: ptr.To(false),
361369 Values: []HeaderValue{
362370 {
363371 ClaimSource: &ClaimSource{
@@ -370,7 +378,8 @@ func getPassAccessTokenHeader() Header {
370378
371379func getAuthorizationHeader() Header {
372380 return Header{
373− Name: "Authorization",
381+ Name: "Authorization",
382+ PreserveRequestValue: ptr.To(false),
374383 Values: []HeaderValue{
375384 {
376385 ClaimSource: &ClaimSource{
@@ -384,7 +393,8 @@ func getAuthorizationHeader() Header {
384393
385394func getPreferredUsernameHeader() Header {
386395 return Header{
387− Name: "X-Forwarded-Preferred-Username",
396+ Name: "X-Forwarded-Preferred-Username",
397+ PreserveRequestValue: ptr.To(false),
388398 Values: []HeaderValue{
389399 {
390400 ClaimSource: &ClaimSource{
@@ -398,7 +408,8 @@ func getPreferredUsernameHeader() Header {
398408func getXAuthRequestHeaders() []Header {
399409 headers := []Header{
400410 {
401− Name: "X-Auth-Request-User",
411+ Name: "X-Auth-Request-User",
412+ PreserveRequestValue: ptr.To(false),
402413 Values: []HeaderValue{
403414 {
404415 ClaimSource: &ClaimSource{
@@ -408,7 +419,8 @@ func getXAuthRequestHeaders() []Header {
408419 },
409420 },
410421 {
411− Name: "X-Auth-Request-Email",
422+ Name: "X-Auth-Request-Email",
423+ PreserveRequestValue: ptr.To(false),
412424 Values: []HeaderValue{
413425 {
414426 ClaimSource: &ClaimSource{
@@ -418,7 +430,8 @@ func getXAuthRequestHeaders() []Header {
418430 },
419431 },
420432 {
421− Name: "X-Auth-Request-Preferred-Username",
433+ Name: "X-Auth-Request-Preferred-Username",
434+ PreserveRequestValue: ptr.To(false),
422435 Values: []HeaderValue{
423436 {
424437 ClaimSource: &ClaimSource{
@@ -428,7 +441,8 @@ func getXAuthRequestHeaders() []Header {
428441 },
429442 },
430443 {
431− Name: "X-Auth-Request-Groups",
444+ Name: "X-Auth-Request-Groups",
445+ PreserveRequestValue: ptr.To(false),
432446 Values: []HeaderValue{
433447 {
434448 ClaimSource: &ClaimSource{
@@ -444,7 +458,8 @@ func getXAuthRequestHeaders() []Header {
444458
445459func getXAuthRequestAccessTokenHeader() Header {
446460 return Header{
447− Name: "X-Auth-Request-Access-Token",
461+ Name: "X-Auth-Request-Access-Token",
462+ PreserveRequestValue: ptr.To(false),
448463 Values: []HeaderValue{
449464 {
450465 ClaimSource: &ClaimSource{
@@ -684,11 +699,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
684699 ClientSecretFile: l.ClientSecretFile,
685700 Type: ProviderType(l.ProviderType),
686701 CAFiles: l.ProviderCAFiles,
687− UseSystemTrustStore: l.UseSystemTrustStore,
702+ UseSystemTrustStore: &l.UseSystemTrustStore,
688703 LoginURL: l.LoginURL,
689704 RedeemURL: l.RedeemURL,
690705 ProfileURL: l.ProfileURL,
691− SkipClaimsFromProfileURL: l.SkipClaimsFromProfileURL,
706+ SkipClaimsFromProfileURL: &l.SkipClaimsFromProfileURL,
692707 ProtectedResource: l.ProtectedResource,
693708 ValidateURL: l.ValidateURL,
694709 Scope: l.Scope,
@@ -701,10 +716,10 @@ func (l *LegacyProvider) convert() (Providers, error) {
701716 // This part is out of the switch section for all providers that support OIDC
702717 provider.OIDCConfig = OIDCOptions{
703718 IssuerURL: l.OIDCIssuerURL,
704− InsecureAllowUnverifiedEmail: l.InsecureOIDCAllowUnverifiedEmail,
705− InsecureSkipIssuerVerification: l.InsecureOIDCSkipIssuerVerification,
706− InsecureSkipNonce: l.InsecureOIDCSkipNonce,
707− SkipDiscovery: l.SkipOIDCDiscovery,
719+ InsecureAllowUnverifiedEmail: &l.InsecureOIDCAllowUnverifiedEmail,
720+ InsecureSkipIssuerVerification: &l.InsecureOIDCSkipIssuerVerification,
721+ InsecureSkipNonce: &l.InsecureOIDCSkipNonce,
722+ SkipDiscovery: &l.SkipOIDCDiscovery,
708723 JwksURL: l.OIDCJwksURL,
709724 UserIDClaim: l.UserIDClaim,
710725 EmailClaim: l.OIDCEmailClaim,
@@ -772,15 +787,15 @@ func (l *LegacyProvider) convert() (Providers, error) {
772787 Groups: l.GoogleGroups,
773788 AdminEmail: l.GoogleAdminEmail,
774789 ServiceAccountJSON: l.GoogleServiceAccountJSON,
775− UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials,
790+ UseApplicationDefaultCredentials: &l.GoogleUseApplicationDefaultCredentials,
776791 TargetPrincipal: l.GoogleTargetPrincipal,
777− UseOrganizationID: l.GoogleUseOrganizationID,
792+ UseOrganizationID: &l.GoogleUseOrganizationID,
778793 AdminAPIUserScope: l.GoogleAdminAPIUserScope,
779794 }
780795 case "entra-id":
781796 provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{
782797 AllowedTenants: l.EntraIDAllowedTenants,
783− FederatedTokenAuth: l.EntraIDFederatedTokenAuth,
798+ FederatedTokenAuth: &l.EntraIDFederatedTokenAuth,
784799 }
785800 }
786801
pkg/apis/options/legacy_options_test.go+110−75
@@ -3,6 +3,8 @@ package options
33import (
44 "time"
55
6+ . "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil"
7+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
68 . "github.com/onsi/ginkgo/v2"
79 . "github.com/onsi/gomega"
810)
@@ -15,8 +17,8 @@ var _ = Describe("Legacy Options", func() {
1517 legacyOpts := NewLegacyOptions()
1618
1719 // Set upstreams and related options to test their conversion
18− flushInterval := Duration(5 * time.Second)
19− timeout := Duration(5 * time.Second)
20+ flushInterval := 5 * time.Second
21+ timeout := 5 * time.Second
2022 legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
2123 legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
2224 legacyOpts.LegacyUpstreams.PassHostHeader = true
@@ -26,7 +28,6 @@ var _ = Describe("Legacy Options", func() {
2628 legacyOpts.LegacyProvider.ClientID = "oauth-proxy"
2729 legacyOpts.LegacyUpstreams.DisableKeepAlives = false
2830
29− truth := true
3031 staticCode := 204
3132 opts.UpstreamServers = UpstreamConfig{
3233 Upstreams: []Upstream{
@@ -35,35 +36,35 @@ var _ = Describe("Legacy Options", func() {
3536 Path: "/baz",
3637 URI: "http://foo.bar/baz",
3738 FlushInterval: &flushInterval,
38− InsecureSkipTLSVerify: true,
39− PassHostHeader: &truth,
40− ProxyWebSockets: &truth,
39+ InsecureSkipTLSVerify: ptr.To(true),
40+ PassHostHeader: ptr.To(true),
41+ ProxyWebSockets: ptr.To(true),
4142 Timeout: &timeout,
42− DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
43+ DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
4344 },
4445 {
4546 ID: "/bar",
4647 Path: "/bar",
4748 URI: "file:///var/lib/website",
4849 FlushInterval: &flushInterval,
49− InsecureSkipTLSVerify: true,
50− PassHostHeader: &truth,
51− ProxyWebSockets: &truth,
50+ InsecureSkipTLSVerify: ptr.To(true),
51+ PassHostHeader: ptr.To(true),
52+ ProxyWebSockets: ptr.To(true),
5253 Timeout: &timeout,
53− DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
54+ DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
5455 },
5556 {
5657 ID: "static://204",
5758 Path: "/",
5859 URI: "",
59− Static: true,
60+ Static: ptr.To(true),
6061 StaticCode: &staticCode,
6162 FlushInterval: nil,
62− InsecureSkipTLSVerify: false,
63+ InsecureSkipTLSVerify: ptr.To(false),
6364 PassHostHeader: nil,
6465 ProxyWebSockets: nil,
6566 Timeout: nil,
66− DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
67+ DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
6768 },
6869 },
6970 }
@@ -71,7 +72,7 @@ var _ = Describe("Legacy Options", func() {
7172 opts.InjectRequestHeaders = []Header{
7273 {
7374 Name: "X-Forwarded-Groups",
74− PreserveRequestValue: false,
75+ PreserveRequestValue: ptr.To(false),
7576 Values: []HeaderValue{
7677 {
7778 ClaimSource: &ClaimSource{
@@ -82,7 +83,7 @@ var _ = Describe("Legacy Options", func() {
8283 },
8384 {
8485 Name: "X-Forwarded-User",
85− PreserveRequestValue: false,
86+ PreserveRequestValue: ptr.To(false),
8687 Values: []HeaderValue{
8788 {
8889 ClaimSource: &ClaimSource{
@@ -93,7 +94,7 @@ var _ = Describe("Legacy Options", func() {
9394 },
9495 {
9596 Name: "X-Forwarded-Email",
96− PreserveRequestValue: false,
97+ PreserveRequestValue: ptr.To(false),
9798 Values: []HeaderValue{
9899 {
99100 ClaimSource: &ClaimSource{
@@ -104,7 +105,7 @@ var _ = Describe("Legacy Options", func() {
104105 },
105106 {
106107 Name: "X-Forwarded-Preferred-Username",
107− PreserveRequestValue: false,
108+ PreserveRequestValue: ptr.To(false),
108109 Values: []HeaderValue{
109110 {
110111 ClaimSource: &ClaimSource{
@@ -121,18 +122,21 @@ var _ = Describe("Legacy Options", func() {
121122 BindAddress: "127.0.0.1:4180",
122123 }
123124
124− opts.Providers[0].ClientID = "oauth-proxy"
125125 opts.Providers[0].ID = "google=oauth-proxy"
126− opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
126+ opts.Providers[0].ClientID = "oauth-proxy"
127127 opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"}
128128 opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
129+ opts.Providers[0].OIDCConfig.InsecureSkipNonce = ptr.To(true)
130+ opts.Providers[0].OIDCConfig.InsecureSkipIssuerVerification = ptr.To(false)
129131 opts.Providers[0].LoginURLParameters = []LoginURLParameter{
130132 {Name: "approval_prompt", Default: []string{"force"}},
131133 }
132134
133135 converted, err := legacyOpts.ToOptions()
136+ opts.EnsureDefaults()
137+
134138 Expect(err).ToNot(HaveOccurred())
135− Expect(converted).To(Equal(opts))
139+ Expect(converted).To(EqualOpts(opts))
136140 })
137141 })
138142
@@ -147,8 +151,8 @@ var _ = Describe("Legacy Options", func() {
147151 skipVerify := true
148152 passHostHeader := false
149153 proxyWebSockets := true
150− flushInterval := Duration(5 * time.Second)
151− timeout := Duration(5 * time.Second)
154+ flushInterval := 5 * time.Second
155+ timeout := 5 * time.Second
152156 disableKeepAlives := true
153157
154158 // Test cases and expected outcomes
@@ -157,12 +161,12 @@ var _ = Describe("Legacy Options", func() {
157161 ID: "/baz",
158162 Path: "/baz",
159163 URI: validHTTP,
160− InsecureSkipTLSVerify: skipVerify,
164+ InsecureSkipTLSVerify: &skipVerify,
161165 PassHostHeader: &passHostHeader,
162166 ProxyWebSockets: &proxyWebSockets,
163167 FlushInterval: &flushInterval,
164168 Timeout: &timeout,
165− DisableKeepAlives: disableKeepAlives,
169+ DisableKeepAlives: &disableKeepAlives,
166170 }
167171
168172 // Test cases and expected outcomes
@@ -171,12 +175,12 @@ var _ = Describe("Legacy Options", func() {
171175 ID: "/",
172176 Path: "/",
173177 URI: emptyPathHTTP,
174− InsecureSkipTLSVerify: skipVerify,
178+ InsecureSkipTLSVerify: &skipVerify,
175179 PassHostHeader: &passHostHeader,
176180 ProxyWebSockets: &proxyWebSockets,
177181 FlushInterval: &flushInterval,
178182 Timeout: &timeout,
179− DisableKeepAlives: disableKeepAlives,
183+ DisableKeepAlives: &disableKeepAlives,
180184 }
181185
182186 validFileWithFragment := "file:///var/lib/website#/bar"
@@ -184,12 +188,12 @@ var _ = Describe("Legacy Options", func() {
184188 ID: "/bar",
185189 Path: "/bar",
186190 URI: "file:///var/lib/website",
187− InsecureSkipTLSVerify: skipVerify,
191+ InsecureSkipTLSVerify: &skipVerify,
188192 PassHostHeader: &passHostHeader,
189193 ProxyWebSockets: &proxyWebSockets,
190194 FlushInterval: &flushInterval,
191195 Timeout: &timeout,
192− DisableKeepAlives: disableKeepAlives,
196+ DisableKeepAlives: &disableKeepAlives,
193197 }
194198
195199 validStatic := "static://204"
@@ -198,14 +202,14 @@ var _ = Describe("Legacy Options", func() {
198202 ID: validStatic,
199203 Path: "/",
200204 URI: "",
201− Static: true,
205+ Static: ptr.To(true),
202206 StaticCode: &validStaticCode,
203− InsecureSkipTLSVerify: false,
207+ InsecureSkipTLSVerify: ptr.To(false),
204208 PassHostHeader: nil,
205209 ProxyWebSockets: nil,
206210 FlushInterval: nil,
207211 Timeout: nil,
208− DisableKeepAlives: false,
212+ DisableKeepAlives: ptr.To(false),
209213 }
210214
211215 invalidStatic := "static://abc"
@@ -214,14 +218,14 @@ var _ = Describe("Legacy Options", func() {
214218 ID: invalidStatic,
215219 Path: "/",
216220 URI: "",
217− Static: true,
221+ Static: ptr.To(true),
218222 StaticCode: &invalidStaticCode,
219− InsecureSkipTLSVerify: false,
223+ InsecureSkipTLSVerify: ptr.To(false),
220224 PassHostHeader: nil,
221225 ProxyWebSockets: nil,
222226 FlushInterval: nil,
223227 Timeout: nil,
224− DisableKeepAlives: false,
228+ DisableKeepAlives: ptr.To(false),
225229 }
226230
227231 invalidHTTP := ":foo"
@@ -308,13 +312,13 @@ var _ = Describe("Legacy Options", func() {
308312 }
309313
310314 withPreserveRequestValue := func(h Header, preserve bool) Header {
311− h.PreserveRequestValue = preserve
315+ h.PreserveRequestValue = &preserve
312316 return h
313317 }
314318
315319 xForwardedUser := Header{
316320 Name: "X-Forwarded-User",
317− PreserveRequestValue: false,
321+ PreserveRequestValue: ptr.To(false),
318322 Values: []HeaderValue{
319323 {
320324 ClaimSource: &ClaimSource{
@@ -326,7 +330,7 @@ var _ = Describe("Legacy Options", func() {
326330
327331 xForwardedEmail := Header{
328332 Name: "X-Forwarded-Email",
329− PreserveRequestValue: false,
333+ PreserveRequestValue: ptr.To(false),
330334 Values: []HeaderValue{
331335 {
332336 ClaimSource: &ClaimSource{
@@ -338,7 +342,7 @@ var _ = Describe("Legacy Options", func() {
338342
339343 xForwardedGroups := Header{
340344 Name: "X-Forwarded-Groups",
341− PreserveRequestValue: false,
345+ PreserveRequestValue: ptr.To(false),
342346 Values: []HeaderValue{
343347 {
344348 ClaimSource: &ClaimSource{
@@ -350,7 +354,7 @@ var _ = Describe("Legacy Options", func() {
350354
351355 xForwardedPreferredUsername := Header{
352356 Name: "X-Forwarded-Preferred-Username",
353− PreserveRequestValue: false,
357+ PreserveRequestValue: ptr.To(false),
354358 Values: []HeaderValue{
355359 {
356360 ClaimSource: &ClaimSource{
@@ -362,7 +366,7 @@ var _ = Describe("Legacy Options", func() {
362366
363367 basicAuthHeader := Header{
364368 Name: "Authorization",
365− PreserveRequestValue: false,
369+ PreserveRequestValue: ptr.To(false),
366370 Values: []HeaderValue{
367371 {
368372 ClaimSource: &ClaimSource{
@@ -378,7 +382,7 @@ var _ = Describe("Legacy Options", func() {
378382
379383 xForwardedUserWithEmail := Header{
380384 Name: "X-Forwarded-User",
381− PreserveRequestValue: false,
385+ PreserveRequestValue: ptr.To(false),
382386 Values: []HeaderValue{
383387 {
384388 ClaimSource: &ClaimSource{
@@ -390,7 +394,7 @@ var _ = Describe("Legacy Options", func() {
390394
391395 xForwardedAccessToken := Header{
392396 Name: "X-Forwarded-Access-Token",
393− PreserveRequestValue: false,
397+ PreserveRequestValue: ptr.To(false),
394398 Values: []HeaderValue{
395399 {
396400 ClaimSource: &ClaimSource{
@@ -402,7 +406,7 @@ var _ = Describe("Legacy Options", func() {
402406
403407 basicAuthHeaderWithEmail := Header{
404408 Name: "Authorization",
405− PreserveRequestValue: false,
409+ PreserveRequestValue: ptr.To(false),
406410 Values: []HeaderValue{
407411 {
408412 ClaimSource: &ClaimSource{
@@ -418,7 +422,7 @@ var _ = Describe("Legacy Options", func() {
418422
419423 xAuthRequestUser := Header{
420424 Name: "X-Auth-Request-User",
421− PreserveRequestValue: false,
425+ PreserveRequestValue: ptr.To(false),
422426 Values: []HeaderValue{
423427 {
424428 ClaimSource: &ClaimSource{
@@ -430,7 +434,7 @@ var _ = Describe("Legacy Options", func() {
430434
431435 xAuthRequestEmail := Header{
432436 Name: "X-Auth-Request-Email",
433− PreserveRequestValue: false,
437+ PreserveRequestValue: ptr.To(false),
434438 Values: []HeaderValue{
435439 {
436440 ClaimSource: &ClaimSource{
@@ -442,7 +446,7 @@ var _ = Describe("Legacy Options", func() {
442446
443447 xAuthRequestGroups := Header{
444448 Name: "X-Auth-Request-Groups",
445− PreserveRequestValue: false,
449+ PreserveRequestValue: ptr.To(false),
446450 Values: []HeaderValue{
447451 {
448452 ClaimSource: &ClaimSource{
@@ -454,7 +458,7 @@ var _ = Describe("Legacy Options", func() {
454458
455459 xAuthRequestPreferredUsername := Header{
456460 Name: "X-Auth-Request-Preferred-Username",
457− PreserveRequestValue: false,
461+ PreserveRequestValue: ptr.To(false),
458462 Values: []HeaderValue{
459463 {
460464 ClaimSource: &ClaimSource{
@@ -466,7 +470,7 @@ var _ = Describe("Legacy Options", func() {
466470
467471 xAuthRequestAccessToken := Header{
468472 Name: "X-Auth-Request-Access-Token",
469− PreserveRequestValue: false,
473+ PreserveRequestValue: ptr.To(false),
470474 Values: []HeaderValue{
471475 {
472476 ClaimSource: &ClaimSource{
@@ -478,7 +482,7 @@ var _ = Describe("Legacy Options", func() {
478482
479483 authorizationHeader := Header{
480484 Name: "Authorization",
481− PreserveRequestValue: false,
485+ PreserveRequestValue: ptr.To(false),
482486 Values: []HeaderValue{
483487 {
484488 ClaimSource: &ClaimSource{
@@ -943,37 +947,51 @@ var _ = Describe("Legacy Options", func() {
943947 {Name: "approval_prompt", Default: []string{"force"}},
944948 }
945949
946− defaultProvider := Provider{
947− ID: "google=" + clientID,
948− ClientID: clientID,
949− Type: "google",
950− LoginURLParameters: defaultURLParams,
950+ defaultOIDCOptions := OIDCOptions{
951+ SkipDiscovery: ptr.To(false),
952+ InsecureSkipNonce: ptr.To(false),
953+ InsecureAllowUnverifiedEmail: ptr.To(false),
954+ InsecureSkipIssuerVerification: ptr.To(false),
955+ }
956+
957+ defaultGoogleOptions := GoogleOptions{
958+ UseOrganizationID: ptr.To(false),
959+ UseApplicationDefaultCredentials: ptr.To(false),
951960 }
961+
952962 defaultLegacyProvider := LegacyProvider{
953963 ClientID: clientID,
954964 ProviderType: "google",
955965 }
956966
957− defaultProviderWithPrompt := Provider{
958− ID: "google=" + clientID,
959− ClientID: clientID,
960− Type: "google",
961− LoginURLParameters: []LoginURLParameter{
962− {Name: "prompt", Default: []string{"switch_user"}},
963− },
967+ defaultProvider := Provider{
968+ ID: "google=" + clientID,
969+ ClientID: clientID,
970+ Type: "google",
971+ OIDCConfig: defaultOIDCOptions,
972+ GoogleConfig: defaultGoogleOptions,
973+ LoginURLParameters: defaultURLParams,
974+ UseSystemTrustStore: ptr.To(false),
975+ SkipClaimsFromProfileURL: ptr.To(false),
964976 }
977+
965978 defaultLegacyProviderWithPrompt := LegacyProvider{
966979 ClientID: clientID,
967980 ProviderType: "google",
968981 Prompt: "switch_user",
969982 }
970983
971− displayNameProvider := Provider{
972− ID: "displayName",
973− Name: "displayName",
974− ClientID: clientID,
975− Type: "google",
976− LoginURLParameters: defaultURLParams,
984+ defaultProviderWithPrompt := Provider{
985+ ID: "google=" + clientID,
986+ ClientID: clientID,
987+ Type: "google",
988+ OIDCConfig: defaultOIDCOptions,
989+ GoogleConfig: defaultGoogleOptions,
990+ LoginURLParameters: []LoginURLParameter{
991+ {Name: "prompt", Default: []string{"switch_user"}},
992+ },
993+ UseSystemTrustStore: ptr.To(false),
994+ SkipClaimsFromProfileURL: ptr.To(false),
977995 }
978996
979997 displayNameLegacyProvider := LegacyProvider{
@@ -982,16 +1000,33 @@ var _ = Describe("Legacy Options", func() {
9821000 ProviderType: "google",
9831001 }
9841002
1003+ displayNameProvider := Provider{
1004+ ID: "displayName",
1005+ Name: "displayName",
1006+ ClientID: clientID,
1007+ Type: "google",
1008+ OIDCConfig: defaultOIDCOptions,
1009+ GoogleConfig: defaultGoogleOptions,
1010+ LoginURLParameters: defaultURLParams,
1011+ UseSystemTrustStore: ptr.To(false),
1012+ SkipClaimsFromProfileURL: ptr.To(false),
1013+ }
1014+
9851015 internalConfigProvider := Provider{
986− ID: "google=" + clientID,
987− ClientID: clientID,
988− Type: "google",
1016+ ID: "google=" + clientID,
1017+ ClientID: clientID,
1018+ Type: "google",
1019+ OIDCConfig: defaultOIDCOptions,
9891020 GoogleConfig: GoogleOptions{
990− AdminEmail: "email@email.com",
991− ServiceAccountJSON: "test.json",
992− Groups: []string{"1", "2"},
1021+ AdminEmail: "email@email.com",
1022+ ServiceAccountJSON: "test.json",
1023+ Groups: []string{"1", "2"},
1024+ UseOrganizationID: ptr.To(false),
1025+ UseApplicationDefaultCredentials: ptr.To(false),
9931026 },
994− LoginURLParameters: defaultURLParams,
1027+ LoginURLParameters: defaultURLParams,
1028+ UseSystemTrustStore: ptr.To(false),
1029+ SkipClaimsFromProfileURL: ptr.To(false),
9951030 }
9961031
9971032 internalConfigLegacyProvider := LegacyProvider{
pkg/apis/options/load.go+82−42
@@ -9,10 +9,10 @@ import (
99 "strings"
1010
1111 "github.com/a8m/envsubst"
12− "github.com/ghodss/yaml"
1312 "github.com/go-viper/mapstructure/v2"
1413 "github.com/spf13/pflag"
1514 "github.com/spf13/viper"
15+ "go.yaml.in/yaml/v3"
1616)
1717
1818// Load reads in the config file at the path given, then merges in environment
@@ -55,6 +55,87 @@ func Load(configFileName string, flagSet *pflag.FlagSet, into interface{}) error
5555 return nil
5656}
5757
58+// LoadYAML will load a YAML based configuration file into the options interface provided.
59+func LoadYAML(configFileName string, opts interface{}) error {
60+ buffer, err := loadAndSubstituteEnvs(configFileName)
61+ if err != nil {
62+ return err
63+ }
64+
65+ // Generic interface for loading arbitrary yaml structure
66+ var intermediate map[string]interface{}
67+
68+ if err := yaml.Unmarshal(buffer, &intermediate); err != nil {
69+ return fmt.Errorf("error unmarshalling config: %w", err)
70+ }
71+
72+ // Using mapstructure to decode arbitrary yaml structure into options and
73+ // merge with existing values instead of overwriting everything. This is especially
74+ // important as we have a lot of default values for boolean which are supposed to be
75+ // true by default. Normally by just parsing through yaml all booleans that aren't
76+ // referenced in the config file would be parsed as false and we cannot identify after
77+ // the fact if they have been explicitly set to false or have not been referenced.
78+ return Decode(intermediate, opts)
79+}
80+
81+// Decode processes an input map and decodes it into a given struct while preserving default values.
82+// It ensures proper conversion of duration values from strings, floats, and int64 into time.Duration.
83+//
84+// Parameters:
85+// - input: A map[string]interface{} representing the input data.
86+// - result: A pointer to a struct where the decoded values will be stored.
87+//
88+// Returns:
89+// - An error if decoding fails or if there are unmapped keys.
90+func Decode(input interface{}, result interface{}) error {
91+ decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
92+ DecodeHook: mapstructure.ComposeDecodeHookFunc(
93+ toDurationHookFunc(),
94+ stringToBytesHookFunc(),
95+ ),
96+ Metadata: nil, // Don't track any metadata
97+ Result: result, // Decode the result into the prefilled options
98+ TagName: "yaml", // Parse all fields that use the yaml tag
99+ ZeroFields: false, // Don't clean the default values from the result map (options)
100+ ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields
101+ IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml
102+ })
103+ if err != nil {
104+ return fmt.Errorf("error creating decoder for config: %w", err)
105+ }
106+
107+ if err := decoder.Decode(input); err != nil {
108+ return fmt.Errorf("error decoding config: %w", err)
109+ }
110+
111+ return nil
112+}
113+
114+// loadAndSubstituteEnvs reads the yaml config into a generic byte buffer and
115+// substitute env references
116+func loadAndSubstituteEnvs(configFileName string) ([]byte, error) {
117+ if configFileName == "" {
118+ return nil, errors.New("no configuration file provided")
119+ }
120+
121+ unparsedBuffer, err := os.ReadFile(configFileName)
122+ if err != nil {
123+ return nil, fmt.Errorf("unable to load config file: %w", err)
124+ }
125+
126+ modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
127+ if err != nil {
128+ return nil, fmt.Errorf("error normalizing substitution string : %w", err)
129+ }
130+
131+ buffer, err := envsubst.Bytes(modifiedBuffer)
132+ if err != nil {
133+ return nil, fmt.Errorf("error in substituting env variables : %w", err)
134+ }
135+
136+ return buffer, nil
137+}
138+
58139// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
59140// to the fields in the options interface provided.
60141// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
@@ -140,47 +221,6 @@ func isUnexported(name string) bool {
140221 return first == strings.ToLower(first)
141222}
142223
143−// LoadYAML will load a YAML based configuration file into the options interface provided.
144−func LoadYAML(configFileName string, into interface{}) error {
145− buffer, err := loadAndParseYaml(configFileName)
146− if err != nil {
147− return err
148− }
149−
150− // UnmarshalStrict will return an error if the config includes options that are
151− // not mapped to fields of the into struct
152− if err := yaml.UnmarshalStrict(buffer, into, yaml.DisallowUnknownFields); err != nil {
153− return fmt.Errorf("error unmarshalling config: %w", err)
154− }
155−
156− return nil
157−}
158−
159−// loadAndParseYaml reads the config from the filesystem and
160−// execute the environment variable substitution
161−func loadAndParseYaml(configFileName string) ([]byte, error) {
162− if configFileName == "" {
163− return nil, errors.New("no configuration file provided")
164− }
165−
166− unparsedBuffer, err := os.ReadFile(configFileName)
167− if err != nil {
168− return nil, fmt.Errorf("unable to load config file: %w", err)
169− }
170−
171− modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
172− if err != nil {
173− return nil, fmt.Errorf("error normalizing substitution string : %w", err)
174− }
175−
176− buffer, err := envsubst.Bytes(modifiedBuffer)
177− if err != nil {
178− return nil, fmt.Errorf("error in substituting env variables : %w", err)
179− }
180−
181− return buffer, nil
182−}
183−
184224// normalizeSubstitution normalizes dollar signs ($) with numerals like
185225// $1 or $2 properly by correctly escaping them
186226func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {
pkg/apis/options/load_test.go+11−9
@@ -155,7 +155,7 @@ var _ = Describe("Load", func() {
155155 }
156156 err := Load(configFileName, flagSet, input)
157157 if o.expectedErr != nil {
158− Expect(err).To(MatchError(o.expectedErr.Error()))
158+ Expect(err).To(MatchError(ContainSubstring(o.expectedErr.Error())))
159159 } else {
160160 Expect(err).ToNot(HaveOccurred())
161161 }
@@ -416,7 +416,7 @@ sub:
416416
417417 err := LoadYAML(configFileName, input)
418418 if in.expectedErr != nil {
419− Expect(err).To(MatchError(in.expectedErr.Error()))
419+ Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
420420 } else {
421421 Expect(err).ToNot(HaveOccurred())
422422 }
@@ -445,7 +445,7 @@ sub:
445445 configFile: []byte("\tfoo: bar"),
446446 input: &TestOptions{},
447447 expectedOutput: &TestOptions{},
448− expectedErr: errors.New("error unmarshalling config: error converting YAML to JSON: yaml: found character that cannot start any token"),
448+ expectedErr: errors.New("error unmarshalling config: yaml: found character that cannot start any token"),
449449 }),
450450 Entry("with extra fields in the YAML", loadYAMLTableInput{
451451 configFile: append(testOptionsConfigBytesFull, []byte("foo: bar\n")...),
@@ -459,19 +459,19 @@ sub:
459459 StringSliceOption: []string{"a", "b", "c"},
460460 },
461461 },
462− expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
462+ expectedErr: errors.New("has invalid keys: foo"),
463463 }),
464464 Entry("with an incorrect type for a string field", loadYAMLTableInput{
465465 configFile: []byte(`stringOption: ["a", "b"]`),
466466 input: &TestOptions{},
467467 expectedOutput: &TestOptions{},
468− expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field TestOptions.StringOption of type string"),
468+ expectedErr: errors.New("'stringOption' expected type 'string', got unconvertible type"),
469469 }),
470470 Entry("with an incorrect type for an array field", loadYAMLTableInput{
471471 configFile: []byte(`stringSliceOption: "a"`),
472472 input: &TestOptions{},
473473 expectedOutput: &TestOptions{},
474− 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"),
474+ 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"),
475475 }),
476476 Entry("with a config file containing environment variable references", loadYAMLTableInput{
477477 configFile: []byte("stringOption: ${TESTUSER}"),
@@ -526,11 +526,13 @@ upstreamConfig:
526526injectRequestHeaders:
527527- name: X-Forwarded-User
528528 values:
529− - claim: user
529+ - claimSource:
530+ claim: user
530531injectResponseHeaders:
531532- name: X-Secret
532533 values:
533− - value: c2VjcmV0
534+ - secretSource:
535+ value: secret
534536`)
535537
536538 By("Creating a config file")
@@ -548,7 +550,7 @@ injectResponseHeaders:
548550 into := &AlphaOptions{}
549551 Expect(LoadYAML(configFileName, into)).To(Succeed())
550552
551− flushInterval := Duration(500 * time.Millisecond)
553+ flushInterval := 500 * time.Millisecond
552554
553555 Expect(into).To(Equal(&AlphaOptions{
554556 UpstreamConfig: UpstreamConfig{
pkg/apis/options/login_url_parameters.go+5−5
@@ -71,19 +71,19 @@ package options
7171// character.
7272type LoginURLParameter struct {
7373 // Name specifies the name of the query parameter.
74− Name string `json:"name"`
74+ Name string `yaml:"name"`
7575
7676 // Default specifies a default value or values that will be
7777 // passed to the IdP if not overridden.
7878 //+optional
79− Default []string `json:"default,omitempty"`
79+ Default []string `yaml:"default,omitempty"`
8080
8181 // Allow specifies rules about how the default (if any) may be
8282 // overridden via the query string to `/oauth2/start`. Only
8383 // values that match one or more of the allow rules will be
8484 // forwarded to the IdP.
8585 //+optional
86− Allow []URLParameterRule `json:"allow,omitempty"`
86+ Allow []URLParameterRule `yaml:"allow,omitempty"`
8787}
8888
8989// URLParameterRule represents a rule by which query parameters
@@ -92,11 +92,11 @@ type LoginURLParameter struct {
9292// login URL. Either Value or Pattern should be supplied, not both.
9393type URLParameterRule struct {
9494 // A Value rule matches just this specific value
95− Value *string `json:"value,omitempty"`
95+ Value *string `yaml:"value,omitempty"`
9696
9797 // A Pattern rule gives a regular expression that must be matched by
9898 // some substring of the value. The expression is _not_ automatically
9999 // anchored to the start and end of the value, if you _want_ to restrict
100100 // the whole parameter value you must anchor it yourself with `^` and `$`.
101− Pattern *string `json:"pattern,omitempty"`
101+ Pattern *string `yaml:"pattern,omitempty"`
102102}
pkg/apis/options/options.go+20
@@ -168,3 +168,23 @@ func NewFlagSet() *pflag.FlagSet {
168168
169169 return flagSet
170170}
171+
172+// EnsureDefaults configures the defaults for all options
173+// to ensure no unexpected empty strings for enum types or nils for booleans
174+func (o *Options) EnsureDefaults() {
175+ o.Providers.EnsureDefaults()
176+ o.UpstreamServers.EnsureDefaults()
177+
178+ for i := range o.InjectRequestHeaders {
179+ o.InjectRequestHeaders[i].EnsureDefaults()
180+ }
181+ for i := range o.InjectResponseHeaders {
182+ o.InjectResponseHeaders[i].EnsureDefaults()
183+ }
184+
185+ // TBD: Uncomment as we add EnsureDefaults methods
186+ // o.Cookie.EnsureDefaults()
187+ // o.Session.EnsureDefaults()
188+ // o.Templates.EnsureDefaults()
189+ // o.Logging.EnsureDefaults()
190+}
pkg/apis/options/providers.go+187−73
@@ -1,11 +1,53 @@
11package options
22
3+import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
4+
35const (
46 // OIDCEmailClaim is the generic email claim used by the OIDC provider.
5− OIDCEmailClaim = "email"
7+ OIDCEmailClaim string = "email"
68
79 // OIDCGroupsClaim is the generic groups claim used by the OIDC provider.
8− OIDCGroupsClaim = "groups"
10+ OIDCGroupsClaim string = "groups"
11+
12+ // DefaultSkipDiscovery is the default value
13+ // for OIDCOptions.SkipDiscovery
14+ DefaultSkipDiscovery bool = false
15+
16+ // DefaultInsecureSkipNonce is the default value
17+ // for OIDCOptions.InsecureSkipNonce
18+ DefaultInsecureSkipNonce bool = true
19+
20+ // DefaultInsecureAllowUnverifiedEmail is the default value
21+ // for OIDCOptions.InsecureAllowUnverifiedEmail
22+ DefaultInsecureAllowUnverifiedEmail bool = false
23+
24+ // DefaultInsecureSkipIssuerVerification is the default value
25+ // for OIDCOptions.InsecureSkipIssuerVerification
26+ DefaultInsecureSkipIssuerVerification bool = false
27+
28+ // DefaultSkipClaimsFromProfileURL is the default value
29+ // for Provider.SkipClaimsFromProfileURL
30+ DefaultSkipClaimsFromProfileURL bool = false
31+
32+ // DefaultADFSSkipScope is the default value
33+ // for ADFSOptions.SkipScope
34+ DefaultADFSSkipScope bool = false
35+
36+ // DefaultMicrosoftEntraIDUseFederatedToken is the default value
37+ // for MicrosoftEntraIDOptions.FederatedTokenAuth
38+ DefaultMicrosoftEntraIDUseFederatedToken bool = false
39+
40+ // DefaultGoogleUseOrganizationID is the default value
41+ // for GoogleOptions.UseOrganizationID
42+ DefaultGoogleUseOrganizationID bool = false
43+
44+ // DefaultGoogleUseApplicationDefaultCredentials is the default values
45+ // for GoogleOptions.UseApplicationDefaultCredentials
46+ DefaultUseApplicationDefaultCredentials bool = false
47+
48+ // DefaultUseSystemTrustStore is the default value
49+ // for Provider.UseSystemTrustStore
50+ DefaultUseSystemTrustStore bool = false
951)
1052
1153// OIDCAudienceClaims is the generic audience claim list used by the OIDC provider.
@@ -22,78 +64,78 @@ type Providers []Provider
2264type Provider struct {
2365 // ClientID is the OAuth Client ID that is defined in the provider
2466 // This value is required for all providers.
25− ClientID string `json:"clientID,omitempty"`
67+ ClientID string `yaml:"clientID,omitempty"`
2668 // ClientSecret is the OAuth Client Secret that is defined in the provider
2769 // This value is required for all providers.
28− ClientSecret string `json:"clientSecret,omitempty"`
70+ ClientSecret string `yaml:"clientSecret,omitempty"`
2971 // ClientSecretFile is the name of the file
3072 // containing the OAuth Client Secret, it will be used if ClientSecret is not set.
31− ClientSecretFile string `json:"clientSecretFile,omitempty"`
73+ ClientSecretFile string `yaml:"clientSecretFile,omitempty"`
3274
3375 // KeycloakConfig holds all configurations for Keycloak provider.
34− KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"`
76+ KeycloakConfig KeycloakOptions `yaml:"keycloakConfig,omitempty"`
3577 // AzureConfig holds all configurations for Azure provider.
36− AzureConfig AzureOptions `json:"azureConfig,omitempty"`
78+ AzureConfig AzureOptions `yaml:"azureConfig,omitempty"`
3779 // MicrosoftEntraIDConfig holds all configurations for Entra ID provider.
38− MicrosoftEntraIDConfig MicrosoftEntraIDOptions `json:"microsoftEntraIDConfig,omitempty"`
80+ MicrosoftEntraIDConfig MicrosoftEntraIDOptions `yaml:"microsoftEntraIDConfig,omitempty"`
3981 // ADFSConfig holds all configurations for ADFS provider.
40− ADFSConfig ADFSOptions `json:"ADFSConfig,omitempty"`
82+ ADFSConfig ADFSOptions `yaml:"ADFSConfig,omitempty"`
4183 // BitbucketConfig holds all configurations for Bitbucket provider.
42− BitbucketConfig BitbucketOptions `json:"bitbucketConfig,omitempty"`
84+ BitbucketConfig BitbucketOptions `yaml:"bitbucketConfig,omitempty"`
4385 // GitHubConfig holds all configurations for GitHubC provider.
44− GitHubConfig GitHubOptions `json:"githubConfig,omitempty"`
86+ GitHubConfig GitHubOptions `yaml:"githubConfig,omitempty"`
4587 // GitLabConfig holds all configurations for GitLab provider.
46− GitLabConfig GitLabOptions `json:"gitlabConfig,omitempty"`
88+ GitLabConfig GitLabOptions `yaml:"gitlabConfig,omitempty"`
4789 // GoogleConfig holds all configurations for Google provider.
48− GoogleConfig GoogleOptions `json:"googleConfig,omitempty"`
90+ GoogleConfig GoogleOptions `yaml:"googleConfig,omitempty"`
4991 // OIDCConfig holds all configurations for OIDC provider
5092 // or providers utilize OIDC configurations.
51− OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"`
93+ OIDCConfig OIDCOptions `yaml:"oidcConfig,omitempty"`
5294 // LoginGovConfig holds all configurations for LoginGov provider.
53− LoginGovConfig LoginGovOptions `json:"loginGovConfig,omitempty"`
95+ LoginGovConfig LoginGovOptions `yaml:"loginGovConfig,omitempty"`
5496
5597 // ID should be a unique identifier for the provider.
5698 // This value is required for all providers.
57− ID string `json:"id,omitempty"`
99+ ID string `yaml:"id,omitempty"`
58100 // Type is the OAuth provider
59101 // must be set from the supported providers group,
60102 // otherwise 'Google' is set as default
61− Type ProviderType `json:"provider,omitempty"`
103+ Type ProviderType `yaml:"provider,omitempty"`
62104 // Name is the providers display name
63105 // if set, it will be shown to the users in the login page.
64− Name string `json:"name,omitempty"`
106+ Name string `yaml:"name,omitempty"`
65107 // CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
66108 // If not specified, the default Go trust sources are used instead
67− CAFiles []string `json:"caFiles,omitempty"`
109+ CAFiles []string `yaml:"caFiles,omitempty"`
68110 // UseSystemTrustStore determines if your custom CA files and the system trust store are used
69111 // If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.
70− UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty"`
112+ UseSystemTrustStore *bool `yaml:"useSystemTrustStore,omitempty"`
71113 // LoginURL is the authentication endpoint
72− LoginURL string `json:"loginURL,omitempty"`
114+ LoginURL string `yaml:"loginURL,omitempty"`
73115 // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL
74− LoginURLParameters []LoginURLParameter `json:"loginURLParameters,omitempty"`
116+ LoginURLParameters []LoginURLParameter `yaml:"loginURLParameters,omitempty"`
75117 // AuthRequestResponseMode defines the response mode to request during authorization request
76− AuthRequestResponseMode string `json:"authRequestResponseMode,omitempty"`
118+ AuthRequestResponseMode string `yaml:"authRequestResponseMode,omitempty"`
77119 // RedeemURL is the token redemption endpoint
78− RedeemURL string `json:"redeemURL,omitempty"`
120+ RedeemURL string `yaml:"redeemURL,omitempty"`
79121 // ProfileURL is the profile access endpoint
80− ProfileURL string `json:"profileURL,omitempty"`
122+ ProfileURL string `yaml:"profileURL,omitempty"`
81123 // SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
82124 // default set to 'false'
83− SkipClaimsFromProfileURL bool `json:"skipClaimsFromProfileURL,omitempty"`
125+ SkipClaimsFromProfileURL *bool `yaml:"skipClaimsFromProfileURL,omitempty"`
84126 // ProtectedResource is the resource that is protected (Azure AD and ADFS only)
85− ProtectedResource string `json:"resource,omitempty"`
127+ ProtectedResource string `yaml:"resource,omitempty"`
86128 // ValidateURL is the access token validation endpoint
87− ValidateURL string `json:"validateURL,omitempty"`
129+ ValidateURL string `yaml:"validateURL,omitempty"`
88130 // Scope is the OAuth scope specification
89− Scope string `json:"scope,omitempty"`
131+ Scope string `yaml:"scope,omitempty"`
90132 // AllowedGroups is a list of restrict logins to members of this group
91− AllowedGroups []string `json:"allowedGroups,omitempty"`
133+ AllowedGroups []string `yaml:"allowedGroups,omitempty"`
92134 // The code challenge method
93− CodeChallengeMethod string `json:"code_challenge_method,omitempty"`
135+ CodeChallengeMethod string `yaml:"code_challenge_method,omitempty"`
94136
95137 // URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session
96− BackendLogoutURL string `json:"backendLogoutURL"`
138+ BackendLogoutURL string `yaml:"backendLogoutURL"`
97139}
98140
99141// ProviderType is used to enumerate the different provider type options
@@ -157,19 +199,19 @@ const (
157199
158200type KeycloakOptions struct {
159201 // Group enables to restrict login to members of indicated group
160− Groups []string `json:"groups,omitempty"`
202+ Groups []string `yaml:"groups,omitempty"`
161203
162204 // Role enables to restrict login to users with role (only available when using the keycloak-oidc provider)
163− Roles []string `json:"roles,omitempty"`
205+ Roles []string `yaml:"roles,omitempty"`
164206}
165207
166208type AzureOptions struct {
167209 // Tenant directs to a tenant-specific or common (tenant-independent) endpoint
168210 // Default value is 'common'
169− Tenant string `json:"tenant,omitempty"`
211+ Tenant string `yaml:"tenant,omitempty"`
170212 // GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
171213 // Default value is 'id'
172− GraphGroupField string `json:"graphGroupField,omitempty"`
214+ GraphGroupField string `yaml:"graphGroupField,omitempty"`
173215}
174216
175217type MicrosoftEntraIDOptions struct {
@@ -177,116 +219,117 @@ type MicrosoftEntraIDOptions struct {
177219 // issued by different issuers and OIDC issuer verification needs to be disabled.
178220 // When not specified, all tenants are allowed. Redundant for single-tenant apps
179221 // (regular ID token validation matches the issuer).
180− AllowedTenants []string `json:"allowedTenants,omitempty"`
222+ AllowedTenants []string `yaml:"allowedTenants,omitempty"`
181223
182224 // FederatedTokenAuth enable oAuth2 client authentication with federated token projected
183225 // by Entra Workload Identity plugin, instead of client secret.
184− FederatedTokenAuth bool `json:"federatedTokenAuth,omitempty"`
226+ FederatedTokenAuth *bool `yaml:"federatedTokenAuth,omitempty"`
185227}
186228
187229type ADFSOptions struct {
188230 // Skip adding the scope parameter in login request
189231 // Default value is 'false'
190− SkipScope bool `json:"skipScope,omitempty"`
232+ SkipScope *bool `yaml:"skipScope,omitempty"`
191233}
192234
193235type BitbucketOptions struct {
194236 // Team sets restrict logins to members of this team
195− Team string `json:"team,omitempty"`
237+ Team string `yaml:"team,omitempty"`
196238 // Repository sets restrict logins to user with access to this repository
197− Repository string `json:"repository,omitempty"`
239+ Repository string `yaml:"repository,omitempty"`
198240}
199241
200242type GitHubOptions struct {
201243 // Org sets restrict logins to members of this organisation
202− Org string `json:"org,omitempty"`
244+ Org string `yaml:"org,omitempty"`
203245 // Team sets restrict logins to members of this team
204− Team string `json:"team,omitempty"`
246+ Team string `yaml:"team,omitempty"`
205247 // Repo sets restrict logins to collaborators of this repository
206− Repo string `json:"repo,omitempty"`
248+ Repo string `yaml:"repo,omitempty"`
207249 // Token is the token to use when verifying repository collaborators
208250 // it must have push access to the repository
209− Token string `json:"token,omitempty"`
251+ Token string `yaml:"token,omitempty"`
210252 // Users allows users with these usernames to login
211253 // even if they do not belong to the specified org and team or collaborators
212− Users []string `json:"users,omitempty"`
254+ Users []string `yaml:"users,omitempty"`
213255}
214256
215257type GitLabOptions struct {
216258 // Group sets restrict logins to members of this group
217− Group []string `json:"group,omitempty"`
259+ Group []string `yaml:"group,omitempty"`
218260 // Projects restricts logins to members of these projects
219− Projects []string `json:"projects,omitempty"`
261+ Projects []string `yaml:"projects,omitempty"`
220262}
221263
222264type GoogleOptions struct {
223265 // Groups sets restrict logins to members of this Google group
224− Groups []string `json:"group,omitempty"`
266+ Groups []string `yaml:"group,omitempty"`
225267 // AdminEmail is the Google admin to impersonate for api calls
226− AdminEmail string `json:"adminEmail,omitempty"`
268+ AdminEmail string `yaml:"adminEmail,omitempty"`
227269 // ServiceAccountJSON is the path to the service account json credentials
228− ServiceAccountJSON string `json:"serviceAccountJson,omitempty"`
270+ ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"`
229271 // UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON
230− UseApplicationDefaultCredentials bool `json:"useApplicationDefaultCredentials,omitempty"`
272+ UseApplicationDefaultCredentials *bool `yaml:"useApplicationDefaultCredentials,omitempty"`
231273 // TargetPrincipal is the Google Service Account used for Application Default Credentials
232− TargetPrincipal string `json:"targetPrincipal,omitempty"`
274+ TargetPrincipal string `yaml:"targetPrincipal,omitempty"`
233275 // UseOrganizationId indicates whether to use the organization ID as the UserName claim
234− UseOrganizationID bool `json:"useOrganizationID,omitempty"`
276+ UseOrganizationID *bool `yaml:"useOrganizationID,omitempty"`
235277 // admin scope needed for fetching user organization information from admin api, can be one of cloud, user or defaults to readonly
236− AdminAPIUserScope string `json:"adminAPIUserScope,omitempty"`
278+ AdminAPIUserScope string `yaml:"adminAPIUserScope,omitempty"`
237279}
238280
239281type OIDCOptions struct {
240282 // IssuerURL is the OpenID Connect issuer URL
241283 // eg: https://accounts.google.com
242− IssuerURL string `json:"issuerURL,omitempty"`
284+ IssuerURL string `yaml:"issuerURL,omitempty"`
243285 // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
244286 // default set to 'false'
245− InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
287+ InsecureAllowUnverifiedEmail *bool `yaml:"insecureAllowUnverifiedEmail,omitempty"`
246288 // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
247289 // default set to 'false'
248− InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"`
290+ InsecureSkipIssuerVerification *bool `yaml:"insecureSkipIssuerVerification,omitempty"`
249291 // InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
250292 // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
251293 // after the initial OAuth redeem & subsequent token refreshes.
252294 // default set to 'true'
253295 // Warning: In a future release, this will change to 'false' by default for enhanced security.
254− InsecureSkipNonce bool `json:"insecureSkipNonce,omitempty"`
296+ InsecureSkipNonce *bool `yaml:"insecureSkipNonce,omitempty"`
255297 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
256298 // default set to 'false'
257− SkipDiscovery bool `json:"skipDiscovery,omitempty"`
299+ SkipDiscovery *bool `yaml:"skipDiscovery,omitempty"`
258300 // JwksURL is the OpenID Connect JWKS URL
259301 // eg: https://www.googleapis.com/oauth2/v3/certs
260− JwksURL string `json:"jwksURL,omitempty"`
302+ JwksURL string `yaml:"jwksURL,omitempty"`
261303 // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
262304 // for verifying JWT tokens
263− PublicKeyFiles []string `json:"publicKeyFiles,omitempty"`
305+ PublicKeyFiles []string `yaml:"publicKeyFiles,omitempty"`
264306 // EmailClaim indicates which claim contains the user email,
265307 // default set to 'email'
266− EmailClaim string `json:"emailClaim,omitempty"`
308+ EmailClaim string `yaml:"emailClaim,omitempty"`
267309 // GroupsClaim indicates which claim contains the user groups
268310 // default set to 'groups'
269− GroupsClaim string `json:"groupsClaim,omitempty"`
311+ GroupsClaim string `yaml:"groupsClaim,omitempty"`
270312 // UserIDClaim indicates which claim contains the user ID
271313 // default set to 'email'
272− UserIDClaim string `json:"userIDClaim,omitempty"`
314+ UserIDClaim string `yaml:"userIDClaim,omitempty"`
273315 // AudienceClaim allows to define any claim that is verified against the client id
274316 // By default `aud` claim is used for verification.
275− AudienceClaims []string `json:"audienceClaims,omitempty"`
317+ AudienceClaims []string `yaml:"audienceClaims,omitempty"`
276318 // ExtraAudiences is a list of additional audiences that are allowed
277319 // to pass verification in addition to the client id.
278− ExtraAudiences []string `json:"extraAudiences,omitempty"`
320+ ExtraAudiences []string `yaml:"extraAudiences,omitempty"`
279321}
280322
281323type LoginGovOptions struct {
282324 // JWTKey is a private key in PEM format used to sign JWT,
283− JWTKey string `json:"jwtKey,omitempty"`
325+ JWTKey string `yaml:"jwtKey,omitempty"`
284326 // JWTKeyFile is a path to the private key file in PEM format used to sign the JWT
285− JWTKeyFile string `json:"jwtKeyFile,omitempty"`
327+ JWTKeyFile string `yaml:"jwtKeyFile,omitempty"`
286328 // PubJWKURL is the JWK pubkey access endpoint
287− PubJWKURL string `json:"pubjwkURL,omitempty"`
329+ PubJWKURL string `yaml:"pubjwkURL,omitempty"`
288330}
289331
332+// Legacy default providers configuration
290333func providerDefaults() Providers {
291334 providers := Providers{
292335 {
@@ -295,9 +338,9 @@ func providerDefaults() Providers {
295338 Tenant: "common",
296339 },
297340 OIDCConfig: OIDCOptions{
298− InsecureAllowUnverifiedEmail: false,
299− InsecureSkipNonce: true,
300− SkipDiscovery: false,
341+ InsecureAllowUnverifiedEmail: ptr.To(DefaultInsecureAllowUnverifiedEmail),
342+ InsecureSkipNonce: ptr.To(DefaultInsecureSkipNonce),
343+ SkipDiscovery: ptr.To(DefaultSkipDiscovery),
301344 UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
302345 EmailClaim: OIDCEmailClaim,
303346 GroupsClaim: OIDCGroupsClaim,
@@ -308,3 +351,74 @@ func providerDefaults() Providers {
308351 }
309352 return providers
310353}
354+
355+// EnsureDefaults sets any default values for Providers fields.
356+func (p Providers) EnsureDefaults() {
357+ for i := range p {
358+ p[i].EnsureDefaults()
359+ }
360+}
361+
362+// EnsureDefaults sets any default values for Provider fields.
363+func (p *Provider) EnsureDefaults() {
364+ if p.SkipClaimsFromProfileURL == nil {
365+ p.SkipClaimsFromProfileURL = ptr.To(DefaultSkipClaimsFromProfileURL)
366+ }
367+ if p.UseSystemTrustStore == nil {
368+ p.UseSystemTrustStore = ptr.To(DefaultUseSystemTrustStore)
369+ }
370+
371+ p.OIDCConfig.EnsureDefaults()
372+ p.MicrosoftEntraIDConfig.EnsureDefaults()
373+ p.ADFSConfig.EnsureDefaults()
374+ p.GoogleConfig.EnsureDefaults()
375+}
376+
377+// EnsureDefaults sets any default values for OIDCOptions fields.
378+func (o *OIDCOptions) EnsureDefaults() {
379+ // Ensure OIDC defaults
380+ if o.InsecureAllowUnverifiedEmail == nil {
381+ o.InsecureAllowUnverifiedEmail = ptr.To(DefaultInsecureAllowUnverifiedEmail)
382+ }
383+ if o.InsecureSkipNonce == nil {
384+ o.InsecureSkipNonce = ptr.To(DefaultInsecureSkipNonce)
385+ }
386+ if o.SkipDiscovery == nil {
387+ o.SkipDiscovery = ptr.To(DefaultSkipDiscovery)
388+ }
389+ if o.UserIDClaim == "" {
390+ o.UserIDClaim = OIDCEmailClaim
391+ }
392+ if o.EmailClaim == "" {
393+ o.EmailClaim = OIDCEmailClaim
394+ }
395+ if o.GroupsClaim == "" {
396+ o.GroupsClaim = OIDCGroupsClaim
397+ }
398+ if len(o.AudienceClaims) == 0 {
399+ o.AudienceClaims = OIDCAudienceClaims
400+ }
401+}
402+
403+// EnsureDefaults sets any default values for MicrosoftEntraIDOptions fields.
404+func (me *MicrosoftEntraIDOptions) EnsureDefaults() {
405+ if me.FederatedTokenAuth == nil {
406+ me.FederatedTokenAuth = ptr.To(DefaultMicrosoftEntraIDUseFederatedToken)
407+ }
408+}
409+
410+// EnsureDefaults sets any default values for ADFSOptions fields.
411+func (a *ADFSOptions) EnsureDefaults() {
412+ if a.SkipScope == nil {
413+ a.SkipScope = ptr.To(DefaultADFSSkipScope)
414+ }
415+}
416+
417+// EnsureDefaults sets any default values for GoogleOptions fields.
418+func (g *GoogleOptions) EnsureDefaults() {
419+ g.UseOrganizationID = ptr.To(DefaultGoogleUseOrganizationID)
420+
421+ if g.UseApplicationDefaultCredentials == nil {
422+ g.UseApplicationDefaultCredentials = ptr.To(DefaultUseApplicationDefaultCredentials)
423+ }
424+}
pkg/apis/options/secret_source.gonew file+19
@@ -0,0 +1,19 @@
1+package options
2+
3+// SecretSource references an individual secret value.
4+// Only one source within the struct should be defined at any time.
5+type SecretSource struct {
6+ // Value expects a base64 encoded string value.
7+ Value []byte `yaml:"value,omitempty"`
8+
9+ // FromEnv expects the name of an environment variable.
10+ FromEnv string `yaml:"fromEnv,omitempty"`
11+
12+ // FromFile expects a path to a file containing the secret value.
13+ FromFile string `yaml:"fromFile,omitempty"`
14+}
15+
16+// EnsureDefaults sets any default values for SecretSource fields.
17+func (ss *SecretSource) EnsureDefaults() {
18+ // No defaults to set currently
19+}
pkg/apis/options/server.go+7−7
@@ -4,15 +4,15 @@ package options
44type Server struct {
55 // BindAddress is the address on which to serve traffic.
66 // Leave blank or set to "-" to disable.
7− BindAddress string
7+ BindAddress string `yaml:"bindAddress,omitempty"`
88
99 // SecureBindAddress is the address on which to serve secure traffic.
1010 // Leave blank or set to "-" to disable.
11− SecureBindAddress string
11+ SecureBindAddress string `yaml:"secureBindAddress,omitempty"`
1212
1313 // TLS contains the information for loading the certificate and key for the
1414 // secure traffic and further configuration for the TLS server.
15− TLS *TLS
15+ TLS *TLS `yaml:"tls,omitempty"`
1616}
1717
1818// TLS contains the information for loading a TLS certificate and key
@@ -20,15 +20,15 @@ type Server struct {
2020type TLS struct {
2121 // Key is the TLS key data to use.
2222 // Typically this will come from a file.
23− Key *SecretSource
23+ Key *SecretSource `yaml:"key,omitempty"`
2424
2525 // Cert is the TLS certificate data to use.
2626 // Typically this will come from a file.
27− Cert *SecretSource
27+ Cert *SecretSource `yaml:"cert,omitempty"`
2828
2929 // MinVersion is the minimal TLS version that is acceptable.
3030 // E.g. Set to "TLS1.3" to select TLS version 1.3
31− MinVersion string
31+ MinVersion string `yaml:"minVersion,omitempty"`
3232
3333 // CipherSuites is a list of TLS cipher suites that are allowed.
3434 // E.g.:
@@ -36,5 +36,5 @@ type TLS struct {
3636 // - TLS_RSA_WITH_AES_256_GCM_SHA384
3737 // If not specified, the default Go safe cipher list is used.
3838 // List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants).
39− CipherSuites []string
39+ CipherSuites []string `yaml:"cipherSuites,omitempty"`
4040}
pkg/apis/options/upstreams.go+78−17
@@ -1,24 +1,50 @@
11package options
22
3−import "time"
3+import (
4+ "net/http"
5+ "time"
6+
7+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
8+)
49
510const (
611 // DefaultUpstreamFlushInterval is the default value for the Upstream FlushInterval.
7− DefaultUpstreamFlushInterval = 1 * time.Second
12+ DefaultUpstreamFlushInterval time.Duration = 1 * time.Second
813
914 // DefaultUpstreamTimeout is the maximum duration a network dial to a upstream server for a response.
10− DefaultUpstreamTimeout = 30 * time.Second
15+ DefaultUpstreamTimeout time.Duration = 30 * time.Second
16+
17+ // DefaultUpstreamStatic determines if upstreams are static by default.
18+ DefaultUpstreamStatic bool = false
19+
20+ // DefaultUpstreamStaticCode is the default response code for static upstreams.
21+ DefaultUpstreamStaticCode int = http.StatusOK // 200
22+
23+ // DefaultUpstreamProxyRawPath determines if upstreams will proxy the raw url path by default.
24+ DefaultUpstreamProxyRawPath bool = false
25+
26+ // DefaultUpstreamInsecureSkipTLSVerify determines if upstreams will skip TLS verification by default.
27+ DefaultUpsteamInsecureSkipTLSVerify bool = false
28+
29+ // DefaultUpstreamPassHostHeader determines if upstreams will pass the host header by default.
30+ DefaultUpstreamPassHostHeader bool = true
31+
32+ // DefaultUpstreamProxyWebSockets determines if upstreams will proxy websockets by default.
33+ DefaultUpstreamProxyWebSockets bool = true
34+
35+ // DefaultUpstreamDisableKeepAlives determines if upstreams will disable keep-alives by default.
36+ DefaultUpstreamDisableKeepAlives bool = false
1137)
1238
1339// UpstreamConfig is a collection of definitions for upstream servers.
1440type UpstreamConfig struct {
1541 // ProxyRawPath will pass the raw url path to upstream allowing for urls
1642 // like: "/%2F/" which would otherwise be redirected to "/"
17− ProxyRawPath bool `json:"proxyRawPath,omitempty"`
43+ ProxyRawPath *bool `yaml:"proxyRawPath,omitempty"`
1844
1945 // Upstreams represents the configuration for the upstream servers.
2046 // Requests will be proxied to this upstream if the path matches the request path.
21− Upstreams []Upstream `json:"upstreams,omitempty"`
47+ Upstreams []Upstream `yaml:"upstreams,omitempty"`
2248}
2349
2450// Upstream represents the configuration for an upstream server.
@@ -26,7 +52,7 @@ type UpstreamConfig struct {
2652type Upstream struct {
2753 // ID should be a unique identifier for the upstream.
2854 // This value is required for all upstreams.
29− ID string `json:"id,omitempty"`
55+ ID string `yaml:"id,omitempty"`
3056
3157 // Path is used to map requests to the upstream server.
3258 // The closest match will take precedence and all Paths must be unique.
@@ -36,7 +62,7 @@ type Upstream struct {
3662 // - `^/foo$`: Match only the explicit path `/foo`
3763 // - `^/bar/$`: Match any path prefixed with `/bar/`
3864 // - `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget
39− Path string `json:"path,omitempty"`
65+ Path string `yaml:"path,omitempty"`
4066
4167 // RewriteTarget allows users to rewrite the request path before it is sent to
4268 // the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
@@ -46,7 +72,7 @@ type Upstream struct {
4672 // the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
4773 // upstream server. Or if the upstream were `file:///app`, a request for
4874 // `/baz/info.html` would return the contents of the file `/app/foo/info.html`.
49− RewriteTarget string `json:"rewriteTarget,omitempty"`
75+ RewriteTarget string `yaml:"rewriteTarget,omitempty"`
5076
5177 // The URI of the upstream server. This may be an HTTP(S) server of a File
5278 // based URL. It may include a path, in which case all requests will be served
@@ -58,43 +84,78 @@ type Upstream struct {
5884 // - file://host/path
5985 // If the URI's path is "/base" and the incoming request was for "/dir",
6086 // the upstream request will be for "/base/dir".
61− URI string `json:"uri,omitempty"`
87+ URI string `yaml:"uri,omitempty"`
6288
6389 // InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
6490 // This option is insecure and will allow potential Man-In-The-Middle attacks
6591 // between OAuth2 Proxy and the upstream server.
6692 // Defaults to false.
67− InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
93+ InsecureSkipTLSVerify *bool `yaml:"insecureSkipTLSVerify,omitempty"`
6894
6995 // Static will make all requests to this upstream have a static response.
7096 // The response will have a body of "Authenticated" and a response code
7197 // matching StaticCode.
7298 // If StaticCode is not set, the response will return a 200 response.
73− Static bool `json:"static,omitempty"`
99+ Static *bool `yaml:"static,omitempty"`
74100
75101 // StaticCode determines the response code for the Static response.
76102 // This option can only be used with Static enabled.
77− StaticCode *int `json:"staticCode,omitempty"`
103+ StaticCode *int `yaml:"staticCode,omitempty"`
78104
79105 // FlushInterval is the period between flushing the response buffer when
80106 // streaming response from the upstream.
81107 // Defaults to 1 second.
82− FlushInterval *Duration `json:"flushInterval,omitempty"`
108+ FlushInterval *time.Duration `yaml:"flushInterval,omitempty"`
83109
84110 // PassHostHeader determines whether the request host header should be proxied
85111 // to the upstream server.
86112 // Defaults to true.
87− PassHostHeader *bool `json:"passHostHeader,omitempty"`
113+ PassHostHeader *bool `yaml:"passHostHeader,omitempty"`
88114
89115 // ProxyWebSockets enables proxying of websockets to upstream servers
90116 // Defaults to true.
91− ProxyWebSockets *bool `json:"proxyWebSockets,omitempty"`
117+ ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"`
92118
93119 // Timeout is the maximum duration the server will wait for a response from the upstream server.
94120 // Defaults to 30 seconds.
95− Timeout *Duration `json:"timeout,omitempty"`
121+ Timeout *time.Duration `yaml:"timeout,omitempty"`
96122
97123 // DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
98124 // Defaults to false.
99− DisableKeepAlives bool `json:"disableKeepAlives,omitempty"`
125+ DisableKeepAlives *bool `yaml:"disableKeepAlives,omitempty"`
126+}
127+
128+// EnsureDefaults sets any default values for UpstreamConfig fields.
129+func (uc *UpstreamConfig) EnsureDefaults() {
130+ if uc.ProxyRawPath == nil {
131+ uc.ProxyRawPath = ptr.To(DefaultUpstreamProxyRawPath)
132+ }
133+ for i := range uc.Upstreams {
134+ uc.Upstreams[i].EnsureDefaults()
135+ }
136+}
137+
138+// EnsureDefaults sets any default values for Upstream fields.
139+func (u *Upstream) EnsureDefaults() {
140+ if u.InsecureSkipTLSVerify == nil {
141+ u.InsecureSkipTLSVerify = ptr.To(DefaultUpsteamInsecureSkipTLSVerify)
142+ }
143+ if u.Static == nil {
144+ u.Static = ptr.To(DefaultUpstreamStatic)
145+ }
146+ if u.FlushInterval == nil {
147+ u.FlushInterval = ptr.To(DefaultUpstreamFlushInterval)
148+ }
149+ if u.PassHostHeader == nil {
150+ u.PassHostHeader = ptr.To(DefaultUpstreamPassHostHeader)
151+ }
152+ if u.ProxyWebSockets == nil {
153+ u.ProxyWebSockets = ptr.To(DefaultUpstreamProxyWebSockets)
154+ }
155+ if u.Timeout == nil {
156+ u.Timeout = ptr.To(DefaultUpstreamTimeout)
157+ }
158+ if u.DisableKeepAlives == nil {
159+ u.DisableKeepAlives = ptr.To(DefaultUpstreamDisableKeepAlives)
160+ }
100161}
pkg/apis/sessions/session_state_test.go+4−7
@@ -8,15 +8,12 @@ import (
88 "time"
99
1010 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1112 . "github.com/onsi/gomega"
1213 "github.com/stretchr/testify/assert"
1314 "github.com/stretchr/testify/require"
1415)
1516
16−func timePtr(t time.Time) *time.Time {
17− return &t
18−}
19−
2017func TestCreatedAtNow(t *testing.T) {
2118 g := NewWithT(t)
2219 ss := &SessionState{}
@@ -137,10 +134,10 @@ func TestString(t *testing.T) {
137134}
138135
139136func TestIsExpired(t *testing.T) {
140− s := &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))}
137+ s := &SessionState{ExpiresOn: ptr.To(time.Now().Add(time.Duration(-1) * time.Minute))}
141138 assert.Equal(t, true, s.IsExpired())
142139
143− s = &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Minute))}
140+ s = &SessionState{ExpiresOn: ptr.To(time.Now().Add(time.Duration(1) * time.Minute))}
144141 assert.Equal(t, false, s.IsExpired())
145142
146143 s = &SessionState{}
@@ -154,7 +151,7 @@ func TestAge(t *testing.T) {
154151 assert.Equal(t, time.Duration(0), ss.Age())
155152
156153 // Set CreatedAt to 1 hour ago
157− ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour))
154+ ss.CreatedAt = ptr.To(time.Now().Add(-1 * time.Hour))
158155 assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
159156}
160157
pkg/middleware/headers.go+3−2
@@ -9,6 +9,7 @@ import (
99 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1010 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/header"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1213)
1314
1415func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, error) {
@@ -27,7 +28,7 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro
2728func newStripHeaders(headers []options.Header) alice.Constructor {
2829 headersToStrip := []options.Header{}
2930 for _, header := range headers {
30− if !header.PreserveRequestValue {
31+ if !ptr.Deref(header.PreserveRequestValue, options.DefaultHeaderPreserveRequestValue) {
3132 headersToStrip = append(headersToStrip, header)
3233 }
3334 }
@@ -53,7 +54,7 @@ func flattenHeaders(headers http.Header) {
5354func stripHeaders(headers []options.Header, next http.Handler) http.Handler {
5455 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
5556 for _, header := range headers {
56− if header.InsecureSkipHeaderNormalization {
57+ if ptr.Deref(header.InsecureSkipHeaderNormalization, options.DefaultInsecureSkipHeaderNormalization) {
5758 req.Header.Del(header.Name)
5859 continue
5960 }
pkg/middleware/headers_test.go+6−5
@@ -8,6 +8,7 @@ import (
88 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
99 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1010 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1112 . "github.com/onsi/ginkgo/v2"
1213 . "github.com/onsi/gomega"
1314)
@@ -115,7 +116,7 @@ var _ = Describe("Headers Suite", func() {
115116 headers: []options.Header{
116117 {
117118 Name: "Claim",
118− PreserveRequestValue: true,
119+ PreserveRequestValue: ptr.To(true),
119120 Values: []options.HeaderValue{
120121 {
121122 ClaimSource: &options.ClaimSource{
@@ -160,7 +161,7 @@ var _ = Describe("Headers Suite", func() {
160161 headers: []options.Header{
161162 {
162163 Name: "Claim",
163− PreserveRequestValue: true,
164+ PreserveRequestValue: ptr.To(true),
164165 Values: []options.HeaderValue{
165166 {
166167 ClaimSource: &options.ClaimSource{
@@ -230,7 +231,7 @@ var _ = Describe("Headers Suite", func() {
230231 headers: []options.Header{
231232 {
232233 Name: "X-Auth-Request-User",
233− InsecureSkipHeaderNormalization: true,
234+ InsecureSkipHeaderNormalization: ptr.To(true),
234235 Values: []options.HeaderValue{
235236 {
236237 ClaimSource: &options.ClaimSource{Claim: "user"},
@@ -385,7 +386,7 @@ var _ = Describe("Headers Suite", func() {
385386 headers: []options.Header{
386387 {
387388 Name: "Claim",
388− PreserveRequestValue: true,
389+ PreserveRequestValue: ptr.To(true),
389390 Values: []options.HeaderValue{
390391 {
391392 ClaimSource: &options.ClaimSource{
@@ -432,7 +433,7 @@ var _ = Describe("Headers Suite", func() {
432433 headers: []options.Header{
433434 {
434435 Name: "Claim",
435− PreserveRequestValue: true,
436+ PreserveRequestValue: ptr.To(true),
436437 Values: []options.HeaderValue{
437438 {
438439 ClaimSource: &options.ClaimSource{
pkg/middleware/jwt_session_test.go+2−2
@@ -304,7 +304,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
304304 authorizationHeader: fmt.Sprintf("Bearer %s", nonVerifiedToken),
305305 expectedErr: k8serrors.NewAggregate([]error{
306306 errors.New("unable to verify bearer token"),
307− errors.New("oidc: malformed jwt: oidc: malformed jwt payload: illegal base64 data at input byte 8"),
307+ errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"),
308308 }),
309309 expectedSession: nil,
310310 }),
@@ -317,7 +317,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
317317 authorizationHeader: "Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6",
318318 expectedErr: k8serrors.NewAggregate([]error{
319319 errors.New("unable to verify bearer token"),
320− errors.New("oidc: malformed jwt: oidc: malformed jwt payload: illegal base64 data at input byte 8"),
320+ errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"),
321321 }),
322322 expectedSession: nil,
323323 }),
pkg/requests/result_test.go+2−2
@@ -104,8 +104,8 @@ var _ = Describe("Result suite", func() {
104104
105105 Context("UnmarshalInto", func() {
106106 type testStruct struct {
107− A string `json:"a"`
108− B int `json:"b"`
107+ A string `yaml:"a"`
108+ B int `yaml:"b"`
109109 }
110110
111111 type unmarshalIntoTableInput struct {
pkg/upstream/http.go+9−8
@@ -11,6 +11,7 @@ import (
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1212 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1313 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
14+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1415)
1516
1617const (
@@ -53,7 +54,7 @@ func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *option
5354
5455 // Set up a WebSocket proxy if required
5556 var wsProxy http.Handler
56− if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
57+ if ptr.Deref(upstream.ProxyWebSockets, options.DefaultUpstreamProxyWebSockets) {
5758 wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
5859 }
5960
@@ -137,26 +138,26 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
137138
138139 // Change default duration for waiting for an upstream response
139140 if upstream.Timeout != nil {
140− transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
141+ transport.ResponseHeaderTimeout = *upstream.Timeout
141142 }
142143
143144 // Configure options on the SingleHostReverseProxy
144145 if upstream.FlushInterval != nil {
145− proxy.FlushInterval = upstream.FlushInterval.Duration()
146+ proxy.FlushInterval = *upstream.FlushInterval
146147 } else {
147148 proxy.FlushInterval = options.DefaultUpstreamFlushInterval
148149 }
149150
150151 // InsecureSkipVerify is a configurable option we allow
151152 /* #nosec G402 */
152− if upstream.InsecureSkipTLSVerify {
153+ if ptr.Deref(upstream.InsecureSkipTLSVerify, options.DefaultUpsteamInsecureSkipTLSVerify) {
153154 transport.TLSClientConfig.InsecureSkipVerify = true
154155 }
155156
156157 // Ensure we always pass the original request path
157158 setProxyDirector(proxy)
158159
159− if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
160+ if !ptr.Deref(upstream.PassHostHeader, options.DefaultUpstreamPassHostHeader) {
160161 setProxyUpstreamHostHeader(proxy, target)
161162 }
162163
@@ -168,7 +169,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
168169
169170 // Pass on DisableKeepAlives to the transport settings
170171 // to allow for disabling HTTP keep-alive connections
171− transport.DisableKeepAlives = upstream.DisableKeepAlives
172+ transport.DisableKeepAlives = ptr.Deref(upstream.DisableKeepAlives, options.DefaultUpstreamDisableKeepAlives)
172173
173174 // Apply the customized transport to our proxy before returning it
174175 proxy.Transport = transport
@@ -200,14 +201,14 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
200201}
201202
202203// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
203−func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
204+func newWebSocketReverseProxy(u *url.URL, skipTLSVerify *bool) http.Handler {
204205 wsProxy := httputil.NewSingleHostReverseProxy(u)
205206
206207 // Inherit default transport options from Go's stdlib
207208 transport := http.DefaultTransport.(*http.Transport).Clone()
208209
209210 /* #nosec G402 */
210− if skipTLSVerify {
211+ if ptr.Deref(skipTLSVerify, false) {
211212 transport.TLSClientConfig.InsecureSkipVerify = true
212213 }
213214
pkg/upstream/http_test.go+23−24
@@ -15,16 +15,15 @@ import (
1515 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1616 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1717 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
18+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1819 . "github.com/onsi/ginkgo/v2"
1920 . "github.com/onsi/gomega"
2021 "golang.org/x/net/websocket"
2122)
2223
2324var _ = Describe("HTTP Upstream Suite", func() {
24− defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval)
25− defaultTimeout := options.Duration(options.DefaultUpstreamTimeout)
26− truth := true
27− falsum := false
25+ defaultFlushInterval := options.DefaultUpstreamFlushInterval
26+ defaultTimeout := options.DefaultUpstreamTimeout
2827
2928 type httpUpstreamTableInput struct {
3029 id string
@@ -57,15 +56,15 @@ var _ = Describe("HTTP Upstream Suite", func() {
5756 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
5857 rw := httptest.NewRecorder()
5958
60− flush := options.Duration(1 * time.Second)
59+ flush := 1 * time.Second
6160
62− timeout := options.Duration(options.DefaultUpstreamTimeout)
61+ timeout := options.DefaultUpstreamTimeout
6362
6463 upstream := options.Upstream{
6564 ID: in.id,
6665 PassHostHeader: &in.passUpstreamHostHeader,
67− ProxyWebSockets: &falsum,
68− InsecureSkipTLSVerify: false,
66+ ProxyWebSockets: ptr.To(false),
67+ InsecureSkipTLSVerify: ptr.To(false),
6968 FlushInterval: &flush,
7069 Timeout: &timeout,
7170 }
@@ -343,9 +342,9 @@ var _ = Describe("HTTP Upstream Suite", func() {
343342
344343 upstream := options.Upstream{
345344 ID: "noPassHost",
346− PassHostHeader: &falsum,
347− ProxyWebSockets: &falsum,
348− InsecureSkipTLSVerify: false,
345+ PassHostHeader: ptr.To(false),
346+ ProxyWebSockets: ptr.To(false),
347+ InsecureSkipTLSVerify: ptr.To(false),
349348 FlushInterval: &defaultFlushInterval,
350349 Timeout: &defaultTimeout,
351350 }
@@ -373,11 +372,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
373372
374373 type newUpstreamTableInput struct {
375374 proxyWebSockets bool
376− flushInterval options.Duration
375+ flushInterval time.Duration
377376 skipVerify bool
378377 sigData *options.SignatureData
379378 errorHandler func(http.ResponseWriter, *http.Request, error)
380− timeout options.Duration
379+ timeout time.Duration
381380 disableKeepAlives bool
382381 }
383382
@@ -389,10 +388,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
389388 upstream := options.Upstream{
390389 ID: "foo123",
391390 FlushInterval: &in.flushInterval,
392− InsecureSkipTLSVerify: in.skipVerify,
391+ InsecureSkipTLSVerify: &in.skipVerify,
393392 ProxyWebSockets: &in.proxyWebSockets,
394393 Timeout: &in.timeout,
395− DisableKeepAlives: in.disableKeepAlives,
394+ DisableKeepAlives: &in.disableKeepAlives,
396395 }
397396
398397 handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler)
@@ -406,10 +405,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
406405
407406 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
408407 Expect(ok).To(BeTrue())
409− Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
408+ Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
410409 transport, ok := proxy.Transport.(*http.Transport)
411410 Expect(ok).To(BeTrue())
412− Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout.Duration()))
411+ Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout))
413412 Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
414413 if in.skipVerify {
415414 Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true))
@@ -428,7 +427,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
428427 }),
429428 Entry("with a non standard flush interval", &newUpstreamTableInput{
430429 proxyWebSockets: false,
431− flushInterval: options.Duration(5 * time.Second),
430+ flushInterval: 5 * time.Second,
432431 skipVerify: false,
433432 sigData: nil,
434433 errorHandler: nil,
@@ -466,7 +465,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
466465 skipVerify: false,
467466 sigData: nil,
468467 errorHandler: nil,
469− timeout: options.Duration(5 * time.Second),
468+ timeout: 5 * time.Second,
470469 }),
471470 Entry("with a DisableKeepAlives", &newUpstreamTableInput{
472471 proxyWebSockets: false,
@@ -483,13 +482,13 @@ var _ = Describe("HTTP Upstream Suite", func() {
483482 var proxyServer *httptest.Server
484483
485484 BeforeEach(func() {
486− flush := options.Duration(1 * time.Second)
487− timeout := options.Duration(options.DefaultUpstreamTimeout)
485+ flush := 1 * time.Second
486+ timeout := options.DefaultUpstreamTimeout
488487 upstream := options.Upstream{
489488 ID: "websocketProxy",
490− PassHostHeader: &truth,
491− ProxyWebSockets: &truth,
492− InsecureSkipTLSVerify: false,
489+ PassHostHeader: ptr.To(true),
490+ ProxyWebSockets: ptr.To(true),
491+ InsecureSkipTLSVerify: ptr.To(false),
493492 FlushInterval: &flush,
494493 Timeout: &timeout,
495494 }
pkg/upstream/proxy.go+4−3
@@ -14,6 +14,7 @@ import (
1414 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1515 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
1616 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
17+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1718)
1819
1920// ProxyErrorHandler is a function that will be used to render error pages when
@@ -27,12 +28,12 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData,
2728 serveMux: mux.NewRouter(),
2829 }
2930
30− if upstreams.ProxyRawPath {
31+ if ptr.Deref(upstreams.ProxyRawPath, options.DefaultUpstreamProxyRawPath) {
3132 m.serveMux.UseEncodedPath()
3233 }
3334
3435 for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
35− if upstream.Static {
36+ if ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
3637 if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
3738 return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
3839 }
@@ -74,7 +75,7 @@ func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
7475
7576// registerStaticResponseHandler registers a static response handler with at the given path.
7677func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream, writer pagewriter.Writer) error {
77− logger.Printf("mapping path %q => static response %d", upstream.Path, derefStaticCode(upstream.StaticCode))
78+ logger.Printf("mapping path %q => static response %d", upstream.Path, ptr.Deref(upstream.StaticCode, options.DefaultUpstreamStaticCode))
7879 return m.registerHandler(upstream, newStaticResponseHandler(upstream.ID, upstream.StaticCode), writer)
7980}
8081
pkg/upstream/proxy_test.go+6−5
@@ -10,6 +10,7 @@ import (
1010 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1212 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
13+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1314 . "github.com/onsi/ginkgo/v2"
1415 . "github.com/onsi/gomega"
1516)
@@ -60,19 +61,19 @@ var _ = Describe("Proxy Suite", func() {
6061 {
6162 ID: "static-backend",
6263 Path: "/static/",
63− Static: true,
64+ Static: ptr.To(true),
6465 StaticCode: &ok,
6566 },
6667 {
6768 ID: "static-backend-no-trailing-slash",
6869 Path: "/static",
69− Static: true,
70+ Static: ptr.To(true),
7071 StaticCode: &accepted,
7172 },
7273 {
7374 ID: "static-backend-long",
7475 Path: "/static/long",
75− Static: true,
76+ Static: ptr.To(true),
7677 StaticCode: &accepted,
7778 },
7879 {
@@ -83,7 +84,7 @@ var _ = Describe("Proxy Suite", func() {
8384 {
8485 ID: "single-path-backend",
8586 Path: "/single-path",
86− Static: true,
87+ Static: ptr.To(true),
8788 StaticCode: &ok,
8889 },
8990 {
@@ -346,7 +347,7 @@ var _ = Describe("Proxy Suite", func() {
346347 upstream: "",
347348 }),
348349 Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{
349− upstreams: options.UpstreamConfig{ProxyRawPath: true},
350+ upstreams: options.UpstreamConfig{ProxyRawPath: ptr.To(true)},
350351 target: "http://example.localhost/%2F/test1/%2F/test2",
351352 response: testHTTPResponse{
352353 code: 404,
pkg/upstream/static.go+3−11
@@ -5,16 +5,16 @@ import (
55 "net/http"
66
77 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
89 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
10+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
911)
1012
11−const defaultStaticResponseCode = 200
12−
1313// newStaticResponseHandler creates a new staticResponseHandler that serves a
1414// a static response code.
1515func newStaticResponseHandler(upstream string, code *int) http.Handler {
1616 return &staticResponseHandler{
17− code: derefStaticCode(code),
17+ code: ptr.Deref(code, options.DefaultUpstreamStaticCode),
1818 upstream: upstream,
1919 }
2020}
@@ -38,11 +38,3 @@ func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Requ
3838 logger.Errorf("Error writing static response: %v", err)
3939 }
4040}
41−
42−// derefStaticCode returns the derefenced value, or the default if the value is nil
43−func derefStaticCode(code *int) int {
44− if code != nil {
45− return *code
46− }
47− return defaultStaticResponseCode
48−}
pkg/util/ptr/ptr.gonew file+14
@@ -0,0 +1,14 @@
1+package ptr
2+
3+// To generically returns a pointer to the given value.
4+func To[T any](v T) *T {
5+ return &v
6+}
7+
8+// Deref returns the value of the pointer or def(ault) if nil.
9+func Deref[T any](p *T, def T) T {
10+ if p == nil {
11+ return def
12+ }
13+ return *p
14+}
pkg/util/ptr/ptr_test.gonew file+38
@@ -0,0 +1,38 @@
1+package ptr
2+
3+import (
4+ "testing"
5+
6+ "github.com/stretchr/testify/assert"
7+)
8+
9+func TestTo(t *testing.T) {
10+ p := To(42)
11+ assert.NotNil(t, p)
12+ assert.Equal(t, 42, *p)
13+
14+ s := To("hello")
15+ assert.NotNil(t, s)
16+ assert.Equal(t, "hello", *s)
17+
18+ b := To(true)
19+ assert.NotNil(t, b)
20+ assert.True(t, *b)
21+}
22+
23+func TestDeref(t *testing.T) {
24+ v := Deref(To(99), 0)
25+ assert.Equal(t, 99, v)
26+
27+ v = Deref[int](nil, 123)
28+ assert.Equal(t, 123, v)
29+
30+ s := Deref[string](nil, "default")
31+ assert.Equal(t, "default", s)
32+
33+ b := Deref(To(true), false)
34+ assert.True(t, b)
35+
36+ b = Deref[bool](nil, false)
37+ assert.False(t, b)
38+}
pkg/validation/options.go+2−1
@@ -15,6 +15,7 @@ import (
1515 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
1616 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
1717 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
18+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1819)
1920
2021// Validate checks that required options are set and validates those that they
@@ -34,7 +35,7 @@ func Validate(o *options.Options) error {
3435 transport := requests.DefaultTransport.(*http.Transport)
3536 transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 -- InsecureSkipVerify is a configurable option we allow
3637 } else if len(o.Providers[0].CAFiles) > 0 {
37− pool, err := util.GetCertPool(o.Providers[0].CAFiles, o.Providers[0].UseSystemTrustStore)
38+ pool, err := util.GetCertPool(o.Providers[0].CAFiles, ptr.Deref(o.Providers[0].UseSystemTrustStore, options.DefaultUseSystemTrustStore))
3839 if err == nil {
3940 transport := requests.DefaultTransport.(*http.Transport)
4041 transport.TLSClientConfig = &tls.Config{
pkg/validation/options_test.go+3−2
@@ -9,6 +9,7 @@ import (
99 "time"
1010
1111 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1213 "github.com/stretchr/testify/assert"
1314)
1415
@@ -68,7 +69,7 @@ func TestGoogleGroupOptionsWithoutServiceAccountJSON(t *testing.T) {
6869
6970func TestGoogleGroupOptionsWithoutAdminEmail(t *testing.T) {
7071 o := testOptions()
71− o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true
72+ o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.To(true)
7273 err := Validate(o)
7374 assert.NotEqual(t, nil, err)
7475
@@ -81,7 +82,7 @@ func TestGoogleGroupOptionsWithoutGroups(t *testing.T) {
8182 o := testOptions()
8283 // Set admin email and application default credentials but no groups - should still require them
8384 o.Providers[0].GoogleConfig.AdminEmail = "admin@example.com"
84− o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true
85+ o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.To(true)
8586 err := Validate(o)
8687 // Should pass validation since google-group is now optional
8788 assert.Equal(t, nil, err)
pkg/validation/providers.go+4−3
@@ -5,6 +5,7 @@ import (
55 "os"
66
77 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
89)
910
1011// validateProviders is the initial validation migration for multiple providrers
@@ -64,7 +65,7 @@ func validateProvider(provider options.Provider, providerIDs map[string]struct{}
6465// providerRequiresClientSecret checks if provider requires client secret to be set
6566// or it can be omitted in favor of JWT token to authenticate oAuth client
6667func providerRequiresClientSecret(provider options.Provider) bool {
67− if provider.Type == "entra-id" && provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
68+ if provider.Type == "entra-id" && ptr.Deref(provider.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken) {
6869 return false
6970 }
7071
@@ -96,7 +97,7 @@ func validateGoogleConfig(provider options.Provider) []string {
9697
9798 hasAdminEmail := provider.GoogleConfig.AdminEmail != ""
9899 hasSAJSON := provider.GoogleConfig.ServiceAccountJSON != ""
99− useADC := provider.GoogleConfig.UseApplicationDefaultCredentials
100+ useADC := ptr.Deref(provider.GoogleConfig.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials)
100101
101102 if !hasAdminEmail && !hasSAJSON && !useADC {
102103 return msgs
@@ -123,7 +124,7 @@ func validateGoogleConfig(provider options.Provider) []string {
123124func validateEntraConfig(provider options.Provider) []string {
124125 msgs := []string{}
125126
126− if provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
127+ if ptr.Deref(provider.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken) {
127128 federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
128129
129130 if federatedTokenPath == "" {
pkg/validation/sessions_test.go+2−2
@@ -193,8 +193,8 @@ var _ = Describe("Sessions", func() {
193193 unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
194194 unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
195195 unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
196− refusedSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: dial tcp 127.0.0.1:65535: connect: connection refused"
197− refusedSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: dial tcp 127.0.0.1:65535: connect: connection refused"
196+ refusedSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
197+ refusedSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
198198 )
199199
200200 type redisStoreTableInput struct {
pkg/validation/upstreams.go+7−6
@@ -5,6 +5,7 @@ import (
55 "net/url"
66
77 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
89)
910
1011func validateUpstreams(upstreams options.UpstreamConfig) []string {
@@ -54,22 +55,22 @@ func validateUpstream(upstream options.Upstream, ids, paths map[string]struct{})
5455func validateStaticUpstream(upstream options.Upstream) []string {
5556 msgs := []string{}
5657
57− if !upstream.Static && upstream.StaticCode != nil {
58+ if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) && upstream.StaticCode != nil {
5859 msgs = append(msgs, fmt.Sprintf("upstream %q has staticCode (%d), but is not a static upstream, set 'static' for a static response", upstream.ID, *upstream.StaticCode))
5960 }
6061
6162 // Checks after this only make sense when the upstream is static
62− if !upstream.Static {
63+ if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
6364 return msgs
6465 }
6566
6667 if upstream.URI != "" {
6768 msgs = append(msgs, fmt.Sprintf("upstream %q has uri, but is a static upstream, this will have no effect.", upstream.ID))
6869 }
69− if upstream.InsecureSkipTLSVerify {
70+ if ptr.Deref(upstream.InsecureSkipTLSVerify, options.DefaultUpsteamInsecureSkipTLSVerify) {
7071 msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
7172 }
72− if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
73+ if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval {
7374 msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
7475 }
7576 if upstream.PassHostHeader != nil {
@@ -85,13 +86,13 @@ func validateStaticUpstream(upstream options.Upstream) []string {
8586func validateUpstreamURI(upstream options.Upstream) []string {
8687 msgs := []string{}
8788
88− if !upstream.Static && upstream.URI == "" {
89+ if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) && upstream.URI == "" {
8990 msgs = append(msgs, fmt.Sprintf("upstream %q has empty uri: uris are required for all non-static upstreams", upstream.ID))
9091 return msgs
9192 }
9293
9394 // Checks after this only make sense the upstream is not static
94− if upstream.Static {
95+ if ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
9596 return msgs
9697 }
9798
pkg/validation/upstreams_test.go+7−7
@@ -4,6 +4,7 @@ import (
44 "time"
55
66 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
7+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
78 . "github.com/onsi/ginkgo/v2"
89 . "github.com/onsi/gomega"
910)
@@ -14,9 +15,8 @@ var _ = Describe("Upstreams", func() {
1415 errStrings []string
1516 }
1617
17− flushInterval := options.Duration(5 * time.Second)
18+ flushInterval := 5 * time.Second
1819 staticCode200 := 200
19− truth := true
2020
2121 validHTTPUpstream := options.Upstream{
2222 ID: "validHTTPUpstream",
@@ -26,7 +26,7 @@ var _ = Describe("Upstreams", func() {
2626 validStaticUpstream := options.Upstream{
2727 ID: "validStaticUpstream",
2828 Path: "/validStaticUpstream",
29− Static: true,
29+ Static: ptr.To(true),
3030 }
3131 validFileUpstream := options.Upstream{
3232 ID: "validFileUpstream",
@@ -145,11 +145,11 @@ var _ = Describe("Upstreams", func() {
145145 ID: "foo",
146146 Path: "/foo",
147147 URI: "ftp://foo",
148− Static: true,
148+ Static: ptr.To(true),
149149 FlushInterval: &flushInterval,
150− PassHostHeader: &truth,
151− ProxyWebSockets: &truth,
152− InsecureSkipTLSVerify: true,
150+ PassHostHeader: ptr.To(true),
151+ ProxyWebSockets: ptr.To(true),
152+ InsecureSkipTLSVerify: ptr.To(true),
153153 },
154154 },
155155 },
providers/adfs.go+2−1
@@ -8,6 +8,7 @@ import (
88
99 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1010 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
11+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1112)
1213
1314// ADFSProvider represents an ADFS based Identity Provider
@@ -50,7 +51,7 @@ func NewADFSProvider(p *ProviderData, opts options.Provider) *ADFSProvider {
5051
5152 return &ADFSProvider{
5253 OIDCProvider: oidcProvider,
53− skipScope: opts.ADFSConfig.SkipScope,
54+ skipScope: ptr.Deref(opts.ADFSConfig.SkipScope, options.DefaultADFSSkipScope),
5455 oidcEnrichFunc: oidcProvider.EnrichSession,
5556 oidcRefreshFunc: oidcProvider.RefreshSession,
5657 }
providers/adfs_test.gomode 100755 → 100644+2−1
@@ -16,6 +16,7 @@ import (
1616 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1717 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1818 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
19+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1920 . "github.com/onsi/ginkgo/v2"
2021 . "github.com/onsi/gomega"
2122)
@@ -172,7 +173,7 @@ var _ = Describe("ADFS Provider Tests", func() {
172173 ProtectedResource: resource,
173174 Scope: "",
174175 }, options.Provider{
175− ADFSConfig: options.ADFSOptions{SkipScope: true},
176+ ADFSConfig: options.ADFSOptions{SkipScope: ptr.To(true)},
176177 })
177178
178179 result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "", url.Values{})
providers/google.go+5−5
@@ -19,6 +19,7 @@ import (
1919 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
2020 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
2121 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
22+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
2223 "golang.org/x/oauth2"
2324 "golang.org/x/oauth2/google"
2425 admin "google.golang.org/api/admin/directory/v1"
@@ -108,12 +109,11 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
108109 },
109110 }
110111
111− if opts.UseOrganizationID || opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
112−
112+ if ptr.Deref(opts.UseOrganizationID, options.DefaultGoogleUseOrganizationID) || opts.ServiceAccountJSON != "" || ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
113113 // reuse admin service to avoid multiple calls for token
114114 var adminService *admin.Service
115115
116− if opts.UseOrganizationID {
116+ if ptr.Deref(opts.UseOrganizationID, options.DefaultGoogleUseOrganizationID) {
117117 // add user scopes to admin api
118118 userScope := getAdminAPIUserScope(opts.AdminAPIUserScope)
119119 for index, scope := range possibleScopesList {
@@ -132,7 +132,7 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
132132 }
133133 }
134134
135− if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
135+ if opts.ServiceAccountJSON != "" || ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
136136 if adminService == nil {
137137 adminService = getAdminService(opts)
138138 }
@@ -304,7 +304,7 @@ var possibleScopesList = [...]string{
304304}
305305
306306func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource {
307− if opts.UseApplicationDefaultCredentials {
307+ if ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
308308 ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
309309 TargetPrincipal: getTargetPrincipal(ctx, opts),
310310 Scopes: strings.Split(scope, " "),
providers/ms_entra_id.go+2−1
@@ -16,6 +16,7 @@ import (
1616 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1717 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
1818 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
19+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1920 "github.com/spf13/cast"
2021 "golang.org/x/oauth2"
2122)
@@ -51,7 +52,7 @@ func NewMicrosoftEntraIDProvider(p *ProviderData, opts options.Provider) *Micros
5152 OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
5253
5354 multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants,
54− federatedTokenAuth: opts.MicrosoftEntraIDConfig.FederatedTokenAuth,
55+ federatedTokenAuth: ptr.Deref(opts.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken),
5556 microsoftGraphURL: microsoftGraphURL,
5657 }
5758}
providers/ms_entra_id_test.go+4−3
@@ -13,6 +13,7 @@ import (
1313 "github.com/coreos/go-oidc/v3/oidc"
1414 "github.com/golang-jwt/jwt/v5"
1515 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
16+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1617 "github.com/stretchr/testify/assert"
1718
1819 . "github.com/onsi/gomega"
@@ -24,7 +25,7 @@ func TestAzureEntraOIDCProviderNewMultiTenant(t *testing.T) {
2425 provider := NewMicrosoftEntraIDProvider(&ProviderData{},
2526 options.Provider{OIDCConfig: options.OIDCOptions{
2627 IssuerURL: "https://login.microsoftonline.com/common/v2.0",
27− InsecureSkipIssuerVerification: true,
28+ InsecureSkipIssuerVerification: ptr.To(true),
2829 }},
2930 )
3031 g.Expect(provider.Data().ProviderName).To(Equal("Microsoft Entra ID"))
@@ -90,8 +91,8 @@ func TestAzureEntraOIDCProviderValidateSessionAllowedTenants(t *testing.T) {
9091 options.Provider{
9192 OIDCConfig: options.OIDCOptions{
9293 IssuerURL: "https://login.microsoftonline.com/common/v2.0",
93− InsecureSkipIssuerVerification: true,
94− InsecureSkipNonce: true,
94+ InsecureSkipIssuerVerification: ptr.To(true),
95+ InsecureSkipNonce: ptr.To(true),
9596 },
9697 MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
9798 AllowedTenants: []string{"85d7d600-7804-4d92-8d43-9c33c21c130c"},
providers/oidc.go+2−1
@@ -12,6 +12,7 @@ import (
1212 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1313 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1414 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
15+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1516 "golang.org/x/oauth2"
1617)
1718
@@ -50,7 +51,7 @@ func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
5051
5152 return &OIDCProvider{
5253 ProviderData: p,
53− SkipNonce: opts.InsecureSkipNonce,
54+ SkipNonce: ptr.Deref(opts.InsecureSkipNonce, options.DefaultInsecureSkipNonce),
5455 }
5556}
5657
providers/oidc_test.go+1−1
@@ -63,7 +63,7 @@ func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider {
6363 }
6464
6565 p := NewOIDCProvider(providerData, options.OIDCOptions{
66− InsecureSkipNonce: skipNonce,
66+ InsecureSkipNonce: &skipNonce,
6767 })
6868
6969 return p
providers/providers.go+5−4
@@ -9,6 +9,7 @@ import (
99 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1010 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1111 internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
12+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
1213 k8serrors "k8s.io/apimachinery/pkg/util/errors"
1314)
1415
@@ -98,8 +99,8 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
9899 IssuerURL: providerConfig.OIDCConfig.IssuerURL,
99100 JWKsURL: providerConfig.OIDCConfig.JwksURL,
100101 PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles,
101− SkipDiscovery: providerConfig.OIDCConfig.SkipDiscovery,
102− SkipIssuerVerification: providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
102+ SkipDiscovery: ptr.Deref(providerConfig.OIDCConfig.SkipDiscovery, options.DefaultSkipDiscovery),
103+ SkipIssuerVerification: ptr.Deref(providerConfig.OIDCConfig.InsecureSkipIssuerVerification, options.DefaultInsecureSkipIssuerVerification),
103104 })
104105 if err != nil {
105106 return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err)
@@ -143,10 +144,10 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
143144 }
144145
145146 // Make the OIDC options available to all providers that support it
146− p.AllowUnverifiedEmail = providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail
147+ p.AllowUnverifiedEmail = ptr.Deref(providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail, options.DefaultInsecureAllowUnverifiedEmail)
147148 p.EmailClaim = providerConfig.OIDCConfig.EmailClaim
148149 p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim
149− p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL
150+ p.SkipClaimsFromProfileURL = ptr.Deref(providerConfig.SkipClaimsFromProfileURL, options.DefaultSkipClaimsFromProfileURL)
150151
151152 // Set PKCE enabled or disabled based on discovery and force options
152153 p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig)
providers/providers_test.go+5−4
@@ -5,6 +5,7 @@ import (
55 "testing"
66
77 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
89 . "github.com/onsi/gomega"
910)
1011
@@ -81,7 +82,7 @@ func TestSkipOIDCDiscovery(t *testing.T) {
8182 ClientSecretFile: clientSecret,
8283 OIDCConfig: options.OIDCOptions{
8384 IssuerURL: msIssuerURL,
84− SkipDiscovery: true,
85+ SkipDiscovery: ptr.To(true),
8586 },
8687 }
8788
@@ -108,7 +109,7 @@ func TestURLsCorrectlyParsed(t *testing.T) {
108109 RedeemURL: msTokenURL,
109110 OIDCConfig: options.OIDCOptions{
110111 IssuerURL: msIssuerURL,
111− SkipDiscovery: true,
112+ SkipDiscovery: ptr.To(true),
112113 JwksURL: msKeysURL,
113114 },
114115 }
@@ -216,7 +217,7 @@ func TestScope(t *testing.T) {
216217 AllowedGroups: tc.allowedGroups,
217218 OIDCConfig: options.OIDCOptions{
218219 IssuerURL: msIssuerURL,
219− SkipDiscovery: true,
220+ SkipDiscovery: ptr.To(true),
220221 JwksURL: msKeysURL,
221222 },
222223 }
@@ -297,7 +298,7 @@ func TestEmailClaimCorrectlySet(t *testing.T) {
297298 RedeemURL: msTokenURL,
298299 OIDCConfig: options.OIDCOptions{
299300 IssuerURL: msIssuerURL,
300− SkipDiscovery: true,
301+ SkipDiscovery: ptr.To(true),
301302 JwksURL: msKeysURL,
302303 UserIDClaim: tc.userIDClaim,
303304 EmailClaim: tc.emailClaim,