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: - linters:
- revive - revive
path: _test\.go path: _test\.go
text: 'dot-imports:' text: "dot-imports:"
# # If we have tests in shared test folders, these can be less strictly linted # # If we have tests in shared test folders, these can be less strictly linted
- linters: - linters:
- bodyclose - bodyclose

View File

@ -75,6 +75,10 @@ DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --pus
.PHONY: build-docker .PHONY: build-docker
build-docker: build-distroless build-alpine ## Build multi architecture docker images in both flavours (distroless / alpine) 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 .PHONY: build-distroless
build-distroless: ## Build multi architecture distroless based docker image build-distroless: ## Build multi architecture distroless based docker image
$(DOCKER_BUILDX_X_PLATFORM) -t $(REGISTRY)/$(REPOSITORY):latest -t $(REGISTRY)/$(REPOSITORY):${VERSION} . $(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) # make alpha-config-<command> (eg make nginx-up, make nginx-down)
# #
# Access http://localhost:4180 to initiate a login cycle # Access http://localhost:4180 to initiate a login cycle
version: '3.0' version: "3.0"
services: services:
oauth2-proxy: oauth2-proxy:
container_name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.12.0 image: quay.io/oauth2-proxy/oauth2-proxy:v7.12.0
command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml
hostname: oauth2-proxy
volumes: volumes:
- "./oauth2-proxy-alpha-config.cfg:/oauth2-proxy.cfg" - "./oauth2-proxy-alpha-config.cfg:/oauth2-proxy.cfg"
- "./oauth2-proxy-alpha-config.yaml:/oauth2-proxy-alpha-config.yaml" - "./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) # make <command> (eg. make up, make down)
# #
# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle # Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle
version: '3.0' version: "3.0"
services: services:
oauth2-proxy: oauth2-proxy:
container_name: oauth2-proxy container_name: oauth2-proxy

View File

