This commit is contained in:
Jan Larwig 2025-10-20 18:06:48 +02:00 committed by GitHub
commit 40b75a6f0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 797 additions and 659 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

@ -75,6 +75,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.12.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))
@ -499,9 +489,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
@ -512,10 +502,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
@ -547,10 +537,10 @@ Requests will be proxied to this upstream if the path matches the request path.
| `insecureSkipTLSVerify` | _bool_ | InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.<br/>This option is insecure and will allow potential Man-In-The-Middle attacks<br/>between OAuth2 Proxy and the upstream server.<br/>Defaults to false. |
| `static` | _bool_ | Static will make all requests to this upstream have a static response.<br/>The response will have a body of "Authenticated" and a response code<br/>matching StaticCode.<br/>If StaticCode is not set, the response will return a 200 response. |
| `staticCode` | _int_ | StaticCode determines the response code for the Static response.<br/>This option can only be used with Static enabled. |
| `flushInterval` | _[Duration](#duration)_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
| `flushInterval` | _duration_ | FlushInterval is the period between flushing the response buffer when<br/>streaming response from the upstream.<br/>Defaults to 1 second. |
| `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied<br/>to the upstream server.<br/>Defaults to true. |
| `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers<br/>Defaults to true. |
| `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
| `timeout` | _duration_ | Timeout is the maximum duration the server will wait for a response from the upstream server.<br/>Defaults to 30 seconds. |
| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
### UpstreamConfig

13
go.mod
View File

@ -13,7 +13,6 @@ require (
github.com/coreos/go-oidc/v3 v3.14.1
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
@ -24,7 +23,7 @@ require (
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
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/gomega v1.38.0
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
@ -37,13 +36,14 @@ require (
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
google.golang.org/api v0.243.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.3
)
require (
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth v0.16.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
@ -83,9 +83,8 @@ require (
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/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

89
go.sum
View File

@ -1,12 +1,11 @@
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.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
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=
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/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
@ -20,12 +19,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw=
github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@ -44,12 +38,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
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/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=
@ -61,17 +51,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/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=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@ -85,23 +68,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
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/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=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/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=
@ -111,8 +85,8 @@ github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3Tc
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/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
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=
@ -120,8 +94,6 @@ 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_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
@ -132,7 +104,6 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D
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/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=
@ -142,8 +113,6 @@ 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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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=
@ -165,18 +134,12 @@ 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/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=
@ -186,8 +149,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -197,8 +158,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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=
@ -206,8 +165,6 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl
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.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -219,8 +176,6 @@ 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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -234,53 +189,29 @@ 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.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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/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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.240.0 h1:PxG3AA2UIqT1ofIzWV2COM3j3JagKTKSwy7L6RHNXNU=
google.golang.org/api v0.240.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg=
google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
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/api v0.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ=
google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=

38
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"
"gopkg.in/yaml.v3"
)
func main() {
@ -67,12 +67,18 @@ 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.")
return loadYamlOptions(yamlConfig, config, extraFlags, args)
}
return opts, nil
}
// loadLegacyOptions loads the old toml options using the legacy flagset
@ -97,17 +103,17 @@ func loadLegacyOptions(config string, extraFlags *pflag.FlagSet, args []string)
return opts, nil
}
// loadAlphaOptions loads the old style config excluding options converted to
// loadYamlOptions loads the old style config excluding options converted to
// the new alpha format, then merges the alpha options, loaded from YAML,
// into the core configuration.
func loadAlphaOptions(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
func loadYamlOptions(yamlConfig, config string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
opts, err := loadOptions(config, extraFlags, args)
if err != nil {
return nil, fmt.Errorf("failed to load core options: %v", err)
}
alphaOpts := &options.AlphaOptions{}
if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil {
alphaOpts := options.NewAlphaOptions(opts)
if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
return nil, fmt.Errorf("failed to load alpha options: %v", err)
}
@ -137,10 +143,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,12 @@ 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 +23,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 +40,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,13 +114,8 @@ 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
durationPtr := func(d time.Duration) *time.Duration {
return &d
}
testExpectedOptions := func() *options.Options {
@ -117,13 +130,15 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts.UpstreamServers = options.UpstreamConfig{
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: durationPtr(options.DefaultUpstreamFlushInterval),
PassHostHeader: ptr.Ptr(true),
ProxyWebSockets: ptr.Ptr(true),
Timeout: durationPtr(options.DefaultUpstreamTimeout),
InsecureSkipTLSVerify: ptr.Ptr(false),
DisableKeepAlives: ptr.Ptr(false),
},
},
}
@ -136,32 +151,45 @@ redirect_url="http://localhost:4180/oauth2/callback"
Claim: "user",
Prefix: "Basic ",
BasicAuthPassword: &options.SecretSource{
Value: []byte("super-secret-password"),
Value: []byte("c3VwZXItc2VjcmV0LXBhc3N3b3Jk"),
},
},
},
},
}
authHeader.PreserveRequestValue = ptr.Ptr(false)
opts.InjectRequestHeaders = append([]options.Header{authHeader}, opts.InjectRequestHeaders...)
authHeader.PreserveRequestValue = nil
opts.InjectResponseHeaders = append(opts.InjectResponseHeaders, authHeader)
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.Ptr(false),
SkipClaimsFromProfileURL: ptr.Ptr(false),
GoogleConfig: options.GoogleOptions{
AdminEmail: "admin@example.com",
UseApplicationDefaultCredentials: ptr.Ptr(false),
TargetPrincipal: "principal",
},
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.Ptr(true),
InsecureAllowUnverifiedEmail: ptr.Ptr(false),
InsecureSkipIssuerVerification: ptr.Ptr(false),
SkipDiscovery: ptr.Ptr(false),
},
LoginURLParameters: []options.LoginURLParameter{
{Name: "approval_prompt", Default: []string{"force"}},
@ -226,7 +254,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
opts, err := loadConfiguration(configFileName, alphaConfigFileName, extraFlags, in.args)
if in.expectedErr != nil {
Expect(err).To(MatchError(in.expectedErr.Error()))
Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
} else {
Expect(err).ToNot(HaveOccurred())
}
@ -245,19 +273,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.Ptr(true),
},
})
if err != nil {
@ -2223,7 +2224,7 @@ func TestTrustedIPs(t *testing.T) {
{
ID: "static",
Path: "/",
Static: true,
Static: ptr.Ptr(true),
},
},
}

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
}
// MergeInto replaces alpha options in the Options struct with the values
// from the AlphaOptions
func (a *AlphaOptions) MergeInto(opts *Options) {
opts.UpstreamServers = a.UpstreamConfig
opts.InjectRequestHeaders = a.InjectRequestHeaders
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
}

View File

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

View File

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

@ -5,26 +5,26 @@ package options
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"`
// 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
@ -32,14 +32,14 @@ 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"`
}

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"
)
@ -136,18 +137,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 +165,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.Ptr(true)
upstream.StaticCode = &responseCode
// This is not allowed to be empty and must be unique
@ -175,12 +176,12 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
// Force defaults compatible with static responses
upstream.URI = ""
upstream.InsecureSkipTLSVerify = false
upstream.InsecureSkipTLSVerify = ptr.Ptr(false)
upstream.PassHostHeader = nil
upstream.ProxyWebSockets = nil
upstream.FlushInterval = nil
upstream.Timeout = nil
upstream.DisableKeepAlives = false
upstream.DisableKeepAlives = ptr.Ptr(false)
case "unix":
upstream.Path = "/"
}
@ -253,7 +254,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
}
for i := range requestHeaders {
requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders
requestHeaders[i].PreserveRequestValue = ptr.Ptr(!l.SkipAuthStripHeaders)
}
return requestHeaders
@ -680,11 +681,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,
@ -697,10 +698,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,
@ -768,13 +769,13 @@ func (l *LegacyProvider) convert() (Providers, error) {
Groups: l.GoogleGroups,
AdminEmail: l.GoogleAdminEmail,
ServiceAccountJSON: l.GoogleServiceAccountJSON,
UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials,
UseApplicationDefaultCredentials: &l.GoogleUseApplicationDefaultCredentials,
TargetPrincipal: l.GoogleTargetPrincipal,
}
case "entra-id":
provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{
AllowedTenants: l.EntraIDAllowedTenants,
FederatedTokenAuth: l.EntraIDFederatedTokenAuth,
FederatedTokenAuth: &l.EntraIDFederatedTokenAuth,
}
}

