Merge pull request #2628 from tuunit/use-mapstructures-for-parsing-and-merging

structured config #1: introduce mapstructure decoder for yaml parsing
This commit is contained in:
Jan Larwig 2025-11-28 18:14:11 +01:00 committed by GitHub
commit e27921ee80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 1308 additions and 794 deletions

View File

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

View File

@ -74,6 +74,10 @@ DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --pus
.PHONY: build-docker
build-docker: build-distroless build-alpine ## Build multi architecture docker images in both flavours (distroless / alpine)
.PHONY: build-docker-local
build-docker-local: ## Build distroless docker image and locally load into docker images
$(DOCKER_BUILDX) --load -t $(REGISTRY)/$(REPOSITORY):${VERSION}-local .
.PHONY: build-distroless
build-distroless: ## Build multi architecture distroless based docker image
$(DOCKER_BUILDX_X_PLATFORM) -t $(REGISTRY)/$(REPOSITORY):latest -t $(REGISTRY)/$(REPOSITORY):${VERSION} .

View File

@ -10,11 +10,58 @@
# make alpha-config-<command> (eg make nginx-up, make nginx-down)
#
# Access http://localhost:4180 to initiate a login cycle
version: '3.0'
version: "3.0"
services:
oauth2-proxy:
container_name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.13.0
command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml
hostname: oauth2-proxy
volumes:
- "./oauth2-proxy-alpha-config.cfg:/oauth2-proxy.cfg"
- "./oauth2-proxy-alpha-config.yaml:/oauth2-proxy-alpha-config.yaml"
restart: unless-stopped
ports:
- 4180:4180/tcp
networks:
dex: {}
httpbin: {}
depends_on:
- dex
- httpbin
dex:
container_name: dex
image: ghcr.io/dexidp/dex:v2.43.1
command: dex serve /dex.yaml
hostname: dex
volumes:
- "./dex.yaml:/dex.yaml"
restart: unless-stopped
ports:
- 5556:5556/tcp
networks:
dex:
aliases:
- dex.localtest.me
etcd: {}
depends_on:
- etcd
httpbin:
container_name: httpbin
image: kennethreitz/httpbin
ports: []
networks:
httpbin: {}
etcd:
container_name: etcd
image: gcr.io/etcd-development/etcd:v3.6.2
entrypoint: /usr/local/bin/etcd
command:
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd:2379
networks:
etcd: {}
networks:
dex: {}
etcd: {}
httpbin: {}

View File

@ -9,7 +9,7 @@
# make <command> (eg. make up, make down)
#
# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle
version: '3.0'
version: "3.0"
services:
oauth2-proxy:
container_name: oauth2-proxy

View File

@ -1,5 +1,4 @@
http_address="0.0.0.0:4180"
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
email_domains="example.com"
cookie_secure="false"
redirect_url="http://localhost:4180/oauth2/callback"
redirect_url="http://oauth2-proxy.localtest.me:4180/oauth2/callback"

View File

@ -1,23 +1,23 @@
upstreams:
- id: httpbin
path: /
uri: http://httpbin
server:
bindAddress: "0.0.0.0:4180"
upstreamConfig:
upstreams:
- id: httpbin
path: /
uri: http://httpbin
injectRequestHeaders:
- name: X-Forwarded-Groups
values:
- claim: groups
- name: X-Forwarded-User
values:
- claim: user
- name: X-Forwarded-Email
values:
- claim: email
- name: X-Forwarded-Preferred-Username
values:
- claim: preferred_username
- name: X-Forwarded-User
values:
- claimSource:
claim: user
- name: X-Forwarded-Email
values:
- claimSource:
claim: email
providers:
- provider: oidc
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
clientID: oauth2-proxy
oidcConfig:
issuerURL: http://dex.localhost:5556/dex
- id: oidc
provider: oidc
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
clientID: oauth2-proxy
oidcConfig:
issuerURL: http://dex.localtest.me:5556/dex

View File