@ -1,5 +1,4 @@
http_address="0.0.0.0:4180"
cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
email_domains="example.com" email_domains="example.com"
cookie_secure="false" 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: server:
- id: httpbin bindAddress: "0.0.0.0:4180"
path: / upstreamConfig:
uri: http://httpbin upstreams:
- id: httpbin
path: /
uri: http://httpbin
injectRequestHeaders: injectRequestHeaders:
- name: X-Forwarded-Groups - name: X-Forwarded-User
values: values:
- claim: groups - claimSource:
- name: X-Forwarded-User claim: user
values: - name: X-Forwarded-Email
- claim: user values:
- name: X-Forwarded-Email - claimSource:
values: claim: email
- claim: email
- name: X-Forwarded-Preferred-Username
values:
- claim: preferred_username
providers: providers:
- provider: oidc - id: oidc
clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK provider: oidc
clientID: oauth2-proxy clientSecret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
oidcConfig: clientID: oauth2-proxy
issuerURL: http://dex.localhost:5556/dex 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. | | `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. | | `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 ### GitHubOptions
(**Appears on:** [Provider](#provider)) (**Appears on:** [Provider](#provider))
@ -499,9 +489,9 @@ Server represents the configuration for an HTTP(S) server
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `BindAddress` | _string_ | BindAddress is the address on which to serve traffic.<br/>Leave blank or set to "-" to disable. | | `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. | | `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` | _[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 ### TLS
@ -512,10 +502,10 @@ as well as an optional minimal TLS version that is acceptable.
| Field | Type | Description | | Field | Type | Description |
| ----- | ---- | ----------- | | ----- | ---- | ----------- |
| `Key` | _[SecretSource](#secretsource)_ | Key is the TLS key data to use.<br/>Typically this will come from a file. | | `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. | | `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 | | `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). | | `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 ### 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. | | `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. | | `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. | | `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. | | `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. | | `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. | | `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.<br/>Defaults to false. |
### UpstreamConfig ### UpstreamConfig

13
go.mod
View File

@ -13,7 +13,6 @@ require (
github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-oidc/v3 v3.14.1
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/fsnotify/fsnotify v1.9.0 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-jose/go-jose/v3 v3.0.4
github.com/go-viper/mapstructure/v2 v2.4.0 github.com/go-viper/mapstructure/v2 v2.4.0
github.com/golang-jwt/jwt/v5 v5.2.3 github.com/golang-jwt/jwt/v5 v5.2.3
@ -24,7 +23,7 @@ require (
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
github.com/onsi/ginkgo/v2 v2.23.4 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/pierrec/lz4/v4 v4.1.22
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.11.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/net v0.42.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.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/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.3 k8s.io/apimachinery v0.33.3
) )
require ( 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 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // 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/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.35.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/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/grpc v1.73.0 // indirect google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // 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.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= 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 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 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 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= 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 h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw=
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss= 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/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 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU= 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/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 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= 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/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 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw=
github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk= github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 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/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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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 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/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/go-jose/go-jose/v4 v4.1.1 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-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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 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/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 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 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 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= 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 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 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 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= 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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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/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 h1:hI1uC2A3vJFjwvBn0G0a7QBRdBUp6Y048BtLAHRTKPo=
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa/go.mod h1:8vxFeeg++MqgCHwehSuwTlYCF0ALyDJbYJ1JsKi7v6s= 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/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 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= 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.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 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 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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= 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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 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/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 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 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/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 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= 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/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 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 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 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1 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= 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 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 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 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= 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 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 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 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= 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 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 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-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.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.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 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= 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= 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.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.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.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 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0 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-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.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.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 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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= 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.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.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 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.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.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 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= 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-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.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.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.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 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 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= 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.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ=
google.golang.org/api v0.240.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
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/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/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 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 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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.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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=

38
main.go
View File

@ -5,12 +5,12 @@ import (
"os" "os"
"runtime" "runtime"
"github.com/ghodss/yaml"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "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/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/version" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/version"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
) )
func main() { func main() {
@ -67,12 +67,18 @@ func main() {
// loadConfiguration will load in the user's configuration. // loadConfiguration will load in the user's configuration.
// It will either load the alpha configuration (if alphaConfig is given) // It will either load the alpha configuration (if alphaConfig is given)
// or the legacy configuration. // or the legacy configuration.
func loadConfiguration(config, alphaConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) { func loadConfiguration(config, yamlConfig string, extraFlags *pflag.FlagSet, args []string) (*options.Options, error) {
if alphaConfig != "" { opts, err := loadLegacyOptions(config, extraFlags, args)
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.") if err != nil {
return loadAlphaOptions(config, alphaConfig, extraFlags, args) 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 // 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 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, // the new alpha format, then merges the alpha options, loaded from YAML,
// into the core configuration. // 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) opts, err := loadOptions(config, extraFlags, args)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load core options: %v", err) return nil, fmt.Errorf("failed to load core options: %v", err)
} }
alphaOpts := &options.AlphaOptions{} alphaOpts := options.NewAlphaOptions(opts)
if err := options.LoadYAML(alphaConfig, alphaOpts); err != nil { if err := options.LoadYAML(yamlConfig, alphaOpts); err != nil {
return nil, fmt.Errorf("failed to load alpha options: %v", err) 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 // printConvertedConfig extracts alpha options from the loaded configuration
// and renders these to stdout in YAML format. // and renders these to stdout in YAML format.
func printConvertedConfig(opts *options.Options) error { func printConvertedConfig(opts *options.Options) error {
alphaConfig := &options.AlphaOptions{} alphaConfig := options.NewAlphaOptions(opts)
alphaConfig.ExtractFrom(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 { if err != nil {
return fmt.Errorf("unable to marshal config: %v", err) return fmt.Errorf("unable to marshal config: %v", err)
} }

View File

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

View File

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

View File

@ -12,13 +12,13 @@ type AlphaOptions struct {
// UpstreamConfig is used to configure upstream servers. // UpstreamConfig is used to configure upstream servers.
// Once a user is authenticated, requests to the server will be proxied to // 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. // 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 // InjectRequestHeaders is used to configure headers that should be added
// to requests to upstream servers. // to requests to upstream servers.
// Headers may source values from either the authenticated user's session // Headers may source values from either the authenticated user's session
// or from a static secret value. // 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 // InjectResponseHeaders is used to configure headers that should be added
// to responses from the proxy. // to responses from the proxy.
@ -27,35 +27,31 @@ type AlphaOptions struct {
// auth_request module. // auth_request module.
// Headers may source values from either the authenticated user's session // Headers may source values from either the authenticated user's session
// or from a static secret value. // 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. // Server is used to configure the HTTP(S) server for the proxy application.
// You may choose to run both HTTP and HTTPS servers simultaneously. // You may choose to run both HTTP and HTTPS servers simultaneously.
// This can be done by setting the BindAddress and the SecureBindAddress 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. // 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. // MetricsServer is used to configure the HTTP(S) server for metrics.
// You may choose to run both HTTP and HTTPS servers simultaneously. // You may choose to run both HTTP and HTTPS servers simultaneously.
// This can be done by setting the BindAddress and the SecureBindAddress 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. // 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 // Providers is used to configure your provider. **Multiple-providers is not
// yet working.** [This feature is tracked in // yet working.** [This feature is tracked in
// #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) // #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 // Initialize alpha options with default values and settings of the core options
// from the AlphaOptions func NewAlphaOptions(opts *Options) *AlphaOptions {
func (a *AlphaOptions) MergeInto(opts *Options) { aOpts := &AlphaOptions{}
opts.UpstreamServers = a.UpstreamConfig aOpts.ExtractFrom(opts)
opts.InjectRequestHeaders = a.InjectRequestHeaders return aOpts
opts.InjectResponseHeaders = a.InjectResponseHeaders
opts.Server = a.Server
opts.MetricsServer = a.MetricsServer
opts.Providers = a.Providers
} }
// ExtractFrom populates the fields in the AlphaOptions with the values from // 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.MetricsServer = opts.MetricsServer
a.Providers = opts.Providers 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 //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 package options

View File

@ -5,26 +5,26 @@ package options
type Header struct { type Header struct {
// Name is the header name to be used for this set of values. // Name is the header name to be used for this set of values.
// Names should be unique within a list of Headers. // 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 // PreserveRequestValue determines whether any values for this header
// should be preserved for the request to the upstream server. // should be preserved for the request to the upstream server.
// This option only applies to injected request headers. // This option only applies to injected request headers.
// Defaults to false (headers that match this header will be stripped). // Defaults to false (headers that match this header will be stripped).
PreserveRequestValue bool `json:"preserveRequestValue,omitempty"` PreserveRequestValue *bool `yaml:"preserveRequestValue,omitempty"`
// Values contains the desired values for this header // 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 // HeaderValue represents a single header value and the sources that can
// make up the header value // make up the header value
type HeaderValue struct { type HeaderValue struct {
// Allow users to load the value from a secret source // 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 // 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 // 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 // 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` // loaded from. Available claims: `access_token` `id_token` `created_at`
// `expires_on` `refresh_token` `email` `user` `groups` `preferred_username`. // `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 // Prefix is an optional prefix that will be prepended to the value of the
// claim if it is non-empty. // 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. // BasicAuthPassword converts this claim into a basic auth header.
// Note the value of claim will become the basic auth username and the // Note the value of claim will become the basic auth username and the
// basicAuthPassword will be used as the password value. // 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" "time"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -136,18 +137,18 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
u.Path = "/" u.Path = "/"
} }
flushInterval := Duration(l.FlushInterval) flushInterval := l.FlushInterval
timeout := Duration(l.Timeout) timeout := l.Timeout
upstream := Upstream{ upstream := Upstream{
ID: u.Path, ID: u.Path,
Path: u.Path, Path: u.Path,
URI: upstreamString, URI: upstreamString,
InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify, InsecureSkipTLSVerify: &l.SSLUpstreamInsecureSkipVerify,
PassHostHeader: &l.PassHostHeader, PassHostHeader: &l.PassHostHeader,
ProxyWebSockets: &l.ProxyWebSockets, ProxyWebSockets: &l.ProxyWebSockets,
FlushInterval: &flushInterval, FlushInterval: &flushInterval,
Timeout: &timeout, Timeout: &timeout,
DisableKeepAlives: l.DisableKeepAlives, DisableKeepAlives: &l.DisableKeepAlives,
} }
switch u.Scheme { 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) logger.Errorf("unable to convert %q to int, use default \"200\"", u.Host)
responseCode = 200 responseCode = 200
} }
upstream.Static = true upstream.Static = ptr.Ptr(true)
upstream.StaticCode = &responseCode upstream.StaticCode = &responseCode
// This is not allowed to be empty and must be unique // 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 // Force defaults compatible with static responses
upstream.URI = "" upstream.URI = ""
upstream.InsecureSkipTLSVerify = false upstream.InsecureSkipTLSVerify = ptr.Ptr(false)
upstream.PassHostHeader = nil upstream.PassHostHeader = nil
upstream.ProxyWebSockets = nil upstream.ProxyWebSockets = nil
upstream.FlushInterval = nil upstream.FlushInterval = nil
upstream.Timeout = nil upstream.Timeout = nil
upstream.DisableKeepAlives = false upstream.DisableKeepAlives = ptr.Ptr(false)
case "unix": case "unix":
upstream.Path = "/" upstream.Path = "/"
} }
@ -253,7 +254,7 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
} }
for i := range requestHeaders { for i := range requestHeaders {
requestHeaders[i].PreserveRequestValue = !l.SkipAuthStripHeaders requestHeaders[i].PreserveRequestValue = ptr.Ptr(!l.SkipAuthStripHeaders)
} }
return requestHeaders return requestHeaders
@ -680,11 +681,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
ClientSecretFile: l.ClientSecretFile, ClientSecretFile: l.ClientSecretFile,
Type: ProviderType(l.ProviderType), Type: ProviderType(l.ProviderType),
CAFiles: l.ProviderCAFiles, CAFiles: l.ProviderCAFiles,
UseSystemTrustStore: l.UseSystemTrustStore, UseSystemTrustStore: &l.UseSystemTrustStore,
LoginURL: l.LoginURL, LoginURL: l.LoginURL,
RedeemURL: l.RedeemURL, RedeemURL: l.RedeemURL,
ProfileURL: l.ProfileURL, ProfileURL: l.ProfileURL,
SkipClaimsFromProfileURL: l.SkipClaimsFromProfileURL, SkipClaimsFromProfileURL: &l.SkipClaimsFromProfileURL,
ProtectedResource: l.ProtectedResource, ProtectedResource: l.ProtectedResource,
ValidateURL: l.ValidateURL, ValidateURL: l.ValidateURL,
Scope: l.Scope, 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 // This part is out of the switch section for all providers that support OIDC
provider.OIDCConfig = OIDCOptions{ provider.OIDCConfig = OIDCOptions{
IssuerURL: l.OIDCIssuerURL, IssuerURL: l.OIDCIssuerURL,
InsecureAllowUnverifiedEmail: l.InsecureOIDCAllowUnverifiedEmail, InsecureAllowUnverifiedEmail: &l.InsecureOIDCAllowUnverifiedEmail,
InsecureSkipIssuerVerification: l.InsecureOIDCSkipIssuerVerification, InsecureSkipIssuerVerification: &l.InsecureOIDCSkipIssuerVerification,
InsecureSkipNonce: l.InsecureOIDCSkipNonce, InsecureSkipNonce: &l.InsecureOIDCSkipNonce,
SkipDiscovery: l.SkipOIDCDiscovery, SkipDiscovery: &l.SkipOIDCDiscovery,
JwksURL: l.OIDCJwksURL, JwksURL: l.OIDCJwksURL,
UserIDClaim: l.UserIDClaim, UserIDClaim: l.UserIDClaim,
EmailClaim: l.OIDCEmailClaim, EmailClaim: l.OIDCEmailClaim,
@ -768,13 +769,13 @@ func (l *LegacyProvider) convert() (Providers, error) {
Groups: l.GoogleGroups, Groups: l.GoogleGroups,
AdminEmail: l.GoogleAdminEmail, AdminEmail: l.GoogleAdminEmail,
ServiceAccountJSON: l.GoogleServiceAccountJSON, ServiceAccountJSON: l.GoogleServiceAccountJSON,
UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials, UseApplicationDefaultCredentials: &l.GoogleUseApplicationDefaultCredentials,
TargetPrincipal: l.GoogleTargetPrincipal, TargetPrincipal: l.GoogleTargetPrincipal,
} }
case "entra-id": case "entra-id":
provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{ provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{
AllowedTenants: l.EntraIDAllowedTenants, AllowedTenants: l.EntraIDAllowedTenants,
FederatedTokenAuth: l.EntraIDFederatedTokenAuth, FederatedTokenAuth: &l.EntraIDFederatedTokenAuth,
} }
} }