View File

@ -3,6 +3,7 @@ package options
import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@ -15,8 +16,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 +27,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 +35,35 @@ var _ = Describe("Legacy Options", func() {
Path: "/baz",
URI: "http://foo.bar/baz",
FlushInterval: &flushInterval,
InsecureSkipTLSVerify: true,
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: ptr.Ptr(true),
PassHostHeader: ptr.Ptr(true),
ProxyWebSockets: ptr.Ptr(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.Ptr(true),
PassHostHeader: ptr.Ptr(true),
ProxyWebSockets: ptr.Ptr(true),
Timeout: &timeout,
DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
},
{
ID: "static://204",
Path: "/",
URI: "",
Static: true,
Static: ptr.Ptr(true),
StaticCode: &staticCode,
FlushInterval: nil,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.Ptr(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
Timeout: nil,
DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives,
DisableKeepAlives: &legacyOpts.LegacyUpstreams.DisableKeepAlives,
},
},
}
@ -71,7 +71,7 @@ var _ = Describe("Legacy Options", func() {
opts.InjectRequestHeaders = []Header{
{
Name: "X-Forwarded-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -82,7 +82,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -93,7 +93,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -104,7 +104,7 @@ var _ = Describe("Legacy Options", func() {
},
{
Name: "X-Forwarded-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -123,7 +123,7 @@ var _ = Describe("Legacy Options", func() {
opts.Providers[0].ClientID = "oauth-proxy"
opts.Providers[0].ID = "google=oauth-proxy"
opts.Providers[0].OIDCConfig.InsecureSkipNonce = true
opts.Providers[0].OIDCConfig.InsecureSkipNonce = ptr.Ptr(true)
opts.Providers[0].OIDCConfig.AudienceClaims = []string{"aud"}
opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
opts.Providers[0].LoginURLParameters = []LoginURLParameter{
@ -147,8 +147,8 @@ var _ = Describe("Legacy Options", func() {
skipVerify := true
passHostHeader := false
proxyWebSockets := true
flushInterval := Duration(5 * time.Second)
timeout := Duration(5 * time.Second)
flushInterval := 5 * time.Second
timeout := 5 * time.Second
disableKeepAlives := true
// Test cases and expected outcomes
@ -157,12 +157,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 +171,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 +184,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 +198,14 @@ var _ = Describe("Legacy Options", func() {
ID: validStatic,
Path: "/",
URI: "",
Static: true,
Static: ptr.Ptr(true),
StaticCode: &validStaticCode,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.Ptr(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
FlushInterval: nil,
Timeout: nil,
DisableKeepAlives: false,
DisableKeepAlives: ptr.Ptr(false),
}
invalidStatic := "static://abc"
@ -214,14 +214,14 @@ var _ = Describe("Legacy Options", func() {
ID: invalidStatic,
Path: "/",
URI: "",
Static: true,
Static: ptr.Ptr(true),
StaticCode: &invalidStaticCode,
InsecureSkipTLSVerify: false,
InsecureSkipTLSVerify: ptr.Ptr(false),
PassHostHeader: nil,
ProxyWebSockets: nil,
FlushInterval: nil,
Timeout: nil,
DisableKeepAlives: false,
DisableKeepAlives: ptr.Ptr(false),
}
invalidHTTP := ":foo"
@ -308,13 +308,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.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -326,7 +326,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedEmail := Header{
Name: "X-Forwarded-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -338,7 +338,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedGroups := Header{
Name: "X-Forwarded-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -350,7 +350,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedPreferredUsername := Header{
Name: "X-Forwarded-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -362,7 +362,7 @@ var _ = Describe("Legacy Options", func() {
basicAuthHeader := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -378,7 +378,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedUserWithEmail := Header{
Name: "X-Forwarded-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -390,7 +390,7 @@ var _ = Describe("Legacy Options", func() {
xForwardedAccessToken := Header{
Name: "X-Forwarded-Access-Token",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -402,7 +402,7 @@ var _ = Describe("Legacy Options", func() {
basicAuthHeaderWithEmail := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -418,7 +418,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestUser := Header{
Name: "X-Auth-Request-User",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -430,7 +430,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestEmail := Header{
Name: "X-Auth-Request-Email",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -442,7 +442,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestGroups := Header{
Name: "X-Auth-Request-Groups",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -454,7 +454,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestPreferredUsername := Header{
Name: "X-Auth-Request-Preferred-Username",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -466,7 +466,7 @@ var _ = Describe("Legacy Options", func() {
xAuthRequestAccessToken := Header{
Name: "X-Auth-Request-Access-Token",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{
@ -478,7 +478,7 @@ var _ = Describe("Legacy Options", func() {
authorizationHeader := Header{
Name: "Authorization",
PreserveRequestValue: false,
PreserveRequestValue: ptr.Ptr(false),
Values: []HeaderValue{
{
ClaimSource: &ClaimSource{

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"
"gopkg.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 json 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 json
})
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

@ -1,5 +1,7 @@
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"
@ -22,78 +24,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 +159,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,110 +179,110 @@ 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"`
}
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"`
}
func providerDefaults() Providers {
@ -291,9 +293,9 @@ func providerDefaults() Providers {
Tenant: "common",
},
OIDCConfig: OIDCOptions{
InsecureAllowUnverifiedEmail: false,
InsecureSkipNonce: true,
SkipDiscovery: false,
InsecureAllowUnverifiedEmail: ptr.Ptr(false),
InsecureSkipNonce: ptr.Ptr(true),
SkipDiscovery: ptr.Ptr(false),
UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
EmailClaim: OIDCEmailClaim,
GroupsClaim: OIDCGroupsClaim,

View File

@ -0,0 +1,14 @@
package options
// SecretSource references an individual secret value.
// Only one source within the struct should be defined at any time.
type SecretSource struct {
// Value expects a base64 encoded string value.
Value []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"`
}

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

@ -14,11 +14,11 @@ const (
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 +26,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 +36,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 +46,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 +58,43 @@ 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"`
}

View File

@ -27,7 +27,7 @@ func NewRequestHeaderInjector(headers []options.Header) (alice.Constructor, erro
func newStripHeaders(headers []options.Header) alice.Constructor {
headersToStrip := []string{}
for _, header := range headers {
if !header.PreserveRequestValue {
if !(*header.PreserveRequestValue) {
headersToStrip = append(headersToStrip, header.Name)
}
}

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.Ptr(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.Ptr(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
@ -341,7 +342,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
@ -388,7 +389,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{
{
Name: "Claim",
PreserveRequestValue: true,
PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{

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

@ -54,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 {
wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
wsProxy = newWebSocketReverseProxy(u, *upstream.InsecureSkipTLSVerify)
}
var auth hmacauth.HmacAuth
@ -137,19 +137,19 @@ 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 *upstream.InsecureSkipTLSVerify {
transport.TLSClientConfig.InsecureSkipVerify = true
}
@ -168,7 +168,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 = *upstream.DisableKeepAlives
// Apply the customized transport to our proxy before returning it
proxy.Transport = transport

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.Ptr(false),
InsecureSkipTLSVerify: ptr.Ptr(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.Ptr(false),
ProxyWebSockets: ptr.Ptr(false),
InsecureSkipTLSVerify: ptr.Ptr(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.Ptr(true),
ProxyWebSockets: ptr.Ptr(true),
InsecureSkipTLSVerify: ptr.Ptr(false),
FlushInterval: &flush,
Timeout: &timeout,
}

View File

@ -27,12 +27,12 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData,
serveMux: mux.NewRouter(),
}
if upstreams.ProxyRawPath {
if *upstreams.ProxyRawPath {
m.serveMux.UseEncodedPath()
}
for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
if upstream.Static {
if *upstream.Static {
if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
}

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.Ptr(true),
StaticCode: &ok,
},
{
ID: "static-backend-no-trailing-slash",
Path: "/static",
Static: true,
Static: ptr.Ptr(true),
StaticCode: &accepted,
},
{
ID: "static-backend-long",
Path: "/static/long",
Static: true,
Static: ptr.Ptr(true),
StaticCode: &accepted,
},
{
@ -83,7 +84,7 @@ var _ = Describe("Proxy Suite", func() {
{
ID: "single-path-backend",
Path: "/single-path",
Static: true,
Static: ptr.Ptr(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.Ptr(true)},
target: "http://example.localhost/%2F/test1/%2F/test2",
response: testHTTPResponse{
code: 404,

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

@ -0,0 +1,14 @@
package ptr
// Ptr generically returns a pointer to the given value.
func Ptr[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 TestPtr(t *testing.T) {
p := Ptr(42)
assert.NotNil(t, p)
assert.Equal(t, 42, *p)
s := Ptr("hello")
assert.NotNil(t, s)
assert.Equal(t, "hello", *s)
b := Ptr(true)
assert.NotNil(t, b)
assert.True(t, *b)
}
func TestDeref(t *testing.T) {
v := Deref(Ptr(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(Ptr(true), false)
assert.True(t, b)
b = Deref[bool](nil, false)
assert.False(t, b)
}

View File

@ -34,7 +34,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, *o.Providers[0].UseSystemTrustStore)
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.Ptr(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.Ptr(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" && *provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
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, false)
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 *provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
if federatedTokenPath == "" {

View File

@ -54,22 +54,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 !*upstream.Static && 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 !*upstream.Static {
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 *upstream.InsecureSkipTLSVerify {
msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != options.DefaultUpstreamFlushInterval {
if upstream.FlushInterval != nil && *upstream.FlushInterval != options.DefaultUpstreamFlushInterval {
msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.PassHostHeader != nil {
@ -85,13 +85,13 @@ func validateStaticUpstream(upstream options.Upstream) []string {
func validateUpstreamURI(upstream options.Upstream) []string {
msgs := []string{}
if !upstream.Static && upstream.URI == "" {
if !*upstream.Static && 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 *upstream.Static {
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.Ptr(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.Ptr(true),
FlushInterval: &flushInterval,
PassHostHeader: &truth,
ProxyWebSockets: &truth,
InsecureSkipTLSVerify: true,
PassHostHeader: ptr.Ptr(true),
ProxyWebSockets: ptr.Ptr(true),
InsecureSkipTLSVerify: ptr.Ptr(true),
},
},
},

View File

@ -50,7 +50,7 @@ func NewADFSProvider(p *ProviderData, opts options.Provider) *ADFSProvider {
return &ADFSProvider{
OIDCProvider: oidcProvider,
skipScope: opts.ADFSConfig.SkipScope,
skipScope: *opts.ADFSConfig.SkipScope,
oidcEnrichFunc: oidcProvider.EnrichSession,
oidcRefreshFunc: oidcProvider.RefreshSession,
}

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.Ptr(true)},
})
result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "", url.Values{})

View File

@ -102,7 +102,7 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
},
}
if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
if opts.ServiceAccountJSON != "" || *opts.UseApplicationDefaultCredentials {
provider.configureGroups(opts)
}
@ -259,7 +259,7 @@ var possibleScopesList = [...]string{
}
func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource {
if opts.UseApplicationDefaultCredentials {
if *opts.UseApplicationDefaultCredentials {
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: getTargetPrincipal(ctx, opts),
Scopes: []string{scope},

View File

@ -51,7 +51,7 @@ func NewMicrosoftEntraIDProvider(p *ProviderData, opts options.Provider) *Micros
OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants,
federatedTokenAuth: opts.MicrosoftEntraIDConfig.FederatedTokenAuth,
federatedTokenAuth: *opts.MicrosoftEntraIDConfig.FederatedTokenAuth,
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.Ptr(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.Ptr(true),
InsecureSkipNonce: ptr.Ptr(true),
},
MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
AllowedTenants: []string{"85d7d600-7804-4d92-8d43-9c33c21c130c"},

View File

@ -50,7 +50,7 @@ func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
return &OIDCProvider{
ProviderData: p,
SkipNonce: opts.InsecureSkipNonce,
SkipNonce: *opts.InsecureSkipNonce,
}
}

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

@ -98,8 +98,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: *providerConfig.OIDCConfig.SkipDiscovery,
SkipIssuerVerification: *providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
})
if err != nil {
return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err)
@ -143,10 +143,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 = *providerConfig.OIDCConfig.InsecureAllowUnverifiedEmail
p.EmailClaim = providerConfig.OIDCConfig.EmailClaim
p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim
p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL
p.SkipClaimsFromProfileURL = *providerConfig.SkipClaimsFromProfileURL
// 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.Ptr(true),
},
}
@ -108,7 +109,7 @@ func TestURLsCorrectlyParsed(t *testing.T) {
RedeemURL: msTokenURL,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.Ptr(true),
JwksURL: msKeysURL,
},
}
@ -216,7 +217,7 @@ func TestScope(t *testing.T) {
AllowedGroups: tc.allowedGroups,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.Ptr(true),
JwksURL: msKeysURL,
},
}
@ -297,7 +298,7 @@ func TestEmailClaimCorrectlySet(t *testing.T) {
RedeemURL: msTokenURL,
OIDCConfig: options.OIDCOptions{
IssuerURL: msIssuerURL,
SkipDiscovery: true,
SkipDiscovery: ptr.Ptr(true),
JwksURL: msKeysURL,
UserIDClaim: tc.userIDClaim,
EmailClaim: tc.emailClaim,