@ -204,16 +204,6 @@ ClaimSource allows loading a header value from a claim within the session
| `prefix` | _string_ | Prefix is an optional prefix that will be prepended to the value of the<br/>claim if it is non-empty. |
| `basicAuthPassword` | _[SecretSource](#secretsource)_ | BasicAuthPassword converts this claim into a basic auth header.<br/>Note the value of claim will become the basic auth username and the<br/>basicAuthPassword will be used as the password value. |
### Duration
#### (`string` alias)
(**Appears on:** [Upstream](#upstream))
Duration is as string representation of a period of time.
A duration string is a is a possibly signed sequence of decimal numbers,
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
### GitHubOptions
(**Appears on:** [Provider](#provider))
@ -502,9 +492,9 @@ Server represents the configuration for an HTTP(S) server
| Field | Type | Description |
| ----- | ---- | ----------- |
| `BindAddress` | _string_ | BindAddress is the address on which to serve traffic.<br/>Leave blank or set to "-" to disable. |
| `SecureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.<br/>Leave blank or set to "-" to disable. |
| `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. |
| `bindAddress` | _string_ | BindAddress is the address on which to serve traffic.<br/>Leave blank or set to "-" to disable. |
| `secureBindAddress` | _string_ | SecureBindAddress is the address on which to serve secure traffic.<br/>Leave blank or set to "-" to disable. |
| `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. |
### TLS
@ -515,10 +505,10 @@ as well as an optional minimal TLS version that is acceptable.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `Key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.<br/>Typically this will come from a file. |
| `Cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.<br/>Typically this will come from a file. |
| `MinVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.<br/>E.g. Set to "TLS1.3" to select TLS version 1.3 |
| `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). |
| `key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.<br/>Typically this will come from a file. |
| `cert` | _[SecretSource](#secretsource)_ | Cert is the TLS certificate data to use.<br/>Typically this will come from a file. |
| `minVersion` | _string_ | MinVersion is the minimal TLS version that is acceptable.<br/>E.g. Set to "TLS1.3" to select TLS version 1.3 |
| `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). |
### URLParameterRule
@ -550,10 +540,10 @@ Requests will be proxied to this upstream if the path matches the request path.
| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.<br/>This option is insecure and will allow potential Man-In-The-Middle attacks<br/>between OAuth2 Proxy and the upstream server.<br/>Defaults to false. |
| `static` | _bool_ | Static will make all requests to this upstream have a static response.<br/>The response will have a body of "Authenticated" and a response code<br/>matching StaticCode.<br/>If StaticCode is not set, the response will return a 200 response. |
| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
### UpstreamConfig

78
go.mod
View File

@ -3,56 +3,57 @@ module github.com/oauth2-proxy/oauth2-proxy/v7
go 1.25.3
require (
cloud.google.com/go/compute/metadata v0.7.0
cloud.google.com/go/compute/metadata v0.9.0
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
github.com/a8m/envsubst v1.4.3
github.com/alicebob/miniredis/v2 v2.35.0
github.com/bitly/go-simplejson v0.5.1
github.com/bsm/redislock v0.9.4
github.com/coreos/go-oidc/v3 v3.14.1
github.com/coreos/go-oidc/v3 v3.16.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/fsnotify/fsnotify v1.9.0
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/go-jose/go-jose/v3 v3.0.4
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/justinas/alice v1.2.0
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/pierrec/lz4/v4 v4.1.22
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.11.0
github.com/spf13/cast v1.9.2
github.com/spf13/pflag v1.0.7
github.com/spf13/viper v1.20.1
github.com/prometheus/client_golang v1.23.2
github.com/redis/go-redis/v9 v9.16.0
github.com/spf13/cast v1.10.0
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/vmihailenco/msgpack/v5 v5.4.1
golang.org/x/crypto v0.40.0
golang.org/x/net v0.42.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
google.golang.org/api v0.242.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.43.0
golang.org/x/net v0.46.0
golang.org/x/oauth2 v0.32.0
golang.org/x/sync v0.17.0
google.golang.org/api v0.254.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
k8s.io/apimachinery v0.33.3
k8s.io/apimachinery v0.34.1
)
require (
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
@ -61,28 +62,25 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/prometheus/common v0.67.2 // indirect
github.com/prometheus/procfs v0.19.1 // indirect
github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.35.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

203
go.sum
View File

@ -1,13 +1,15 @@
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw=
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.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
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.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
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.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
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.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
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d h1:KJIErDwbSHjnp/SGzE5ed8Aol7JsKiI5X7yWKAtzhM0=
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.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
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.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=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.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
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
github.com/prometheus/procfs v0.19.1 h1:QVtROpTkphuXuNlnCv3m1ut3JytkXHtQ3xvck/YmzMM=
github.com/prometheus/procfs v0.19.1/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.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
github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.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
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.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=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg=
google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4=
google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=

47
main.go
View File

@ -5,12 +5,12 @@ import (
"os"
"runtime"
"github.com/ghodss/yaml"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/version"
"github.com/spf13/pflag"
"go.yaml.in/yaml/v3"
)
func main() {
@ -20,7 +20,7 @@ func main() {
// Because we parse early to determine alpha vs legacy config, we have to
// ignore any unknown flags for now
configFlagSet.ParseErrorsWhitelist.UnknownFlags = true
configFlagSet.ParseErrorsAllowlist.UnknownFlags = true
config := configFlagSet.String("config", "", "path to config file")
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() {
// loadConfiguration will load in the user's configuration.
// It will either load the alpha configuration (if alphaConfig is given)
// or the legacy configuration.
func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
if alphaConfig != "" {
logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
return loadAlphaOptions(config, alphaConfig, extraFlags, args)
func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadLegacyOptions(config, extraFlags, args)
if err != nil {
return nil, fmt.Errorf("failed to load legacy options: %w", err)
}
return loadLegacyOptions(config, extraFlags, args)
if yamlConfig != "" {
logger.Printf("WARNING: You are using alpha configuration. The structure in this configuration file may change without notice. You MUST remove conflicting options from your existing configuration.")
opts, err = loadYamlOptions(yamlConfig, config, extraFlags, args)
if err != nil {
return nil, fmt.Errorf("failed to load yaml options: %w", err)
}
}
// Ensure defaults after loading configuration
opts.EnsureDefaults()
return opts, nil
}
// loadLegacyOptions loads the old toml options using the legacy flagset
@ -97,21 +108,21 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string)
return opts, nil
}
// loadAlphaOptions loads the old style config excluding options converted to
// loadYamlOptions loads the old style config excluding options converted to
// the new alpha format, then merges the alpha options, loaded from YAML,
// into the core configuration.
func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
func loadYamlOptions(yamlConfig, config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadOptions(config, extraFlags, args)
if err != nil {
return nil, fmt.Errorf("failed to load core options: %v", err)
}
alphaOpts := &options.AlphaOptions{}
if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
alphaOpts := options.NewAlphaOptions(opts)
if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
return nil, fmt.Errorf("failed to load alpha options: %v", err)
}
alphaOpts.MergeInto(opts)
alphaOpts.MergeOptionsWithDefaults(opts)
return opts, nil
}
@ -137,10 +148,16 @@ func loadOptions(config string, extraFlags *pflag.FlagSet, args []string) (*opti
// printConvertedConfig extracts alpha options from the loaded configuration
// and renders these to stdout in YAML format.
func printConvertedConfig(opts *options.Options) error {
alphaConfig := &options.AlphaOptions{}
alphaConfig.ExtractFrom(opts)
alphaConfig := options.NewAlphaOptions(opts)
data, err := yaml.Marshal(alphaConfig)
// Generic interface for loading arbitrary yaml structure
var buffer map[string]interface{}
if err := options.Decode(alphaConfig, &buffer); err != nil {
return fmt.Errorf("unable to decode alpha config into interface: %w", err)
}
data, err := yaml.Marshal(buffer)
if err != nil {
return fmt.Errorf("unable to marshal config: %v", err)
}

View File

@ -2,13 +2,11 @@ package main
import (
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
@ -24,14 +22,15 @@ var _ = Describe("Configuration Loading Suite", func() {
http_address="127.0.0.1:4180"
upstreams="http://httpbin"
set_basic_auth="true"
basic_auth_password="super-secret-password"
basic_auth_password="c3VwZXItc2VjcmV0LXBhc3N3b3Jk"
client_id="oauth2-proxy"
client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
google_admin_email="admin@example.com"
google_target_principal="principal"
`
const testAlphaConfig = `
upstreamConfig:
proxyrawpath: false
upstreams:
- id: /
path: /
@ -40,45 +39,63 @@ upstreamConfig:
passHostHeader: true
proxyWebSockets: true
timeout: 30s
insecureSkipTLSVerify: false
disableKeepAlives: false
injectRequestHeaders:
- name: Authorization
preserveRequestValue: false
values:
- claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- claimSource:
claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- name: X-Forwarded-Groups
preserveRequestValue: false
values:
- claim: groups
- claimSource:
claim: groups
- name: X-Forwarded-User
preserveRequestValue: false
values:
- claim: user
- claimSource:
claim: user
- name: X-Forwarded-Email
preserveRequestValue: false
values:
- claim: email
- claimSource:
claim: email
- name: X-Forwarded-Preferred-Username
preserveRequestValue: false
values:
- claim: preferred_username
- claimSource:
claim: preferred_username
injectResponseHeaders:
- name: Authorization
values:
- claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
- claimSource:
claim: user
prefix: "Basic "
basicAuthPassword:
value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
server:
bindAddress: "127.0.0.1:4180"
providers:
- provider: google
ID: google=oauth2-proxy
- id: google=oauth2-proxy
provider: google
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
clientID: oauth2-proxy
azureConfig:
tenant: common
useSystemTrustStore: false
skipClaimsFromProfileURL: false
googleConfig:
adminEmail: admin@example.com
targetPrincipal: principal
useApplicationDefaultCredentials: false
oidcConfig:
groupsClaim: groups
emailClaim: email
userIDClaim: email
insecureSkipIssuerVerification: false
insecureSkipNonce: true
audienceClaims: [aud]
extraAudiences: []
@ -96,15 +113,6 @@ cookie_secure="false"
redirect_url="http://localhost:4180/oauth2/callback"
`
boolPtr := func(b bool) *bool {
return &b
}
durationPtr := func(d time.Duration) *options.Duration {
du := options.Duration(d)
return &du
}
testExpectedOptions := func() *options.Options {
opts, err := options.NewLegacyOptions().ToOptions()
Expect(err).ToNot(HaveOccurred())
@ -115,28 +123,33 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts.RawRedirectURL = "http://localhost:4180/oauth2/callback"
opts.UpstreamServers = options.UpstreamConfig{
ProxyRawPath: ptr.To(false),
Upstreams: []options.Upstream{
{
ID: "/",
Path: "/",
URI: "http://httpbin",
FlushInterval: durationPtr(options.DefaultUpstreamFlushInterval),
PassHostHeader: boolPtr(true),
ProxyWebSockets: boolPtr(true),
Timeout: durationPtr(options.DefaultUpstreamTimeout),
ID: "/",
Path: "/",
URI: "http://httpbin",
FlushInterval: ptr.To(options.DefaultUpstreamFlushInterval),
PassHostHeader: ptr.To(true),
ProxyWebSockets: ptr.To(true),
Timeout: ptr.To(options.DefaultUpstreamTimeout),
Static: ptr.To(false),
InsecureSkipTLSVerify: ptr.To(false),
DisableKeepAlives: ptr.To(false),
},
},
}
authHeader := options.Header{
Name: "Authorization",
Name: "Authorization",
PreserveRequestValue: ptr.To(false),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
Prefix: "Basic ",
BasicAuthPassword: &options.SecretSource{
Value: []byte("super-secret-password"),
Value: []byte("c3VwZXItc2VjcmV0LXBhc3N3b3Jk"),
},
},
},
@ -148,20 +161,37 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts.Providers = options.Providers{
options.Provider{
ID: "google=oauth2-proxy",
Type: "google",
ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
ClientID: "oauth2-proxy",
ID: "google=oauth2-proxy",
Type: "google",
ClientSecret: "b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK",
ClientID: "oauth2-proxy",
UseSystemTrustStore: ptr.To(false),
SkipClaimsFromProfileURL: ptr.To(false),
GoogleConfig: options.GoogleOptions{
AdminEmail: "admin@example.com",
TargetPrincipal: "principal",
UseOrganizationID: ptr.To(false),
UseApplicationDefaultCredentials: ptr.To(false),
},
AzureConfig: options.AzureOptions{
Tenant: "common",
},
OIDCConfig: options.OIDCOptions{
GroupsClaim: "groups",
EmailClaim: "email",
UserIDClaim: "email",
AudienceClaims: []string{"aud"},
ExtraAudiences: []string{},
InsecureSkipNonce: true,
GroupsClaim: "groups",
EmailClaim: "email",
UserIDClaim: "email",
AudienceClaims: []string{"aud"},
ExtraAudiences: []string{},
InsecureSkipNonce: ptr.To(true),
InsecureAllowUnverifiedEmail: ptr.To(false),
InsecureSkipIssuerVerification: ptr.To(false),
SkipDiscovery: ptr.To(false),
},
MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
FederatedTokenAuth: ptr.To(false),
},
ADFSConfig: options.ADFSOptions{
SkipScope: ptr.To(false),
},
LoginURLParameters: []options.LoginURLParameter{
{Name: "approval_prompt", Default: []string{"force"}},
@ -226,12 +256,13 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}
Expect(in.expectedOptions).ToNot(BeNil())
Expect(opts).To(EqualOpts(in.expectedOptions()))
expectedOpts := in.expectedOptions()
Expect(opts).To(EqualOpts(expectedOpts))
},
Entry("with legacy configuration", loadConfigurationTableInput{
configContent: testCoreConfig + testLegacyConfig,
@ -245,19 +276,19 @@ redirect_url="http://localhost:4180/oauth2/callback"
Entry("with bad legacy configuration", loadConfigurationTableInput{
configContent: testCoreConfig + "unknown_field=\"something\"",
expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
}),
Entry("with bad alpha configuration", loadConfigurationTableInput{
configContent: testCoreConfig,
alphaConfigContent: testAlphaConfig + ":",
expectedOptions: func() *options.Options { return nil },
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")),
expectedErr: errors.New("failed to load alpha options: error unmarshalling config: yaml: line 1: did not find expected key"),
}),
Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
configContent: testCoreConfig + "unknown_field=\"something\"",
alphaConfigContent: testAlphaConfig,
expectedOptions: func() *options.Options { return nil },
expectedErr: errors.New("failed to load core options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"),
}),
)
})

View File

@ -23,6 +23,7 @@ import (
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/v7/providers"
"github.com/stretchr/testify/assert"
@ -506,7 +507,7 @@ func TestStaticProxyUpstream(t *testing.T) {
ProxyUpstream: options.Upstream{
ID: "static-proxy",
Path: "/static-proxy",
Static: true,
Static: ptr.To(true),
},
})
if err != nil {
@ -2223,7 +2224,7 @@ func TestTrustedIPs(t *testing.T) {
{
ID: "static",
Path: "/",
Static: true,
Static: ptr.To(true),
},
},
}

View File

@ -6,6 +6,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
// TokenToSessionFunc takes a raw ID Token and converts it into a SessionState.
@ -40,7 +41,10 @@ func CreateTokenToSessionFunc(verify VerifyFunc) TokenToSessionFunc {
claims.Email = claims.Subject
}
if claims.Verified != nil && !*claims.Verified {
// Ensure email is verified
// If the email is not verified, return an error
// If the email_verified claim is missing, assume it is verified
if !ptr.Deref(claims.Verified, true) {
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
}

View File

@ -12,13 +12,13 @@ type AlphaOptions struct {
// UpstreamConfig is used to configure upstream servers.
// Once a user is authenticated, requests to the server will be proxied to
// these upstream servers based on the path mappings defined in this list.
UpstreamConfig UpstreamConfig `json:"upstreamConfig,omitempty"`
UpstreamConfig UpstreamConfig `yaml:"upstreamConfig,omitempty"`
// InjectRequestHeaders is used to configure headers that should be added
// to requests to upstream servers.
// Headers may source values from either the authenticated user's session
// or from a static secret value.
InjectRequestHeaders []Header `json:"injectRequestHeaders,omitempty"`
InjectRequestHeaders []Header `yaml:"injectRequestHeaders,omitempty"`
// InjectResponseHeaders is used to configure headers that should be added
// to responses from the proxy.
@ -27,35 +27,31 @@ type AlphaOptions struct {
// auth_request module.
// Headers may source values from either the authenticated user's session
// or from a static secret value.
InjectResponseHeaders []Header `json:"injectResponseHeaders,omitempty"`
InjectResponseHeaders []Header `yaml:"injectResponseHeaders,omitempty"`
// Server is used to configure the HTTP(S) server for the proxy application.
// You may choose to run both HTTP and HTTPS servers simultaneously.
// This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
// To use the secure server you must configure a TLS certificate and key.
Server Server `json:"server,omitempty"`
Server Server `yaml:"server,omitempty"`
// MetricsServer is used to configure the HTTP(S) server for metrics.
// You may choose to run both HTTP and HTTPS servers simultaneously.
// This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
// To use the secure server you must configure a TLS certificate and key.
MetricsServer Server `json:"metricsServer,omitempty"`
MetricsServer Server `yaml:"metricsServer,omitempty"`
// Providers is used to configure your provider. **Multiple-providers is not
// yet working.** [This feature is tracked in
// #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926)
Providers Providers `json:"providers,omitempty"`
Providers Providers `yaml:"providers,omitempty"`
}
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
// Initialize alpha options with default values and settings of the core options
func NewAlphaOptions(opts *Options) *AlphaOptions {
aOpts := &AlphaOptions{}
aOpts.ExtractFrom(opts)
return aOpts
}
// ExtractFrom populates the fields in the AlphaOptions with the values from
@ -68,3 +64,14 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) {
a.MetricsServer = opts.MetricsServer
a.Providers = opts.Providers
}
// MergeOptionsWithDefaults replaces alpha options in the Options struct
// with the values from the AlphaOptions and ensures the defaults
func (a *AlphaOptions) MergeOptionsWithDefaults(opts *Options) {
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
}

View File

@ -1,63 +0,0 @@
package options
import (
"fmt"
"strconv"
"time"
)
// SecretSource references an individual secret value.
// Only one source within the struct should be defined at any time.
type SecretSource struct {
// Value expects a base64 encoded string value.
Value []byte `json:"value,omitempty"`
// FromEnv expects the name of an environment variable.
FromEnv string `json:"fromEnv,omitempty"`
// FromFile expects a path to a file containing the secret value.
FromFile string `json:"fromFile,omitempty"`
}
// Duration is an alias for time.Duration so that we can ensure the marshalling
// and unmarshalling of string durations is done as users expect.
// Intentional blank line below to keep this first part of the comment out of
// any generated references.
// Duration is as string representation of a period of time.
// A duration string is a is a possibly signed sequence of decimal numbers,
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// +reference-gen:alias-name=string
type Duration time.Duration
// UnmarshalJSON parses the duration string and sets the value of duration
// to the value of the duration string.
func (d *Duration) UnmarshalJSON(data []byte) error {
input := string(data)
if unquoted, err := strconv.Unquote(input); err == nil {
input = unquoted
}
du, err := time.ParseDuration(input)
if err != nil {
return err
}
*d = Duration(du)
return nil
}
// MarshalJSON ensures that when the string is marshalled to JSON as a human
// readable string.
func (d *Duration) MarshalJSON() ([]byte, error) {
dStr := fmt.Sprintf("%q", d.Duration().String())
return []byte(dStr), nil
}
// Duration returns the time.Duration version of this Duration
func (d *Duration) Duration() time.Duration {
if d == nil {
return time.Duration(0)
}
return time.Duration(*d)
}

View File

@ -1,87 +0,0 @@
package options
import (
"encoding/json"
"errors"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Common", func() {
Context("Duration", func() {
type marshalJSONTableInput struct {
duration Duration
expectedJSON string
}
DescribeTable("MarshalJSON",
func(in marshalJSONTableInput) {
data, err := in.duration.MarshalJSON()
Expect(err).ToNot(HaveOccurred())
Expect(string(data)).To(Equal(in.expectedJSON))
var d Duration
Expect(json.Unmarshal(data, &d)).To(Succeed())
Expect(d).To(Equal(in.duration))
},
Entry("30 seconds", marshalJSONTableInput{
duration: Duration(30 * time.Second),
expectedJSON: "\"30s\"",
}),
Entry("1 minute", marshalJSONTableInput{
duration: Duration(1 * time.Minute),
expectedJSON: "\"1m0s\"",
}),
Entry("1 hour 15 minutes", marshalJSONTableInput{
duration: Duration(75 * time.Minute),
expectedJSON: "\"1h15m0s\"",
}),
Entry("A zero Duration", marshalJSONTableInput{
duration: Duration(0),
expectedJSON: "\"0s\"",
}),
)
type unmarshalJSONTableInput struct {
json string
expectedErr error
expectedDuration Duration
}
DescribeTable("UnmarshalJSON",
func(in unmarshalJSONTableInput) {
// A duration must be initialised pointer before UnmarshalJSON will work.
zero := Duration(0)
d := &zero
err := d.UnmarshalJSON([]byte(in.json))
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
} else {
Expect(err).ToNot(HaveOccurred())
}
Expect(d).ToNot(BeNil())
Expect(*d).To(Equal(in.expectedDuration))
},
Entry("1m", unmarshalJSONTableInput{
json: "\"1m\"",
expectedDuration: Duration(1 * time.Minute),
}),
Entry("30s", unmarshalJSONTableInput{
json: "\"30s\"",
expectedDuration: Duration(30 * time.Second),
}),
Entry("1h15m", unmarshalJSONTableInput{
json: "\"1h15m\"",
expectedDuration: Duration(75 * time.Minute),
}),
Entry("am", unmarshalJSONTableInput{
json: "\"am\"",
expectedErr: errors.New("time: invalid duration \"am\""),
expectedDuration: Duration(0),
}),
)
})
})

View File

@ -1,3 +1,3 @@
//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20220223111546-d3b50d1a591a
//go:generate -command reference-gen go run github.com/oauth2-proxy/tools/reference-gen/cmd/reference-gen@v0.0.0-20250404153144-32055bc45bc3
//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
package options

View File

@ -1,17 +1,26 @@
package options
import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
const (
// DefaultHeaderPreserveRequestValue is the default value for Header.PreserveRequestValue
DefaultHeaderPreserveRequestValue bool = false
// DefaultInsecureSkipHeaderNormalization is the default value for Header.InsecureSkipHeaderNormalization
DefaultInsecureSkipHeaderNormalization bool = false
)
// Header represents an individual header that will be added to a request or
// response header.
type Header struct {
// Name is the header name to be used for this set of values.
// Names should be unique within a list of Headers.
Name string `json:"name,omitempty"`
Name string `yaml:"name,omitempty"`
// PreserveRequestValue determines whether any values for this header
// should be preserved for the request to the upstream server.
// This option only applies to injected request headers.
// Defaults to false (headers that match this header will be stripped).
PreserveRequestValue bool `json:"preserveRequestValue,omitempty"`
PreserveRequestValue *bool `yaml:"preserveRequestValue,omitempty"`
// InsecureSkipHeaderNormalization disables normalizing the header name
// According to RFC 7230 Section 3.2 there aren't any rules about
@ -21,20 +30,20 @@ type Header struct {
// treated as the same header. Additionally underscores (_) in header names
// will be converted to dashes (-) when normalizing.
// Defaults to false (header names will be normalized).
InsecureSkipHeaderNormalization bool `json:"InsecureSkipHeaderNormalization,omitempty"`
InsecureSkipHeaderNormalization *bool `yaml:"InsecureSkipHeaderNormalization,omitempty"`
// Values contains the desired values for this header
Values []HeaderValue `json:"values,omitempty"`
Values []HeaderValue `yaml:"values,omitempty"`
}
// HeaderValue represents a single header value and the sources that can
// make up the header value
type HeaderValue struct {
// Allow users to load the value from a secret source
*SecretSource `json:",omitempty"`
*SecretSource `yaml:"secretSource,omitempty"`
// Allow users to load the value from a session claim
*ClaimSource `json:",omitempty"`
*ClaimSource `yaml:"claimSource,omitempty"`
}
// ClaimSource allows loading a header value from a claim within the session
@ -42,14 +51,39 @@ type ClaimSource struct {
// Claim is the name of the claim in the session that the value should be
// loaded from. Available claims: `access_token` `id_token` `created_at`
// `expires_on` `refresh_token` `email` `user` `groups` `preferred_username`.
Claim string `json:"claim,omitempty"`
Claim string `yaml:"claim,omitempty"`
// Prefix is an optional prefix that will be prepended to the value of the
// claim if it is non-empty.
Prefix string `json:"prefix,omitempty"`
Prefix string `yaml:"prefix,omitempty"`
// BasicAuthPassword converts this claim into a basic auth header.
// Note the value of claim will become the basic auth username and the
// basicAuthPassword will be used as the password value.
BasicAuthPassword *SecretSource `json:"basicAuthPassword,omitempty"`
BasicAuthPassword *SecretSource `yaml:"basicAuthPassword,omitempty"`
}
// EnsureDefaults sets any default values for Header fields.
func (h *Header) EnsureDefaults() {
if h.PreserveRequestValue == nil {
h.PreserveRequestValue = ptr.To(DefaultHeaderPreserveRequestValue)
}
for i := range h.Values {
h.Values[i].EnsureDefaults()
}
}
// EnsureDefaults sets any default values for HeaderValue fields.
func (hv *HeaderValue) EnsureDefaults() {
if hv.ClaimSource != nil {
hv.ClaimSource.EnsureDefaults()
}
if hv.SecretSource != nil {
hv.SecretSource.EnsureDefaults()
}
}
// EnsureDefaults sets any default values for ClaimSource fields.
func (hc *ClaimSource) EnsureDefaults() {
// No defaults to set currently
}

57
pkg/apis/options/hooks.go Normal file
View File

@ -0,0 +1,57 @@
package options
import (
"reflect"
"time"
"github.com/go-viper/mapstructure/v2"
)
// Duration is an alias for time.Duration so that we can ensure the marshalling
// and unmarshalling of string durations is done as users expect.
// Intentional blank line below to keep this first part of the comment out of
// any generated references.
// Duration is as string representation of a period of time.
// A duration string is a is a possibly signed sequence of decimal numbers,
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// Conversion from string or floating point to golang duration type
// This way floating points will be converted to seconds and strings
// of type 3s or 5m will be parsed with time.ParseDuration
func toDurationHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if t != reflect.TypeOf(time.Duration(0)) {
return data, nil
}
switch f.Kind() {
case reflect.String:
return time.ParseDuration(data.(string))
case reflect.Float64:
return time.Duration(data.(float64) * float64(time.Second)), nil
case reflect.Int64:
return time.Duration(data.(int64)), nil
default:
return data, nil
}
}
}
// StringToBytesHookFunc returns a DecodeHookFunc that converts string to []byte.
func stringToBytesHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() == reflect.String && t == reflect.TypeOf([]byte{}) {
return []byte(data.(string)), nil
}
return data, nil
}
}