View File

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

View File

@ -9,10 +9,10 @@ import (
"strings" "strings"
"github.com/a8m/envsubst" "github.com/a8m/envsubst"
"github.com/ghodss/yaml"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3"
) )
// Load reads in the config file at the path given, then merges in environment // 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 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 // registerFlags uses `cfg` and `flag` tags to associate flags in the flagSet
// to the fields in the options interface provided. // to the fields in the options interface provided.
// Each exported field in the options must have a `cfg` tag otherwise an error will occur. // 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) 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 // normalizeSubstitution normalizes dollar signs ($) with numerals like
// $1 or $2 properly by correctly escaping them // $1 or $2 properly by correctly escaping them
func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) { func normalizeSubstitution(unparsedBuffer []byte) ([]byte, error) {

View File

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

View File

@ -71,19 +71,19 @@ package options
// character. // character.
type LoginURLParameter struct { type LoginURLParameter struct {
// Name specifies the name of the query parameter. // 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 // Default specifies a default value or values that will be
// passed to the IdP if not overridden. // passed to the IdP if not overridden.
//+optional //+optional
Default []string `json:"default,omitempty"` Default []string `yaml:"default,omitempty"`
// Allow specifies rules about how the default (if any) may be // Allow specifies rules about how the default (if any) may be
// overridden via the query string to `/oauth2/start`. Only // overridden via the query string to `/oauth2/start`. Only
// values that match one or more of the allow rules will be // values that match one or more of the allow rules will be
// forwarded to the IdP. // forwarded to the IdP.
//+optional //+optional
Allow []URLParameterRule `json:"allow,omitempty"` Allow []URLParameterRule `yaml:"allow,omitempty"`
} }
// URLParameterRule represents a rule by which query parameters // 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. // login URL. Either Value or Pattern should be supplied, not both.
type URLParameterRule struct { type URLParameterRule struct {
// A Value rule matches just this specific value // 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 // A Pattern rule gives a regular expression that must be matched by
// some substring of the value. The expression is _not_ automatically // some substring of the value. The expression is _not_ automatically
// anchored to the start and end of the value, if you _want_ to restrict // 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 `$`. // 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 package options
import "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
const ( const (
// OIDCEmailClaim is the generic email claim used by the OIDC provider. // OIDCEmailClaim is the generic email claim used by the OIDC provider.
OIDCEmailClaim = "email" OIDCEmailClaim = "email"
@ -22,78 +24,78 @@ type Providers []Provider
type Provider struct { type Provider struct {
// ClientID is the OAuth Client ID that is defined in the provider // ClientID is the OAuth Client ID that is defined in the provider
// This value is required for all providers. // 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 // ClientSecret is the OAuth Client Secret that is defined in the provider
// This value is required for all providers. // This value is required for all providers.
ClientSecret string `json:"clientSecret,omitempty"` ClientSecret string `yaml:"clientSecret,omitempty"`
// ClientSecretFile is the name of the file // ClientSecretFile is the name of the file
// containing the OAuth Client Secret, it will be used if ClientSecret is not set. // 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 holds all configurations for Keycloak provider.
KeycloakConfig KeycloakOptions `json:"keycloakConfig,omitempty"` KeycloakConfig KeycloakOptions `yaml:"keycloakConfig,omitempty"`
// AzureConfig holds all configurations for Azure provider. // 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 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 holds all configurations for ADFS provider.
ADFSConfig ADFSOptions `json:"ADFSConfig,omitempty"` ADFSConfig ADFSOptions `yaml:"ADFSConfig,omitempty"`
// BitbucketConfig holds all configurations for Bitbucket provider. // 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 holds all configurations for GitHubC provider.
GitHubConfig GitHubOptions `json:"githubConfig,omitempty"` GitHubConfig GitHubOptions `yaml:"githubConfig,omitempty"`
// GitLabConfig holds all configurations for GitLab provider. // 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 holds all configurations for Google provider.
GoogleConfig GoogleOptions `json:"googleConfig,omitempty"` GoogleConfig GoogleOptions `yaml:"googleConfig,omitempty"`
// OIDCConfig holds all configurations for OIDC provider // OIDCConfig holds all configurations for OIDC provider
// or providers utilize OIDC configurations. // or providers utilize OIDC configurations.
OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"` OIDCConfig OIDCOptions `yaml:"oidcConfig,omitempty"`
// LoginGovConfig holds all configurations for LoginGov provider. // 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. // ID should be a unique identifier for the provider.
// This value is required for all providers. // This value is required for all providers.
ID string `json:"id,omitempty"` ID string `yaml:"id,omitempty"`
// Type is the OAuth provider // Type is the OAuth provider
// must be set from the supported providers group, // must be set from the supported providers group,
// otherwise 'Google' is set as default // otherwise 'Google' is set as default
Type ProviderType `json:"provider,omitempty"` Type ProviderType `yaml:"provider,omitempty"`
// Name is the providers display name // Name is the providers display name
// if set, it will be shown to the users in the login page. // 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. // 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 // 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 // UseSystemTrustStore determines if your custom CA files and the system trust store are used
// If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files. // If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.
UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty"` UseSystemTrustStore *bool `yaml:"useSystemTrustStore,omitempty"`
// LoginURL is the authentication endpoint // 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 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 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 is the token redemption endpoint
RedeemURL string `json:"redeemURL,omitempty"` RedeemURL string `yaml:"redeemURL,omitempty"`
// ProfileURL is the profile access endpoint // 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 // SkipClaimsFromProfileURL allows to skip request to Profile URL for resolving claims not present in id_token
// default set to 'false' // default set to 'false'
SkipClaimsFromProfileURL bool `json:"skipClaimsFromProfileURL,omitempty"` SkipClaimsFromProfileURL *bool `yaml:"skipClaimsFromProfileURL,omitempty"`
// ProtectedResource is the resource that is protected (Azure AD and ADFS only) // ProtectedResource is the resource that is protected (Azure AD and ADFS only)
ProtectedResource string `json:"resource,omitempty"` ProtectedResource string `yaml:"resource,omitempty"`
// ValidateURL is the access token validation endpoint // ValidateURL is the access token validation endpoint
ValidateURL string `json:"validateURL,omitempty"` ValidateURL string `yaml:"validateURL,omitempty"`
// Scope is the OAuth scope specification // 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 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 // 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 // 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 // ProviderType is used to enumerate the different provider type options
@ -157,19 +159,19 @@ const (
type KeycloakOptions struct { type KeycloakOptions struct {
// Group enables to restrict login to members of indicated group // 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) // 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 { type AzureOptions struct {
// Tenant directs to a tenant-specific or common (tenant-independent) endpoint // Tenant directs to a tenant-specific or common (tenant-independent) endpoint
// Default value is 'common' // 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 // GraphGroupField configures the group field to be used when building the groups list from Microsoft Graph
// Default value is 'id' // Default value is 'id'
GraphGroupField string `json:"graphGroupField,omitempty"` GraphGroupField string `yaml:"graphGroupField,omitempty"`
} }
type MicrosoftEntraIDOptions struct { type MicrosoftEntraIDOptions struct {
@ -177,110 +179,110 @@ type MicrosoftEntraIDOptions struct {
// issued by different issuers and OIDC issuer verification needs to be disabled. // issued by different issuers and OIDC issuer verification needs to be disabled.
// When not specified, all tenants are allowed. Redundant for single-tenant apps // When not specified, all tenants are allowed. Redundant for single-tenant apps
// (regular ID token validation matches the issuer). // (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 // FederatedTokenAuth enable oAuth2 client authentication with federated token projected
// by Entra Workload Identity plugin, instead of client secret. // by Entra Workload Identity plugin, instead of client secret.
FederatedTokenAuth bool `json:"federatedTokenAuth,omitempty"` FederatedTokenAuth *bool `yaml:"federatedTokenAuth,omitempty"`
} }
type ADFSOptions struct { type ADFSOptions struct {
// Skip adding the scope parameter in login request // Skip adding the scope parameter in login request
// Default value is 'false' // Default value is 'false'
SkipScope bool `json:"skipScope,omitempty"` SkipScope *bool `yaml:"skipScope,omitempty"`
} }
type BitbucketOptions struct { type BitbucketOptions struct {
// Team sets restrict logins to members of this team // 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 sets restrict logins to user with access to this repository
Repository string `json:"repository,omitempty"` Repository string `yaml:"repository,omitempty"`
} }
type GitHubOptions struct { type GitHubOptions struct {
// Org sets restrict logins to members of this organisation // 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 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 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 // Token is the token to use when verifying repository collaborators
// it must have push access to the repository // 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 // Users allows users with these usernames to login
// even if they do not belong to the specified org and team or collaborators // 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 { type GitLabOptions struct {
// Group sets restrict logins to members of this group // 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 restricts logins to members of these projects
Projects []string `json:"projects,omitempty"` Projects []string `yaml:"projects,omitempty"`
} }
type GoogleOptions struct { type GoogleOptions struct {
// Groups sets restrict logins to members of this Google group // 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 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 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 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 is the Google Service Account used for Application Default Credentials
TargetPrincipal string `json:"targetPrincipal,omitempty"` TargetPrincipal string `yaml:"targetPrincipal,omitempty"`
} }
type OIDCOptions struct { type OIDCOptions struct {
// IssuerURL is the OpenID Connect issuer URL // IssuerURL is the OpenID Connect issuer URL
// eg: https://accounts.google.com // 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 // InsecureAllowUnverifiedEmail prevents failures if an email address in an id_token is not verified
// default set to 'false' // 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 // InsecureSkipIssuerVerification skips verification of ID token issuers. When false, ID Token Issuers must match the OIDC discovery URL
// default set to 'false' // 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 // 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 // the random nonce sent in the initial OAuth flow. Otherwise, the nonce is checked
// after the initial OAuth redeem & subsequent token refreshes. // after the initial OAuth redeem & subsequent token refreshes.
// default set to 'true' // default set to 'true'
// Warning: In a future release, this will change to 'false' by default for enhanced security. // 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 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
// default set to 'false' // default set to 'false'
SkipDiscovery bool `json:"skipDiscovery,omitempty"` SkipDiscovery *bool `yaml:"skipDiscovery,omitempty"`
// JwksURL is the OpenID Connect JWKS URL // JwksURL is the OpenID Connect JWKS URL
// eg: https://www.googleapis.com/oauth2/v3/certs // eg: https://www.googleapis.com/oauth2/v3/certs
JwksURL string `json:"jwksURL,omitempty"` JwksURL string `yaml:"jwksURL,omitempty"`
// PublicKeyFiles is a list of paths pointing to public key files in PEM format to use // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
// for verifying JWT tokens // for verifying JWT tokens
PublicKeyFiles []string `json:"publicKeyFiles,omitempty"` PublicKeyFiles []string `yaml:"publicKeyFiles,omitempty"`
// EmailClaim indicates which claim contains the user email, // EmailClaim indicates which claim contains the user email,
// default set to 'email' // default set to 'email'
EmailClaim string `json:"emailClaim,omitempty"` EmailClaim string `yaml:"emailClaim,omitempty"`
// GroupsClaim indicates which claim contains the user groups // GroupsClaim indicates which claim contains the user groups
// default set to 'groups' // default set to 'groups'
GroupsClaim string `json:"groupsClaim,omitempty"` GroupsClaim string `yaml:"groupsClaim,omitempty"`
// UserIDClaim indicates which claim contains the user ID // UserIDClaim indicates which claim contains the user ID
// default set to 'email' // 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 // AudienceClaim allows to define any claim that is verified against the client id
// By default `aud` claim is used for verification. // 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 // ExtraAudiences is a list of additional audiences that are allowed
// to pass verification in addition to the client id. // to pass verification in addition to the client id.
ExtraAudiences []string `json:"extraAudiences,omitempty"` ExtraAudiences []string `yaml:"extraAudiences,omitempty"`
} }
type LoginGovOptions struct { type LoginGovOptions struct {
// JWTKey is a private key in PEM format used to sign JWT, // 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 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 is the JWK pubkey access endpoint
PubJWKURL string `json:"pubjwkURL,omitempty"` PubJWKURL string `yaml:"pubjwkURL,omitempty"`
} }
func providerDefaults() Providers { func providerDefaults() Providers {
@ -291,9 +293,9 @@ func providerDefaults() Providers {
Tenant: "common", Tenant: "common",
}, },
OIDCConfig: OIDCOptions{ OIDCConfig: OIDCOptions{
InsecureAllowUnverifiedEmail: false, InsecureAllowUnverifiedEmail: ptr.Ptr(false),
InsecureSkipNonce: true, InsecureSkipNonce: ptr.Ptr(true),
SkipDiscovery: false, SkipDiscovery: ptr.Ptr(false),
UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim UserIDClaim: OIDCEmailClaim, // Deprecated: Use OIDCEmailClaim
EmailClaim: OIDCEmailClaim, EmailClaim: OIDCEmailClaim,
GroupsClaim: OIDCGroupsClaim, 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 { type Server struct {
// BindAddress is the address on which to serve traffic. // BindAddress is the address on which to serve traffic.
// Leave blank or set to "-" to disable. // Leave blank or set to "-" to disable.
BindAddress string BindAddress string `yaml:"bindAddress,omitempty"`
// SecureBindAddress is the address on which to serve secure traffic. // SecureBindAddress is the address on which to serve secure traffic.
// Leave blank or set to "-" to disable. // 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 // TLS contains the information for loading the certificate and key for the
// secure traffic and further configuration for the TLS server. // 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 // TLS contains the information for loading a TLS certificate and key
@ -20,15 +20,15 @@ type Server struct {
type TLS struct { type TLS struct {
// Key is the TLS key data to use. // Key is the TLS key data to use.
// Typically this will come from a file. // Typically this will come from a file.
Key *SecretSource Key *SecretSource `yaml:"key,omitempty"`
// Cert is the TLS certificate data to use. // Cert is the TLS certificate data to use.
// Typically this will come from a file. // Typically this will come from a file.
Cert *SecretSource Cert *SecretSource `yaml:"cert,omitempty"`
// MinVersion is the minimal TLS version that is acceptable. // MinVersion is the minimal TLS version that is acceptable.
// E.g. Set to "TLS1.3" to select TLS version 1.3 // 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. // CipherSuites is a list of TLS cipher suites that are allowed.
// E.g.: // E.g.:
@ -36,5 +36,5 @@ type TLS struct {
// - TLS_RSA_WITH_AES_256_GCM_SHA384 // - TLS_RSA_WITH_AES_256_GCM_SHA384
// If not specified, the default Go safe cipher list is used. // 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). // 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 { type UpstreamConfig struct {
// ProxyRawPath will pass the raw url path to upstream allowing for urls // ProxyRawPath will pass the raw url path to upstream allowing for urls
// like: "/%2F/" which would otherwise be redirected to "/" // like: "/%2F/" which would otherwise be redirected to "/"
ProxyRawPath bool `json:"proxyRawPath,omitempty"` ProxyRawPath *bool `yaml:"proxyRawPath,omitempty"`
// Upstreams represents the configuration for the upstream servers. // Upstreams represents the configuration for the upstream servers.
// Requests will be proxied to this upstream if the path matches the request path. // Requests will be proxied to this upstream if the path matches the request path.
Upstreams []Upstream `json:"upstreams,omitempty"` Upstreams []Upstream `yaml:"upstreams,omitempty"`
} }
// Upstream represents the configuration for an upstream server. // Upstream represents the configuration for an upstream server.
@ -26,7 +26,7 @@ type UpstreamConfig struct {
type Upstream struct { type Upstream struct {
// ID should be a unique identifier for the upstream. // ID should be a unique identifier for the upstream.
// This value is required for all upstreams. // 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. // Path is used to map requests to the upstream server.
// The closest match will take precedence and all Paths must be unique. // 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` // - `^/foo$`: Match only the explicit path `/foo`
// - `^/bar/$`: Match any path prefixed with `/bar/` // - `^/bar/$`: Match any path prefixed with `/bar/`
// - `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget // - `^/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 // 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 // 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 // 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 // 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`. // `/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 // 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 // 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 // - file://host/path
// If the URI's path is "/base" and the incoming request was for "/dir", // If the URI's path is "/base" and the incoming request was for "/dir",
// the upstream request will be for "/base/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. // InsecureSkipTLSVerify will skip TLS verification of upstream HTTPS hosts.
// This option is insecure and will allow potential Man-In-The-Middle attacks // This option is insecure and will allow potential Man-In-The-Middle attacks
// between OAuth2 Proxy and the upstream server. // between OAuth2 Proxy and the upstream server.
// Defaults to false. // Defaults to false.
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"` InsecureSkipTLSVerify *bool `yaml:"insecureSkipTLSVerify,omitempty"`
// Static will make all requests to this upstream have a static response. // Static will make all requests to this upstream have a static response.
// The response will have a body of "Authenticated" and a response code // The response will have a body of "Authenticated" and a response code
// matching StaticCode. // matching StaticCode.
// If StaticCode is not set, the response will return a 200 response. // If StaticCode is not set, the response will return a 200 response.
Static bool `json:"static,omitempty"` Static *bool `yaml:"static,omitempty"`
// StaticCode determines the response code for the Static response. // StaticCode determines the response code for the Static response.
// This option can only be used with Static enabled. // This option can only be used with Static enabled.
StaticCode *int `json:"staticCode,omitempty"` StaticCode *int `yaml:"staticCode,omitempty"`
// FlushInterval is the period between flushing the response buffer when // FlushInterval is the period between flushing the response buffer when
// streaming response from the upstream. // streaming response from the upstream.
// Defaults to 1 second. // 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 // PassHostHeader determines whether the request host header should be proxied
// to the upstream server. // to the upstream server.
// Defaults to true. // Defaults to true.
PassHostHeader *bool `json:"passHostHeader,omitempty"` PassHostHeader *bool `yaml:"passHostHeader,omitempty"`
// ProxyWebSockets enables proxying of websockets to upstream servers // ProxyWebSockets enables proxying of websockets to upstream servers
// Defaults to true. // 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. // Timeout is the maximum duration the server will wait for a response from the upstream server.
// Defaults to 30 seconds. // 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. // DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
// Defaults to false. // 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 { func newStripHeaders(headers []options.Header) alice.Constructor {
headersToStrip := []string{} headersToStrip := []string{}
for _, header := range headers { for _, header := range headers {
if !header.PreserveRequestValue { if !(*header.PreserveRequestValue) {
headersToStrip = append(headersToStrip, header.Name) headersToStrip = append(headersToStrip, header.Name)
} }
} }

View File

@ -8,6 +8,7 @@ import (
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware" 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/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" 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/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -115,7 +116,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{ headers: []options.Header{
{ {
Name: "Claim", Name: "Claim",
PreserveRequestValue: true, PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{ Values: []options.HeaderValue{
{ {
ClaimSource: &options.ClaimSource{ ClaimSource: &options.ClaimSource{
@ -160,7 +161,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{ headers: []options.Header{
{ {
Name: "Claim", Name: "Claim",
PreserveRequestValue: true, PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{ Values: []options.HeaderValue{
{ {
ClaimSource: &options.ClaimSource{ ClaimSource: &options.ClaimSource{
@ -341,7 +342,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{ headers: []options.Header{
{ {
Name: "Claim", Name: "Claim",
PreserveRequestValue: true, PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{ Values: []options.HeaderValue{
{ {
ClaimSource: &options.ClaimSource{ ClaimSource: &options.ClaimSource{
@ -388,7 +389,7 @@ var _ = Describe("Headers Suite", func() {
headers: []options.Header{ headers: []options.Header{
{ {
Name: "Claim", Name: "Claim",
PreserveRequestValue: true, PreserveRequestValue: ptr.Ptr(true),
Values: []options.HeaderValue{ Values: []options.HeaderValue{
{ {
ClaimSource: &options.ClaimSource{ ClaimSource: &options.ClaimSource{

View File

@ -104,8 +104,8 @@ var _ = Describe("Result suite", func() {
Context("UnmarshalInto", func() { Context("UnmarshalInto", func() {
type testStruct struct { type testStruct struct {
A string `json:"a"` A string `yaml:"a"`
B int `json:"b"` B int `yaml:"b"`
} }
type unmarshalIntoTableInput struct { 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 // Set up a WebSocket proxy if required
var wsProxy http.Handler var wsProxy http.Handler
if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets { if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify) wsProxy = newWebSocketReverseProxy(u, *upstream.InsecureSkipTLSVerify)
} }
var auth hmacauth.HmacAuth 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 // Change default duration for waiting for an upstream response
if upstream.Timeout != nil { if upstream.Timeout != nil {
transport.ResponseHeaderTimeout = upstream.Timeout.Duration() transport.ResponseHeaderTimeout = *upstream.Timeout
} }
// Configure options on the SingleHostReverseProxy // Configure options on the SingleHostReverseProxy
if upstream.FlushInterval != nil { if upstream.FlushInterval != nil {
proxy.FlushInterval = upstream.FlushInterval.Duration() proxy.FlushInterval = *upstream.FlushInterval
} else { } else {
proxy.FlushInterval = options.DefaultUpstreamFlushInterval proxy.FlushInterval = options.DefaultUpstreamFlushInterval
} }
// InsecureSkipVerify is a configurable option we allow // InsecureSkipVerify is a configurable option we allow
/* #nosec G402 */ /* #nosec G402 */
if upstream.InsecureSkipTLSVerify { if *upstream.InsecureSkipTLSVerify {
transport.TLSClientConfig.InsecureSkipVerify = true 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 // Pass on DisableKeepAlives to the transport settings
// to allow for disabling HTTP keep-alive connections // 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 // Apply the customized transport to our proxy before returning it
proxy.Transport = transport proxy.Transport = transport

View File

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

View File

@ -27,12 +27,12 @@ func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData,
serveMux: mux.NewRouter(), serveMux: mux.NewRouter(),
} }
if upstreams.ProxyRawPath { if *upstreams.ProxyRawPath {
m.serveMux.UseEncodedPath() m.serveMux.UseEncodedPath()
} }
for _, upstream := range sortByPathLongest(upstreams.Upstreams) { for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
if upstream.Static { if *upstream.Static {
if err := m.registerStaticResponseHandler(upstream, writer); err != nil { if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err) 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" 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/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter" "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/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -60,19 +61,19 @@ var _ = Describe("Proxy Suite", func() {
{ {
ID: "static-backend", ID: "static-backend",
Path: "/static/", Path: "/static/",
Static: true, Static: ptr.Ptr(true),
StaticCode: &ok, StaticCode: &ok,
}, },
{ {
ID: "static-backend-no-trailing-slash", ID: "static-backend-no-trailing-slash",
Path: "/static", Path: "/static",
Static: true, Static: ptr.Ptr(true),
StaticCode: &accepted, StaticCode: &accepted,
}, },
{ {
ID: "static-backend-long", ID: "static-backend-long",
Path: "/static/long", Path: "/static/long",
Static: true, Static: ptr.Ptr(true),
StaticCode: &accepted, StaticCode: &accepted,
}, },
{ {
@ -83,7 +84,7 @@ var _ = Describe("Proxy Suite", func() {
{ {
ID: "single-path-backend", ID: "single-path-backend",
Path: "/single-path", Path: "/single-path",
Static: true, Static: ptr.Ptr(true),
StaticCode: &ok, StaticCode: &ok,
}, },
{ {
@ -346,7 +347,7 @@ var _ = Describe("Proxy Suite", func() {
upstream: "", upstream: "",
}), }),
Entry("containing an escaped '/' with ProxyRawPath", &proxyTableInput{ 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", target: "http://example.localhost/%2F/test1/%2F/test2",
response: testHTTPResponse{ response: testHTTPResponse{
code: 404, 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 := requests.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 -- InsecureSkipVerify is a configurable option we allow transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402 -- InsecureSkipVerify is a configurable option we allow
} else if len(o.Providers[0].CAFiles) > 0 { } 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 { if err == nil {
transport := requests.DefaultTransport.(*http.Transport) transport := requests.DefaultTransport.(*http.Transport)
transport.TLSClientConfig = &tls.Config{ transport.TLSClientConfig = &tls.Config{

View File

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

View File

@ -5,6 +5,7 @@ import (
"os" "os"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" "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 // 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 // providerRequiresClientSecret checks if provider requires client secret to be set
// or it can be omitted in favor of JWT token to authenticate oAuth client // or it can be omitted in favor of JWT token to authenticate oAuth client
func providerRequiresClientSecret(provider options.Provider) bool { func providerRequiresClientSecret(provider options.Provider) bool {
if provider.Type == "entra-id" && provider.MicrosoftEntraIDConfig.FederatedTokenAuth { if provider.Type == "entra-id" && *provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
return false return false
} }
@ -96,7 +97,7 @@ func validateGoogleConfig(provider options.Provider) []string {
hasAdminEmail := provider.GoogleConfig.AdminEmail != "" hasAdminEmail := provider.GoogleConfig.AdminEmail != ""
hasSAJSON := provider.GoogleConfig.ServiceAccountJSON != "" hasSAJSON := provider.GoogleConfig.ServiceAccountJSON != ""
useADC := provider.GoogleConfig.UseApplicationDefaultCredentials useADC := ptr.Deref(provider.GoogleConfig.UseApplicationDefaultCredentials, false)
if !hasAdminEmail && !hasSAJSON && !useADC { if !hasAdminEmail && !hasSAJSON && !useADC {
return msgs return msgs
@ -123,7 +124,7 @@ func validateGoogleConfig(provider options.Provider) []string {
func validateEntraConfig(provider options.Provider) []string { func validateEntraConfig(provider options.Provider) []string {
msgs := []string{} msgs := []string{}
if provider.MicrosoftEntraIDConfig.FederatedTokenAuth { if *provider.MicrosoftEntraIDConfig.FederatedTokenAuth {
federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") federatedTokenPath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
if federatedTokenPath == "" { if federatedTokenPath == "" {

View File

@ -54,22 +54,22 @@ func validateUpstream(upstream options.Upstream, ids, paths map[string]struct{})
func validateStaticUpstream(upstream options.Upstream) []string { func validateStaticUpstream(upstream options.Upstream) []string {
msgs := []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)) 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 // Checks after this only make sense when the upstream is static
if !upstream.Static { if !*upstream.Static {
return msgs return msgs
} }
if upstream.URI != "" { if upstream.URI != "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has uri, but is a static upstream, this will have no effect.", upstream.ID)) 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)) 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)) 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 { if upstream.PassHostHeader != nil {
@ -85,13 +85,13 @@ func validateStaticUpstream(upstream options.Upstream) []string {
func validateUpstreamURI(upstream options.Upstream) []string { func validateUpstreamURI(upstream options.Upstream) []string {
msgs := []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)) msgs = append(msgs, fmt.Sprintf("upstream %q has empty uri: uris are required for all non-static upstreams", upstream.ID))
return msgs return msgs
} }
// Checks after this only make sense the upstream is not static // Checks after this only make sense the upstream is not static
if upstream.Static { if *upstream.Static {
return msgs return msgs
} }

View File

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

View File

@ -50,7 +50,7 @@ func NewADFSProvider(p *ProviderData, opts options.Provider) *ADFSProvider {
return &ADFSProvider{ return &ADFSProvider{
OIDCProvider: oidcProvider, OIDCProvider: oidcProvider,
skipScope: opts.ADFSConfig.SkipScope, skipScope: *opts.ADFSConfig.SkipScope,
oidcEnrichFunc: oidcProvider.EnrichSession, oidcEnrichFunc: oidcProvider.EnrichSession,
oidcRefreshFunc: oidcProvider.RefreshSession, 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/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc" 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/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@ -172,7 +173,7 @@ var _ = Describe("ADFS Provider Tests", func() {
ProtectedResource: resource, ProtectedResource: resource,
Scope: "", Scope: "",
}, options.Provider{ }, options.Provider{
ADFSConfig: options.ADFSOptions{SkipScope: true}, ADFSConfig: options.ADFSOptions{SkipScope: ptr.Ptr(true)},
}) })
result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "", url.Values{}) 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) provider.configureGroups(opts)
} }
@ -259,7 +259,7 @@ var possibleScopesList = [...]string{
} }
func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource { 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{ ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: getTargetPrincipal(ctx, opts), TargetPrincipal: getTargetPrincipal(ctx, opts),
Scopes: []string{scope}, Scopes: []string{scope},

View File

@ -51,7 +51,7 @@ func NewMicrosoftEntraIDProvider(p *ProviderData, opts options.Provider) *Micros
OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig), OIDCProvider: NewOIDCProvider(p, opts.OIDCConfig),
multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants, multiTenantAllowedTenants: opts.MicrosoftEntraIDConfig.AllowedTenants,
federatedTokenAuth: opts.MicrosoftEntraIDConfig.FederatedTokenAuth, federatedTokenAuth: *opts.MicrosoftEntraIDConfig.FederatedTokenAuth,
microsoftGraphURL: microsoftGraphURL, microsoftGraphURL: microsoftGraphURL,
} }
} }

View File

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

View File

@ -50,7 +50,7 @@ func NewOIDCProvider(p *ProviderData, opts options.OIDCOptions) *OIDCProvider {
return &OIDCProvider{ return &OIDCProvider{
ProviderData: p, 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{ p := NewOIDCProvider(providerData, options.OIDCOptions{
InsecureSkipNonce: skipNonce, InsecureSkipNonce: &skipNonce,
}) })
return p return p

View File

@ -98,8 +98,8 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
IssuerURL: providerConfig.OIDCConfig.IssuerURL, IssuerURL: providerConfig.OIDCConfig.IssuerURL,
JWKsURL: providerConfig.OIDCConfig.JwksURL, JWKsURL: providerConfig.OIDCConfig.JwksURL,
PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles, PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles,
SkipDiscovery: providerConfig.OIDCConfig.SkipDiscovery, SkipDiscovery: *providerConfig.OIDCConfig.SkipDiscovery,
SkipIssuerVerification: providerConfig.OIDCConfig.InsecureSkipIssuerVerification, SkipIssuerVerification: *providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err) 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 // 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.EmailClaim = providerConfig.OIDCConfig.EmailClaim
p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim p.GroupsClaim = providerConfig.OIDCConfig.GroupsClaim
p.SkipClaimsFromProfileURL = providerConfig.SkipClaimsFromProfileURL p.SkipClaimsFromProfileURL = *providerConfig.SkipClaimsFromProfileURL
// Set PKCE enabled or disabled based on discovery and force options // Set PKCE enabled or disabled based on discovery and force options
p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig) p.CodeChallengeMethod = parseCodeChallengeMethod(providerConfig)

View File

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