View File

@ -0,0 +1,96 @@
package options
import (
"testing"
"time"
)
func TestToDurationHook(t *testing.T) {
type result struct {
Duration time.Duration `yaml:"duration"`
}
tests := []struct {
name string
input map[string]interface{}
out result
expected time.Duration
expectedErr bool
}{
{
name: "Valid String Duration with single unit",
input: map[string]interface{}{"duration": "3s"},
out: result{},
expected: 3 * time.Second,
expectedErr: false,
},
{
name: "Valid String Duration with multiple units",
input: map[string]interface{}{"duration": "1h20m30s"},
out: result{},
expected: 1*time.Hour + 20*time.Minute + 30*time.Second,
expectedErr: false,
},
{
name: "Valid Float Duration",
input: map[string]interface{}{"duration": 2.5},
out: result{},
expected: 2500 * time.Millisecond,
expectedErr: false,
},
{
name: "Valid Int64 Duration",
input: map[string]interface{}{"duration": int64(5000000000)},
out: result{},
expected: 5 * time.Second,
expectedErr: false,
},
{
name: "Invalid String",
input: map[string]interface{}{"duration": "invalid"},
out: result{},
expected: 0,
expectedErr: true,
},
{
name: "Unsupported Type",
input: map[string]interface{}{"duration": true},
out: result{},
expected: 0,
expectedErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result struct {
Duration time.Duration `yaml:"duration"`
}
err := Decode(tt.input, &result)
if (err != nil) != tt.expectedErr {
t.Errorf("expected error: %v, got: %v", tt.expectedErr, err)
}
if !tt.expectedErr {
if result.Duration != tt.expected {
t.Errorf("expected: %v, got: %v", tt.expected, result.Duration)
}
}
})
}
}
func TestStringToBytesHook(t *testing.T) {
var result struct {
Value []byte `yaml:"value"`
}
if err := Decode(map[string]interface{}{"value": "hello-world"}, &result); err != nil {
t.Fatal(err)
}
if string(result.Value) != "hello-world" {
t.Errorf("expected %q, got %q", "hello-world", string(result.Value))
}
}

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/spf13/pflag"
)
@ -95,6 +96,7 @@ func (l *LegacyOptions) ToOptions() (*Options, error) {
return nil, fmt.Errorf("error converting provider: %v", err)
}
l.Options.Providers = providers
l.Options.EnsureDefaults()
return &l.Options, nil
}
@ -136,18 +138,18 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
u.Path = "/"
}
flushInterval := Duration(l.FlushInterval)
timeout := Duration(l.Timeout)
flushInterval := l.FlushInterval
timeout := l.Timeout
upstream := Upstream{
ID: u.Path,
Path: u.Path,
URI: upstreamString,
InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify,
InsecureSkipTLSVerify: &l.SSLUpstreamInsecureSkipVerify,
PassHostHeader: &l.PassHostHeader,
ProxyWebSockets: &l.ProxyWebSockets,
FlushInterval: &flushInterval,
Timeout: &timeout,
DisableKeepAlives: l.DisableKeepAlives,
DisableKeepAlives: &l.DisableKeepAlives,
}
switch u.Scheme {
@ -164,7 +166,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
logger.Errorf("unable to convert %q to int, use default \"200\"", u.Host)
responseCode = 200
}
upstream.Static = true
upstream.Static = ptr.To(true)
upstream.StaticCode = &responseCode
// This is not allowed to be empty and must be unique
@ -175,12 +177,12 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
// Force defaults compatible with static responses
upstream.URI = ""
upstream.InsecureSkipTLSVerify = false
upstream.InsecureSkipTLSVerify = ptr.To(false)
upstream.DisableKeepAlives = ptr.To(false)
upstream.PassHostHeader = nil
upstream.ProxyWebSockets = nil
upstream.FlushInterval = nil
upstream.Timeout = nil
upstream.DisableKeepAlives = false
case "unix":
upstream.Path = "/"
}
@ -253,7 +255,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
}
for i := range requestHeaders {
requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
requestHeaders[i].PreserveRequestValue = ptr.To(!l.SkipAuthStripHeaders)
}
return requestHeaders
@ -287,7 +289,8 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
}
return Header{
Name: "Authorization",
Name: "Authorization",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -305,7 +308,8 @@ func getBasicAuthHeader(preferEmailToUser bool, basicAuthPassword string) Header
func getPassUserHeaders(preferEmailToUser bool) []Header {
headers := []Header{
{
Name: "X-Forwarded-Groups",
Name: "X-Forwarded-Groups",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -319,7 +323,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
if preferEmailToUser {
return append(headers,
Header{
Name: "X-Forwarded-User",
Name: "X-Forwarded-User",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -333,7 +338,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
return append(headers,
Header{
Name: "X-Forwarded-User",
Name: "X-Forwarded-User",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -343,7 +349,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
},
},
Header{
Name: "X-Forwarded-Email",
Name: "X-Forwarded-Email",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -357,7 +364,8 @@ func getPassUserHeaders(preferEmailToUser bool) []Header {
func getPassAccessTokenHeader() Header {
return Header{
Name: "X-Forwarded-Access-Token",
Name: "X-Forwarded-Access-Token",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -370,7 +378,8 @@ func getPassAccessTokenHeader() Header {
func getAuthorizationHeader() Header {
return Header{
Name: "Authorization",
Name: "Authorization",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -384,7 +393,8 @@ func getAuthorizationHeader() Header {
func getPreferredUsernameHeader() Header {
return Header{
Name: "X-Forwarded-Preferred-Username",
Name: "X-Forwarded-Preferred-Username",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -398,7 +408,8 @@ func getPreferredUsernameHeader() Header {
func getXAuthRequestHeaders() []Header {
headers := []Header{
{
Name: "X-Auth-Request-User",
Name: "X-Auth-Request-User",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -408,7 +419,8 @@ func getXAuthRequestHeaders() []Header {
},
},
{
Name: "X-Auth-Request-Email",
Name: "X-Auth-Request-Email",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -418,7 +430,8 @@ func getXAuthRequestHeaders() []Header {
},
},
{
Name: "X-Auth-Request-Preferred-Username",
Name: "X-Auth-Request-Preferred-Username",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -428,7 +441,8 @@ func getXAuthRequestHeaders() []Header {
},
},
{
Name: "X-Auth-Request-Groups",
Name: "X-Auth-Request-Groups",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -444,7 +458,8 @@ func getXAuthRequestHeaders() []Header {
func getXAuthRequestAccessTokenHeader() Header {
return Header{
Name: "X-Auth-Request-Access-Token",
Name: "X-Auth-Request-Access-Token",
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -684,11 +699,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
ClientSecretFile: l.ClientSecretFile,
Type: ProviderType(l.ProviderType),
CAFiles: l.ProviderCAFiles,
UseSystemTrustStore: l.UseSystemTrustStore,
UseSystemTrustStore: &l.UseSystemTrustStore,
LoginURL: l.LoginURL,
RedeemURL: l.RedeemURL,
ProfileURL: l.ProfileURL,
SkipClaimsFromProfileURL: l.SkipClaimsFromProfileURL,
SkipClaimsFromProfileURL: &l.SkipClaimsFromProfileURL,
ProtectedResource: l.ProtectedResource,
ValidateURL: l.ValidateURL,
Scope: l.Scope,
@ -701,10 +716,10 @@ func (l *LegacyProvider) convert() (Providers, error) {
// This part is out of the switch section for all providers that support OIDC
provider.OIDCConfig = OIDCOptions{
IssuerURL: l.OIDCIssuerURL,
InsecureAllowUnverifiedEmail: l.InsecureOIDCAllowUnverifiedEmail,
InsecureSkipIssuerVerification: l.InsecureOIDCSkipIssuerVerification,
InsecureSkipNonce: l.InsecureOIDCSkipNonce,
SkipDiscovery: l.SkipOIDCDiscovery,
InsecureAllowUnverifiedEmail: &l.InsecureOIDCAllowUnverifiedEmail,
InsecureSkipIssuerVerification: &l.InsecureOIDCSkipIssuerVerification,
InsecureSkipNonce: &l.InsecureOIDCSkipNonce,
SkipDiscovery: &l.SkipOIDCDiscovery,
JwksURL: l.OIDCJwksURL,
UserIDClaim: l.UserIDClaim,
EmailClaim: l.OIDCEmailClaim,
@ -772,15 +787,15 @@ func (l *LegacyProvider) convert() (Providers, error) {
Groups: l.GoogleGroups,
AdminEmail: l.GoogleAdminEmail,
ServiceAccountJSON: l.GoogleServiceAccountJSON,
UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials,
UseApplicationDefaultCredentials: &l.GoogleUseApplicationDefaultCredentials,
TargetPrincipal: l.GoogleTargetPrincipal,
UseOrganizationID: l.GoogleUseOrganizationID,
UseOrganizationID: &l.GoogleUseOrganizationID,
AdminAPIUserScope: l.GoogleAdminAPIUserScope,
}
case "entra-id":
provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{
AllowedTenants: l.EntraIDAllowedTenants,
FederatedTokenAuth: l.EntraIDFederatedTokenAuth,
FederatedTokenAuth: &l.EntraIDFederatedTokenAuth,
}
}

View File

@ -3,6 +3,8 @@ package options
import (
"time"
. "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -15,8 +17,8 @@ var _ = Describe("Legacy Options", func() {
legacyOpts := NewLegacyOptions()
// Set upstreams and related options to test their conversion
flushInterval := Duration(5 * time.Second)
timeout := Duration(5 * time.Second)
flushInterval := 5 * time.Second
timeout := 5 * time.Second
legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
legacyOpts.LegacyUpstreams.Timeout = time.Duration(timeout)
legacyOpts.LegacyUpstreams.PassHostHeader = true
@ -26,7 +28,6 @@ var _ = Describe("Legacy Options", func() {
legacyOpts.LegacyProvider.ClientID = "oauth-proxy"
legacyOpts.LegacyUpstreams.DisableKeepAlives = false
truth := true
staticCode := 204
opts.UpstreamServers = UpstreamConfig{
Upstreams: []Upstream{
@ -35,35 +36,35 @@ var _ = Describe("Legacy Options", func() {
Path: "/baz",
URI: "http://foo.bar/baz",
FlushInterval: &flushInterval,
InsecureSkipTLSVerify: true,
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: ptr.To(true),
PassHostHeader: ptr.To(true),
ProxyWebSockets: ptr.To(true),
Timeout: &timeout,
DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
},
{
ID: "/bar",
Path: "/bar",
URI: "file:///var/lib/website",
FlushInterval: &flushInterval,
InsecureSkipTLSVerify: true,
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: ptr.To(true),
PassHostHeader: ptr.To(true),
ProxyWebSockets: ptr.To(true),
Timeout: &timeout,
DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
},
{
ID: "static://204",
Path: "/",
URI: "",
Static: true,
Static: ptr.To(true),
StaticCode: &staticCode,
FlushInterval: nil,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.To(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
Timeout: nil,
DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
},
},
}
@ -71,7 +72,7 @@ var _ = Describe("Legacy Options", func() {
opts.InjectRequestHeaders = []Header{
{
Name: "X-Forwarded-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -82,7 +83,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -93,7 +94,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -104,7 +105,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -121,18 +122,21 @@ var _ = Describe("Legacy Options", func() {
BindAddress: "127.0.0.1:4180",
}
opts.Providers[0].ClientID = "oauth-proxy"
opts.Providers[0].ID = "google=oauth-proxy"
opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
opts.Providers[0].ClientID = "oauth-proxy"
opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"}
opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
opts.Providers[0].OIDCConfig.InsecureSkipNonce = ptr.To(true)
opts.Providers[0].OIDCConfig.InsecureSkipIssuerVerification = ptr.To(false)
opts.Providers[0].LoginURLParameters = []LoginURLParameter{
{Name: "approval_prompt", Default: []string{"force"}},
}
converted, err := legacyOpts.ToOptions()
opts.EnsureDefaults()
Expect(err).ToNot(HaveOccurred())
Expect(converted).To(Equal(opts))
Expect(converted).To(EqualOpts(opts))
})
})
@ -147,8 +151,8 @@ var _ = Describe("Legacy Options", func() {
skipVerify := true
passHostHeader := false
proxyWebSockets := true
flushInterval := Duration(5 * time.Second)
timeout := Duration(5 * time.Second)
flushInterval := 5 * time.Second
timeout := 5 * time.Second
disableKeepAlives := true
// Test cases and expected outcomes
@ -157,12 +161,12 @@ var _ = Describe("Legacy Options", func() {
ID: "/baz",
Path: "/baz",
URI: validHTTP,
InsecureSkipTLSVerify: skipVerify,
InsecureSkipTLSVerify: &skipVerify,
PassHostHeader: &passHostHeader,
ProxyWebSockets: &proxyWebSockets,
FlushInterval: &flushInterval,
Timeout: &timeout,
DisableKeepAlives: disableKeepAlives,
DisableKeepAlives: &disableKeepAlives,
}
// Test cases and expected outcomes
@ -171,12 +175,12 @@ var _ = Describe("Legacy Options", func() {
ID: "/",
Path: "/",
URI: emptyPathHTTP,
InsecureSkipTLSVerify: skipVerify,
InsecureSkipTLSVerify: &skipVerify,
PassHostHeader: &passHostHeader,
ProxyWebSockets: &proxyWebSockets,
FlushInterval: &flushInterval,
Timeout: &timeout,
DisableKeepAlives: disableKeepAlives,
DisableKeepAlives: &disableKeepAlives,
}
validFileWithFragment := "file:///var/lib/website#/bar"
@ -184,12 +188,12 @@ var _ = Describe("Legacy Options", func() {
ID: "/bar",
Path: "/bar",
URI: "file:///var/lib/website",
InsecureSkipTLSVerify: skipVerify,
InsecureSkipTLSVerify: &skipVerify,
PassHostHeader: &passHostHeader,
ProxyWebSockets: &proxyWebSockets,
FlushInterval: &flushInterval,
Timeout: &timeout,
DisableKeepAlives: disableKeepAlives,
DisableKeepAlives: &disableKeepAlives,
}
validStatic := "static://204"
@ -198,14 +202,14 @@ var _ = Describe("Legacy Options", func() {
ID: validStatic,
Path: "/",
URI: "",
Static: true,
Static: ptr.To(true),
StaticCode: &validStaticCode,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.To(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
FlushInterval: nil,
Timeout: nil,
DisableKeepAlives: false,
DisableKeepAlives: ptr.To(false),
}
invalidStatic := "static://abc"
@ -214,14 +218,14 @@ var _ = Describe("Legacy Options", func() {
ID: invalidStatic,
Path: "/",
URI: "",
Static: true,
Static: ptr.To(true),
StaticCode: &invalidStaticCode,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.To(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
FlushInterval: nil,
Timeout: nil,
DisableKeepAlives: false,
DisableKeepAlives: ptr.To(false),
}
invalidHTTP := ":foo"
@ -308,13 +312,13 @@ var _ = Describe("Legacy Options", func() {
}
withPreserveRequestValue := func(h Header, preserve bool) Header {
h.PreserveRequestValue = preserve
h.PreserveRequestValue = &preserve
return h
}
xForwardedUser := Header{
Name: "X-Forwarded-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -326,7 +330,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedEmail := Header{
Name: "X-Forwarded-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -338,7 +342,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedGroups := Header{
Name: "X-Forwarded-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -350,7 +354,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedPreferredUsername := Header{
Name: "X-Forwarded-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -362,7 +366,7 @@ var _ = Describe("Legacy Options", func() {
basicAuthHeader := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -378,7 +382,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedUserWithEmail := Header{
Name: "X-Forwarded-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -390,7 +394,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedAccessToken := Header{
Name: "X-Forwarded-Access-Token",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -402,7 +406,7 @@ var _ = Describe("Legacy Options", func() {
basicAuthHeaderWithEmail := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -418,7 +422,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestUser := Header{
Name: "X-Auth-Request-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -430,7 +434,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestEmail := Header{
Name: "X-Auth-Request-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -442,7 +446,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestGroups := Header{
Name: "X-Auth-Request-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -454,7 +458,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestPreferredUsername := Header{
Name: "X-Auth-Request-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -466,7 +470,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestAccessToken := Header{
Name: "X-Auth-Request-Access-Token",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -478,7 +482,7 @@ var _ = Describe("Legacy Options", func() {
authorizationHeader := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.To(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -943,37 +947,51 @@ var _ = Describe("Legacy Options", func() {
{Name: "approval_prompt", Default: []string{"force"}},
}
defaultProvider := Provider{
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
LoginURLParameters: defaultURLParams,
defaultOIDCOptions := OIDCOptions{
SkipDiscovery: ptr.To(false),
InsecureSkipNonce: ptr.To(false),
InsecureAllowUnverifiedEmail: ptr.To(false),
InsecureSkipIssuerVerification: ptr.To(false),
}
defaultGoogleOptions := GoogleOptions{
UseOrganizationID: ptr.To(false),
UseApplicationDefaultCredentials: ptr.To(false),
}
defaultLegacyProvider := LegacyProvider{
ClientID: clientID,
ProviderType: "google",
}
defaultProviderWithPrompt := Provider{
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
LoginURLParameters: []LoginURLParameter{
{Name: "prompt", Default: []string{"switch_user"}},
},
defaultProvider := Provider{
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
OIDCConfig: defaultOIDCOptions,
GoogleConfig: defaultGoogleOptions,
LoginURLParameters: defaultURLParams,
UseSystemTrustStore: ptr.To(false),
SkipClaimsFromProfileURL: ptr.To(false),
}
defaultLegacyProviderWithPrompt := LegacyProvider{
ClientID: clientID,
ProviderType: "google",
Prompt: "switch_user",
}
displayNameProvider := Provider{
ID: "displayName",
Name: "displayName",
ClientID: clientID,
Type: "google",
LoginURLParameters: defaultURLParams,
defaultProviderWithPrompt := Provider{
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
OIDCConfig: defaultOIDCOptions,
GoogleConfig: defaultGoogleOptions,
LoginURLParameters: []LoginURLParameter{
{Name: "prompt", Default: []string{"switch_user"}},
},
UseSystemTrustStore: ptr.To(false),
SkipClaimsFromProfileURL: ptr.To(false),
}
displayNameLegacyProvider := LegacyProvider{
@ -982,16 +1000,33 @@ var _ = Describe("Legacy Options", func() {
ProviderType: "google",
}
displayNameProvider := Provider{
ID: "displayName",
Name: "displayName",
ClientID: clientID,
Type: "google",
OIDCConfig: defaultOIDCOptions,
GoogleConfig: defaultGoogleOptions,
LoginURLParameters: defaultURLParams,
UseSystemTrustStore: ptr.To(false),
SkipClaimsFromProfileURL: ptr.To(false),
}
internalConfigProvider := Provider{
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
ID: "google=" + clientID,
ClientID: clientID,
Type: "google",
OIDCConfig: defaultOIDCOptions,
GoogleConfig: GoogleOptions{
AdminEmail: "email@email.com",
ServiceAccountJSON: "test.json",
Groups: []string{"1", "2"},
AdminEmail: "email@email.com",
ServiceAccountJSON: "test.json",
Groups: []string{"1", "2"},
UseOrganizationID: ptr.To(false),
UseApplicationDefaultCredentials: ptr.To(false),
},
LoginURLParameters: defaultURLParams,
LoginURLParameters: defaultURLParams,
UseSystemTrustStore: ptr.To(false),
SkipClaimsFromProfileURL: ptr.To(false),
}
internalConfigLegacyProvider := LegacyProvider{

View File

@ -9,10 +9,10 @@ import (
"strings"
"github.com/a8m/envsubst"
"github.com/ghodss/yaml"
"github.com/go-viper/mapstructure/v2"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.yaml.in/yaml/v3"
)
// 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
return nil
}
// LoadYAML will load a YAML based configuration file into the options interface provided.
func LoadYAML(configFileName string, opts interface{}) error {
buffer, err := loadAndSubstituteEnvs(configFileName)
if err != nil {
return err
}
// Generic interface for loading arbitrary yaml structure
var intermediate map[string]interface{}
if err := yaml.Unmarshal(buffer, &intermediate); err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
// Using mapstructure to decode arbitrary yaml structure into options and
// merge with existing values instead of overwriting everything. This is especially
// important as we have a lot of default values for boolean which are supposed to be
// true by default. Normally by just parsing through yaml all booleans that aren't
// referenced in the config file would be parsed as false and we cannot identify after
// the fact if they have been explicitly set to false or have not been referenced.
return Decode(intermediate, opts)
}
// Decode processes an input map and decodes it into a given struct while preserving default values.
// It ensures proper conversion of duration values from strings, floats, and int64 into time.Duration.
//
// Parameters:
// - input: A map[string]interface{} representing the input data.
// - result: A pointer to a struct where the decoded values will be stored.
//
// Returns:
// - An error if decoding fails or if there are unmapped keys.
func Decode(input interface{}, result interface{}) error {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
toDurationHookFunc(),
stringToBytesHookFunc(),
),
Metadata: nil, // Don't track any metadata
Result: result, // Decode the result into the prefilled options
TagName: "yaml", // Parse all fields that use the yaml tag
ZeroFields: false, // Don't clean the default values from the result map (options)
ErrorUnused: true, // Throw an error if keys have been used that aren't mapped to any struct fields
IgnoreUntaggedFields: true, // Ignore fields in structures that aren't tagged with yaml
})
if err != nil {
return fmt.Errorf("error creating decoder for config: %w", err)
}
if err := decoder.Decode(input); err != nil {
return fmt.Errorf("error decoding config: %w", err)
}
return nil
}
// loadAndSubstituteEnvs reads the yaml config into a generic byte buffer and
// substitute env references
func loadAndSubstituteEnvs(configFileName string) ([]byte, error) {
if configFileName == "" {
return nil, errors.New("no configuration file provided")
}
unparsedBuffer, err := os.ReadFile(configFileName)
if err != nil {
return nil, fmt.Errorf("unable to load config file: %w", err)
}
modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
if err != nil {
return nil, fmt.Errorf("error normalizing substitution string : %w", err)
}
buffer, err := envsubst.Bytes(modifiedBuffer)
if err != nil {
return nil, fmt.Errorf("error in substituting env variables : %w", err)
}
return buffer, nil
}
// registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
// to the fields in the options interface provided.
// Each exported field in the options must have a `cfg` tag otherwise an error will occur.
@ -140,47 +221,6 @@ func isUnexported(name string) bool {
return first == strings.ToLower(first)
}
// LoadYAML will load a YAML based configuration file into the options interface provided.
func LoadYAML(configFileName string, into interface{}) error {
buffer, err := loadAndParseYaml(configFileName)
if err != nil {
return err
}
// UnmarshalStrict will return an error if the config includes options that are
// not mapped to fields of the into struct
if err := yaml.UnmarshalStrict(buffer, into, yaml.DisallowUnknownFields); err != nil {
return fmt.Errorf("error unmarshalling config: %w", err)
}
return nil
}
// loadAndParseYaml reads the config from the filesystem and
// execute the environment variable substitution
func loadAndParseYaml(configFileName string) ([]byte, error) {
if configFileName == "" {
return nil, errors.New("no configuration file provided")
}
unparsedBuffer, err := os.ReadFile(configFileName)
if err != nil {
return nil, fmt.Errorf("unable to load config file: %w", err)
}
modifiedBuffer, err := normalizeSubstitution(unparsedBuffer)
if err != nil {
return nil, fmt.Errorf("error normalizing substitution string : %w", err)
}
buffer, err := envsubst.Bytes(modifiedBuffer)
if err != nil {
return nil, fmt.Errorf("error in substituting env variables : %w", err)
}
return buffer, nil
}
// normalizeSubstitution normalizes dollar signs ($) with numerals like
// $1 or $2 properly by correctly escaping them
func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {

View File

@ -155,7 +155,7 @@ var _ = Describe("Load", func() {
}
err := Load(configFileName, flagSet, input)
if o.expectedErr != nil {
Expect(err).To(MatchError(o.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(o.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}
@ -416,7 +416,7 @@ sub:
err := LoadYAML(configFileName, input)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}
@ -445,7 +445,7 @@ sub:
configFile: []byte("\tfoo: bar"),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error converting YAML to JSON: yaml: found character that cannot start any token"),
expectedErr: errors.New("error unmarshalling config: yaml: found character that cannot start any token"),
}),
Entry("with extra fields in the YAML", loadYAMLTableInput{
configFile: append(testOptionsConfigBytesFull, []byte("foo: bar\n")...),
@ -459,19 +459,19 @@ sub:
StringSliceOption: []string{"a", "b", "c"},
},
},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
expectedErr: errors.New("has invalid keys: foo"),
}),
Entry("with an incorrect type for a string field", loadYAMLTableInput{
configFile: []byte(`stringOption: ["a", "b"]`),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go struct field TestOptions.StringOption of type string"),
expectedErr: errors.New("'stringOption' expected type 'string', got unconvertible type"),
}),
Entry("with an incorrect type for an array field", loadYAMLTableInput{
configFile: []byte(`stringSliceOption: "a"`),
input: &TestOptions{},
expectedOutput: &TestOptions{},
expectedErr: errors.New("error unmarshalling config: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go struct field TestOptions.TestOptionSubStruct.StringSliceOption of type []string"),
expectedErr: errors.New("error decoding config: decoding failed due to the following error(s):\n\n'stringSliceOption' source data must be an array or slice, got string"),
}),
Entry("with a config file containing environment variable references", loadYAMLTableInput{
configFile: []byte("stringOption: ${TESTUSER}"),
@ -526,11 +526,13 @@ upstreamConfig:
injectRequestHeaders:
- name: X-Forwarded-User
values:
- claim: user
- claimSource:
claim: user
injectResponseHeaders:
- name: X-Secret
values:
- value: c2VjcmV0
- secretSource:
value: secret
`)
By("Creating a config file")
@ -548,7 +550,7 @@ injectResponseHeaders:
into := &AlphaOptions{}
Expect(LoadYAML(configFileName, into)).To(Succeed())
flushInterval := Duration(500 * time.Millisecond)
flushInterval := 500 * time.Millisecond
Expect(into).To(Equal(&AlphaOptions{
UpstreamConfig: UpstreamConfig{

View File

@ -71,19 +71,19 @@ package options
// character.
type LoginURLParameter struct {
// Name specifies the name of the query parameter.
Name string `json:"name"`
Name string `yaml:"name"`
// Default specifies a default value or values that will be
// passed to the IdP if not overridden.
//+optional
Default []string `json:"default,omitempty"`
Default []string `yaml:"default,omitempty"`
// Allow specifies rules about how the default (if any) may be
// overridden via the query string to `/oauth2/start`. Only
// values that match one or more of the allow rules will be
// forwarded to the IdP.
//+optional
Allow []URLParameterRule `json:"allow,omitempty"`
Allow []URLParameterRule `yaml:"allow,omitempty"`
}
// URLParameterRule represents a rule by which query parameters
@ -92,11 +92,11 @@ type LoginURLParameter struct {
// login URL. Either Value or Pattern should be supplied, not both.
type URLParameterRule struct {
// A Value rule matches just this specific value
Value *string `json:"value,omitempty"`
Value *string `yaml:"value,omitempty"`
// A Pattern rule gives a regular expression that must be matched by
// some substring of the value. The expression is _not_ automatically
// anchored to the start and end of the value, if you _want_ to restrict
// the whole parameter value you must anchor it yourself with `^` and `$`.
Pattern *string `json:"pattern,omitempty"`
Pattern *string `yaml:"pattern,omitempty"`
}

View File

@ -168,3 +168,23 @@ func NewFlagSet() *pflag.FlagSet {
return flagSet
}
// EnsureDefaults configures the defaults for all options
// to ensure no unexpected empty strings for enum types or nils for booleans
func (o *Options) EnsureDefaults() {
o.Providers.EnsureDefaults()
o.UpstreamServers.EnsureDefaults()
for i := range o.InjectRequestHeaders {
o.InjectRequestHeaders[i].EnsureDefaults()
}
for i := range o.InjectResponseHeaders {
o.InjectResponseHeaders[i].EnsureDefaults()
}
// TBD: Uncomment as we add EnsureDefaults methods
// o.Cookie.EnsureDefaults()
// o.Session.EnsureDefaults()
// o.Templates.EnsureDefaults()
// o.Logging.EnsureDefaults()
}

View File

@ -1,11 +1,53 @@
package options
import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
const (
// OIDCEmailClaim is the generic email claim used by the OIDC provider.
OIDCEmailClaim = "email"
OIDCEmailClaim string = "email"
// OIDCGroupsClaim is the generic groups claim used by the OIDC provider.
OIDCGroupsClaim = "groups"
OIDCGroupsClaim string = "groups"
// DefaultSkipDiscovery is the default value
// for OIDCOptions.SkipDiscovery
DefaultSkipDiscovery bool = false
// DefaultInsecureSkipNonce is the default value
// for OIDCOptions.InsecureSkipNonce
DefaultInsecureSkipNonce bool = true
// DefaultInsecureAllowUnverifiedEmail is the default value
// for OIDCOptions.InsecureAllowUnverifiedEmail
DefaultInsecureAllowUnverifiedEmail bool = false
// DefaultInsecureSkipIssuerVerification is the default value
// for OIDCOptions.InsecureSkipIssuerVerification
DefaultInsecureSkipIssuerVerification bool = false
// DefaultSkipClaimsFromProfileURL is the default value
// for Provider.SkipClaimsFromProfileURL
DefaultSkipClaimsFromProfileURL bool = false
// DefaultADFSSkipScope is the default value
// for ADFSOptions.SkipScope
DefaultADFSSkipScope bool = false
// DefaultMicrosoftEntraIDUseFederatedToken is the default value
// for MicrosoftEntraIDOptions.FederatedTokenAuth
DefaultMicrosoftEntraIDUseFederatedToken bool = false
// DefaultGoogleUseOrganizationID is the default value
// for GoogleOptions.UseOrganizationID
DefaultGoogleUseOrganizationID bool = false
// DefaultGoogleUseApplicationDefaultCredentials is the default values
// for GoogleOptions.UseApplicationDefaultCredentials
DefaultUseApplicationDefaultCredentials bool = false
// DefaultUseSystemTrustStore is the default value
// for Provider.UseSystemTrustStore
DefaultUseSystemTrustStore bool = false
)
// OIDCAudienceClaims is the generic audience claim list used by the OIDC provider.
@ -22,78 +64,78 @@ type Providers []Provider
type Provider struct {
// ClientID is the OAuth Client ID that is defined in the provider
// This value is required for all providers.
ClientID string `json:"clientID,omitempty"`
ClientID string `yaml:"clientID,omitempty"`
// ClientSecret is the OAuth Client Secret that is defined in the provider
// This value is required for all providers.
ClientSecret string `json:"clientSecret,omitempty"`
ClientSecret string `yaml:"clientSecret,omitempty"`
// ClientSecretFile is the name of the file
// containing the OAuth Client Secret, it will be used if ClientSecret is not set.
ClientSecretFile string `json:"clientSecretFile,omitempty"`
ClientSecretFile string `yaml:"clientSecretFile,omitempty"`
// KeycloakConfig holds all configurations for Keycloak provider.
KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"`
KeycloakConfig KeycloakOptions `yaml:"keycloakConfig,omitempty"`
// AzureConfig holds all configurations for Azure provider.
AzureConfig AzureOptions `json:"azureConfig,omitempty"`
AzureConfig AzureOptions `yaml:"azureConfig,omitempty"`
// MicrosoftEntraIDConfig holds all configurations for Entra ID provider.
MicrosoftEntraIDConfig MicrosoftEntraIDOptions `json:"microsoftEntraIDConfig,omitempty"`
MicrosoftEntraIDConfig MicrosoftEntraIDOptions `yaml:"microsoftEntraIDConfig,omitempty"`
// ADFSConfig holds all configurations for ADFS provider.
ADFSConfig ADFSOptions `json:"ADFSConfig,omitempty"`
ADFSConfig ADFSOptions `yaml:"ADFSConfig,omitempty"`
// BitbucketConfig holds all configurations for Bitbucket provider.
BitbucketConfig BitbucketOptions `json:"bitbucketConfig,omitempty"`
BitbucketConfig BitbucketOptions `yaml:"bitbucketConfig,omitempty"`
// GitHubConfig holds all configurations for GitHubC provider.
GitHubConfig GitHubOptions `json:"githubConfig,omitempty"`
GitHubConfig GitHubOptions `yaml:"githubConfig,omitempty"`
// GitLabConfig holds all configurations for GitLab provider.
GitLabConfig GitLabOptions `json:"gitlabConfig,omitempty"`
GitLabConfig GitLabOptions `yaml:"gitlabConfig,omitempty"`
// GoogleConfig holds all configurations for Google provider.
GoogleConfig GoogleOptions `json:"googleConfig,omitempty"`
GoogleConfig GoogleOptions `yaml:"googleConfig,omitempty"`
// OIDCConfig holds all configurations for OIDC provider
// or providers utilize OIDC configurations.
OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"`
OIDCConfig OIDCOptions `yaml:"oidcConfig,omitempty"`
// LoginGovConfig holds all configurations for LoginGov provider.
LoginGovConfig LoginGovOptions `json:"loginGovConfig,omitempty"`
LoginGovConfig LoginGovOptions `yaml:"loginGovConfig,omitempty"`
// ID should be a unique identifier for the provider.
// This value is required for all providers.
ID string `json:"id,omitempty"`
ID string `yaml:"id,omitempty"`
// Type is the OAuth provider
// must be set from the supported providers group,
// otherwise 'Google' is set as default
Type ProviderType `json:"provider,omitempty"`
Type ProviderType `yaml:"provider,omitempty"`
// Name is the providers display name
// if set, it will be shown to the users in the login page.
Name string `json:"name,omitempty"`
Name string `yaml:"name,omitempty"`
// CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
// If not specified, the default Go trust sources are used instead
CAFiles []string `json:"caFiles,omitempty"`
CAFiles []string `yaml:"caFiles,omitempty"`
// UseSystemTrustStore determines if your custom CA files and the system trust store are used
// If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.
UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty"`
UseSystemTrustStore *bool `yaml:"useSystemTrustStore,omitempty"`
// LoginURL is the authentication endpoint
LoginURL string `json:"loginURL,omitempty"`
LoginURL string `yaml:"loginURL,omitempty"`
// LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL
LoginURLParameters []LoginURLParameter `json:"loginURLParameters,omitempty"`
LoginURLParameters []LoginURLParameter `yaml:"loginURLParameters,omitempty"`
// AuthRequestResponseMode defines the response mode to request during authorization request
AuthRequestResponseMode string `json:"authRequestResponseMode,omitempty"`
AuthRequestResponseMode string `yaml:"authRequestResponseMode,omitempty"`
// RedeemURL is the token redemption endpoint
RedeemURL string `json:"redeemURL,omitempty"`
RedeemURL string `yaml:"redeemURL,omitempty"`
// ProfileURL is the profile access endpoint
ProfileURL string `json:"profileURL,omitempty"`
ProfileURL string `yaml:"profileURL,omitempty"`
// SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
// default set to 'false'
SkipClaimsFromProfileURL bool `json:"skipClaimsFromProfileURL,omitempty"`
SkipClaimsFromProfileURL *bool `yaml:"skipClaimsFromProfileURL,omitempty"`
// ProtectedResource is the resource that is protected (Azure AD and ADFS only)
ProtectedResource string `json:"resource,omitempty"`
ProtectedResource string `yaml:"resource,omitempty"`
// ValidateURL is the access token validation endpoint
ValidateURL string `json:"validateURL,omitempty"`
ValidateURL string `yaml:"validateURL,omitempty"`
// Scope is the OAuth scope specification
Scope string `json:"scope,omitempty"`
Scope string `yaml:"scope,omitempty"`
// AllowedGroups is a list of restrict logins to members of this group
AllowedGroups []string `json:"allowedGroups,omitempty"`
AllowedGroups []string `yaml:"allowedGroups,omitempty"`
// The code challenge method
CodeChallengeMethod string `json:"code_challenge_method,omitempty"`
CodeChallengeMethod string `yaml:"code_challenge_method,omitempty"`
// URL to call to perform backend logout, `{id_token}` would be replaced by the actual `id_token` if available in the session
BackendLogoutURL string `json:"backendLogoutURL"`
BackendLogoutURL string `yaml:"backendLogoutURL"`
}
// ProviderType is used to enumerate the different provider type options
@ -157,19 +199,19 @@ const (
type KeycloakOptions struct {
// Group enables to restrict login to members of indicated group
Groups []string `json:"groups,omitempty"`
Groups []string `yaml:"groups,omitempty"`
// Role enables to restrict login to users with role (only available when using the keycloak-oidc provider)
Roles []string `json:"roles,omitempty"`
Roles []string `yaml:"roles,omitempty"`
}
type AzureOptions struct {
// Tenant directs to a tenant-specific or common (tenant-independent) endpoint
// Default value is 'common'
Tenant string `json:"tenant,omitempty"`
Tenant string `yaml:"tenant,omitempty"`
// GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
// Default value is 'id'
GraphGroupField string `json:"graphGroupField,omitempty"`
GraphGroupField string `yaml:"graphGroupField,omitempty"`
}
type MicrosoftEntraIDOptions struct {
@ -177,116 +219,117 @@ type MicrosoftEntraIDOptions struct {
// issued by different issuers and OIDC issuer verification needs to be disabled.
// When not specified, all tenants are allowed. Redundant for single-tenant apps
// (regular ID token validation matches the issuer).
AllowedTenants []string `json:"allowedTenants,omitempty"`
AllowedTenants []string `yaml:"allowedTenants,omitempty"`
// FederatedTokenAuth enable oAuth2 client authentication with federated token projected
// by Entra Workload Identity plugin, instead of client secret.
FederatedTokenAuth bool `json:"federatedTokenAuth,omitempty"`
FederatedTokenAuth *bool `yaml:"federatedTokenAuth,omitempty"`
}
type ADFSOptions struct {
// Skip adding the scope parameter in login request
// Default value is 'false'
SkipScope bool `json:"skipScope,omitempty"`
SkipScope *bool `yaml:"skipScope,omitempty"`
}
type BitbucketOptions struct {
// Team sets restrict logins to members of this team
Team string `json:"team,omitempty"`
Team string `yaml:"team,omitempty"`
// Repository sets restrict logins to user with access to this repository
Repository string `json:"repository,omitempty"`
Repository string `yaml:"repository,omitempty"`
}
type GitHubOptions struct {
// Org sets restrict logins to members of this organisation
Org string `json:"org,omitempty"`
Org string `yaml:"org,omitempty"`
// Team sets restrict logins to members of this team
Team string `json:"team,omitempty"`
Team string `yaml:"team,omitempty"`
// Repo sets restrict logins to collaborators of this repository
Repo string `json:"repo,omitempty"`
Repo string `yaml:"repo,omitempty"`
// Token is the token to use when verifying repository collaborators
// it must have push access to the repository
Token string `json:"token,omitempty"`
Token string `yaml:"token,omitempty"`
// Users allows users with these usernames to login
// even if they do not belong to the specified org and team or collaborators
Users []string `json:"users,omitempty"`
Users []string `yaml:"users,omitempty"`
}
type GitLabOptions struct {
// Group sets restrict logins to members of this group
Group []string `json:"group,omitempty"`
Group []string `yaml:"group,omitempty"`
// Projects restricts logins to members of these projects
Projects []string `json:"projects,omitempty"`
Projects []string `yaml:"projects,omitempty"`
}
type GoogleOptions struct {
// Groups sets restrict logins to members of this Google group
Groups []string `json:"group,omitempty"`
Groups []string `yaml:"group,omitempty"`
// AdminEmail is the Google admin to impersonate for api calls
AdminEmail string `json:"adminEmail,omitempty"`
AdminEmail string `yaml:"adminEmail,omitempty"`
// ServiceAccountJSON is the path to the service account json credentials
ServiceAccountJSON string `json:"serviceAccountJson,omitempty"`
ServiceAccountJSON string `yaml:"serviceAccountJson,omitempty"`
// UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON
UseApplicationDefaultCredentials bool `json:"useApplicationDefaultCredentials,omitempty"`
UseApplicationDefaultCredentials *bool `yaml:"useApplicationDefaultCredentials,omitempty"`
// TargetPrincipal is the Google Service Account used for Application Default Credentials
TargetPrincipal string `json:"targetPrincipal,omitempty"`
TargetPrincipal string `yaml:"targetPrincipal,omitempty"`
// UseOrganizationId indicates whether to use the organization ID as the UserName claim
UseOrganizationID bool `json:"useOrganizationID,omitempty"`
UseOrganizationID *bool `yaml:"useOrganizationID,omitempty"`
// admin scope needed for fetching user organization information from admin api, can be one of cloud, user or defaults to readonly
AdminAPIUserScope string `json:"adminAPIUserScope,omitempty"`
AdminAPIUserScope string `yaml:"adminAPIUserScope,omitempty"`
}
type OIDCOptions struct {
// IssuerURL is the OpenID Connect issuer URL
// eg: https://accounts.google.com
IssuerURL string `json:"issuerURL,omitempty"`
IssuerURL string `yaml:"issuerURL,omitempty"`
// InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
// default set to 'false'
InsecureAllowUnverifiedEmail bool `json:"insecureAllowUnverifiedEmail,omitempty"`
InsecureAllowUnverifiedEmail *bool `yaml:"insecureAllowUnverifiedEmail,omitempty"`
// InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
// default set to 'false'
InsecureSkipIssuerVerification bool `json:"insecureSkipIssuerVerification,omitempty"`
InsecureSkipIssuerVerification *bool `yaml:"insecureSkipIssuerVerification,omitempty"`
// InsecureSkipNonce skips verifying the ID Token's nonce claim that must match
// the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
// after the initial OAuth redeem & subsequent token refreshes.
// default set to 'true'
// Warning: In a future release, this will change to 'false' by default for enhanced security.
InsecureSkipNonce bool `json:"insecureSkipNonce,omitempty"`
InsecureSkipNonce *bool `yaml:"insecureSkipNonce,omitempty"`
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
// default set to 'false'
SkipDiscovery bool `json:"skipDiscovery,omitempty"`
SkipDiscovery *bool `yaml:"skipDiscovery,omitempty"`
// JwksURL is the OpenID Connect JWKS URL
// eg: https://www.googleapis.com/oauth2/v3/certs
JwksURL string `json:"jwksURL,omitempty"`
JwksURL string `yaml:"jwksURL,omitempty"`
// PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
// for verifying JWT tokens
PublicKeyFiles []string `json:"publicKeyFiles,omitempty"`
PublicKeyFiles []string `yaml:"publicKeyFiles,omitempty"`
// EmailClaim indicates which claim contains the user email,
// default set to 'email'
EmailClaim string `json:"emailClaim,omitempty"`
EmailClaim string `yaml:"emailClaim,omitempty"`
// GroupsClaim indicates which claim contains the user groups
// default set to 'groups'
GroupsClaim string `json:"groupsClaim,omitempty"`
GroupsClaim string `yaml:"groupsClaim,omitempty"`
// UserIDClaim indicates which claim contains the user ID
// default set to 'email'
UserIDClaim string `json:"userIDClaim,omitempty"`
UserIDClaim string `yaml:"userIDClaim,omitempty"`
// AudienceClaim allows to define any claim that is verified against the client id
// By default `aud` claim is used for verification.
AudienceClaims []string `json:"audienceClaims,omitempty"`
AudienceClaims []string `yaml:"audienceClaims,omitempty"`
// ExtraAudiences is a list of additional audiences that are allowed
// to pass verification in addition to the client id.
ExtraAudiences []string `json:"extraAudiences,omitempty"`
ExtraAudiences []string `yaml:"extraAudiences,omitempty"`
}
type LoginGovOptions struct {
// JWTKey is a private key in PEM format used to sign JWT,
JWTKey string `json:"jwtKey,omitempty"`
JWTKey string `yaml:"jwtKey,omitempty"`
// JWTKeyFile is a path to the private key file in PEM format used to sign the JWT
JWTKeyFile string `json:"jwtKeyFile,omitempty"`
JWTKeyFile string `yaml:"jwtKeyFile,omitempty"`
// PubJWKURL is the JWK pubkey access endpoint
PubJWKURL string `json:"pubjwkURL,omitempty"`
PubJWKURL string `yaml:"pubjwkURL,omitempty"`
}
// Legacy default providers configuration
func providerDefaults() Providers {
providers := Providers{
{
@ -295,9 +338,9 @@ func providerDefaults() Providers {
Tenant: "common",
},
OIDCConfig: OIDCOptions{
InsecureAllowUnverifiedEmail: false,
InsecureSkipNonce: true,
SkipDiscovery: false,
InsecureAllowUnverifiedEmail: ptr.To(DefaultInsecureAllowUnverifiedEmail),
InsecureSkipNonce: ptr.To(DefaultInsecureSkipNonce),
SkipDiscovery: ptr.To(DefaultSkipDiscovery),
UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
EmailClaim: OIDCEmailClaim,
GroupsClaim: OIDCGroupsClaim,
@ -308,3 +351,74 @@ func providerDefaults() Providers {
}
return providers
}
// EnsureDefaults sets any default values for Providers fields.
func (p Providers) EnsureDefaults() {
for i := range p {
p[i].EnsureDefaults()
}
}
// EnsureDefaults sets any default values for Provider fields.
func (p *Provider) EnsureDefaults() {
if p.SkipClaimsFromProfileURL == nil {
p.SkipClaimsFromProfileURL = ptr.To(DefaultSkipClaimsFromProfileURL)
}
if p.UseSystemTrustStore == nil {
p.UseSystemTrustStore = ptr.To(DefaultUseSystemTrustStore)
}
p.OIDCConfig.EnsureDefaults()
p.MicrosoftEntraIDConfig.EnsureDefaults()
p.ADFSConfig.EnsureDefaults()
p.GoogleConfig.EnsureDefaults()
}
// EnsureDefaults sets any default values for OIDCOptions fields.
func (o *OIDCOptions) EnsureDefaults() {
// Ensure OIDC defaults
if o.InsecureAllowUnverifiedEmail == nil {
o.InsecureAllowUnverifiedEmail = ptr.To(DefaultInsecureAllowUnverifiedEmail)
}
if o.InsecureSkipNonce == nil {
o.InsecureSkipNonce = ptr.To(DefaultInsecureSkipNonce)
}
if o.SkipDiscovery == nil {
o.SkipDiscovery = ptr.To(DefaultSkipDiscovery)
}
if o.UserIDClaim == "" {
o.UserIDClaim = OIDCEmailClaim
}
if o.EmailClaim == "" {
o.EmailClaim = OIDCEmailClaim
}
if o.GroupsClaim == "" {
o.GroupsClaim = OIDCGroupsClaim
}
if len(o.AudienceClaims) == 0 {
o.AudienceClaims = OIDCAudienceClaims
}
}
// EnsureDefaults sets any default values for MicrosoftEntraIDOptions fields.
func (me *MicrosoftEntraIDOptions) EnsureDefaults() {
if me.FederatedTokenAuth == nil {
me.FederatedTokenAuth = ptr.To(DefaultMicrosoftEntraIDUseFederatedToken)
}
}
// EnsureDefaults sets any default values for ADFSOptions fields.
func (a *ADFSOptions) EnsureDefaults() {
if a.SkipScope == nil {
a.SkipScope = ptr.To(DefaultADFSSkipScope)
}
}
// EnsureDefaults sets any default values for GoogleOptions fields.
func (g *GoogleOptions) EnsureDefaults() {
g.UseOrganizationID = ptr.To(DefaultGoogleUseOrganizationID)
if g.UseApplicationDefaultCredentials == nil {
g.UseApplicationDefaultCredentials = ptr.To(DefaultUseApplicationDefaultCredentials)
}
}

View File

@ -0,0 +1,19 @@
package options
// SecretSource references an individual secret value.
// Only one source within the struct should be defined at any time.
type SecretSource struct {
// Value expects a base64 encoded string value.
Value []byte `yaml:"value,omitempty"`
// FromEnv expects the name of an environment variable.
FromEnv string `yaml:"fromEnv,omitempty"`
// FromFile expects a path to a file containing the secret value.
FromFile string `yaml:"fromFile,omitempty"`
}
// EnsureDefaults sets any default values for SecretSource fields.
func (ss *SecretSource) EnsureDefaults() {
// No defaults to set currently
}

View File

@ -4,15 +4,15 @@ package options
type Server struct {
// BindAddress is the address on which to serve traffic.
// Leave blank or set to "-" to disable.
BindAddress string
BindAddress string `yaml:"bindAddress,omitempty"`
// SecureBindAddress is the address on which to serve secure traffic.
// Leave blank or set to "-" to disable.
SecureBindAddress string
SecureBindAddress string `yaml:"secureBindAddress,omitempty"`
// TLS contains the information for loading the certificate and key for the
// secure traffic and further configuration for the TLS server.
TLS *TLS
TLS *TLS `yaml:"tls,omitempty"`
}
// TLS contains the information for loading a TLS certificate and key
@ -20,15 +20,15 @@ type Server struct {
type TLS struct {
// Key is the TLS key data to use.
// Typically this will come from a file.
Key *SecretSource
Key *SecretSource `yaml:"key,omitempty"`
// Cert is the TLS certificate data to use.
// Typically this will come from a file.
Cert *SecretSource
Cert *SecretSource `yaml:"cert,omitempty"`
// MinVersion is the minimal TLS version that is acceptable.
// E.g. Set to "TLS1.3" to select TLS version 1.3
MinVersion string
MinVersion string `yaml:"minVersion,omitempty"`
// CipherSuites is a list of TLS cipher suites that are allowed.
// E.g.:
@ -36,5 +36,5 @@ type TLS struct {
// - TLS_RSA_WITH_AES_256_GCM_SHA384
// If not specified, the default Go safe cipher list is used.
// List of valid cipher suites can be found in the [crypto/tls documentation](https://pkg.go.dev/crypto/tls#pkg-constants).
CipherSuites []string
CipherSuites []string `yaml:"cipherSuites,omitempty"`
}

View File

@ -1,24 +1,50 @@
package options
import "time"
import (
"net/http"
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
const (
// DefaultUpstreamFlushInterval is the default value for the Upstream FlushInterval.
DefaultUpstreamFlushInterval = 1 * time.Second
DefaultUpstreamFlushInterval time.Duration = 1 * time.Second
// DefaultUpstreamTimeout is the maximum duration a network dial to a upstream server for a response.
DefaultUpstreamTimeout = 30 * time.Second
DefaultUpstreamTimeout time.Duration = 30 * time.Second
// DefaultUpstreamStatic determines if upstreams are static by default.
DefaultUpstreamStatic bool = false
// DefaultUpstreamStaticCode is the default response code for static upstreams.
DefaultUpstreamStaticCode int = http.StatusOK // 200
// DefaultUpstreamProxyRawPath determines if upstreams will proxy the raw url path by default.
DefaultUpstreamProxyRawPath bool = false
// DefaultUpstreamInsecureSkipTLSVerify determines if upstreams will skip TLS verification by default.
DefaultUpsteamInsecureSkipTLSVerify bool = false
// DefaultUpstreamPassHostHeader determines if upstreams will pass the host header by default.
DefaultUpstreamPassHostHeader bool = true
// DefaultUpstreamProxyWebSockets determines if upstreams will proxy websockets by default.
DefaultUpstreamProxyWebSockets bool = true
// DefaultUpstreamDisableKeepAlives determines if upstreams will disable keep-alives by default.
DefaultUpstreamDisableKeepAlives bool = false
)
// UpstreamConfig is a collection of definitions for upstream servers.
type UpstreamConfig struct {
// ProxyRawPath will pass the raw url path to upstream allowing for urls
// like: "/%2F/" which would otherwise be redirected to "/"
ProxyRawPath bool `json:"proxyRawPath,omitempty"`
ProxyRawPath *bool `yaml:"proxyRawPath,omitempty"`
// Upstreams represents the configuration for the upstream servers.
// Requests will be proxied to this upstream if the path matches the request path.
Upstreams []Upstream `json:"upstreams,omitempty"`
Upstreams []Upstream `yaml:"upstreams,omitempty"`
}
// Upstream represents the configuration for an upstream server.
@ -26,7 +52,7 @@ type UpstreamConfig struct {
type Upstream struct {
// ID should be a unique identifier for the upstream.
// This value is required for all upstreams.
ID string `json:"id,omitempty"`
ID string `yaml:"id,omitempty"`
// Path is used to map requests to the upstream server.
// The closest match will take precedence and all Paths must be unique.
@ -36,7 +62,7 @@ type Upstream struct {
// - `^/foo$`: Match only the explicit path `/foo`
// - `^/bar/$`: Match any path prefixed with `/bar/`
// - `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget
Path string `json:"path,omitempty"`
Path string `yaml:"path,omitempty"`
// RewriteTarget allows users to rewrite the request path before it is sent to
// the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem
@ -46,7 +72,7 @@ type Upstream struct {
// the request `/baz/abc/123` to `/foo/abc/123` before proxying to the
// upstream server. Or if the upstream were `file:///app`, a request for
// `/baz/info.html` would return the contents of the file `/app/foo/info.html`.
RewriteTarget string `json:"rewriteTarget,omitempty"`
RewriteTarget string `yaml:"rewriteTarget,omitempty"`
// The URI of the upstream server. This may be an HTTP(S) server of a File
// based URL. It may include a path, in which case all requests will be served
@ -58,43 +84,78 @@ type Upstream struct {
// - file://host/path
// If the URI's path is "/base" and the incoming request was for "/dir",
// the upstream request will be for "/base/dir".
URI string `json:"uri,omitempty"`
URI string `yaml:"uri,omitempty"`
// InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
// This option is insecure and will allow potential Man-In-The-Middle attacks
// between OAuth2 Proxy and the upstream server.
// Defaults to false.
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
InsecureSkipTLSVerify *bool `yaml:"insecureSkipTLSVerify,omitempty"`
// Static will make all requests to this upstream have a static response.
// The response will have a body of "Authenticated" and a response code
// matching StaticCode.
// If StaticCode is not set, the response will return a 200 response.
Static bool `json:"static,omitempty"`
Static *bool `yaml:"static,omitempty"`
// StaticCode determines the response code for the Static response.
// This option can only be used with Static enabled.
StaticCode *int `json:"staticCode,omitempty"`
StaticCode *int `yaml:"staticCode,omitempty"`
// FlushInterval is the period between flushing the response buffer when
// streaming response from the upstream.
// Defaults to 1 second.
FlushInterval *Duration `json:"flushInterval,omitempty"`
FlushInterval *time.Duration `yaml:"flushInterval,omitempty"`
// PassHostHeader determines whether the request host header should be proxied
// to the upstream server.
// Defaults to true.
PassHostHeader *bool `json:"passHostHeader,omitempty"`
PassHostHeader *bool `yaml:"passHostHeader,omitempty"`
// ProxyWebSockets enables proxying of websockets to upstream servers
// Defaults to true.
ProxyWebSockets *bool `json:"proxyWebSockets,omitempty"`
ProxyWebSockets *bool `yaml:"proxyWebSockets,omitempty"`
// Timeout is the maximum duration the server will wait for a response from the upstream server.
// Defaults to 30 seconds.
Timeout *Duration `json:"timeout,omitempty"`
Timeout *time.Duration `yaml:"timeout,omitempty"`
// DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
// Defaults to false.
DisableKeepAlives bool `json:"disableKeepAlives,omitempty"`
DisableKeepAlives *bool `yaml:"disableKeepAlives,omitempty"`
}
// EnsureDefaults sets any default values for UpstreamConfig fields.
func (uc *UpstreamConfig) EnsureDefaults() {
if uc.ProxyRawPath == nil {
uc.ProxyRawPath = ptr.To(DefaultUpstreamProxyRawPath)
}
for i := range uc.Upstreams {
uc.Upstreams[i].EnsureDefaults()
}
}
// EnsureDefaults sets any default values for Upstream fields.
func (u *Upstream) EnsureDefaults() {
if u.InsecureSkipTLSVerify == nil {
u.InsecureSkipTLSVerify = ptr.To(DefaultUpsteamInsecureSkipTLSVerify)
}
if u.Static == nil {
u.Static = ptr.To(DefaultUpstreamStatic)
}
if u.FlushInterval == nil {
u.FlushInterval = ptr.To(DefaultUpstreamFlushInterval)
}
if u.PassHostHeader == nil {
u.PassHostHeader = ptr.To(DefaultUpstreamPassHostHeader)
}
if u.ProxyWebSockets == nil {
u.ProxyWebSockets = ptr.To(DefaultUpstreamProxyWebSockets)
}
if u.Timeout == nil {
u.Timeout = ptr.To(DefaultUpstreamTimeout)
}
if u.DisableKeepAlives == nil {
u.DisableKeepAlives = ptr.To(DefaultUpstreamDisableKeepAlives)
}
}

View File

@ -8,15 +8,12 @@ import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func timePtr(t time.Time) *time.Time {
return &t
}
func TestCreatedAtNow(t *testing.T) {
g := NewWithT(t)
ss := &SessionState{}
@ -137,10 +134,10 @@ func TestString(t *testing.T) {
}
func TestIsExpired(t *testing.T) {
s := &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(-1) * time.Minute))}
s := &SessionState{ExpiresOn: ptr.To(time.Now().Add(time.Duration(-1) * time.Minute))}
assert.Equal(t, true, s.IsExpired())
s = &SessionState{ExpiresOn: timePtr(time.Now().Add(time.Duration(1) * time.Minute))}
s = &SessionState{ExpiresOn: ptr.To(time.Now().Add(time.Duration(1) * time.Minute))}
assert.Equal(t, false, s.IsExpired())
s = &SessionState{}
@ -154,7 +151,7 @@ func TestAge(t *testing.T) {
assert.Equal(t, time.Duration(0), ss.Age())
// Set CreatedAt to 1 hour ago
ss.CreatedAt = timePtr(time.Now().Add(-1 * time.Hour))
ss.CreatedAt = ptr.To(time.Now().Add(-1 * time.Hour))
assert.Equal(t, time.Hour, ss.Age().Round(time.Minute))
}

View File

@ -9,6 +9,7 @@ import (
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/header"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, error) {
@ -27,7 +28,7 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro
func newStripHeaders(headers []options.Header) alice.Constructor {
headersToStrip := []options.Header{}
for _, header := range headers {
if !header.PreserveRequestValue {
if !ptr.Deref(header.PreserveRequestValue, options.DefaultHeaderPreserveRequestValue) {
headersToStrip = append(headersToStrip, header)
}
}
@ -53,7 +54,7 @@ func flattenHeaders(headers http.Header) {
func stripHeaders(headers []options.Header, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
for _, header := range headers {
if header.InsecureSkipHeaderNormalization {
if ptr.Deref(header.InsecureSkipHeaderNormalization, options.DefaultInsecureSkipHeaderNormalization) {
req.Header.Del(header.Name)
continue
}

View File

@ -8,6 +8,7 @@ import (
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -115,7 +116,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.To(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
@ -160,7 +161,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.To(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
@ -230,7 +231,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "X-Auth-Request-User",
InsecureSkipHeaderNormalization: true,
InsecureSkipHeaderNormalization: ptr.To(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{Claim: "user"},
@ -385,7 +386,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.To(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
@ -432,7 +433,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.To(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{

View File

@ -304,7 +304,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
authorizationHeader: fmt.Sprintf("Bearer %s", nonVerifiedToken),
expectedErr: k8serrors.NewAggregate([]error{
errors.New("unable to verify bearer token"),
errors.New("oidc: malformed jwt: oidc: malformed jwt payload: illegal base64 data at input byte 8"),
errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"),
}),
expectedSession: nil,
}),
@ -317,7 +317,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
authorizationHeader: "Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6",
expectedErr: k8serrors.NewAggregate([]error{
errors.New("unable to verify bearer token"),
errors.New("oidc: malformed jwt: oidc: malformed jwt payload: illegal base64 data at input byte 8"),
errors.New("oidc: malformed jwt: illegal base64 data at input byte 8"),
}),
expectedSession: nil,
}),

View File

@ -104,8 +104,8 @@ var _ = Describe("Result suite", func() {
Context("UnmarshalInto", func() {
type testStruct struct {
A string `json:"a"`
B int `json:"b"`
A string `yaml:"a"`
B int `yaml:"b"`
}
type unmarshalIntoTableInput struct {

View File

@ -11,6 +11,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
const (
@ -53,7 +54,7 @@ func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *option
// Set up a WebSocket proxy if required
var wsProxy http.Handler
if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
if ptr.Deref(upstream.ProxyWebSockets, options.DefaultUpstreamProxyWebSockets) {
wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
}
@ -137,26 +138,26 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
// Change default duration for waiting for an upstream response
if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration()
transport.ResponseHeaderTimeout = *upstream.Timeout
}
// Configure options on the SingleHostReverseProxy
if upstream.FlushInterval != nil {
proxy.FlushInterval = upstream.FlushInterval.Duration()
proxy.FlushInterval = *upstream.FlushInterval
} else {
proxy.FlushInterval = options.DefaultUpstreamFlushInterval
}
// InsecureSkipVerify is a configurable option we allow
/* #nosec G402 */
if upstream.InsecureSkipTLSVerify {
if ptr.Deref(upstream.InsecureSkipTLSVerify, options.DefaultUpsteamInsecureSkipTLSVerify) {
transport.TLSClientConfig.InsecureSkipVerify = true
}
// Ensure we always pass the original request path
setProxyDirector(proxy)
if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
if !ptr.Deref(upstream.PassHostHeader, options.DefaultUpstreamPassHostHeader) {
setProxyUpstreamHostHeader(proxy, target)
}
@ -168,7 +169,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
// Pass on DisableKeepAlives to the transport settings
// to allow for disabling HTTP keep-alive connections
transport.DisableKeepAlives = upstream.DisableKeepAlives
transport.DisableKeepAlives = ptr.Deref(upstream.DisableKeepAlives, options.DefaultUpstreamDisableKeepAlives)
// Apply the customized transport to our proxy before returning it
proxy.Transport = transport
@ -200,14 +201,14 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
}
// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
func newWebSocketReverseProxy(u *url.URL, skipTLSVerify *bool) http.Handler {
wsProxy := httputil.NewSingleHostReverseProxy(u)
// Inherit default transport options from Go's stdlib
transport := http.DefaultTransport.(*http.Transport).Clone()
/* #nosec G402 */
if skipTLSVerify {
if ptr.Deref(skipTLSVerify, false) {
transport.TLSClientConfig.InsecureSkipVerify = true
}

View File

@ -15,16 +15,15 @@ import (
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"golang.org/x/net/websocket"
)
var _ = Describe("HTTP Upstream Suite", func() {
defaultFlushInterval := options.Duration(options.DefaultUpstreamFlushInterval)
defaultTimeout := options.Duration(options.DefaultUpstreamTimeout)
truth := true
falsum := false
defaultFlushInterval := options.DefaultUpstreamFlushInterval
defaultTimeout := options.DefaultUpstreamTimeout
type httpUpstreamTableInput struct {
id string
@ -57,15 +56,15 @@ var _ = Describe("HTTP Upstream Suite", func() {
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
rw := httptest.NewRecorder()
flush := options.Duration(1 * time.Second)
flush := 1 * time.Second
timeout := options.Duration(options.DefaultUpstreamTimeout)
timeout := options.DefaultUpstreamTimeout
upstream := options.Upstream{
ID: in.id,
PassHostHeader: &in.passUpstreamHostHeader,
ProxyWebSockets: &falsum,
InsecureSkipTLSVerify: false,
ProxyWebSockets: ptr.To(false),
InsecureSkipTLSVerify: ptr.To(false),
FlushInterval: &flush,
Timeout: &timeout,
}
@ -343,9 +342,9 @@ var _ = Describe("HTTP Upstream Suite", func() {
upstream := options.Upstream{
ID: "noPassHost",
PassHostHeader: &falsum,
ProxyWebSockets: &falsum,
InsecureSkipTLSVerify: false,
PassHostHeader: ptr.To(false),
ProxyWebSockets: ptr.To(false),
InsecureSkipTLSVerify: ptr.To(false),
FlushInterval: &defaultFlushInterval,
Timeout: &defaultTimeout,
}
@ -373,11 +372,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
type newUpstreamTableInput struct {
proxyWebSockets bool
flushInterval options.Duration
flushInterval time.Duration
skipVerify bool
sigData *options.SignatureData
errorHandler func(http.ResponseWriter, *http.Request, error)
timeout options.Duration
timeout time.Duration
disableKeepAlives bool
}
@ -389,10 +388,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
upstream := options.Upstream{
ID: "foo123",
FlushInterval: &in.flushInterval,
InsecureSkipTLSVerify: in.skipVerify,
InsecureSkipTLSVerify: &in.skipVerify,
ProxyWebSockets: &in.proxyWebSockets,
Timeout: &in.timeout,
DisableKeepAlives: in.disableKeepAlives,
DisableKeepAlives: &in.disableKeepAlives,
}
handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler)
@ -406,10 +405,10 @@ var _ = Describe("HTTP Upstream Suite", func() {
proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
Expect(ok).To(BeTrue())
Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
transport, ok := proxy.Transport.(*http.Transport)
Expect(ok).To(BeTrue())
Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout.Duration()))
Expect(transport.ResponseHeaderTimeout).To(Equal(in.timeout))
Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
if in.skipVerify {
Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true))
@ -428,7 +427,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
}),
Entry("with a non standard flush interval", &newUpstreamTableInput{
proxyWebSockets: false,
flushInterval: options.Duration(5 * time.Second),
flushInterval: 5 * time.Second,
skipVerify: false,
sigData: nil,
errorHandler: nil,
@ -466,7 +465,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
skipVerify: false,
sigData: nil,
errorHandler: nil,
timeout: options.Duration(5 * time.Second),
timeout: 5 * time.Second,
}),
Entry("with a DisableKeepAlives", &newUpstreamTableInput{
proxyWebSockets: false,
@ -483,13 +482,13 @@ var _ = Describe("HTTP Upstream Suite", func() {
var proxyServer *httptest.Server
BeforeEach(func() {
flush := options.Duration(1 * time.Second)
timeout := options.Duration(options.DefaultUpstreamTimeout)
flush := 1 * time.Second
timeout := options.DefaultUpstreamTimeout
upstream := options.Upstream{
ID: "websocketProxy",
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: false,
PassHostHeader: ptr.To(true),
ProxyWebSockets: ptr.To(true),
InsecureSkipTLSVerify: ptr.To(false),
FlushInterval: &flush,
Timeout: &timeout,
}

View File

@ -14,6 +14,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
// 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,
serveMux: mux.NewRouter(),
}
if upstreams.ProxyRawPath {
if ptr.Deref(upstreams.ProxyRawPath, options.DefaultUpstreamProxyRawPath) {
m.serveMux.UseEncodedPath()
}
for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
if upstream.Static {
if ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
}
@ -74,7 +75,7 @@ func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request
// registerStaticResponseHandler registers a static response handler with at the given path.
func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream, writer pagewriter.Writer) error {
logger.Printf("mapping path %q => static response %d", upstream.Path, derefStaticCode(upstream.StaticCode))
logger.Printf("mapping path %q => static response %d", upstream.Path, ptr.Deref(upstream.StaticCode, options.DefaultUpstreamStaticCode))
return m.registerHandler(upstream, newStaticResponseHandler(upstream.ID, upstream.StaticCode), writer)
}

View File

@ -10,6 +10,7 @@ import (
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -60,19 +61,19 @@ var _ = Describe("Proxy Suite", func() {
{
ID: "static-backend",
Path: "/static/",
Static: true,
Static: ptr.To(true),
StaticCode: &ok,
},
{
ID: "static-backend-no-trailing-slash",
Path: "/static",
Static: true,
Static: ptr.To(true),
StaticCode: &accepted,
},
{
ID: "static-backend-long",
Path: "/static/long",
Static: true,
Static: ptr.To(true),
StaticCode: &accepted,
},
{
@ -83,7 +84,7 @@ var _ = Describe("Proxy Suite", func() {
{
ID: "single-path-backend",
Path: "/single-path",
Static: true,
Static: ptr.To(true),
StaticCode: &ok,
},
{
@ -346,7 +347,7 @@ var _ = Describe("Proxy Suite", func() {
upstream: "",
}),
Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{
upstreams: options.UpstreamConfig{ProxyRawPath: true},
upstreams: options.UpstreamConfig{ProxyRawPath: ptr.To(true)},
target: "http://example.localhost/%2F/test1/%2F/test2",
response: testHTTPResponse{
code: 404,

View File

@ -5,16 +5,16 @@ import (
"net/http"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
const defaultStaticResponseCode = 200
// newStaticResponseHandler creates a new staticResponseHandler that serves a
// a static response code.
func newStaticResponseHandler(upstream string, code *int) http.Handler {
return &staticResponseHandler{
code: derefStaticCode(code),
code: ptr.Deref(code, options.DefaultUpstreamStaticCode),
upstream: upstream,
}
}
@ -38,11 +38,3 @@ func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Requ
logger.Errorf("Error writing static response: %v", err)
}
}
// derefStaticCode returns the derefenced value, or the default if the value is nil
func derefStaticCode(code *int) int {
if code != nil {
return *code
}
return defaultStaticResponseCode
}

14
pkg/util/ptr/ptr.go Normal file
View File

@ -0,0 +1,14 @@
package ptr
// To generically returns a pointer to the given value.
func To[T any](v T) *T {
return &v
}
// Deref returns the value of the pointer or def(ault) if nil.
func Deref[T any](p *T, def T) T {
if p == nil {
return def
}
return *p
}

38
pkg/util/ptr/ptr_test.go Normal file
View File

@ -0,0 +1,38 @@
package ptr
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTo(t *testing.T) {
p := To(42)
assert.NotNil(t, p)
assert.Equal(t, 42, *p)
s := To("hello")
assert.NotNil(t, s)
assert.Equal(t, "hello", *s)
b := To(true)
assert.NotNil(t, b)
assert.True(t, *b)
}
func TestDeref(t *testing.T) {
v := Deref(To(99), 0)
assert.Equal(t, 99, v)
v = Deref[int](nil, 123)
assert.Equal(t, 123, v)
s := Deref[string](nil, "default")
assert.Equal(t, "default", s)
b := Deref(To(true), false)
assert.True(t, b)
b = Deref[bool](nil, false)
assert.False(t, b)
}

View File

@ -15,6 +15,7 @@ import (
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
// Validate checks that required options are set and validates those that they
@ -34,7 +35,7 @@ func Validate(o *options.Options) error {
transport := requests.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 -- InsecureSkipVerify is a configurable option we allow
} else if len(o.Providers[0].CAFiles) > 0 {
pool, err := util.GetCertPool(o.Providers[0].CAFiles, o.Providers[0].UseSystemTrustStore)
pool, err := util.GetCertPool(o.Providers[0].CAFiles, ptr.Deref(o.Providers[0].UseSystemTrustStore, options.DefaultUseSystemTrustStore))
if err == nil {
transport := requests.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{

View File

@ -9,6 +9,7 @@ import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/stretchr/testify/assert"
)
@ -68,7 +69,7 @@ func TestGoogleGroupOptionsWithoutServiceAccountJSON(t *testing.T) {
func TestGoogleGroupOptionsWithoutAdminEmail(t *testing.T) {
o := testOptions()
o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true
o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.To(true)
err := Validate(o)
assert.NotEqual(t, nil, err)
@ -81,7 +82,7 @@ func TestGoogleGroupOptionsWithoutGroups(t *testing.T) {
o := testOptions()
// Set admin email and application default credentials but no groups - should still require them
o.Providers[0].GoogleConfig.AdminEmail = "admin@example.com"
o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = true
o.Providers[0].GoogleConfig.UseApplicationDefaultCredentials = ptr.To(true)
err := Validate(o)
// Should pass validation since google-group is now optional
assert.Equal(t, nil, err)

View File

@ -5,6 +5,7 @@ import (
"os"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
// validateProviders is the initial validation migration for multiple providrers
@ -64,7 +65,7 @@ func validateProvider(provider options.Provider, providerIDs map[string]struct{}
// providerRequiresClientSecret checks if provider requires client secret to be set
// or it can be omitted in favor of JWT token to authenticate oAuth client
func providerRequiresClientSecret(provider options.Provider) bool {
if provider.Type == "entra-id" && provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
if provider.Type == "entra-id" && ptr.Deref(provider.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken) {
return false
}
@ -96,7 +97,7 @@ func validateGoogleConfig(provider options.Provider) []string {
hasAdminEmail := provider.GoogleConfig.AdminEmail != ""
hasSAJSON := provider.GoogleConfig.ServiceAccountJSON != ""
useADC := provider.GoogleConfig.UseApplicationDefaultCredentials
useADC := ptr.Deref(provider.GoogleConfig.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials)
if !hasAdminEmail && !hasSAJSON && !useADC {
return msgs
@ -123,7 +124,7 @@ func validateGoogleConfig(provider options.Provider) []string {
func validateEntraConfig(provider options.Provider) []string {
msgs := []string{}
if provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
if ptr.Deref(provider.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken) {
federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
if federatedTokenPath == "" {

View File

@ -193,8 +193,8 @@ var _ = Describe("Sessions", func() {
unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
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"
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"
refusedSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
refusedSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
)
type redisStoreTableInput struct {

View File

@ -5,6 +5,7 @@ import (
"net/url"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
func validateUpstreams(upstreams options.UpstreamConfig) []string {
@ -54,22 +55,22 @@ func validateUpstream(upstream options.Upstream, ids, paths map[string]struct{})
func validateStaticUpstream(upstream options.Upstream) []string {
msgs := []string{}
if !upstream.Static && upstream.StaticCode != nil {
if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) && upstream.StaticCode != nil {
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))
}
// Checks after this only make sense when the upstream is static
if !upstream.Static {
if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
return msgs
}
if upstream.URI != "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has uri, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.InsecureSkipTLSVerify {
if ptr.Deref(upstream.InsecureSkipTLSVerify, options.DefaultUpsteamInsecureSkipTLSVerify) {
msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval {
msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.PassHostHeader != nil {
@ -85,13 +86,13 @@ func validateStaticUpstream(upstream options.Upstream) []string {
func validateUpstreamURI(upstream options.Upstream) []string {
msgs := []string{}
if !upstream.Static && upstream.URI == "" {
if !ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) && upstream.URI == "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has empty uri: uris are required for all non-static upstreams", upstream.ID))
return msgs
}
// Checks after this only make sense the upstream is not static
if upstream.Static {
if ptr.Deref(upstream.Static, options.DefaultUpstreamStatic) {
return msgs
}

View File

@ -4,6 +4,7 @@ import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -14,9 +15,8 @@ var _ = Describe("Upstreams", func() {
errStrings []string
}
flushInterval := options.Duration(5 * time.Second)
flushInterval := 5 * time.Second
staticCode200 := 200
truth := true
validHTTPUpstream := options.Upstream{
ID: "validHTTPUpstream",
@ -26,7 +26,7 @@ var _ = Describe("Upstreams", func() {
validStaticUpstream := options.Upstream{
ID: "validStaticUpstream",
Path: "/validStaticUpstream",
Static: true,
Static: ptr.To(true),
}
validFileUpstream := options.Upstream{
ID: "validFileUpstream",
@ -145,11 +145,11 @@ var _ = Describe("Upstreams", func() {
ID: "foo",
Path: "/foo",
URI: "ftp://foo",
Static: true,
Static: ptr.To(true),
FlushInterval: &flushInterval,
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: true,
PassHostHeader: ptr.To(true),
ProxyWebSockets: ptr.To(true),
InsecureSkipTLSVerify: ptr.To(true),
},
},
},

View File

@ -8,6 +8,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
)
// ADFSProvider represents an ADFS based Identity Provider
@ -50,7 +51,7 @@ func NewADFSProvider(p *ProviderData, opts options.Provider) *ADFSProvider {
return &ADFSProvider{
OIDCProvider: oidcProvider,
skipScope: opts.ADFSConfig.SkipScope,
skipScope: ptr.Deref(opts.ADFSConfig.SkipScope, options.DefaultADFSSkipScope),
oidcEnrichFunc: oidcProvider.EnrichSession,
oidcRefreshFunc: oidcProvider.RefreshSession,
}

3
providers/adfs_test.go Executable file → Normal file
View File

@ -16,6 +16,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -172,7 +173,7 @@ var _ = Describe("ADFS Provider Tests", func() {
ProtectedResource: resource,
Scope: "",
}, options.Provider{
ADFSConfig: options.ADFSOptions{SkipScope: true},
ADFSConfig: options.ADFSOptions{SkipScope: ptr.To(true)},
})
result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "", url.Values{})

View File

@ -19,6 +19,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1"
@ -108,12 +109,11 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
},
}
if opts.UseOrganizationID || opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
if ptr.Deref(opts.UseOrganizationID, options.DefaultGoogleUseOrganizationID) || opts.ServiceAccountJSON != "" || ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
// reuse admin service to avoid multiple calls for token
var adminService *admin.Service
if opts.UseOrganizationID {
if ptr.Deref(opts.UseOrganizationID, options.DefaultGoogleUseOrganizationID) {
// add user scopes to admin api
userScope := getAdminAPIUserScope(opts.AdminAPIUserScope)
for index, scope := range possibleScopesList {
@ -132,7 +132,7 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
}
}
if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
if opts.ServiceAccountJSON != "" || ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
if adminService == nil {
adminService = getAdminService(opts)
}
@ -304,7 +304,7 @@ var possibleScopesList = [...]string{
}
func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource {
if opts.UseApplicationDefaultCredentials {
if ptr.Deref(opts.UseApplicationDefaultCredentials, options.DefaultUseApplicationDefaultCredentials) {
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: getTargetPrincipal(ctx, opts),
Scopes: strings.Split(scope, " "),

View File

@ -16,6 +16,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/spf13/cast"
"golang.org/x/oauth2"
)
@ -51,7 +52,7 @@ func NewMicrosoftEntraIDProvider(p *ProviderData, opts options.Provider) *Micros
OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants,
federatedTokenAuth: opts.MicrosoftEntraIDConfig.FederatedTokenAuth,
federatedTokenAuth: ptr.Deref(opts.MicrosoftEntraIDConfig.FederatedTokenAuth, options.DefaultMicrosoftEntraIDUseFederatedToken),
microsoftGraphURL: microsoftGraphURL,
}
}

View File

@ -13,6 +13,7 @@ import (
"github.com/coreos/go-oidc/v3/oidc"
"github.com/golang-jwt/jwt/v5"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/stretchr/testify/assert"
. "github.com/onsi/gomega"
@ -24,7 +25,7 @@ func TestAzureEntraOIDCProviderNewMultiTenant(t *testing.T) {
provider := NewMicrosoftEntraIDProvider(&ProviderData{},
options.Provider{OIDCConfig: options.OIDCOptions{
IssuerURL: "https://login.microsoftonline.com/common/v2.0",
InsecureSkipIssuerVerification: true,
InsecureSkipIssuerVerification: ptr.To(true),
}},
)
g.Expect(provider.Data().ProviderName).To(Equal("Microsoft Entra ID"))
@ -90,8 +91,8 @@ func TestAzureEntraOIDCProviderValidateSessionAllowedTenants(t *testing.T) {
options.Provider{
OIDCConfig: options.OIDCOptions{
IssuerURL: "https://login.microsoftonline.com/common/v2.0",
InsecureSkipIssuerVerification: true,
InsecureSkipNonce: true,
InsecureSkipIssuerVerification: ptr.To(true),
InsecureSkipNonce: ptr.To(true),
},
MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
AllowedTenants: []string{"85d7d600-7804-4d92-8d43-9c33c21c130c"},

View File

@ -12,6 +12,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"golang.org/x/oauth2"
)
@ -50,7 +51,7 @@ func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
return &OIDCProvider{
ProviderData: p,
SkipNonce: opts.InsecureSkipNonce,
SkipNonce: ptr.Deref(opts.InsecureSkipNonce, options.DefaultInsecureSkipNonce),
}
}

View File

@ -63,7 +63,7 @@ func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider {
}
p := NewOIDCProvider(providerData, options.OIDCOptions{
InsecureSkipNonce: skipNonce,
InsecureSkipNonce: &skipNonce,
})
return p

View File

@ -9,6 +9,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
k8serrors "k8s.io/apimachinery/pkg/util/errors"
)
@ -98,8 +99,8 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
IssuerURL: providerConfig.OIDCConfig.IssuerURL,
JWKsURL: providerConfig.OIDCConfig.JwksURL,
PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles,
SkipDiscovery: providerConfig.OIDCConfig.SkipDiscovery,
SkipIssuerVerification: providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
SkipDiscovery: ptr.Deref(providerConfig.OIDCConfig.SkipDiscovery, options.DefaultSkipDiscovery),
SkipIssuerVerification: ptr.Deref(providerConfig.OIDCConfig.InsecureSkipIssuerVerification, options.DefaultInsecureSkipIssuerVerification),
})
if err != nil {
return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err)
@ -143,10 +144,10 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
}
// Make the OIDC options available to all providers that support it
p.AllowUnverifiedEmail = providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail
p.AllowUnverifiedEmail = ptr.Deref(providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail, options.DefaultInsecureAllowUnverifiedEmail)
p.EmailClaim = providerConfig.OIDCConfig.EmailClaim
p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim
p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL
p.SkipClaimsFromProfileURL = ptr.Deref(providerConfig.SkipClaimsFromProfileURL, options.DefaultSkipClaimsFromProfileURL)
// Set PKCE enabled or disabled based on discovery and force options
p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig)

View File

@ -5,6 +5,7 @@ import (
"testing"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/gomega"
)
@ -81,7 +82,7 @@ func TestSkipOIDCDiscovery(t *testing.T) {
ClientSecretFile: clientSecret,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.To(true),
},
}
@ -108,7 +109,7 @@ func TestURLsCorrectlyParsed(t *testing.T) {
RedeemURL: msTokenURL,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.To(true),
JwksURL: msKeysURL,
},
}
@ -216,7 +217,7 @@ func TestScope(t *testing.T) {
AllowedGroups: tc.allowedGroups,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.To(true),
JwksURL: msKeysURL,
},
}
@ -297,7 +298,7 @@ func TestEmailClaimCorrectlySet(t *testing.T) {
RedeemURL: msTokenURL,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.To(true),
JwksURL: msKeysURL,
UserIDClaim: tc.userIDClaim,
EmailClaim: tc.emailClaim,