git.iko.soy/typeable/oauth2-proxy
on 761bf3b help sign in
.devcontainer/Dockerfile+1−1
since splitsnapshots
unifiedsplit
@@ -1,4 +1,4 @@
1−FROM mcr.microsoft.com/vscode/devcontainers/go:1-1.26
1+FROM mcr.microsoft.com/vscode/devcontainers/go:1-1.25
2
2
3SHELL ["/bin/bash", "-o", "pipefail", "-c"]
3SHELL ["/bin/bash", "-o", "pipefail", "-c"]
4
4
.github/ISSUE_TEMPLATE/config.yml+2−2
@@ -1,6 +1,6 @@
1blank_issues_enabled: false
1blank_issues_enabled: false
2contact_links:
2contact_links:
3− - name: OAuth2 Proxy Slack
3+ - name: OAuth2-Proxy Slack
4− url: https://cloud-native.slack.com/archives/C098Y5URZ2N
4+ url: https://gophers.slack.com/messages/CM2RSS25N
5 about: Feel free to ask any questions here.
5 about: Feel free to ask any questions here.
6
6
.github/renovate.json5−9
@@ -46,15 +46,6 @@
46 depNameTemplate: "alpine",
46 depNameTemplate: "alpine",
47 datasourceTemplate: "docker",
47 datasourceTemplate: "docker",
48 },
48 },
49− {
50− customType: "regex",
51− fileMatch: [
52− "^Makefile",
53− ],
54− matchStrings: [
55− "# renovate: datasource=(?<datasource>.*?) depName=(?<depName>.*?)\\s+?GOLANGCILINT_VERSION \\?= (?<currentValue>.*)\\s",
56− ],
57− },
58 {
49 {
59 customType: "regex",
50 customType: "regex",
60 fileMatch: ["(^|/)\\.github/workflows/[^/]+\\.ya?ml$"],
51 fileMatch: ["(^|/)\\.github/workflows/[^/]+\\.ya?ml$"],
.github/workflows/ci.yml+14−8
@@ -19,7 +19,7 @@ jobs:
19 runs-on: ubuntu-latest
19 runs-on: ubuntu-latest
20 steps:
20 steps:
21 - name: Check out code
21 - name: Check out code
22− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22+ uses: actions/checkout@v6
23 with:
23 with:
24 fetch-depth: 0
24 fetch-depth: 0
25
25
@@ -30,11 +30,17 @@ jobs:
30 id: go-version
30 id: go-version
31
31
32 - name: Set up Go
32 - name: Set up Go
33− uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
33+ uses: actions/setup-go@v6
34 with:
34 with:
35 go-version: ${{ steps.go-version.outputs.version }}
35 go-version: ${{ steps.go-version.outputs.version }}
36 check-latest: true
36 check-latest: true
37
37
38+ - name: Install golangci-lint
39+ uses: golangci/golangci-lint-action@v9
40+ with:
41+ install-only: true
42+ version: v2.11.4 # renovate: datasource=github-tags depName=golangci/golangci-lint
43+
38 - name: Verify Code Generation
44 - name: Verify Code Generation
39 run: |
45 run: |
40 make verify-generate
46 make verify-generate
@@ -68,14 +74,14 @@ jobs:
68
74
69 - name: Upload Coverage Report
75 - name: Upload Coverage Report
70 if: github.event_name == 'push'
76 if: github.event_name == 'push'
71− uses: qltysh/qlty-action/coverage@08a0a862c159eae9b9003081da6663d96efef637 # v2.3.0
77+ uses: qltysh/qlty-action/coverage@v2
72 with:
78 with:
73 oidc: true
79 oidc: true
74 files: lcov.info
80 files: lcov.info
75
81
76 - name: Run Trivy vulnerability scanner
82 - name: Run Trivy vulnerability scanner
77 if: (!startsWith(github.head_ref, 'release'))
83 if: (!startsWith(github.head_ref, 'release'))
78− uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
84+ uses: aquasecurity/trivy-action@0.35.0
79 with:
85 with:
80 scan-type: 'rootfs'
86 scan-type: 'rootfs'
81 scan-ref: './oauth2-proxy'
87 scan-ref: './oauth2-proxy'
@@ -87,7 +93,7 @@ jobs:
87
93
88 - name: Upload Trivy scan results
94 - name: Upload Trivy scan results
89 if: (!startsWith(github.head_ref, 'release'))
95 if: (!startsWith(github.head_ref, 'release'))
90− uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
96+ uses: github/codeql-action/upload-sarif@v4
91 with:
97 with:
92 sarif_file: 'trivy-results.sarif'
98 sarif_file: 'trivy-results.sarif'
93
99
@@ -95,16 +101,16 @@ jobs:
95 runs-on: ubuntu-latest
101 runs-on: ubuntu-latest
96 steps:
102 steps:
97 - name: Check out code
103 - name: Check out code
98− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
104+ uses: actions/checkout@v6
99 with:
105 with:
100 fetch-depth: 0
106 fetch-depth: 0
101
107
102 - name: Set up QEMU
108 - name: Set up QEMU
103− uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
109+ uses: docker/setup-qemu-action@v3
104
110
105 - name: Set up Docker Buildx
111 - name: Set up Docker Buildx
106 id: buildx
112 id: buildx
107− uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
113+ uses: docker/setup-buildx-action@v3
108
114
109 - name: Docker Build
115 - name: Docker Build
110 if: (!startsWith(github.head_ref, 'release'))
116 if: (!startsWith(github.head_ref, 'release'))
.github/workflows/codeql.yml+4−4
@@ -32,18 +32,18 @@ jobs:
32
32
33 steps:
33 steps:
34 - name: Checkout repository
34 - name: Checkout repository
35− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35+ uses: actions/checkout@v6
36
36
37 # Initializes the CodeQL tools for scanning.
37 # Initializes the CodeQL tools for scanning.
38 - name: Initialize CodeQL
38 - name: Initialize CodeQL
39− uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
39+ uses: github/codeql-action/init@v3
40 with:
40 with:
41 languages: go
41 languages: go
42
42
43 # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
43 # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
44 # If this step fails, then you should remove it and run the build manually (see below)
44 # If this step fails, then you should remove it and run the build manually (see below)
45 - name: Autobuild
45 - name: Autobuild
46− uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
46+ uses: github/codeql-action/autobuild@v3
47
47
48 # ℹ️ Command-line programs to run using the OS shell.
48 # ℹ️ Command-line programs to run using the OS shell.
49 # 📚 https://git.io/JvXDl
49 # 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
57 # make release
57 # make release
58
58
59 - name: Perform CodeQL Analysis
59 - name: Perform CodeQL Analysis
60− uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
60+ uses: github/codeql-action/analyze@v3
.github/workflows/create-release.yml+2−2
@@ -17,7 +17,7 @@ jobs:
17 runs-on: ubuntu-latest
17 runs-on: ubuntu-latest
18 steps:
18 steps:
19 - name: Check out code
19 - name: Check out code
20− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20+ uses: actions/checkout@v6
21 with:
21 with:
22 ref: master
22 ref: master
23 fetch-depth: 0
23 fetch-depth: 0
@@ -54,7 +54,7 @@ jobs:
54 git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
54 git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
55
55
56 - name: Setup node
56 - name: Setup node
57− uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
57+ uses: actions/setup-node@v6
58 with:
58 with:
59 node-version-file: docs/package.json
59 node-version-file: docs/package.json
60
60
.github/workflows/docs.yml+7−7
@@ -14,13 +14,13 @@ jobs:
14 if: github.event_name == 'pull_request'
14 if: github.event_name == 'pull_request'
15 runs-on: ubuntu-latest
15 runs-on: ubuntu-latest
16 steps:
16 steps:
17− - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17+ - uses: actions/checkout@v6
18
18
19 - name: Setup Pages
19 - name: Setup Pages
20 id: pages
20 id: pages
21− uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
21+ uses: actions/configure-pages@v5
22
22
23− - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
23+ - uses: actions/setup-node@v6
24 with:
24 with:
25 # renovate: datasource=node-version depName=node
25 # renovate: datasource=node-version depName=node
26 node-version: 24
26 node-version: 24
@@ -35,9 +35,9 @@ jobs:
35 if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
35 if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
36 runs-on: ubuntu-latest
36 runs-on: ubuntu-latest
37 steps:
37 steps:
38− - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38+ - uses: actions/checkout@v6
39
39
40− - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
40+ - uses: actions/setup-node@v6
41 with:
41 with:
42 # renovate: datasource=node-version depName=node
42 # renovate: datasource=node-version depName=node
43 node-version: 24
43 node-version: 24
@@ -49,7 +49,7 @@ jobs:
49 npm run build
49 npm run build
50
50
51 - name: Upload artifact
51 - name: Upload artifact
52− uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
52+ uses: actions/upload-pages-artifact@v3
53 with:
53 with:
54 path: ./docs/build
54 path: ./docs/build
55
55
@@ -65,4 +65,4 @@ jobs:
65 steps:
65 steps:
66 - name: Deploy to GitHub Pages
66 - name: Deploy to GitHub Pages
67 id: deployment
67 id: deployment
68− uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
68+ uses: actions/deploy-pages@v4
.github/workflows/labeler.yml+1−1
@@ -9,7 +9,7 @@ jobs:
9 pull-requests: write
9 pull-requests: write
10 runs-on: ubuntu-latest
10 runs-on: ubuntu-latest
11 steps:
11 steps:
12− - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
12+ - uses: actions/labeler@v6
13 with:
13 with:
14 sync-labels: true
14 sync-labels: true
15 dot: true
15 dot: true
.github/workflows/nightly.yml+4−4
@@ -12,21 +12,21 @@ jobs:
12 if: github.repository == 'oauth2-proxy/oauth2-proxy'
12 if: github.repository == 'oauth2-proxy/oauth2-proxy'
13 steps:
13 steps:
14 - name: Check out code
14 - name: Check out code
15− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
15+ uses: actions/checkout@v6
16 with:
16 with:
17 ref: master
17 ref: master
18 fetch-depth: 0
18 fetch-depth: 0
19 fetch-tags: true
19 fetch-tags: true
20
20
21 - name: Set up QEMU
21 - name: Set up QEMU
22− uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
22+ uses: docker/setup-qemu-action@v3
23
23
24 - name: Set up Docker Buildx
24 - name: Set up Docker Buildx
25 id: buildx
25 id: buildx
26− uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
26+ uses: docker/setup-buildx-action@v3
27
27
28 - name: Login to quay.io
28 - name: Login to quay.io
29− uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
29+ uses: docker/login-action@v3
30 with:
30 with:
31 registry: quay.io/oauth2-proxy
31 registry: quay.io/oauth2-proxy
32 username: ${{ secrets.REGISTRY_USERNAME_NIGHTLY }}
32 username: ${{ secrets.REGISTRY_USERNAME_NIGHTLY }}
.github/workflows/publish-release.yml+13−7
@@ -20,7 +20,7 @@ jobs:
20 tag: ${{ steps.tag.outputs.version }}
20 tag: ${{ steps.tag.outputs.version }}
21 steps:
21 steps:
22 - name: Check out code
22 - name: Check out code
23− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23+ uses: actions/checkout@v6
24 with:
24 with:
25 ref: ${{ github.event.pull_request.merge_commit_sha }}
25 ref: ${{ github.event.pull_request.merge_commit_sha }}
26 fetch-depth: 0
26 fetch-depth: 0
@@ -50,11 +50,17 @@ jobs:
50 id: go-version
50 id: go-version
51
51
52 - name: Set up Go
52 - name: Set up Go
53− uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
53+ uses: actions/setup-go@v6
54 with:
54 with:
55 go-version: ${{ steps.go-version.outputs.version }}
55 go-version: ${{ steps.go-version.outputs.version }}
56 check-latest: true
56 check-latest: true
57
57
58+ - name: Install golangci-lint
59+ uses: golangci/golangci-lint-action@v9
60+ with:
61+ install-only: true
62+ version: v2.11.4 # renovate: datasource=github-tags depName=golangci/golangci-lint
63+
58 - name: Get go dependencies
64 - name: Get go dependencies
59 run: |
65 run: |
60 go mod download
66 go mod download
@@ -64,7 +70,7 @@ jobs:
64
70
65 # Upload artifacts in case of workflow failure
71 # Upload artifacts in case of workflow failure
66 - name: Upload Artifacts
72 - name: Upload Artifacts
67− uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
73+ uses: actions/upload-artifact@v7
68 with:
74 with:
69 name: oauth2-proxy-artifacts
75 name: oauth2-proxy-artifacts
70 path: |
76 path: |
@@ -100,21 +106,21 @@ jobs:
100 runs-on: ubuntu-latest
106 runs-on: ubuntu-latest
101 steps:
107 steps:
102 - name: Check out code
108 - name: Check out code
103− uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
109+ uses: actions/checkout@v6
104 with:
110 with:
105 ref: ${{ needs.publish.outputs.tag }}
111 ref: ${{ needs.publish.outputs.tag }}
106 fetch-depth: 0
112 fetch-depth: 0
107 fetch-tags: true
113 fetch-tags: true
108
114
109 - name: Set up QEMU
115 - name: Set up QEMU
110− uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
116+ uses: docker/setup-qemu-action@v3
111
117
112 - name: Set up Docker Buildx
118 - name: Set up Docker Buildx
113 id: buildx
119 id: buildx
114− uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
120+ uses: docker/setup-buildx-action@v3
115
121
116 - name: Login to quay.io
122 - name: Login to quay.io
117− uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
123+ uses: docker/login-action@v3
118 with:
124 with:
119 registry: quay.io/oauth2-proxy
125 registry: quay.io/oauth2-proxy
120 username: ${{ secrets.REGISTRY_USERNAME }}
126 username: ${{ secrets.REGISTRY_USERNAME }}
.github/workflows/stale.yml+1−1
@@ -10,7 +10,7 @@ jobs:
10 runs-on: ubuntu-latest
10 runs-on: ubuntu-latest
11
11
12 steps:
12 steps:
13− - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
13+ - uses: actions/stale@v10
14 with:
14 with:
15 repo-token: ${{ secrets.GITHUB_TOKEN }}
15 repo-token: ${{ secrets.GITHUB_TOKEN }}
16 days-before-stale: 180
16 days-before-stale: 180
CHANGELOG.md−104
@@ -6,112 +6,8 @@
6
6
7## Breaking Changes
7## Breaking Changes
8
8
9−## Changes since v7.15.4
10−
11−# V7.15.4
12−
13−## Release Highlights
14−
15−- 🔵 Golang version upgrade to v1.26.7
16− - Upgrade of all dependencies to their latest versions
17−- 🕵️‍♀️ Vulnerabilities have been addressed
18− - [CVE-2026-33818](https://nvd.nist.gov/vuln/detail/CVE-2026-33818)
19− - [CVE-2026-39821](https://nvd.nist.gov/vuln/detail/CVE-2026-39821)
20− - [CVE-2026-39822](https://nvd.nist.gov/vuln/detail/CVE-2026-39822)
21− - [CVE-2026-42505](https://nvd.nist.gov/vuln/detail/CVE-2026-42505)
22− - [CVE-2026-46600](https://nvd.nist.gov/vuln/detail/CVE-2026-46600)
23− - [CVE-2026-56852](https://nvd.nist.gov/vuln/detail/CVE-2026-56852)
24− - [CVE-2026-56853](https://nvd.nist.gov/vuln/detail/CVE-2026-56853)
25− - [CVE-2026-56858](https://nvd.nist.gov/vuln/detail/CVE-2026-56858)
26− - [CVE-2026-56859](https://nvd.nist.gov/vuln/detail/CVE-2026-56859)
27− - [CVE-2026-56860](https://nvd.nist.gov/vuln/detail/CVE-2026-56860)
28− - [CVE-2026-56862](https://nvd.nist.gov/vuln/detail/CVE-2026-56862)
29−
30−## Important Notes
31−
32−## Breaking Changes
33−
34−## Changes since v7.15.3
35−
36−- [#3493](https://github.com/oauth2-proxy/oauth2-proxy/pull/3493) chore(deps): update gomod @tuunit
37−
38−# V7.15.3
39−
40−## Release Highlights
41−
42−- 🔵 Golang version upgrade to v1.26.4
43− - Upgrade of all dependencies to their latest versions
44−- 🕵️‍♀️ Vulnerabilities have ben addressed
45− - [CVE-2026-33811](https://nvd.nist.gov/vuln/detail/)
46− - [CVE-2026-33814](https://nvd.nist.gov/vuln/detail/)
47− - [CVE-2026-39820](https://nvd.nist.gov/vuln/detail/)
48− - [CVE-2026-39836](https://nvd.nist.gov/vuln/detail/)
49− - [CVE-2026-42499](https://nvd.nist.gov/vuln/detail/)
50− - [CVE-2026-42504](https://nvd.nist.gov/vuln/detail/)
51− - [CVE-2026-39823](https://nvd.nist.gov/vuln/detail/)
52− - [CVE-2026-39826](https://nvd.nist.gov/vuln/detail/)
53− - [CVE-2026-39825](https://nvd.nist.gov/vuln/detail/)
54− - [CVE-2026-27145](https://nvd.nist.gov/vuln/detail/)
55− - [CVE-2026-42507](https://nvd.nist.gov/vuln/detail/)
56−
57−## Important Notes
58−
59−## Breaking Changes
60−
61−## Changes since v7.15.2
62−
63−- [#3477](https://github.com/oauth2-proxy/oauth2-proxy/pull/3477) chore(dep): bump go to 1.26 and migrate of reverse proxy handling
64−
65−# V7.15.2
66−
67−## Release Highlights
68−
69−- 🔵 Golang version upgrade to v1.25.9
70− - Upgrade of all dependencies to their latest versions
71− - [CVE-2026-34986](https://nvd.nist.gov/vuln/detail/CVE-2026-34986)
72− - [CVE-2026-32281](https://nvd.nist.gov/vuln/detail/CVE-2026-32281)
73− - [CVE-2026-32289](https://nvd.nist.gov/vuln/detail/CVE-2026-32289)
74− - [CVE-2026-32288](https://nvd.nist.gov/vuln/detail/CVE-2026-32288)
75− - [CVE-2026-32280](https://nvd.nist.gov/vuln/detail/CVE-2026-32280)
76− - [CVE-2026-32282](https://nvd.nist.gov/vuln/detail/CVE-2026-32282)
77− - [CVE-2026-32283](https://nvd.nist.gov/vuln/detail/CVE-2026-32283)
78−- 🕵️‍♀️ Vulnerabilities have been addressed
79−
80−## Important Notes
81−
82−We have had security audits performed on OAuth2 Proxy in the past couple of weeks and as a result we have fixed
83−several CRITICAL vulnerabilities.
84−
85−The security vulnerabilities include multiple authentication bypasses and a potential session fixation attack.
86−For more details and to identify if you are effects, we urge all users of OAuth2 Proxy to read the security
87−disclosures.
88−
89−- (Critical) [GHSA-5hvv-m4w4-gf6v](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-5hvv-m4w4-gf6v) fix: health check user-agent authentication bypass
90−- (Critical) [GHSA-7x63-xv5r-3p2x](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-7x63-xv5r-3p2x) fix: authentication bypass via X-Forwarded-Uri header spoofing
91−- (High) [GHSA-pxq7-h93f-9jrg](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-pxq7-h93f-9jrg) fix: fragment evaluation as part of the allowed routes
92−- (Moderate) [GHSA-c5c4-8r6x-56w3](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-c5c4-8r6x-56w3) fix: email validation bypass via malformed multi-@ email claims
93−
94−Furthermore, for improving the security of OAuth2 Proxy we introduced a new flag `--trusted-proxy-ip` that allows users
95−to explicitly specify trusted reverse proxy IPs for the `X-Forwarded-*` headers. This is an important step to prevent
96−potential header spoofing attacks and to ensure that OAuth2 Proxy only trusts headers from known and trusted sources.
97−We highly recommend users to review their deployment architecture and consider using this flag to enhance the security
98−of their OAuth2 Proxy instances. Check the docs for more details: https://oauth2-proxy.github.io/oauth2-proxy/configuration/overview#proxy-options
99−
100−Furthermore, we want to thank everyone who contributed to the audits and reported potential issues to make open source
101−software like OAuth2 Proxy more secure for everyone.
102−
103−## Breaking Changes
104−
105## Changes since v7.15.1
9## Changes since v7.15.1
106
10
107−- [#3411](https://github.com/oauth2-proxy/oauth2-proxy/pull/3411) chore(deps): update gomod dependencies (@tuunit)
108−- [#3333](https://github.com/oauth2-proxy/oauth2-proxy/pull/3333) fix: invalidate session on fatal OAuth2 refresh errors (@frhack)
109−- [GHSA-f24x-5g9q-753f](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-f24x-5g9q-753f) fix: clear session cookie at beginning of signinpage handler (@fnoehWM / @bella-WI / @tuunit)
110−- [GHSA-5hvv-m4w4-gf6v](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-5hvv-m4w4-gf6v) fix: health check user-agent authentication bypass (@tuunit)
111−- [GHSA-7x63-xv5r-3p2x](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-7x63-xv5r-3p2x) fix: authentication bypass via X-Forwarded-Uri header spoofing (@tuunit)
112−- [GHSA-pxq7-h93f-9jrg](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-pxq7-h93f-9jrg) fix: fragment evaluation as part of the allowed routes (@tuunit)
113−- [GHSA-c5c4-8r6x-56w3](https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-c5c4-8r6x-56w3) fix: email validation bypass via malformed multi-@ email claims (@tuunit)
114−
115# V7.15.1
11# V7.15.1
116
12
117## Release Highlights
13## Release Highlights
MAINTAINERS.md+1−1
@@ -6,7 +6,7 @@ by our [project governance](GOVERNANCE.md).
6| Name | GitHub Handle | Domains of reponsibility | Email Alias | Affiliation |
6| Name | GitHub Handle | Domains of reponsibility | Email Alias | Affiliation |
7| ---------------- | ------------------------------------------------------ | ------------------------ | -------------------------- | ----------- |
7| ---------------- | ------------------------------------------------------ | ------------------------ | -------------------------- | ----------- |
8| Joel Speed | [@JoelSpeed](https://github.com/joelspeed) | Governance, Core | joel@oauth2-proxy.dev | Red Hat |
8| Joel Speed | [@JoelSpeed](https://github.com/joelspeed) | Governance, Core | joel@oauth2-proxy.dev | Red Hat |
9−| Jan Larwig | [@tuunit](https://github.com/tuunit) | Governance, Core | jan@oauth2-proxy.dev | STACKIT |
9+| Jan Larwig | [@tuunit](https://github.com/tuunit) | Governance, Core | jan@oauth2-proxy.dev | IONOS Cloud |
10| JJ Łakis | [@jjlakis](https://github.com/jjlakis) | Provider | jj@oauth2-proxy.dev | - |
10| JJ Łakis | [@jjlakis](https://github.com/jjlakis) | Provider | jj@oauth2-proxy.dev | - |
11| Koen van Zuijlen | [@kvanzuijlen](https://github.com/kvanzuijlen) | CI | koen@oauth2-proxy.dev | - |
11| Koen van Zuijlen | [@kvanzuijlen](https://github.com/kvanzuijlen) | CI | koen@oauth2-proxy.dev | - |
12| Pierluigi Lenoci | [@pierluigilenoci](https://github.com/pierluigilenoci) | Helm | pierluigi@oauth2-proxy.dev | SAP |
12| Pierluigi Lenoci | [@pierluigilenoci](https://github.com/pierluigilenoci) | Helm | pierluigi@oauth2-proxy.dev | SAP |
Makefile+4−9
@@ -25,8 +25,7 @@ help: ## Display this help
25
25
26
26
27GO ?= go
27GO ?= go
28−# renovate: datasource=github-tags depName=golangci/golangci-lint
28+GOLANGCILINT ?= golangci-lint
29−GOLANGCILINT_VERSION ?= v2.11.4
30
29
31BINARY := oauth2-proxy
30BINARY := oauth2-proxy
32VERSION ?= $(shell git describe --always --dirty --tags 2>/dev/null || echo "undefined")
31VERSION ?= $(shell git describe --always --dirty --tags 2>/dev/null || echo "undefined")
@@ -67,7 +66,7 @@ DOCKER_BUILDX_PUSH := $(DOCKER_BUILDX) --push
67DOCKER_BUILDX_PUSH_X_PLATFORM := $(DOCKER_BUILDX_PUSH) --platform ${DOCKER_BUILD_PLATFORM}
66DOCKER_BUILDX_PUSH_X_PLATFORM := $(DOCKER_BUILDX_PUSH) --platform ${DOCKER_BUILD_PLATFORM}
68
67
69DOCKER_BUILD_PLATFORM_ALPINE ?= linux/amd64,linux/arm64,linux/ppc64le,linux/arm/v6,linux/arm/v7,linux/s390x
68DOCKER_BUILD_PLATFORM_ALPINE ?= linux/amd64,linux/arm64,linux/ppc64le,linux/arm/v6,linux/arm/v7,linux/s390x
70−DOCKER_BUILD_RUNTIME_IMAGE_ALPINE ?= alpine:3.24.1
69+DOCKER_BUILD_RUNTIME_IMAGE_ALPINE ?= alpine:3.23.3
71DOCKER_BUILDX_ARGS_ALPINE ?= --build-arg RUNTIME_IMAGE=${DOCKER_BUILD_RUNTIME_IMAGE_ALPINE} ${DOCKER_BUILDX_COMMON_ARGS}
70DOCKER_BUILDX_ARGS_ALPINE ?= --build-arg RUNTIME_IMAGE=${DOCKER_BUILD_RUNTIME_IMAGE_ALPINE} ${DOCKER_BUILDX_COMMON_ARGS}
72DOCKER_BUILDX_X_PLATFORM_ALPINE := docker buildx build ${DOCKER_BUILDX_ARGS_ALPINE} --platform ${DOCKER_BUILD_PLATFORM_ALPINE}
71DOCKER_BUILDX_X_PLATFORM_ALPINE := docker buildx build ${DOCKER_BUILDX_ARGS_ALPINE} --platform ${DOCKER_BUILD_PLATFORM_ALPINE}
73DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --push
72DOCKER_BUILDX_PUSH_X_PLATFORM_ALPINE := $(DOCKER_BUILDX_X_PLATFORM_ALPINE) --push
@@ -144,7 +143,7 @@ verify-generate: generate ## Verify command to check if alpha config docs are in
144##@ Miscellaneous
143##@ Miscellaneous
145
144
146.PHONY: test
145.PHONY: test
147−test: ## Run all Go tests
146+test: lint ## Run all Go tests
148 GO111MODULE=on $(GO) test $(TESTCOVER) -v -race ./...
147 GO111MODULE=on $(GO) test $(TESTCOVER) -v -race ./...
149
148
150.PHONY: release
149.PHONY: release
@@ -158,11 +157,7 @@ clean: ## Cleanup release and build files
158
157
159.PHONY: lint
158.PHONY: lint
160lint: validate-go-version ## Lint all files using golangci-lint
159lint: validate-go-version ## Lint all files using golangci-lint
161− GO111MODULE=on $(GO) run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCILINT_VERSION) run --timeout 10m
160+ GO111MODULE=on $(GOLANGCILINT) run
162−
163−.PHONY: lint-fix
164−lint-fix: validate-go-version ## Lint all files and apply fixes using golangci-lint
165− GO111MODULE=on $(GO) run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCILINT_VERSION) run --timeout 10m --fix
166
161
167.PHONY: validate-go-version
162.PHONY: validate-go-version
168validate-go-version: ## Validate Go environment requirements
163validate-go-version: ## Validate Go environment requirements
README.md+4−4
@@ -12,7 +12,7 @@
12
12
13OAuth2 Proxy is a flexible, open-source tool that can act as either a standalone reverse proxy or a middleware component integrated into existing reverse proxy or load balancer setups. It provides a simple and secure way to protect your web applications with OAuth2 / OIDC authentication. As a reverse proxy, it intercepts requests to your application and redirects users to an OAuth2 provider for authentication. As a middleware, it can be seamlessly integrated into your existing infrastructure to handle authentication for multiple applications.
13OAuth2 Proxy is a flexible, open-source tool that can act as either a standalone reverse proxy or a middleware component integrated into existing reverse proxy or load balancer setups. It provides a simple and secure way to protect your web applications with OAuth2 / OIDC authentication. As a reverse proxy, it intercepts requests to your application and redirects users to an OAuth2 provider for authentication. As a middleware, it can be seamlessly integrated into your existing infrastructure to handle authentication for multiple applications.
14
14
15−OAuth2 Proxy supports a lot of OAuth2 as well as OIDC providers. Either through a generic OIDC client or a specific implementation for Google, Microsoft Entra ID, GitHub, login.gov and others. Through specialised provider implementations OAuth2 Proxy can extract more details about the user like preferred usernames and groups. Those details can then be forwarded as HTTP headers to your upstream applications.
15+OAuth2 Proxy supports a lot of OAuth2 as well as OIDC providers. Either through a generic OIDC client or a specific implementation for Google, Microsoft Entra ID, GitHub, login.gov and others. Through specialised provider implementations oauth2-proxy can extract more details about the user like preferred usernames and groups. Those details can then be forwarded as HTTP headers to your upstream applications.
16
16
17![Simplified Architecture](docs/static/img/simplified-architecture.svg)
17![Simplified Architecture](docs/static/img/simplified-architecture.svg)
18
18
@@ -23,7 +23,7 @@ OAuth2 Proxy's [Installation Docs](https://oauth2-proxy.github.io/oauth2-proxy/i
23## Releases
23## Releases
24
24
25### Binaries
25### Binaries
26−We publish OAuth2 Proxy as compiled binaries on GitHub for all major architectures as well as more exotic ones like `ppc64le` as well as `s390x`.
26+We publish oauth2-proxy as compiled binaries on GitHub for all major architectures as well as more exotic ones like `ppc64le` as well as `s390x`.
27
27
28Check out the [latest release](https://github.com/oauth2-proxy/oauth2-proxy/releases/latest).
28Check out the [latest release](https://github.com/oauth2-proxy/oauth2-proxy/releases/latest).
29
29
@@ -49,9 +49,9 @@ SAP Open Source Program
49Microsoft Azure credits for open source projects
49Microsoft Azure credits for open source projects
50
50
51## Getting Involved
51## Getting Involved
52−[![Slack](https://img.shields.io/badge/slack-CNCF_%23oauth2--proxy-blue?logo=slack)](https://cloud-native.slack.com/archives/C098Y5URZ2N)
52+[![Slack](https://img.shields.io/badge/slack-Gopher_%23oauth2--proxy-red?logo=slack)](https://gophers.slack.com/archives/CM2RSS25N)
53
53
54−Join the #oauth2-proxy [Slack channel](https://cloud-native.slack.com/archives/C098Y5URZ2N) to chat with other users of OAuth2 Proxy or reach out to the maintainers directly. Use the [public invite link](https://slack.cncf.io/) to get an invite for the CNCF space.
54+Join the #oauth2-proxy [Slack channel](https://gophers.slack.com/archives/CM2RSS25N) to chat with other users of oauth2-proxy or reach out to the maintainers directly. Use the [public invite link](https://invite.slack.golangbridge.org/) to get an invite for the Gopher Slack space.
55
55
56OAuth2 Proxy is a community-driven project. We rely on the contribut️ions of our users to continually improve it. While review times can vary, we appreciate your patience and understanding. As a volunteer-driven project, we strive to keep this project stable and might take longer to merge changes.
56OAuth2 Proxy is a community-driven project. We rely on the contribut️ions of our users to continually improve it. While review times can vary, we appreciate your patience and understanding. As a volunteer-driven project, we strive to keep this project stable and might take longer to merge changes.
57
57
contrib/local-environment/Makefile+2−2
@@ -49,8 +49,8 @@ kubernetes-down:
49
49
50.PHONY: traefik-up
50.PHONY: traefik-up
51traefik-up:
51traefik-up:
52− docker compose -f docker-compose-traefik.yaml up -d
52+ docker compose -f docker-compose.yaml -f docker-compose-traefik.yaml up -d
53
53
54.PHONY: traefik-%
54.PHONY: traefik-%
55traefik-%:
55traefik-%:
56− docker compose -f docker-compose-traefik.yaml $*
56+ docker compose -f docker-compose.yaml -f docker-compose-traefik.yaml $*
contrib/local-environment/dex-traefik.yamldeleted−25
@@ -1,25 +0,0 @@
1−# This configuration is intended to be used with the Traefik local environment.
2−# Dex uses in-memory storage because this environment is disposable.
3−issuer: http://dex.localtest.me/dex
4−storage:
5− type: memory
6−web:
7− http: 0.0.0.0:5556
8−oauth2:
9− skipApprovalScreen: true
10−expiry:
11− signingKeys: "4h"
12− idTokens: "1h"
13−staticClients:
14− - id: oauth2-proxy
15− redirectURIs:
16− - "http://oauth2-proxy.localtest.me/oauth2/callback"
17− name: "OAuth2 Proxy"
18− secret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
19−enablePasswordDB: true
20−staticPasswords:
21− - email: "admin@example.com"
22− # bcrypt hash of the string "password"
23− hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
24− username: "admin"
25− userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
contrib/local-environment/dex.yaml+17−13
@@ -3,7 +3,11 @@
3# This should configure Dex to run on port 5556 and provides a static login
3# This should configure Dex to run on port 5556 and provides a static login
4issuer: http://dex.localtest.me:5556/dex
4issuer: http://dex.localtest.me:5556/dex
5storage:
5storage:
6− type: memory
6+ type: etcd
7+ config:
8+ endpoints:
9+ - http://etcd:2379
10+ namespace: dex/
7web:
11web:
8 http: 0.0.0.0:5556
12 http: 0.0.0.0:5556
9oauth2:
13oauth2:
@@ -12,17 +16,17 @@ expiry:
12 signingKeys: "4h"
16 signingKeys: "4h"
13 idTokens: "1h"
17 idTokens: "1h"
14staticClients:
18staticClients:
15− - id: oauth2-proxy
19+- id: oauth2-proxy
16− redirectURIs:
20+ redirectURIs:
17− # These redirect URIs point to the `--redirect-url` for OAuth2 proxy.
21+ # These redirect URIs point to the `--redirect-url` for OAuth2 proxy.
18− - "http://oauth2-proxy.localtest.me:4180/oauth2/callback" # For basic proxy example.
22+ - 'http://oauth2-proxy.localtest.me:4180/oauth2/callback' # For basic proxy example.
19− - "http://oauth2-proxy.localtest.me:8080/oauth2/callback" # For nginx example.
23+ - 'http://oauth2-proxy.oauth2-proxy.localhost/oauth2/callback' # For nginx and traefik example.
20− name: "OAuth2 Proxy"
24+ name: 'OAuth2 Proxy'
21− secret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
25+ secret: b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK
22enablePasswordDB: true
26enablePasswordDB: true
23staticPasswords:
27staticPasswords:
24− - email: "admin@example.com"
28+- email: "admin@example.com"
25− # bcrypt hash of the string "password"
29+ # bcrypt hash of the string "password"
26− hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
30+ hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
27− username: "admin"
31+ username: "admin"
28− userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
32+ userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
contrib/local-environment/docker-compose-alpha-config.yaml+15−2
@@ -1,7 +1,7 @@
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
2# for manual testing and exploration of features.
2# for manual testing and exploration of features.
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
4−# HTTPBin as an example upstream.
4+# etcd for storage for Dex and HTTPBin as an example upstream.
5# This file also uses alpha configuration when configuring OAuth2 Proxy.
5# This file also uses alpha configuration when configuring OAuth2 Proxy.
6#
6#
7# This file is an extension of the main compose file and must be used with it
7# This file is an extension of the main compose file and must be used with it
@@ -14,7 +14,7 @@ version: "3.0"
14services:
14services:
15 oauth2-proxy:
15 oauth2-proxy:
16 container_name: oauth2-proxy
16 container_name: oauth2-proxy
17− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
17+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
18 command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml
18 command: --config /oauth2-proxy.cfg --alpha-config /oauth2-proxy-alpha-config.yaml
19 hostname: oauth2-proxy
19 hostname: oauth2-proxy
20 volumes:
20 volumes:
@@ -43,12 +43,25 @@ services:
43 dex:
43 dex:
44 aliases:
44 aliases:
45 - dex.localtest.me
45 - dex.localtest.me
46+ etcd: {}
47+ depends_on:
48+ - etcd
46 httpbin:
49 httpbin:
47 container_name: httpbin
50 container_name: httpbin
48 image: kennethreitz/httpbin
51 image: kennethreitz/httpbin
49 ports: []
52 ports: []
50 networks:
53 networks:
51 httpbin: {}
54 httpbin: {}
55+ etcd:
56+ container_name: etcd
57+ image: gcr.io/etcd-development/etcd:v3.6.8
58+ entrypoint: /usr/local/bin/etcd
59+ command:
60+ - --listen-client-urls=http://0.0.0.0:2379
61+ - --advertise-client-urls=http://etcd:2379
62+ networks:
63+ etcd: {}
52networks:
64networks:
53 dex: {}
65 dex: {}
66+ etcd: {}
54 httpbin: {}
67 httpbin: {}
contrib/local-environment/docker-compose-gitea.yaml+2−2
@@ -14,7 +14,7 @@ version: '3.0'
14services:
14services:
15 oauth2-proxy:
15 oauth2-proxy:
16 container_name: oauth2-proxy
16 container_name: oauth2-proxy
17− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
17+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
18 command: --config /oauth2-proxy.cfg
18 command: --config /oauth2-proxy.cfg
19 hostname: oauth2-proxy
19 hostname: oauth2-proxy
20 volumes:
20 volumes:
@@ -39,7 +39,7 @@ services:
39 httpbin: {}
39 httpbin: {}
40
40
41 gitea:
41 gitea:
42− image: gitea/gitea:1.27.2
42+ image: gitea/gitea:1.25.5
43 container_name: gitea
43 container_name: gitea
44 environment:
44 environment:
45 - USER_UID=1000
45 - USER_UID=1000
contrib/local-environment/docker-compose-keycloak.yaml+6−6
@@ -10,11 +10,11 @@
10#
10#
11# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle using user=admin@example.com, password=password
11# Access http://oauth2-proxy.localtest.me:4180 to initiate a login cycle using user=admin@example.com, password=password
12# Access http://keycloak.localtest.me:9080 with the same credentials to check out the settings
12# Access http://keycloak.localtest.me:9080 with the same credentials to check out the settings
13−version: "3.0"
13+version: '3.0'
14services:
14services:
15 oauth2-proxy:
15 oauth2-proxy:
16 container_name: oauth2-proxy
16 container_name: oauth2-proxy
17− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
17+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
18 command: --config /oauth2-proxy.cfg
18 command: --config /oauth2-proxy.cfg
19 hostname: oauth2-proxy
19 hostname: oauth2-proxy
20 volumes:
20 volumes:
@@ -40,12 +40,12 @@ services:
40
40
41 keycloak:
41 keycloak:
42 container_name: keycloak
42 container_name: keycloak
43− image: keycloak/keycloak:26.7
43+ image: keycloak/keycloak:25.0
44 hostname: keycloak
44 hostname: keycloak
45 command:
45 command:
46− - "start-dev"
46+ - 'start-dev'
47− - "--http-port=9080"
47+ - '--http-port=9080'
48− - "--import-realm"
48+ - '--import-realm'
49 volumes:
49 volumes:
50 - ./keycloak:/opt/keycloak/data/import
50 - ./keycloak:/opt/keycloak/data/import
51 environment:
51 environment:
contrib/local-environment/docker-compose-nginx.yaml+30−6
@@ -1,28 +1,38 @@
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
2# for manual testing and exploration of features.
2# for manual testing and exploration of features.
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
4−# nginx as a reverse proxy and other HTTP services for upstreams.
4+# etcd for storage for Dex, nginx as a reverse proxy and other http services for upstreams
5#
5#
6# This file is an extension of the main compose file and must be used with it
6# This file is an extension of the main compose file and must be used with it
7# docker-compose -f docker-compose.yaml -f docker-compose-nginx.yaml <command>
7# docker-compose -f docker-compose.yaml -f docker-compose-nginx.yaml <command>
8# Alternatively:
8# Alternatively:
9# make nginx-<command> (eg make nginx-up, make nginx-down)
9# make nginx-<command> (eg make nginx-up, make nginx-down)
10#
10#
11−# Access http://oauth2-proxy.localtest.me:8080 to initiate a login flow.
11+# Access one of the following URLs to initiate a login flow:
12+# - http://oauth2-proxy.localhost
13+# - http://httpbin.oauth2-proxy.localhost
14+#
15+# The OAuth2 Proxy itself is hosted at http://oauth2-proxy.oauth2-proxy.localhost
16+#
17+# Note, the above URLs should work with Chrome, but you may need to add hosts
18+# entries for other browsers
19+# 127.0.0.1 oauth2-proxy.localhost
20+# 127.0.0.1 httpbin.oauth2-proxy.localhost
21+# 127.0.0.1 oauth2-proxy.oauth2-proxy.localhost
12version: "3.0"
22version: "3.0"
13services:
23services:
14 oauth2-proxy:
24 oauth2-proxy:
15− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
25+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
16 ports: []
26 ports: []
17 hostname: oauth2-proxy
27 hostname: oauth2-proxy
18 container_name: oauth2-proxy
28 container_name: oauth2-proxy
19 command: --config /oauth2-proxy.cfg
29 command: --config /oauth2-proxy.cfg
20− restart: unless-stopped
21 volumes:
30 volumes:
22 - "./oauth2-proxy-nginx.cfg:/oauth2-proxy.cfg"
31 - "./oauth2-proxy-nginx.cfg:/oauth2-proxy.cfg"
23 networks:
32 networks:
24 oauth2-proxy: {}
33 oauth2-proxy: {}
25 dex: {}
34 dex: {}
35+ etcd: {}
26 httpbin: {}
36 httpbin: {}
27 depends_on:
37 depends_on:
28 - dex
38 - dex
@@ -31,16 +41,17 @@ services:
31 depends_on:
41 depends_on:
32 - oauth2-proxy
42 - oauth2-proxy
33 container_name: nginx
43 container_name: nginx
34− image: nginx:1.31
44+ image: nginx:1.29
35 restart: unless-stopped
45 restart: unless-stopped
36 ports:
46 ports:
37− - 8080:8080/tcp
47+ - 80:80/tcp
38 hostname: nginx
48 hostname: nginx
39 volumes:
49 volumes:
40 - "./nginx.conf:/etc/nginx/conf.d/default.conf"
50 - "./nginx.conf:/etc/nginx/conf.d/default.conf"
41 networks:
51 networks:
42 oauth2-proxy: {}
52 oauth2-proxy: {}
43 dex: {}
53 dex: {}
54+ etcd: {}
44 httpbin: {}
55 httpbin: {}
45 dex:
56 dex:
46 container_name: dex
57 container_name: dex
@@ -56,13 +67,26 @@ services:
56 dex:
67 dex:
57 aliases:
68 aliases:
58 - dex.localtest.me
69 - dex.localtest.me
70+ etcd: {}
71+ depends_on:
72+ - etcd
59 httpbin:
73 httpbin:
60 container_name: httpbin
74 container_name: httpbin
61 image: kennethreitz/httpbin
75 image: kennethreitz/httpbin
62 ports: []
76 ports: []
63 networks:
77 networks:
64 httpbin: {}
78 httpbin: {}
79+ etcd:
80+ container_name: etcd
81+ image: gcr.io/etcd-development/etcd:v3.6.8
82+ entrypoint: /usr/local/bin/etcd
83+ command:
84+ - --listen-client-urls=http://0.0.0.0:2379
85+ - --advertise-client-urls=http://etcd:2379
86+ networks:
87+ etcd: {}
65networks:
88networks:
66 dex: {}
89 dex: {}
90+ etcd: {}
67 httpbin: {}
91 httpbin: {}
68 oauth2-proxy: {}
92 oauth2-proxy: {}
contrib/local-environment/docker-compose-traefik.yaml+18−42
@@ -1,7 +1,7 @@
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
2# for manual testing and exploration of features.
2# for manual testing and exploration of features.
3−# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider
3+# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
4−# and HTTPBin as an example upstream.
4+# HTTPBin as an example upstream.
5#
5#
6# This can either be created using docker-compose
6# This can either be created using docker-compose
7# docker-compose -f docker-compose-traefik.yaml <command>
7# docker-compose -f docker-compose-traefik.yaml <command>
@@ -9,51 +9,32 @@
9# make traefik-<command> (eg. make traefik-up, make traefik-down)
9# make traefik-<command> (eg. make traefik-up, make traefik-down)
10#
10#
11# Access one of the following URLs to initiate a login flow:
11# Access one of the following URLs to initiate a login flow:
12−# - http://oauth2-proxy.localtest.me
12+# - http://oauth2-proxy.localhost
13−# - http://httpbin.localtest.me
13+# - http://httpbin.oauth2-proxy.localhost
14−# - http://dex.localtest.me/dex
15#
14#
16−# The OAuth2 Proxy itself is hosted at http://oauth2-proxy.localtest.me
15+# The OAuth2 Proxy itself is hosted at http://oauth2-proxy.oauth2-proxy.localhost
16+#
17+# Note, the above URLs should work with Chrome, but you may need to add hosts
18+# entries for other browsers
19+# 127.0.0.1 oauth2-proxy.localhost
20+# 127.0.0.1 httpbin.oauth2-proxy.localhost
21+# 127.0.0.1 oauth2-proxy.oauth2-proxy.localhost
22+version: '3.0'
17services:
23services:
18
24
19 oauth2-proxy:
25 oauth2-proxy:
20− container_name: oauth2-proxy
21− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
26+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
22− command: --config /oauth2-proxy.cfg
23 ports: []
27 ports: []
24 hostname: oauth2-proxy
28 hostname: oauth2-proxy
25 volumes:
29 volumes:
26 - "./oauth2-proxy-traefik.cfg:/oauth2-proxy.cfg"
30 - "./oauth2-proxy-traefik.cfg:/oauth2-proxy.cfg"
27− restart: unless-stopped
28− networks:
29− oauth2-proxy: {}
30− dex: {}
31− depends_on:
32− - dex
33− - httpbin
34−
35− dex:
36− container_name: dex
37− image: ghcr.io/dexidp/dex:v2.45.1
38− command: dex serve /dex.yaml
39− hostname: dex
40− volumes:
41− - "./dex-traefik.yaml:/dex.yaml"
42− restart: unless-stopped
43 networks:
31 networks:
44− dex: {}
32+ oauth2-proxy:
45−
46− httpbin:
47− container_name: httpbin
48− image: kennethreitz/httpbin
49− ports: []
50− networks:
51− httpbin: {}
52
33
53 # Reverse proxy
34 # Reverse proxy
54 gateway:
35 gateway:
55 container_name: traefik
36 container_name: traefik
56− image: traefik:v3.7.10
37+ image: traefik:v2.11.40
57 volumes:
38 volumes:
58 - "./traefik:/etc/traefik"
39 - "./traefik:/etc/traefik"
59 ports:
40 ports:
@@ -62,13 +43,8 @@ services:
62 depends_on:
43 depends_on:
63 - oauth2-proxy
44 - oauth2-proxy
64 networks:
45 networks:
65− oauth2-proxy: {}
46+ oauth2-proxy:
66− httpbin: {}
47+ httpbin:
67− dex:
68− aliases:
69− - dex.localtest.me
70
48
71networks:
49networks:
72− dex: {}
73− httpbin: {}
74− oauth2-proxy: {}
50+ oauth2-proxy:
contrib/local-environment/docker-compose.yaml+15−2
@@ -1,7 +1,7 @@
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
1# This docker-compose file can be used to bring up an example instance of oauth2-proxy
2# for manual testing and exploration of features.
2# for manual testing and exploration of features.
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
3# Alongside OAuth2-Proxy, this file also starts Dex to act as the identity provider,
4−# HTTPBin as an example upstream.
4+# etcd for storage for Dex and HTTPBin as an example upstream.
5#
5#
6# This can either be created using docker-compose
6# This can either be created using docker-compose
7# docker-compose -f docker-compose.yaml <command>
7# docker-compose -f docker-compose.yaml <command>
@@ -13,7 +13,7 @@ version: "3.0"
13services:
13services:
14 oauth2-proxy:
14 oauth2-proxy:
15 container_name: oauth2-proxy
15 container_name: oauth2-proxy
16− image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.4
16+ image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.1
17 command: --config /oauth2-proxy.cfg
17 command: --config /oauth2-proxy.cfg
18 hostname: oauth2-proxy
18 hostname: oauth2-proxy
19 volumes:
19 volumes:
@@ -41,12 +41,25 @@ services:
41 dex:
41 dex:
42 aliases:
42 aliases:
43 - dex.localtest.me
43 - dex.localtest.me
44+ etcd: {}
45+ depends_on:
46+ - etcd
44 httpbin:
47 httpbin:
45 container_name: httpbin
48 container_name: httpbin
46 image: kennethreitz/httpbin
49 image: kennethreitz/httpbin
47 ports: []
50 ports: []
48 networks:
51 networks:
49 httpbin: {}
52 httpbin: {}
53+ etcd:
54+ container_name: etcd
55+ image: gcr.io/etcd-development/etcd:v3.6.8
56+ entrypoint: /usr/local/bin/etcd
57+ command:
58+ - --listen-client-urls=http://0.0.0.0:2379
59+ - --advertise-client-urls=http://etcd:2379
60+ networks:
61+ etcd: {}
50networks:
62networks:
51 dex: {}
63 dex: {}
64+ etcd: {}
52 httpbin: {}
65 httpbin: {}
contrib/local-environment/kubernetes/Chart.lock+3−3
@@ -1,7 +1,7 @@
1dependencies:
1dependencies:
2- name: dex
2- name: dex
3 repository: https://charts.dexidp.io
3 repository: https://charts.dexidp.io
4− version: 0.24.1
4+ version: 0.24.0
5- name: oauth2-proxy
5- name: oauth2-proxy
6 repository: https://oauth2-proxy.github.io/manifests
6 repository: https://oauth2-proxy.github.io/manifests
7 version: 7.18.0
7 version: 7.18.0
@@ -11,5 +11,5 @@ dependencies:
11- name: hello-world
11- name: hello-world
12 repository: https://conservis.github.io/helm-charts
12 repository: https://conservis.github.io/helm-charts
13 version: 1.1.0
13 version: 1.1.0
14−digest: sha256:3fff8bf99cebcd20a73c984423d5f1c2dfa1e5a394f82a5dc60d9aaffe5361b7
14+digest: sha256:fee913531bfb67e5555e995d8fb040c330e6eb4b5a8c777ceb9841135ea9bb84
15−generated: "2026-08-18T07:33:03.791274914Z"
15+generated: "2025-10-28T06:46:35.38300324Z"
contrib/local-environment/kubernetes/Chart.yaml+1−1
@@ -3,7 +3,7 @@ description: K8S example based on https://kind.sigs.k8s.io
3name: kubernetes
3name: kubernetes
4dependencies:
4dependencies:
5 - name: dex
5 - name: dex
6− version: 0.24.1
6+ version: 0.24.0
7 repository: https://charts.dexidp.io
7 repository: https://charts.dexidp.io
8 - name: oauth2-proxy
8 - name: oauth2-proxy
9 version: &chartVersion 7.18.0
9 version: &chartVersion 7.18.0
contrib/local-environment/nginx.conf+76−29
@@ -1,44 +1,91 @@
1+# Reverse proxy to oauth2-proxy
2+server {
3+ listen 80;
4+ server_name oauth2-proxy.oauth2-proxy.localhost;
5+
6+ location / {
7+ proxy_set_header Host $host;
8+ proxy_set_header X-Real-IP $remote_addr;
9+
10+ proxy_pass http://oauth2-proxy:4180/;
11+ }
12+}
13+
1# Reverse proxy to httpbin
14# Reverse proxy to httpbin
2server {
15server {
3− listen 8080;
16+ listen 80;
4− server_name oauth2-proxy.localtest.me;
17+ server_name httpbin.oauth2-proxy.localhost;
5
18
6− location /oauth2/ {
19+ auth_request /internal-auth/oauth2/auth;
20+
21+ # On 401, redirect to the sign_in page via a named location
22+ # This ensures a proper 302 redirect that browsers will follow
23+ error_page 401 = @oauth2_signin;
24+
25+ location / {
7− proxy_pass http://oauth2-proxy:4180;
26+ proxy_pass http://httpbin/;
8− proxy_set_header Host $host;
27+ }
9− proxy_set_header X-Real-IP $remote_addr;
28+
10− proxy_set_header X-Forwarded-Uri $request_uri;
29+ # Named location for OAuth2 sign-in redirect
11− proxy_set_header X-Auth-Request-Redirect $request_uri;
30+ # Returns a proper 302 that works with --skip-provider-button
31+ location @oauth2_signin {
32+ return 302 http://oauth2-proxy.oauth2-proxy.localhost/oauth2/sign_in?rd=$scheme://$host$request_uri;
12 }
33 }
13
34
35+ # auth_request must be a URI so this allows an internal path to then proxy to
36+ # the real auth_request path.
37+ # The trailing /'s are required so that nginx strips the prefix before proxying.
14− location = /oauth2/auth {
38+ location /internal-auth/ {
15− proxy_pass http://oauth2-proxy:4180;
39+ internal; # Ensure external users can't access this path
40+
41+ # Make sure the OAuth2 Proxy knows where the original request came from.
16− proxy_set_header Host $host;
42+ proxy_set_header Host $host;
17− proxy_set_header X-Real-IP $remote_addr;
43+ proxy_set_header X-Real-IP $remote_addr;
18− proxy_set_header X-Forwarded-Uri $request_uri;
44+ proxy_set_header X-Forwarded-Uri $request_uri;
19− # nginx auth_request includes headers but not body
45+
20− proxy_set_header Content-Length "";
46+ proxy_pass http://oauth2-proxy:4180/;
21− proxy_pass_request_body off;
22 }
47 }
48+}
49+
50+# Statically serve the nginx welcome
51+server {
52+ listen 80;
53+ server_name oauth2-proxy.localhost;
23
54
24 location / {
55 location / {
25− auth_request /oauth2/auth;
56+ auth_request /internal-auth/oauth2/auth;
26− error_page 401 = @oauth2_signin;
27
57
28− # pass information via X-User and X-Email headers to backend,
58+ # On 401, redirect to the sign_in page via a named location
29− # requires running with --set-xauthrequest flag
59+ # This ensures a proper 302 redirect that browsers will follow
30− auth_request_set $user $upstream_http_x_auth_request_user;
60+ error_page 401 = @oauth2_signin;
31− auth_request_set $email $upstream_http_x_auth_request_email;
32− proxy_set_header X-User $user;
33− proxy_set_header X-Email $email;
34
61
35− proxy_pass http://httpbin/;
62+ root /usr/share/nginx/html;
36− # or "root /path/to/site;" or "fastcgi_pass ..." etc
63+ index index.html index.htm;
37 }
64 }
38
65
39− # Named location for handling OAuth2 sign-in redirects
66+ # Named location for OAuth2 sign-in redirect
40− # This ensures the browser receives a proper 302 redirect that it will follow
67+ # Returns a proper 302 that works with --skip-provider-button
41 location @oauth2_signin {
68 location @oauth2_signin {
42− return 302 /oauth2/sign_in?rd=$scheme://$http_host$request_uri;
69+ return 302 http://oauth2-proxy.oauth2-proxy.localhost/oauth2/sign_in?rd=$scheme://$host$request_uri;
70+ }
71+
72+ # redirect server error pages to the static page /50x.html
73+ error_page 500 502 503 504 /50x.html;
74+ location = /50x.html {
75+ root /usr/share/nginx/html;
76+ }
77+
78+ # auth_request must be a URI so this allows an internal path to then proxy to
79+ # the real auth_request path.
80+ # The trailing /'s are required so that nginx strips the prefix before proxying.
81+ location /internal-auth/ {
82+ internal; # Ensure external users can't access this path
83+
84+ # Make sure the OAuth2 Proxy knows where the original request came from.
85+ proxy_set_header Host $host;
86+ proxy_set_header X-Real-IP $remote_addr;
87+ proxy_set_header X-Forwarded-Uri $request_uri;
88+
89+ proxy_pass http://oauth2-proxy:4180/;
43 }
90 }
44}
91}
contrib/local-environment/oauth2-proxy-nginx.cfg+6−11
@@ -1,19 +1,14 @@
1http_address="0.0.0.0:4180"
1http_address="0.0.0.0:4180"
2cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
2cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
3+provider="oidc"
3email_domains="example.com"
4email_domains="example.com"
4−cookie_secure="false"
5+oidc_issuer_url="http://dex.localtest.me:5556/dex"
5−upstreams="static://200"
6−cookie_domains=[".localtest.me"] # Required so cookie can be read on all subdomains.
7−whitelist_domains=[".localtest.me"] # Required to allow redirection back to original requested target.
8−
9−# dex provider
10client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
6client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
11client_id="oauth2-proxy"
7client_id="oauth2-proxy"
12−redirect_url="http://oauth2-proxy.localtest.me:8080/oauth2/callback"
8+cookie_secure="false"
13−
14−oidc_issuer_url="http://dex.localtest.me:5556/dex"
15−provider="oidc"
16−provider_display_name="Dex"
17
9
10+redirect_url="http://oauth2-proxy.oauth2-proxy.localhost/oauth2/callback"
11+cookie_domains=".oauth2-proxy.localhost" # Required so cookie can be read on all subdomains.
12+whitelist_domains=".oauth2-proxy.localhost" # Required to allow redirection back to original requested target.
18# Enables the use of `X-Forwarded-*` headers to determine request correctly
13# Enables the use of `X-Forwarded-*` headers to determine request correctly
19reverse_proxy="true"
14reverse_proxy="true"
contrib/local-environment/oauth2-proxy-traefik.cfg+4−4
@@ -2,14 +2,14 @@ http_address="0.0.0.0:4180"
2cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
2cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
3provider="oidc"
3provider="oidc"
4email_domains=["example.com"]
4email_domains=["example.com"]
5−oidc_issuer_url="http://dex.localtest.me/dex"
5+oidc_issuer_url="http://dex.localhost:5556/dex"
6client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
6client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK"
7client_id="oauth2-proxy"
7client_id="oauth2-proxy"
8cookie_secure="false"
8cookie_secure="false"
9
9
10−redirect_url="http://oauth2-proxy.localtest.me/oauth2/callback"
10+redirect_url="http://oauth2-proxy.oauth2-proxy.localhost/oauth2/callback"
11−cookie_domains=".localtest.me" # Required so cookie can be read on all subdomains.
11+cookie_domains=".oauth2-proxy.localhost" # Required so cookie can be read on all subdomains.
12−whitelist_domains=".localtest.me" # Required to allow redirection back to original requested target.
12+whitelist_domains=".oauth2-proxy.localhost" # Required to allow redirection back to original requested target.
13
13
14# Mandatory option when using oauth2-proxy with traefik
14# Mandatory option when using oauth2-proxy with traefik
15reverse_proxy="true"
15reverse_proxy="true"
contrib/local-environment/traefik/dynamic.yaml+4−11
@@ -1,25 +1,22 @@
1http:
1http:
2 routers:
2 routers:
3 oauth2-proxy-route:
3 oauth2-proxy-route:
4− rule: "Host(`oauth2-proxy.localtest.me`)"
4+ rule: "Host(`oauth2-proxy.oauth2-proxy.localhost`)"
5 middlewares:
5 middlewares:
6 - auth-headers
6 - auth-headers
7 service: oauth-backend
7 service: oauth-backend
8− dex-route:
9− rule: "Host(`dex.localtest.me`)"
10− service: dex-service
11 httpbin-route:
8 httpbin-route:
12− rule: "Host(`httpbin.localtest.me`)"
9+ rule: "Host(`httpbin.oauth2-proxy.localhost`)"
13 service: httpbin-service
10 service: httpbin-service
14 middlewares:
11 middlewares:
15 - oauth-auth-redirect # redirects all unauthenticated to oauth2 signin
12 - oauth-auth-redirect # redirects all unauthenticated to oauth2 signin
16 httpbin-route-2:
13 httpbin-route-2:
17− rule: "Host(`httpbin.localtest.me`) && PathPrefix(`/no-auto-redirect`)"
14+ rule: "Host(`httpbin.oauth2-proxy.localhost`) && PathPrefix(`/no-auto-redirect`)"
18 service: httpbin-service
15 service: httpbin-service
19 middlewares:
16 middlewares:
20 - oauth-auth-wo-redirect # unauthenticated session will return a 401
17 - oauth-auth-wo-redirect # unauthenticated session will return a 401
21 services-oauth2-route:
18 services-oauth2-route:
22− rule: "Host(`httpbin.localtest.me`) && PathPrefix(`/oauth2/`)"
19+ rule: "Host(`httpbin.oauth2-proxy.localhost`) && PathPrefix(`/oauth2/`)"
23 middlewares:
20 middlewares:
24 - auth-headers
21 - auth-headers
25 service: oauth-backend
22 service: oauth-backend
@@ -33,10 +30,6 @@ http:
33 loadBalancer:
30 loadBalancer:
34 servers:
31 servers:
35 - url: http://oauth2-proxy:4180
32 - url: http://oauth2-proxy:4180
36− dex-service:
37− loadBalancer:
38− servers:
39− - url: http://dex:5556
40
33
41 middlewares:
34 middlewares:
42 auth-headers:
35 auth-headers:
contrib/local-environment/traefik/traefik.yaml−2
@@ -5,5 +5,3 @@ log:
5providers:
5providers:
6 file:
6 file:
7 filename: /etc/traefik/dynamic.yaml
7 filename: /etc/traefik/dynamic.yaml
8− # Routes are static for this example; disabling watch avoids host inotify limits.
9− watch: false
docs/docs/configuration/alpha_config.md+1−12
@@ -167,17 +167,6 @@ injectResponseHeaders:
167* `claimSource` - `claim` (session claims either from id token or from profile URL)
167* `claimSource` - `claim` (session claims either from id token or from profile URL)
168* `secretSource` - `value` (base64), `fromFile` (file path)
168* `secretSource` - `value` (base64), `fromFile` (file path)
169
169
170−#### Claim value serialization
171−
172−`claimSource` converts the selected claim into one or more string header values. Scalars
173−produce one value, while arrays produce one value per element. Values that cannot be
174−converted directly to strings, such as objects, are JSON-encoded individually.
175−
176−Each non-empty value is added separately under the configured header name. An array of
177−objects therefore produces multiple header values containing one JSON object each, not a
178−single JSON array. HTTP libraries and intermediaries may expose repeated values as a
179−comma-joined string, so consumers should retrieve and parse each header value separately.
180−
181**Request option:** `preserveRequestValue: true` retains existing header values
170**Request option:** `preserveRequestValue: true` retains existing header values
182
171
183**Incompatibility:** Remove legacy flags `pass-user-headers`, `set-xauthrequest`
172**Incompatibility:** Remove legacy flags `pass-user-headers`, `set-xauthrequest`
@@ -689,7 +678,7 @@ Requests will be proxied to this upstream if the path matches the request path.
689| Field | Type | Description |
678| Field | Type | Description |
690| ----- | ---- | ----------- |
679| ----- | ---- | ----------- |
691| `id` | _string_ | ID should be a unique identifier for the upstream.<br/>This value is required for all upstreams. |
680| `id` | _string_ | ID should be a unique identifier for the upstream.<br/>This value is required for all upstreams. |
692−| `path` | _string_ | Path is used to map requests to the upstream server.<br/>The closest match will take precedence and all Paths must be unique.<br/>Without RewriteTarget, Path follows gorilla/mux route syntax.<br/>Eg:<br/>- `/foo`: Match only the explicit path `/foo`<br/>- `/bar/`: Match any path prefixed with `/bar/`<br/>- `/baz/{path:.*}`: Match any path prefixed with `/baz/`<br/>When RewriteTarget is set, Path is treated as a regular expression.<br/>Eg: `^/baz/(.*)$` matches any path prefixed with `/baz` and captures the<br/>remaining path for use with RewriteTarget. |
681+| `path` | _string_ | Path is used to map requests to the upstream server.<br/>The closest match will take precedence and all Paths must be unique.<br/>Path can also take a pattern when used with RewriteTarget.<br/>Path segments can be captured and matched using regular experessions.<br/>Eg:<br/>- `^/foo$`: Match only the explicit path `/foo`<br/>- `^/bar/$`: Match any path prefixed with `/bar/`<br/>- `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget |
693| `rewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to<br/>the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem<br/>(for a `file:` upstream).<br/>Use the Path to capture segments for reuse within the rewrite target.<br/>Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite<br/>the request `/baz/abc/123` to `/foo/abc/123` before proxying to the<br/>upstream server. Or if the upstream were `file:///app`, a request for<br/>`/baz/info.html` would return the contents of the file `/app/foo/info.html`. |
682| `rewriteTarget` | _string_ | RewriteTarget allows users to rewrite the request path before it is sent to<br/>the upstream server (for an HTTP/HTTPS upstream) or mapped to the filesystem<br/>(for a `file:` upstream).<br/>Use the Path to capture segments for reuse within the rewrite target.<br/>Eg: With a Path of `^/baz/(.*)`, a RewriteTarget of `/foo/$1` would rewrite<br/>the request `/baz/abc/123` to `/foo/abc/123` before proxying to the<br/>upstream server. Or if the upstream were `file:///app`, a request for<br/>`/baz/info.html` would return the contents of the file `/app/foo/info.html`. |
694| `uri` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File<br/>based URL. It may include a path, in which case all requests will be served<br/>under that path.<br/>Eg:<br/>- http://localhost:8080<br/>- https://service.localhost<br/>- https://service.localhost/path<br/>- file://host/path<br/>If the URI's path is "/base" and the incoming request was for "/dir",<br/>the upstream request will be for "/base/dir". |
683| `uri` | _string_ | The URI of the upstream server. This may be an HTTP(S) server of a File<br/>based URL. It may include a path, in which case all requests will be served<br/>under that path.<br/>Eg:<br/>- http://localhost:8080<br/>- https://service.localhost<br/>- https://service.localhost/path<br/>- file://host/path<br/>If the URI's path is "/base" and the incoming request was for "/dir",<br/>the upstream request will be for "/base/dir". |
695| `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. |
684| `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. |
docs/docs/configuration/alpha_config.md.tmpl−11
@@ -167,17 +167,6 @@ injectResponseHeaders:
167* `claimSource` - `claim` (session claims either from id token or from profile URL)
167* `claimSource` - `claim` (session claims either from id token or from profile URL)
168* `secretSource` - `value` (base64), `fromFile` (file path)
168* `secretSource` - `value` (base64), `fromFile` (file path)
169
169
170−#### Claim value serialization
171−
172−`claimSource` converts the selected claim into one or more string header values. Scalars
173−produce one value, while arrays produce one value per element. Values that cannot be
174−converted directly to strings, such as objects, are JSON-encoded individually.
175−
176−Each non-empty value is added separately under the configured header name. An array of
177−objects therefore produces multiple header values containing one JSON object each, not a
178−single JSON array. HTTP libraries and intermediaries may expose repeated values as a
179−comma-joined string, so consumers should retrieve and parse each header value separately.
180−
181**Request option:** `preserveRequestValue: true` retains existing header values
170**Request option:** `preserveRequestValue: true` retains existing header values
182
171
183**Incompatibility:** Remove legacy flags `pass-user-headers`, `set-xauthrequest`
172**Incompatibility:** Remove legacy flags `pass-user-headers`, `set-xauthrequest`
docs/docs/configuration/integrations/nginx.md+11−27
@@ -60,23 +60,23 @@ server {
60 auth_request_set $auth_cookie $upstream_http_set_cookie;
60 auth_request_set $auth_cookie $upstream_http_set_cookie;
61 add_header Set-Cookie $auth_cookie;
61 add_header Set-Cookie $auth_cookie;
62
62
63− # Large OIDC tokens can make the session cookie exceed the 4KB limit,
63+ # When using the --set-authorization-header flag, some provider's cookies can exceed the 4kb
64− # causing OAuth2 Proxy to split the cookie into multiple parts.
64+ # limit and so the OAuth2 Proxy splits these into multiple parts.
65 # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
65 # Nginx normally only copies the first `Set-Cookie` header from the auth_request to the response,
66− # so extract the second part when using the default cookie name, `_oauth2_proxy`.
66+ # so if your cookies are larger than 4kb, you will need to extract additional cookies manually.
67− auth_request_set $auth_cookie_upstream_1 $upstream_cookie__oauth2_proxy_1;
67+ auth_request_set $auth_cookie_name_upstream_1 $upstream_cookie_auth_cookie_name_1;
68
68
69 # Extract the Cookie attributes from the first Set-Cookie header and append them
69 # Extract the Cookie attributes from the first Set-Cookie header and append them
70 # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
70 # to the second part ($upstream_cookie_* variables only contain the raw cookie content)
71 if ($auth_cookie ~* "(; .*)") {
71 if ($auth_cookie ~* "(; .*)") {
72− set $auth_cookie_0 $auth_cookie;
72+ set $auth_cookie_name_0 $auth_cookie;
73− set $auth_cookie_1 "_oauth2_proxy_1=$auth_cookie_upstream_1$1";
73+ set $auth_cookie_name_1 "auth_cookie_name_1=$auth_cookie_name_upstream_1$1";
74 }
74 }
75
75
76 # Send both Set-Cookie headers now if there was a second part
76 # Send both Set-Cookie headers now if there was a second part
77− if ($auth_cookie_upstream_1) {
77+ if ($auth_cookie_name_upstream_1) {
78− add_header Set-Cookie $auth_cookie_0;
78+ add_header Set-Cookie $auth_cookie_name_0;
79− add_header Set-Cookie $auth_cookie_1;
79+ add_header Set-Cookie $auth_cookie_name_1;
80 }
80 }
81
81
82 proxy_pass http://backend/;
82 proxy_pass http://backend/;
@@ -91,24 +91,6 @@ server {
91}
91}
92```
92```
93
93
94−### Multi-part session cookies
95−
96−The example above is ready to use with the default `--cookie-name=_oauth2_proxy` setting and handles a session split into two cookies. If you configure a custom cookie name, update both references to the second cookie part:
97−
98−| Cookie configuration | Nginx upstream cookie variable | `Set-Cookie` name |
99−| --- | --- | --- |
100−| Default (`_oauth2_proxy`) | `$upstream_cookie__oauth2_proxy_1` | `_oauth2_proxy_1=` |
101−| `--cookie-name=my_cookie` | `$upstream_cookie_my_cookie_1` | `my_cookie_1=` |
102−
103−For example, with `--cookie-name=my_cookie`, change the relevant lines to:
104−
105−```nginx
106−auth_request_set $auth_cookie_upstream_1 $upstream_cookie_my_cookie_1;
107−set $auth_cookie_1 "my_cookie_1=$auth_cookie_upstream_1$1";
108−```
109−
110−If a session is split into more than two cookies, repeat the extraction for each additional numeric suffix. For consistently large sessions, prefer `--session-store-type=redis` so only a small session ticket is stored in the browser cookie.
111−
112### Understanding the `error_page` redirect pattern
94### Understanding the `error_page` redirect pattern
113
95
114The `auth_request` directive expects the authentication endpoint (`/oauth2/auth`) to return:
96The `auth_request` directive expects the authentication endpoint (`/oauth2/auth`) to return:
@@ -185,6 +167,8 @@ It is recommended to use `--session-store-type=redis` when expecting large sessi
185For a complete example of integrating oauth2-proxy with Kubernetes Dashboard on AKS using Azure Entra ID, including RBAC configuration and troubleshooting, see the [Kubernetes Dashboard on AKS](../providers/ms_entra_id.md#kubernetes-dashboard-on-aks) section in the Microsoft Entra ID provider documentation.
167For a complete example of integrating oauth2-proxy with Kubernetes Dashboard on AKS using Azure Entra ID, including RBAC configuration and troubleshooting, see the [Kubernetes Dashboard on AKS](../providers/ms_entra_id.md#kubernetes-dashboard-on-aks) section in the Microsoft Entra ID provider documentation.
186:::
168:::
187
169
170+You have to substitute *name* with the actual cookie name you configured via --cookie-name parameter. If you don't set a custom cookie name the variable should be "$upstream_cookie__oauth2_proxy_1" instead of "$upstream_cookie_name_1" and the new cookie-name should be "_oauth2_proxy_1=" instead of "name_1=".
171+
188:::note
172:::note
189If you set up your OAuth2 provider to rotate your client secret, you can use the `client-secret-file` option to reload the secret when it is updated.
173If you set up your OAuth2 provider to rotate your client secret, you can use the `client-secret-file` option to reload the secret when it is updated.
190:::
174:::
docs/docs/configuration/overview.md+2−7
@@ -193,10 +193,6 @@ Provider specific options can be found on their respective subpages.
193
193
194### Proxy Options
194### Proxy Options
195
195
196−:::warning
197−When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or CIDR ranges of the reverse proxies that are allowed to send `X-Forwarded-*` headers. If you leave it unset, OAuth2 Proxy currently trusts all source IPs for backwards compatibility, which means a client that can reach OAuth2 Proxy directly may be able to spoof forwarded headers.
198−:::
199−
200| Flag / Config Field | Type | Description | Default |
196| Flag / Config Field | Type | Description | Default |
201| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
197| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
202| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
198| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
@@ -215,11 +211,10 @@ When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or
215| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
211| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
216| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
212| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
217| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
213| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
218−| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
219| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
214| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
220| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
215| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
221−| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times). Path matching is performed against the normalized path only; fragment identifiers (`#`) and their URL-encoded form (`%23`) are stripped before evaluation. | |
216+| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
222−| flag: `--skip-auth-route`<br/>toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex. Path matching is performed against the normalized path only; fragment identifiers (`#`) and their URL-encoded form (`%23`) are stripped before evaluation. | |
217+| flag: `--skip-auth-route`<br/>toml: `skip_auth_routes` | string \| list | bypass authentication for requests that match the method & path. Format: method=path_regex OR method!=path_regex. For all methods: path_regex OR !=path_regex | |
223| flag: `--skip-jwt-bearer-tokens`<br/>toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false |
218| flag: `--skip-jwt-bearer-tokens`<br/>toml: `skip_jwt_bearer_tokens` | bool | will skip requests that have verified JWT bearer tokens (the token must have [`aud`](https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields) that matches this client id or one of the extras from `extra-jwt-issuers`) | false |
224| flag: `--skip-provider-button`<br/>toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
219| flag: `--skip-provider-button`<br/>toml: `skip_provider_button` | bool | will skip sign-in-page to directly reach the next step: oauth/start | false |
225| flag: `--ssl-insecure-skip-verify`<br/>toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false |
220| flag: `--ssl-insecure-skip-verify`<br/>toml: `ssl_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS providers | false |
docs/docs/configuration/providers/atlassian.mddeleted−26
@@ -1,26 +0,0 @@
1−---
2−id: atlassian
3−title: Atlassian Cloud
4−---
5−
6−1. Create an OAuth 2.0 integration in the [Atlassian developer console](https://developer.atlassian.com/console/myapps/).
7−2. Under **Permissions**, add the User identity API with the `read:me` scope.
8−3. Under **Authorization**, set the callback URL to `https://<proxy_host>/oauth2/callback`.
9−4. Copy the client ID and secret from **Settings**.
10−5. Run the proxy with:
11−
12−```shell
13− --provider=atlassian
14− --client-id=<client ID>
15− --client-secret=<secret>
16− --redirect-url=https://<proxy_host>/oauth2/callback
17− --cookie-secret=<cookie secret>
18−```
19−
20−A new app works only for the account that created it. Enable sharing under **Distribution** to let other users sign in.
21−Atlassian then admits only accounts with access to the Atlassian site the app is installed on; others are refused at
22−the consent screen.
23−
24−The proxy reads the user's email from `https://api.atlassian.com/me`. Restricting access further is supported by
25−[email](index.md#email-authentication). With `--cookie-refresh` set, the proxy re-validates the token against `/me` on
26−that interval.
docs/docs/configuration/providers/dtt_sso.mddeleted−45
@@ -1,45 +0,0 @@
1−---
2−id: dtt_sso
3−title: DTT SSO
4−---
5−
6−Signs users in with the DTT SSO (auth-server) using the authorization code flow and its HS256 id_token.
7−
8−1. Register a client in the auth-server config, one per protected service, with the proxy's callback as its
9− only redirect URI:
10−
11− ```yaml
12− clients:
13− redash-gate:
14− secret: <random secret>
15− redirect_uri: https://redash.example.com/oauth2/callback
16− display_name: Redash
17− ```
18−
19−2. Run the proxy with:
20−
21− ```shell
22− --provider=dtt-sso
23− --client-id=redash-gate
24− --client-secret-file=/run/secrets/client_secret
25− --redirect-url=https://redash.example.com/oauth2/callback
26− --cookie-secret-file=/run/secrets/cookie_secret
27− --cookie-name=_dtt_sso_redash
28− --cookie-refresh=5m
29− ```
30−
31− Give each instance its own `--cookie-name`: another oauth2-proxy setting its cookie on a parent domain
32− (the Atlassian proxy uses `_oauth2_proxy` on `.thebestagent.pro`) would otherwise shadow this one.
33−
34−The endpoints default to `https://auth.thebestagent.pro`. For another SSO host set `--login-url`, `--redeem-url`
35−and `--validate-url` to its `/oauth/authorize`, `/oauth/token` and `/oauth/check_token`; the id_token issuer must
36−equal the login URL's origin (the SSO's `site_host`).
37−
38−The scope must stay `openid`, the only scope the SSO accepts.
39−
40−The session carries the SSO `email`, the user UUID as the user, and the SSO `role` as the only group, so
41−`--allowed-group=dtt_admin` restricts access by role. `--authenticated-emails-file` and `--email-domain` restrict
42−by email as usual.
43−
44−With `--cookie-refresh` set, the proxy refreshes the tokens on that interval and re-reads the email and role; a user
45−deleted or signed out in the SSO loses access at the next refresh.
docs/docs/configuration/providers/index.md−2
@@ -9,12 +9,10 @@ with Redirect URI(s) for the domain you intend to run `oauth2-proxy` on.
9Valid providers are :
9Valid providers are :
10
10
11- [ADFS](adfs.md)
11- [ADFS](adfs.md)
12−- [Atlassian Cloud](atlassian.md)
13- [Bitbucket](bitbucket.md)
12- [Bitbucket](bitbucket.md)
14- [Cidaas](cidaas.md)
13- [Cidaas](cidaas.md)
15- [CiscoDuo](cisco_duo.md)
14- [CiscoDuo](cisco_duo.md)
16- [DigitalOcean](digitalocean.md)
15- [DigitalOcean](digitalocean.md)
17−- [DTT SSO](dtt_sso.md)
18- [Facebook](facebook.md)
16- [Facebook](facebook.md)
19- [Gitea](gitea.md)
17- [Gitea](gitea.md)
20- [GitHub](github.md)
18- [GitHub](github.md)
docs/docs/installation.md+1−1
@@ -5,7 +5,7 @@ title: Installation
5
5
61. Choose how to deploy:
61. Choose how to deploy:
7
7
8− a. Using a [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v7.15.4`)
8+ a. Using a [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v7.15.1`)
9
9
10 b. Using Go to install the latest release
10 b. Using Go to install the latest release
11 ```bash
11 ```bash
docs/docusaurus.config.js+1−1
@@ -105,7 +105,7 @@ const config = {
105 dropdownActiveClassDisabled: true,
105 dropdownActiveClassDisabled: true,
106 },
106 },
107 {
107 {
108− href: 'https://cloud-native.slack.com/archives/C098Y5URZ2N',
108+ href: 'https://gophers.slack.com/messages/CM2RSS25N',
109 label: 'Slack',
109 label: 'Slack',
110 position: 'right',
110 position: 'right',
111 },
111 },
docs/package.json−3
@@ -42,8 +42,5 @@
42 },
42 },
43 "engines": {
43 "engines": {
44 "node": ">=18.0"
44 "node": ">=18.0"
45− },
46− "overrides" : {
47− "webpackbar" : "^7.0.0"
48 }
45 }
49}
46}
docs/sidebars.js−2
@@ -46,13 +46,11 @@ const sidebars = {
46 },
46 },
47 items: [
47 items: [
48 "configuration/providers/adfs",
48 "configuration/providers/adfs",
49− "configuration/providers/atlassian",
50 "configuration/providers/azure",
49 "configuration/providers/azure",
51 "configuration/providers/bitbucket",
50 "configuration/providers/bitbucket",
52 "configuration/providers/cidaas",
51 "configuration/providers/cidaas",
53 "configuration/providers/cisco_duo",
52 "configuration/providers/cisco_duo",
54 "configuration/providers/digitalocean",
53 "configuration/providers/digitalocean",
55− "configuration/providers/dtt_sso",
56 "configuration/providers/facebook",
54 "configuration/providers/facebook",
57 "configuration/providers/gitea",
55 "configuration/providers/gitea",
58 "configuration/providers/github",
56 "configuration/providers/github",
docs/versioned_docs/version-7.15.x/configuration/overview.md−5
@@ -193,10 +193,6 @@ Provider specific options can be found on their respective subpages.
193
193
194### Proxy Options
194### Proxy Options
195
195
196−:::warning
197−When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or CIDR ranges of the reverse proxies that are allowed to send `X-Forwarded-*` headers. If you leave it unset, OAuth2 Proxy currently trusts all source IPs for backwards compatibility, which means a client that can reach OAuth2 Proxy directly may be able to spoof forwarded headers.
198−:::
199−
200| Flag / Config Field | Type | Description | Default |
196| Flag / Config Field | Type | Description | Default |
201| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
197| ----------------------------------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
202| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
198| flag: `--allow-query-semicolons`<br/>toml: `allow_query_semicolons` | bool | allow the use of semicolons in query args ([required for some legacy applications](https://github.com/golang/go/issues/25192)) | `false` |
@@ -215,7 +211,6 @@ When `--reverse-proxy` is enabled, configure `--trusted-proxy-ip` to the IPs or
215| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
211| flag: `--redirect-url`<br/>toml: `redirect_url` | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
216| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
212| flag: `--relative-redirect-url`<br/>toml: `relative_redirect_url` | bool | allow relative OAuth Redirect URL.` | false |
217| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
213| flag: `--reverse-proxy`<br/>toml: `reverse_proxy` | bool | are we running behind a reverse proxy, controls whether headers like X-Real-IP are accepted and allows X-Forwarded-\{Proto,Host,Uri\} headers to be used on redirect selection | false |
218−| flag: `--trusted-proxy-ip`<br/>toml: `trusted_proxy_ips` | string \| list | list of IPs or CIDR ranges allowed to supply `X-Forwarded-*` headers when `--reverse-proxy` is enabled. If not set, OAuth2 Proxy preserves backwards compatibility by trusting all source IPs (`0.0.0.0/0`, `::/0`) and logs a warning at startup. Configure this to your reverse proxy addresses to prevent forwarded header spoofing. | `"0.0.0.0/0", "::/0"` |
219| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
214| flag: `--signature-key`<br/>toml: `signature_key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
220| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
215| flag: `--skip-auth-preflight`<br/>toml: `skip_auth_preflight` | bool | will skip authentication for OPTIONS requests | false |
221| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
216| flag: `--skip-auth-regex`<br/>toml: `skip_auth_regex` | string \| list | (DEPRECATED for `--skip-auth-route`) bypass authentication for requests paths that match (may be given multiple times) | |
docs/versioned_docs/version-7.15.x/installation.md+1−1
@@ -5,7 +5,7 @@ title: Installation
5
5
61. Choose how to deploy:
61. Choose how to deploy:
7
7
8− a. Using a [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v7.15.4`)
8+ a. Using a [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v7.15.1`)
9
9
10 b. Using Go to install the latest release
10 b. Using Go to install the latest release
11 ```bash
11 ```bash
go.mod+45−41
@@ -1,83 +1,87 @@
1module github.com/oauth2-proxy/oauth2-proxy/v7
1module github.com/oauth2-proxy/oauth2-proxy/v7
2
2
3−go 1.26.0
3+go 1.25.0
4
4
5require (
5require (
6 cloud.google.com/go/compute/metadata v0.9.0
6 cloud.google.com/go/compute/metadata v0.9.0
7 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
7 github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
8 github.com/a8m/envsubst v1.4.3
8 github.com/a8m/envsubst v1.4.3
9− github.com/alicebob/miniredis/v2 v2.38.0
9+ github.com/alicebob/miniredis/v2 v2.37.0
10 github.com/bitly/go-simplejson v0.5.1
10 github.com/bitly/go-simplejson v0.5.1
11− github.com/bsm/redislock v0.10.0
11+ github.com/bsm/redislock v0.9.4
12− github.com/coreos/go-oidc/v3 v3.20.0
12+ github.com/coreos/go-oidc/v3 v3.17.0
13 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
13 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
14− github.com/fsnotify/fsnotify v1.10.1
14+ github.com/fsnotify/fsnotify v1.9.0
15− github.com/go-jose/go-jose/v3 v3.0.5
15+ github.com/go-jose/go-jose/v3 v3.0.4
16 github.com/go-jose/go-jose/v4 v4.1.4
16 github.com/go-jose/go-jose/v4 v4.1.4
17− github.com/go-viper/mapstructure/v2 v2.5.0
17+ github.com/go-viper/mapstructure/v2 v2.4.0
18 github.com/golang-jwt/jwt/v5 v5.3.1
18 github.com/golang-jwt/jwt/v5 v5.3.1
19 github.com/google/go-cmp v0.7.0
19 github.com/google/go-cmp v0.7.0
20 github.com/google/uuid v1.6.0
20 github.com/google/uuid v1.6.0
21 github.com/gorilla/mux v1.8.1
21 github.com/gorilla/mux v1.8.1
22 github.com/justinas/alice v1.2.0
22 github.com/justinas/alice v1.2.0
23 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
23 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
24− github.com/onsi/ginkgo/v2 v2.32.1
24+ github.com/onsi/ginkgo/v2 v2.28.1
25− github.com/onsi/gomega v1.42.1
25+ github.com/onsi/gomega v1.39.1
26− github.com/pierrec/lz4/v4 v4.1.28
26+ github.com/pierrec/lz4/v4 v4.1.26
27− github.com/prometheus/client_golang v1.24.1
27+ github.com/prometheus/client_golang v1.23.2
28− github.com/redis/go-redis/v9 v9.22.0
28+ github.com/redis/go-redis/v9 v9.18.0
29 github.com/spf13/cast v1.10.0
29 github.com/spf13/cast v1.10.0
30 github.com/spf13/pflag v1.0.10
30 github.com/spf13/pflag v1.0.10
31 github.com/spf13/viper v1.21.0
31 github.com/spf13/viper v1.21.0
32− github.com/stretchr/testify v1.12.0
32+ github.com/stretchr/testify v1.11.1
33 github.com/vmihailenco/msgpack/v5 v5.4.1
33 github.com/vmihailenco/msgpack/v5 v5.4.1
34− go.yaml.in/yaml/v3 v3.0.5
34+ go.yaml.in/yaml/v3 v3.0.4
35− golang.org/x/crypto v0.55.0
35+ golang.org/x/crypto v0.49.0
36− golang.org/x/net v0.58.0
36+ golang.org/x/net v0.52.0
37 golang.org/x/oauth2 v0.36.0
37 golang.org/x/oauth2 v0.36.0
38− golang.org/x/sync v0.22.0
38+ golang.org/x/sync v0.20.0
39− google.golang.org/api v0.293.0
39+ google.golang.org/api v0.272.0
40 gopkg.in/natefinch/lumberjack.v2 v2.2.1
40 gopkg.in/natefinch/lumberjack.v2 v2.2.1
41− k8s.io/apimachinery v0.36.3
41+ k8s.io/apimachinery v0.35.3
42)
42)
43
43
44require (
44require (
45− cloud.google.com/go/auth v0.23.1 // indirect
45+ cloud.google.com/go/auth v0.18.2 // indirect
46 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
46 cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
47− github.com/Masterminds/semver/v3 v3.5.0 // indirect
47+ github.com/Masterminds/semver/v3 v3.4.0 // indirect
48 github.com/beorn7/perks v1.0.1 // indirect
48 github.com/beorn7/perks v1.0.1 // indirect
49 github.com/cespare/xxhash/v2 v2.3.0 // indirect
49 github.com/cespare/xxhash/v2 v2.3.0 // indirect
50+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
51+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
50− github.com/felixge/httpsnoop v1.1.0 // indirect
52+ github.com/felixge/httpsnoop v1.0.4 // indirect
51− github.com/go-logr/logr v1.4.4 // indirect
53+ github.com/go-logr/logr v1.4.3 // indirect
52 github.com/go-logr/stdr v1.2.2 // indirect
54 github.com/go-logr/stdr v1.2.2 // indirect
53 github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
55 github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
54− github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect
56+ github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc // indirect
55 github.com/google/s2a-go v0.1.9 // indirect
57 github.com/google/s2a-go v0.1.9 // indirect
56− github.com/googleapis/enterprise-certificate-proxy v0.3.21 // indirect
58+ github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
57− github.com/googleapis/gax-go/v2 v2.23.0 // indirect
59+ github.com/googleapis/gax-go/v2 v2.19.0 // indirect
58 github.com/kylelemons/godebug v1.1.0 // indirect
60 github.com/kylelemons/godebug v1.1.0 // indirect
59 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
61 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
60− github.com/pelletier/go-toml/v2 v2.4.3 // indirect
62+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
63+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
61 github.com/prometheus/client_model v0.6.2 // indirect
64 github.com/prometheus/client_model v0.6.2 // indirect
62− github.com/prometheus/common v0.70.1 // indirect
65+ github.com/prometheus/common v0.67.5 // indirect
63− github.com/prometheus/procfs v0.21.1 // indirect
66+ github.com/prometheus/procfs v0.20.1 // indirect
64 github.com/sagikazarmark/locafero v0.12.0 // indirect
67 github.com/sagikazarmark/locafero v0.12.0 // indirect
65 github.com/spf13/afero v1.15.0 // indirect
68 github.com/spf13/afero v1.15.0 // indirect
66 github.com/subosito/gotenv v1.6.0 // indirect
69 github.com/subosito/gotenv v1.6.0 // indirect
67 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
70 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
68− github.com/yuin/gopher-lua v1.1.2 // indirect
71+ github.com/yuin/gopher-lua v1.1.1 // indirect
69 go.opentelemetry.io/auto/sdk v1.2.1 // indirect
72 go.opentelemetry.io/auto/sdk v1.2.1 // indirect
70− go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect
73+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
71− go.opentelemetry.io/otel v1.45.0 // indirect
74+ go.opentelemetry.io/otel v1.42.0 // indirect
72− go.opentelemetry.io/otel/metric v1.45.0 // indirect
75+ go.opentelemetry.io/otel/metric v1.42.0 // indirect
73− go.opentelemetry.io/otel/trace v1.45.0 // indirect
76+ go.opentelemetry.io/otel/trace v1.42.0 // indirect
74 go.uber.org/atomic v1.11.0 // indirect
77 go.uber.org/atomic v1.11.0 // indirect
78+ go.yaml.in/yaml/v2 v2.4.4 // indirect
75− golang.org/x/mod v0.40.0 // indirect
79+ golang.org/x/mod v0.34.0 // indirect
76− golang.org/x/sys v0.47.0 // indirect
80+ golang.org/x/sys v0.42.0 // indirect
77− golang.org/x/text v0.41.0 // indirect
81+ golang.org/x/text v0.35.0 // indirect
78− golang.org/x/tools v0.49.0 // indirect
82+ golang.org/x/tools v0.43.0 // indirect
79− google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
83+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
80− google.golang.org/grpc v1.83.0 // indirect
84+ google.golang.org/grpc v1.79.3 // indirect
81− google.golang.org/protobuf v1.36.12 // indirect
85+ google.golang.org/protobuf v1.36.11 // indirect
82 gopkg.in/yaml.v3 v3.0.1 // indirect
86 gopkg.in/yaml.v3 v3.0.1 // indirect
83)
87)
go.sum+104−98
@@ -1,5 +1,5 @@
1−cloud.google.com/go/auth v0.23.1 h1:1tPpBPG02lQHmoiAvs9egyCASqXP0xgobptjZzov/Jg=
1+cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
2−cloud.google.com/go/auth v0.23.1/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0=
2+cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
3cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
3cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
4cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
4cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
5cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
5cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
@@ -8,14 +8,14 @@ github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWO
8github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
8github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss=
9github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
9github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
10github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
10github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
11−github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
11+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
12−github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
12+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
13github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
13github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
14github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
14github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
15github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
15github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
16github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
16github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
17−github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw=
17+github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68=
18−github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
18+github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
19github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
19github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
20github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
20github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
21github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
21github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
@@ -24,43 +24,47 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
24github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
24github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
25github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
25github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
26github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
26github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
27−github.com/bsm/redislock v0.10.0 h1:NAe1OHDnwPmWEzD+LpWBZXAdZrWld/xj/wmqEcQednQ=
27+github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw=
28−github.com/bsm/redislock v0.10.0/go.mod h1:M05WZGjPbX/8ne7LmB7AbOsa2kv7Jzx4fqkLpDFZvJs=
28+github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk=
29github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
29github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
30github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
30github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
31github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
31github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
32github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
32github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
33github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
33github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
34−github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE=
34+github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
35−github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
35+github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
36github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
36github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
37github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
37github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
38github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
38github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
39−github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
39+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
40−github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
40+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
41+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
42+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
43+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
44+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
41github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
45github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
42github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
46github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
43−github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
47+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
44−github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
48+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
45github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
49github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
46github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
50github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
47github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
51github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
48github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
52github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
49github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
53github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
50github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
54github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
51−github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
55+github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
52−github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
56+github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
53github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
57github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
54github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
58github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
55github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
59github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
56−github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
60+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
57−github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
61+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
58github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
62github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
59github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
63github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
60github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
64github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
61github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
65github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
62−github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
66+github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
63−github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
67+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
64github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
68github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
65github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
69github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
66github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
70github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
@@ -72,27 +76,27 @@ github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85q
72github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
76github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
73github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
77github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
74github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
78github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
75−github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
79+github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
76−github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
80+github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
77github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
81github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
78github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
82github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
79github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
83github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
80github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
84github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
81github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
85github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
82−github.com/googleapis/enterprise-certificate-proxy v0.3.21 h1:OFdQ3tnCX/zaQ0Cedur3D3z7kI6HiLX9g3TiAN4/DFU=
86+github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
83−github.com/googleapis/enterprise-certificate-proxy v0.3.21/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w=
87+github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
84−github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
88+github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
85−github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
89+github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
86github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
90github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
87github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
91github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
88github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
92github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
89github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
93github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
90github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
94github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
91github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
95github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
92−github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
96+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
93−github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
97+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
94−github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
98+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
95−github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
99+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
96github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
100github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
97github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
101github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
98github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
102github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -109,25 +113,27 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
109github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
113github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
110github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
114github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
111github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
115github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
112−github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
116+github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
113−github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
117+github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
114−github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
118+github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
115−github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
119+github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
116−github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
120+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
117−github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
121+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
118−github.com/pierrec/lz4/v4 v4.1.28 h1:pPEPwRJ4kybBTfGt28q7lQsRJQHhC08axprdLD5Ppio=
122+github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
119−github.com/pierrec/lz4/v4 v4.1.28/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
123+github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
120github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
124github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
121−github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
125+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
126+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
127+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
122−github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
128+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
123github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
129github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
124github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
130github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
125−github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
131+github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
126−github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
132+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
127−github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
133+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
128−github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
134+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
129−github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
135+github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
130−github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
136+github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
131github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
137github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
132github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
138github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
133github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
139github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
@@ -142,8 +148,8 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
142github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
148github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
143github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
149github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
144github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
150github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
145−github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
151+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
146−github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
152+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
147github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
153github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
148github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
154github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
149github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
155github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
@@ -161,57 +167,57 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV
161github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
167github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
162github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
168github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
163github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
169github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
164−github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
170+github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
165−github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
171+github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
166−github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
172+github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
167−github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
173+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
168go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
174go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
169go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
175go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
170−go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
176+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
171−go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
177+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
172−go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=
178+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
173−go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04=
179+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
174−go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
180+go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
175−go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
181+go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
176−go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
182+go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
177−go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
183+go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
178−go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
184+go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
179−go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
185+go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
180−go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
186+go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
181−go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
187+go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
182−go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
188+go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
183−go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
189+go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
184go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
190go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
185go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
191go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
186go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
192go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
187go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
193go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
188go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
194go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
189go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
195go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
190−go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
196+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
191−go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
197+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
192golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
198golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
193golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
199golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
194golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
200golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
195−golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
201+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
196−golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
202+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
197golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
203golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
198golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
204golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
199−golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
205+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
200−golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
206+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
201golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
207golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
202golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
208golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
203golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
209golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
204golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
210golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
205golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
211golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
206−golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
212+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
207−golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
213+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
208golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
214golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
209golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
215golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
210golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
216golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
211golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
217golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
212golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
218golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
213−golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
219+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
214−golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
220+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
215golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
221golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
216golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
222golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
217golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
223golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -221,8 +227,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
221golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
227golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
222golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
228golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
223golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
229golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
224−golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
230+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
225−golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
231+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
226golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
232golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
227golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
233golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
228golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
234golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -234,31 +240,31 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
234golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
240golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
235golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
241golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
236golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
242golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
237−golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
243+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
238−golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
244+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
239golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
245golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
240golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
246golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
241golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
247golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
242golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
248golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
243golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
249golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
244golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
250golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
245−golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
251+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
246−golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
252+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
247golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
253golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
248−gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
254+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
249−gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
255+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
250−google.golang.org/api v0.293.0 h1:p9XIWOf63U4OgYx120ZwVU8+vl4XTPmWfgVPnmOAS9w=
256+google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
251−google.golang.org/api v0.293.0/go.mod h1:6n5tjEB1gzwniZTepZ0g5u+wM7Bof5GeULCx/zh8ZE0=
257+google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
252−google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
258+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
253−google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
259+google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
254−google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU=
260+google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
255−google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA=
261+google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
256−google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
262+google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
257−google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
263+google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
258−google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
264+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
259−google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
265+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
260−google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
266+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
261−google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
267+google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
262gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
268gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
263gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
269gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
264gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
270gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -267,5 +273,5 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
267gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
273gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
268gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
274gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
269gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
275gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
270−k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM=
276+k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
271−k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE=
277+k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
main_test.go+2−2
@@ -278,7 +278,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
278 Entry("with bad legacy configuration", loadConfigurationTableInput{
278 Entry("with bad legacy configuration", loadConfigurationTableInput{
279 configContent: testCoreConfig + "unknown_field=\"something\"",
279 configContent: testCoreConfig + "unknown_field=\"something\"",
280 expectedOptions: func() *options.Options { return nil },
280 expectedOptions: func() *options.Options { return nil },
281− 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'options.LegacyOptions' has invalid keys: unknown_field"),
281+ 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"),
282 }),
282 }),
283 Entry("with bad alpha configuration", loadConfigurationTableInput{
283 Entry("with bad alpha configuration", loadConfigurationTableInput{
284 configContent: testCoreConfig,
284 configContent: testCoreConfig,
@@ -290,7 +290,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
290 configContent: testCoreConfig + "unknown_field=\"something\"",
290 configContent: testCoreConfig + "unknown_field=\"something\"",
291 alphaConfigContent: testAlphaConfig,
291 alphaConfigContent: testAlphaConfig,
292 expectedOptions: func() *options.Options { return nil },
292 expectedOptions: func() *options.Options { return nil },
293− 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'options.LegacyOptions' has invalid keys: unknown_field"),
293+ 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"),
294 }),
294 }),
295 )
295 )
296
296
oauthproxy.go+16−29
@@ -59,8 +59,6 @@ const (
59)
59)
60
60
61var (
61var (
62− defaultTrustedProxyIPs = []string{"0.0.0.0/0", "::/0"}
63−
64 // ErrNeedsLogin means the user should be redirected to the login page
62 // ErrNeedsLogin means the user should be redirected to the login page
65 ErrNeedsLogin = errors.New("redirect to login page")
63 ErrNeedsLogin = errors.New("redirect to login page")
66
64
@@ -185,14 +183,13 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
185
183
186 logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Secure, opts.Cookie.HTTPOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh)
184 logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domains:%s path:%s samesite:%s refresh:%s", opts.Cookie.Name, opts.Cookie.Secure, opts.Cookie.HTTPOnly, opts.Cookie.Expire, strings.Join(opts.Cookie.Domains, ","), opts.Cookie.Path, opts.Cookie.SameSite, refresh)
187
185
186+ trustedIPs := ip.NewNetSet()
188− trustedIPs, err := ip.ParseNetSet(opts.TrustedIPs)
187+ for _, ipStr := range opts.TrustedIPs {
189− if err != nil {
188+ if ipNet := ip.ParseIPNet(ipStr); ipNet != nil {
190− return nil, err
189+ trustedIPs.AddIPNet(*ipNet)
191− }
190+ } else {
192−
191+ return nil, fmt.Errorf("could not parse IP network (%s)", ipStr)
193− trustedProxies, err := buildTrustedProxyNetSet(opts)
192+ }
194− if err != nil {
195− return nil, err
196 }
193 }
197
194
198 allowedRoutes, err := buildRoutesAllowlist(opts)
195 allowedRoutes, err := buildRoutesAllowlist(opts)
@@ -205,7 +202,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
205 return nil, err
202 return nil, err
206 }
203 }
207
204
208− preAuthChain, err := buildPreAuthChain(opts, sessionStore, trustedProxies)
205+ preAuthChain, err := buildPreAuthChain(opts, sessionStore)
209 if err != nil {
206 if err != nil {
210 return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
207 return nil, fmt.Errorf("could not build pre-auth chain: %v", err)
211 }
208 }
@@ -358,8 +355,8 @@ func (p *OAuthProxy) buildProxySubrouter(s *mux.Router) {
358// buildPreAuthChain constructs a chain that should process every request before
355// buildPreAuthChain constructs a chain that should process every request before
359// the OAuth2 Proxy authentication logic kicks in.
356// the OAuth2 Proxy authentication logic kicks in.
360// For example forcing HTTPS or health checks.
357// For example forcing HTTPS or health checks.
361−func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionStore, trustedProxies *ip.NetSet) (alice.Chain, error) {
358+func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionStore) (alice.Chain, error) {
362− chain := alice.New(middleware.NewScope(opts.ReverseProxy, opts.Logging.RequestIDHeader, trustedProxies))
359+ chain := alice.New(middleware.NewScope(opts.ReverseProxy, opts.Logging.RequestIDHeader))
363
360
364 if opts.ForceHTTPS {
361 if opts.ForceHTTPS {
365 _, httpsPort, err := net.SplitHostPort(opts.Server.SecureBindAddress)
362 _, httpsPort, err := net.SplitHostPort(opts.Server.SecureBindAddress)
@@ -398,16 +395,6 @@ func buildPreAuthChain(opts *options.Options, sessionStore sessionsapi.SessionSt
398 return chain, nil
395 return chain, nil
399}
396}
400
397
401−func buildTrustedProxyNetSet(opts *options.Options) (*ip.NetSet, error) {
402− trustedProxyIPs := opts.TrustedProxyIPs
403− if opts.ReverseProxy && len(trustedProxyIPs) == 0 {
404− logger.Print("WARNING: --reverse-proxy is enabled but no --trusted-proxy-ip CIDRs were configured. All connecting IPs are trusted to supply X-Forwarded-* headers by default (0.0.0.0/0, ::/0). This preserves backwards compatibility but is a potential security risk; configure --trusted-proxy-ip to match your reverse proxy addresses.")
405− trustedProxyIPs = defaultTrustedProxyIPs
406− }
407−
408− return ip.ParseNetSet(trustedProxyIPs)
409−}
410−
411func buildSessionChain(opts *options.Options, provider providers.Provider, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
398func buildSessionChain(opts *options.Options, provider providers.Provider, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
412 chain := alice.New()
399 chain := alice.New()
413
400
@@ -587,7 +574,7 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code i
587
574
588// IsAllowedRequest is used to check if auth should be skipped for this request
575// IsAllowedRequest is used to check if auth should be skipped for this request
589func (p *OAuthProxy) IsAllowedRequest(req *http.Request) bool {
576func (p *OAuthProxy) IsAllowedRequest(req *http.Request) bool {
590− isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == http.MethodOptions
577+ isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
591 return isPreflightRequestAllowed || p.isAllowedRoute(req) || p.isTrustedIP(req)
578 return isPreflightRequestAllowed || p.isAllowedRoute(req) || p.isTrustedIP(req)
592}
579}
593
580
@@ -647,10 +634,6 @@ func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
647// SignInPage writes the sign in template to the response
634// SignInPage writes the sign in template to the response
648func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
635func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
649 prepareNoCache(rw)
636 prepareNoCache(rw)
650−
651− if err := p.ClearSessionCookie(rw, req); err != nil {
652− logger.Printf("Error clearing session cookie: %v", err)
653− }
654 rw.WriteHeader(code)
637 rw.WriteHeader(code)
655
638
656 redirectURL, err := p.appDirector.GetRedirect(req)
639 redirectURL, err := p.appDirector.GetRedirect(req)
@@ -664,12 +647,16 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
664 redirectURL = "/"
647 redirectURL = "/"
665 }
648 }
666
649
650+ if err := p.ClearSessionCookie(rw, req); err != nil {
651+ logger.Printf("Error clearing session cookie: %v", err)
652+ }
653+
667 p.pageWriter.WriteSignInPage(rw, req, redirectURL, code)
654 p.pageWriter.WriteSignInPage(rw, req, redirectURL, code)
668}
655}
669
656
670// ManualSignIn handles basic auth logins to the proxy
657// ManualSignIn handles basic auth logins to the proxy
671func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool, int) {
658func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool, int) {
672− if req.Method != http.MethodPost || p.basicAuthValidator == nil {
659+ if req.Method != "POST" || p.basicAuthValidator == nil {
673 return "", false, http.StatusOK
660 return "", false, http.StatusOK
674 }
661 }
675 user := req.FormValue("username")
662 user := req.FormValue("username")
oauthproxy_test.go+67−219
@@ -54,7 +54,7 @@ func TestRobotsTxt(t *testing.T) {
54 t.Fatal(err)
54 t.Fatal(err)
55 }
55 }
56 rw := httptest.NewRecorder()
56 rw := httptest.NewRecorder()
57− req, _ := http.NewRequest(http.MethodGet, "/robots.txt", nil)
57+ req, _ := http.NewRequest("GET", "/robots.txt", nil)
58 proxy.ServeHTTP(rw, req)
58 proxy.ServeHTTP(rw, req)
59 assert.Equal(t, 200, rw.Code)
59 assert.Equal(t, 200, rw.Code)
60 assert.Equal(t, "User-agent: *\nDisallow: /\n", rw.Body.String())
60 assert.Equal(t, "User-agent: *\nDisallow: /\n", rw.Body.String())
@@ -241,7 +241,7 @@ func TestBasicAuthPassword(t *testing.T) {
241
241
242 // Save the required session
242 // Save the required session
243 rw := httptest.NewRecorder()
243 rw := httptest.NewRecorder()
244− req, _ := http.NewRequest(http.MethodGet, "/", nil)
244+ req, _ := http.NewRequest("GET", "/", nil)
245 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
245 err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
246 Email: emailAddress,
246 Email: emailAddress,
247 })
247 })
@@ -250,7 +250,7 @@ func TestBasicAuthPassword(t *testing.T) {
250 // Extract the cookie value to inject into the test request
250 // Extract the cookie value to inject into the test request
251 cookie := rw.Header().Values("Set-Cookie")[0]
251 cookie := rw.Header().Values("Set-Cookie")[0]
252
252
253− req, _ = http.NewRequest(http.MethodGet, "/", nil)
253+ req, _ = http.NewRequest("GET", "/", nil)
254 req.Header.Set("Cookie", cookie)
254 req.Header.Set("Cookie", cookie)
255 rw = httptest.NewRecorder()
255 rw = httptest.NewRecorder()
256 proxy.ServeHTTP(rw, req)
256 proxy.ServeHTTP(rw, req)
@@ -300,14 +300,14 @@ func TestPassGroupsHeadersWithGroups(t *testing.T) {
300
300
301 // Save the required session
301 // Save the required session
302 rw := httptest.NewRecorder()
302 rw := httptest.NewRecorder()
303− req, _ := http.NewRequest(http.MethodGet, "/", nil)
303+ req, _ := http.NewRequest("GET", "/", nil)
304 err = proxy.sessionStore.Save(rw, req, session)
304 err = proxy.sessionStore.Save(rw, req, session)
305 assert.NoError(t, err)
305 assert.NoError(t, err)
306
306
307 // Extract the cookie value to inject into the test request
307 // Extract the cookie value to inject into the test request
308 cookie := rw.Header().Values("Set-Cookie")[0]
308 cookie := rw.Header().Values("Set-Cookie")[0]
309
309
310− req, _ = http.NewRequest(http.MethodGet, "/", nil)
310+ req, _ = http.NewRequest("GET", "/", nil)
311 req.Header.Set("Cookie", cookie)
311 req.Header.Set("Cookie", cookie)
312 rw = httptest.NewRecorder()
312 rw = httptest.NewRecorder()
313 proxy.ServeHTTP(rw, req)
313 proxy.ServeHTTP(rw, req)
@@ -457,7 +457,7 @@ func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoin
457 return 0, ""
457 return 0, ""
458 }
458 }
459
459
460− req, err := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
460+ req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
461 if err != nil {
461 if err != nil {
462 return 0, ""
462 return 0, ""
463 }
463 }
@@ -608,7 +608,7 @@ func NewSignInPageTest(skipProvider bool) (*SignInPageTest, error) {
608
608
609func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
609func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
610 rw := httptest.NewRecorder()
610 rw := httptest.NewRecorder()
611− req, _ := http.NewRequest(http.MethodGet, endpoint, strings.NewReader(""))
611+ req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
612 sipTest.proxy.ServeHTTP(rw, req)
612 sipTest.proxy.ServeHTTP(rw, req)
613 return rw.Code, rw.Body.String()
613 return rw.Code, rw.Body.String()
614}
614}
@@ -713,50 +713,6 @@ func TestManualSignInCorrectCredentials(t *testing.T) {
713 assert.Equal(t, http.StatusFound, statusCode)
713 assert.Equal(t, http.StatusFound, statusCode)
714}
714}
715
715
716−func TestSignInPageClearsExistingSessionCookie(t *testing.T) {
717− opts := baseTestOptions()
718− err := validation.Validate(opts)
719− require.NoError(t, err)
720−
721− proxy, err := NewOAuthProxy(opts, func(string) bool {
722− return true
723− })
724− require.NoError(t, err)
725−
726− // Create a real session cookie using the actual session store.
727− saveRW := httptest.NewRecorder()
728− saveReq := httptest.NewRequest(http.MethodGet, "/", nil)
729− err = proxy.sessionStore.Save(saveRW, saveReq, &sessions.SessionState{
730− Email: "john.doe@example.com",
731− })
732− require.NoError(t, err)
733−
734− cookies := saveRW.Result().Cookies()
735− require.NotEmpty(t, cookies)
736−
737− // Send that cookie to the sign-in page.
738− req := httptest.NewRequest(http.MethodGet, "/oauth2/sign_in", nil)
739− for _, c := range cookies {
740− req.AddCookie(c)
741− }
742−
743− rw := httptest.NewRecorder()
744− proxy.ServeHTTP(rw, req)
745−
746− assert.Equal(t, http.StatusOK, rw.Code)
747−
748− cleared := false
749− for _, c := range rw.Result().Cookies() {
750− if c.Name == proxy.CookieOptions.Name {
751− cleared = true
752− assert.Equal(t, "", c.Value)
753− assert.Less(t, c.MaxAge, 0)
754− }
755− }
756−
757− assert.True(t, cleared, "expected sign-in page to clear existing session cookie")
758−}
759−
760func TestSignInPageIncludesTargetRedirect(t *testing.T) {
716func TestSignInPageIncludesTargetRedirect(t *testing.T) {
761 sipTest, err := NewSignInPageTest(false)
717 sipTest, err := NewSignInPageTest(false)
762 if err != nil {
718 if err != nil {
@@ -894,7 +850,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
894 // access_token validation.
850 // access_token validation.
895 pcTest.proxy.CookieOptions.Refresh = time.Duration(0)
851 pcTest.proxy.CookieOptions.Refresh = time.Duration(0)
896 pcTest.rw = httptest.NewRecorder()
852 pcTest.rw = httptest.NewRecorder()
897− pcTest.req, _ = http.NewRequest(http.MethodGet, "/", strings.NewReader(""))
853+ pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
898 pcTest.validateUser = true
854 pcTest.validateUser = true
899 return &pcTest, nil
855 return &pcTest, nil
900}
856}
@@ -1027,7 +983,7 @@ func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
1027 if err != nil {
983 if err != nil {
1028 return nil, err
984 return nil, err
1029 }
985 }
1030− pcTest.req, _ = http.NewRequest(http.MethodGet,
986+ pcTest.req, _ = http.NewRequest("GET",
1031 pcTest.opts.ProxyPrefix+"/userinfo", nil)
987 pcTest.opts.ProxyPrefix+"/userinfo", nil)
1032 return pcTest, nil
988 return pcTest, nil
1033}
989}
@@ -1135,7 +1091,7 @@ func NewAuthOnlyEndpointTest(querystring string, modifiers ...OptionsModifier) (
1135 return nil, err
1091 return nil, err
1136 }
1092 }
1137 pcTest.req, _ = http.NewRequest(
1093 pcTest.req, _ = http.NewRequest(
1138− http.MethodGet,
1094+ "GET",
1139 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1095 fmt.Sprintf("%s/auth%s", pcTest.opts.ProxyPrefix, querystring),
1140 nil)
1096 nil)
1141 return pcTest, nil
1097 return pcTest, nil
@@ -1274,7 +1230,7 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
1274 pcTest.validateUser = true
1230 pcTest.validateUser = true
1275
1231
1276 pcTest.rw = httptest.NewRecorder()
1232 pcTest.rw = httptest.NewRecorder()
1277− pcTest.req, _ = http.NewRequest(http.MethodGet,
1233+ pcTest.req, _ = http.NewRequest("GET",
1278 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1234 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1279
1235
1280 created := time.Now()
1236 created := time.Now()
@@ -1367,7 +1323,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
1367 pcTest.validateUser = true
1323 pcTest.validateUser = true
1368
1324
1369 pcTest.rw = httptest.NewRecorder()
1325 pcTest.rw = httptest.NewRecorder()
1370− pcTest.req, _ = http.NewRequest(http.MethodGet,
1326+ pcTest.req, _ = http.NewRequest("GET",
1371 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1327 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1372
1328
1373 created := time.Now()
1329 created := time.Now()
@@ -1447,7 +1403,7 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
1447 pcTest.validateUser = true
1403 pcTest.validateUser = true
1448
1404
1449 pcTest.rw = httptest.NewRecorder()
1405 pcTest.rw = httptest.NewRecorder()
1450− pcTest.req, _ = http.NewRequest(http.MethodGet,
1406+ pcTest.req, _ = http.NewRequest("GET",
1451 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1407 pcTest.opts.ProxyPrefix+authOnlyPath, nil)
1452
1408
1453 created := time.Now()
1409 created := time.Now()
@@ -1495,7 +1451,7 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
1495 }
1451 }
1496 proxy.provider = NewTestProvider(upstreamURL, "")
1452 proxy.provider = NewTestProvider(upstreamURL, "")
1497 rw := httptest.NewRecorder()
1453 rw := httptest.NewRecorder()
1498− req, _ := http.NewRequest(http.MethodOptions, "/preflight-request", nil)
1454+ req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
1499 proxy.ServeHTTP(rw, req)
1455 proxy.ServeHTTP(rw, req)
1500
1456
1501 assert.Equal(t, 200, rw.Code)
1457 assert.Equal(t, 200, rw.Code)
@@ -1652,19 +1608,19 @@ func TestRequestSignature(t *testing.T) {
1652 resp string
1608 resp string
1653 }{
1609 }{
1654 "No request signature": {
1610 "No request signature": {
1655− method: http.MethodGet,
1611+ method: "GET",
1656 body: "",
1612 body: "",
1657 key: "",
1613 key: "",
1658 resp: "no signature received",
1614 resp: "no signature received",
1659 },
1615 },
1660 "Get request": {
1616 "Get request": {
1661− method: http.MethodGet,
1617+ method: "GET",
1662 body: "",
1618 body: "",
1663 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1619 key: "7d9e1aa87a5954e6f9fc59266b3af9d7c35fda2d",
1664 resp: "signatures match",
1620 resp: "signatures match",
1665 },
1621 },
1666 "Post request": {
1622 "Post request": {
1667− method: http.MethodPost,
1623+ method: "POST",
1668 body: `{ "hello": "world!" }`,
1624 body: `{ "hello": "world!" }`,
1669 key: "d90df39e2d19282840252612dd7c81421a372f61",
1625 key: "d90df39e2d19282840252612dd7c81421a372f61",
1670 resp: "signatures match",
1626 resp: "signatures match",
@@ -2189,7 +2145,7 @@ func TestTrustedIPs(t *testing.T) {
2189 reverseProxy: false,
2145 reverseProxy: false,
2190 realClientIPHeader: "X-Real-IP", // Default value
2146 realClientIPHeader: "X-Real-IP", // Default value
2191 req: func() *http.Request {
2147 req: func() *http.Request {
2192− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2148+ req, _ := http.NewRequest("GET", "/", nil)
2193 return req
2149 return req
2194 }(),
2150 }(),
2195 expectTrusted: false,
2151 expectTrusted: false,
@@ -2201,7 +2157,7 @@ func TestTrustedIPs(t *testing.T) {
2201 reverseProxy: false,
2157 reverseProxy: false,
2202 realClientIPHeader: "X-Real-IP",
2158 realClientIPHeader: "X-Real-IP",
2203 req: func() *http.Request {
2159 req: func() *http.Request {
2204− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2160+ req, _ := http.NewRequest("GET", "/", nil)
2205 req.RemoteAddr = "@"
2161 req.RemoteAddr = "@"
2206 return req
2162 return req
2207 }(),
2163 }(),
@@ -2214,7 +2170,7 @@ func TestTrustedIPs(t *testing.T) {
2214 reverseProxy: false,
2170 reverseProxy: false,
2215 realClientIPHeader: "X-Real-IP",
2171 realClientIPHeader: "X-Real-IP",
2216 req: func() *http.Request {
2172 req: func() *http.Request {
2217− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2173+ req, _ := http.NewRequest("GET", "/", nil)
2218 req.RemoteAddr = "@"
2174 req.RemoteAddr = "@"
2219 return req
2175 return req
2220 }(),
2176 }(),
@@ -2227,7 +2183,7 @@ func TestTrustedIPs(t *testing.T) {
2227 reverseProxy: false,
2183 reverseProxy: false,
2228 realClientIPHeader: "X-Real-IP", // Default value
2184 realClientIPHeader: "X-Real-IP", // Default value
2229 req: func() *http.Request {
2185 req: func() *http.Request {
2230− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2186+ req, _ := http.NewRequest("GET", "/", nil)
2231 req.RemoteAddr = "127.0.0.1:43670"
2187 req.RemoteAddr = "127.0.0.1:43670"
2232 return req
2188 return req
2233 }(),
2189 }(),
@@ -2240,7 +2196,7 @@ func TestTrustedIPs(t *testing.T) {
2240 reverseProxy: true,
2196 reverseProxy: true,
2241 realClientIPHeader: "X-Real-IP", // Default value
2197 realClientIPHeader: "X-Real-IP", // Default value
2242 req: func() *http.Request {
2198 req: func() *http.Request {
2243− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2199+ req, _ := http.NewRequest("GET", "/", nil)
2244 req.RemoteAddr = "127.0.0.1:44324"
2200 req.RemoteAddr = "127.0.0.1:44324"
2245 return req
2201 return req
2246 }(),
2202 }(),
@@ -2253,7 +2209,7 @@ func TestTrustedIPs(t *testing.T) {
2253 reverseProxy: true,
2209 reverseProxy: true,
2254 realClientIPHeader: "X-Forwarded-For",
2210 realClientIPHeader: "X-Forwarded-For",
2255 req: func() *http.Request {
2211 req: func() *http.Request {
2256− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2212+ req, _ := http.NewRequest("GET", "/", nil)
2257 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2213 req.Header.Add("X-Forwarded-For", "127.0.0.1")
2258 return req
2214 return req
2259 }(),
2215 }(),
@@ -2266,7 +2222,7 @@ func TestTrustedIPs(t *testing.T) {
2266 reverseProxy: true,
2222 reverseProxy: true,
2267 realClientIPHeader: "X-Forwarded-For",
2223 realClientIPHeader: "X-Forwarded-For",
2268 req: func() *http.Request {
2224 req: func() *http.Request {
2269− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2225+ req, _ := http.NewRequest("GET", "/", nil)
2270 req.Header.Add("X-Forwarded-For", "::1")
2226 req.Header.Add("X-Forwarded-For", "::1")
2271 return req
2227 return req
2272 }(),
2228 }(),
@@ -2279,7 +2235,7 @@ func TestTrustedIPs(t *testing.T) {
2279 reverseProxy: true,
2235 reverseProxy: true,
2280 realClientIPHeader: "X-Forwarded-For",
2236 realClientIPHeader: "X-Forwarded-For",
2281 req: func() *http.Request {
2237 req: func() *http.Request {
2282− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2238+ req, _ := http.NewRequest("GET", "/", nil)
2283 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2239 req.Header.Add("X-Forwarded-For", "12.34.56.78")
2284 return req
2240 return req
2285 }(),
2241 }(),
@@ -2292,7 +2248,7 @@ func TestTrustedIPs(t *testing.T) {
2292 reverseProxy: true,
2248 reverseProxy: true,
2293 realClientIPHeader: "X-Forwarded-For",
2249 realClientIPHeader: "X-Forwarded-For",
2294 req: func() *http.Request {
2250 req: func() *http.Request {
2295− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2251+ req, _ := http.NewRequest("GET", "/", nil)
2296 req.Header.Add("X-Forwarded-For", "::2")
2252 req.Header.Add("X-Forwarded-For", "::2")
2297 return req
2253 return req
2298 }(),
2254 }(),
@@ -2305,7 +2261,7 @@ func TestTrustedIPs(t *testing.T) {
2305 reverseProxy: true,
2261 reverseProxy: true,
2306 realClientIPHeader: "X-Forwarded-For",
2262 realClientIPHeader: "X-Forwarded-For",
2307 req: func() *http.Request {
2263 req: func() *http.Request {
2308− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2264+ req, _ := http.NewRequest("GET", "/", nil)
2309 req.Header.Add("X-Real-IP", "::1")
2265 req.Header.Add("X-Real-IP", "::1")
2310 return req
2266 return req
2311 }(),
2267 }(),
@@ -2318,7 +2274,7 @@ func TestTrustedIPs(t *testing.T) {
2318 reverseProxy: true,
2274 reverseProxy: true,
2319 realClientIPHeader: "X-Forwarded-For",
2275 realClientIPHeader: "X-Forwarded-For",
2320 req: func() *http.Request {
2276 req: func() *http.Request {
2321− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2277+ req, _ := http.NewRequest("GET", "/", nil)
2322 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2278 req.Header.Add("X-Forwarded-For", "adsfljk29242as!!")
2323 return req
2279 return req
2324 }(),
2280 }(),
@@ -2331,7 +2287,7 @@ func TestTrustedIPs(t *testing.T) {
2331 reverseProxy: false,
2287 reverseProxy: false,
2332 realClientIPHeader: "X-Real-IP",
2288 realClientIPHeader: "X-Real-IP",
2333 req: func() *http.Request {
2289 req: func() *http.Request {
2334− req, _ := http.NewRequest(http.MethodGet, "/", nil)
2290+ req, _ := http.NewRequest("GET", "/", nil)
2335 req.RemoteAddr = "adsfljk29242as!!"
2291 req.RemoteAddr = "adsfljk29242as!!"
2336 return req
2292 return req
2337 }(),
2293 }(),
@@ -2427,12 +2383,12 @@ func Test_buildRoutesAllowlist(t *testing.T) {
2427 },
2383 },
2428 expectedRoutes: []expectedAllowedRoute{
2384 expectedRoutes: []expectedAllowedRoute{
2429 {
2385 {
2430− method: http.MethodGet,
2386+ method: "GET",
2431 negate: false,
2387 negate: false,
2432 regexString: "^/foo/bar",
2388 regexString: "^/foo/bar",
2433 },
2389 },
2434 {
2390 {
2435− method: http.MethodPost,
2391+ method: "POST",
2436 negate: false,
2392 negate: false,
2437 regexString: "^/baz/[0-9]+/thing",
2393 regexString: "^/baz/[0-9]+/thing",
2438 },
2394 },
@@ -2485,11 +2441,11 @@ func Test_buildRoutesAllowlist(t *testing.T) {
2485 regexString: "^/baz/[0-9]+/thing/regex",
2441 regexString: "^/baz/[0-9]+/thing/regex",
2486 },
2442 },
2487 {
2443 {
2488− method: http.MethodGet,
2444+ method: "GET",
2489 regexString: "^/foo/bar",
2445 regexString: "^/foo/bar",
2490 },
2446 },
2491 {
2447 {
2492− method: http.MethodPost,
2448+ method: "POST",
2493 regexString: "^/baz/[0-9]+/thing",
2449 regexString: "^/baz/[0-9]+/thing",
2494 },
2450 },
2495 {
2451 {
@@ -2641,7 +2597,7 @@ func TestApiRoutes(t *testing.T) {
2641
2597
2642 for _, tc := range testCases {
2598 for _, tc := range testCases {
2643 t.Run(tc.name, func(t *testing.T) {
2599 t.Run(tc.name, func(t *testing.T) {
2644− req, err := http.NewRequest(http.MethodGet, tc.url, nil)
2600+ req, err := http.NewRequest("GET", tc.url, nil)
2645 req.Header.Set("Accept", tc.contentType)
2601 req.Header.Set("Accept", tc.contentType)
2646 assert.NoError(t, err)
2602 assert.NoError(t, err)
2647
2603
@@ -2679,11 +2635,9 @@ func TestAllowedRequest(t *testing.T) {
2679 }
2635 }
2680 opts.SkipAuthRegex = []string{
2636 opts.SkipAuthRegex = []string{
2681 "^/skip/auth/regex$",
2637 "^/skip/auth/regex$",
2682− "^/public/.*/endpoint$",
2683 }
2638 }
2684 opts.SkipAuthRoutes = []string{
2639 opts.SkipAuthRoutes = []string{
2685 "GET=^/skip/auth/routes/get",
2640 "GET=^/skip/auth/routes/get",
2686− "^/foo/.*/bar$",
2687 }
2641 }
2688 err := validation.Validate(opts)
2642 err := validation.Validate(opts)
2689 assert.NoError(t, err)
2643 assert.NoError(t, err)
@@ -2700,37 +2654,25 @@ func TestAllowedRequest(t *testing.T) {
2700 }{
2654 }{
2701 {
2655 {
2702 name: "Regex GET allowed",
2656 name: "Regex GET allowed",
2703− method: http.MethodGet,
2657+ method: "GET",
2704 url: "/skip/auth/regex",
2658 url: "/skip/auth/regex",
2705 allowed: true,
2659 allowed: true,
2706 },
2660 },
2707 {
2661 {
2708 name: "Regex POST allowed ",
2662 name: "Regex POST allowed ",
2709− method: http.MethodPost,
2663+ method: "POST",
2710 url: "/skip/auth/regex",
2664 url: "/skip/auth/regex",
2711 allowed: true,
2665 allowed: true,
2712 },
2666 },
2713 {
2667 {
2714 name: "Regex denied",
2668 name: "Regex denied",
2715− method: http.MethodGet,
2669+ method: "GET",
2716 url: "/wrong/denied",
2670 url: "/wrong/denied",
2717 allowed: false,
2671 allowed: false,
2718 },
2672 },
2719− {
2720− name: "Regex allowed with fragment-free path",
2721− method: http.MethodGet,
2722− url: "/public/legit/endpoint",
2723− allowed: true,
2724− },
2725− {
2726− name: "Regex denied when path contains encoded fragment suffix",
2727− method: http.MethodGet,
2728− url: "/public/secret%23/endpoint",
2729− allowed: false,
2730− },
2731 {
2673 {
2732 name: "Route allowed",
2674 name: "Route allowed",
2733− method: http.MethodGet,
2675+ method: "GET",
2734 url: "/skip/auth/routes/get",
2676 url: "/skip/auth/routes/get",
2735 allowed: true,
2677 allowed: true,
2736 },
2678 },
@@ -2742,28 +2684,16 @@ func TestAllowedRequest(t *testing.T) {
2742 },
2684 },
2743 {
2685 {
2744 name: "Route denied with wrong path",
2686 name: "Route denied with wrong path",
2745− method: http.MethodGet,
2687+ method: "GET",
2746 url: "/skip/auth/routes/wrong/path",
2688 url: "/skip/auth/routes/wrong/path",
2747 allowed: false,
2689 allowed: false,
2748 },
2690 },
2749 {
2691 {
2750 name: "Route denied with wrong path and method",
2692 name: "Route denied with wrong path and method",
2751− method: http.MethodPost,
2693+ method: "POST",
2752 url: "/skip/auth/routes/wrong/path",
2694 url: "/skip/auth/routes/wrong/path",
2753 allowed: false,
2695 allowed: false,
2754 },
2696 },
2755− {
2756− name: "Route allowed with fragment-free path",
2757− method: http.MethodGet,
2758− url: "/foo/public/bar",
2759− allowed: true,
2760− },
2761− {
2762− name: "Route denied when path contains encoded fragment suffix",
2763− method: http.MethodGet,
2764− url: "/foo/secret%23/bar",
2765− allowed: false,
2766− },
2767 }
2697 }
2768
2698
2769 for _, tc := range testCases {
2699 for _, tc := range testCases {
@@ -2804,11 +2734,9 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2804 }
2734 }
2805 opts.SkipAuthRegex = []string{
2735 opts.SkipAuthRegex = []string{
2806 "^/skip/auth/regex$",
2736 "^/skip/auth/regex$",
2807− "^/public/.*/endpoint$",
2808 }
2737 }
2809 opts.SkipAuthRoutes = []string{
2738 opts.SkipAuthRoutes = []string{
2810 "GET=^/skip/auth/routes/get",
2739 "GET=^/skip/auth/routes/get",
2811− "^/foo/.*/bar$",
2812 }
2740 }
2813 err := validation.Validate(opts)
2741 err := validation.Validate(opts)
2814 assert.NoError(t, err)
2742 assert.NoError(t, err)
@@ -2825,37 +2753,25 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2825 }{
2753 }{
2826 {
2754 {
2827 name: "Regex GET allowed",
2755 name: "Regex GET allowed",
2828− method: http.MethodGet,
2756+ method: "GET",
2829 url: "/skip/auth/regex",
2757 url: "/skip/auth/regex",
2830 allowed: true,
2758 allowed: true,
2831 },
2759 },
2832 {
2760 {
2833 name: "Regex POST allowed ",
2761 name: "Regex POST allowed ",
2834− method: http.MethodPost,
2762+ method: "POST",
2835 url: "/skip/auth/regex",
2763 url: "/skip/auth/regex",
2836 allowed: true,
2764 allowed: true,
2837 },
2765 },
2838 {
2766 {
2839 name: "Regex denied",
2767 name: "Regex denied",
2840− method: http.MethodGet,
2768+ method: "GET",
2841 url: "/wrong/denied",
2769 url: "/wrong/denied",
2842 allowed: false,
2770 allowed: false,
2843 },
2771 },
2844− {
2845− name: "Regex allowed with fragment-free path",
2846− method: http.MethodGet,
2847− url: "/public/legit/endpoint",
2848− allowed: true,
2849− },
2850− {
2851− name: "Regex denied when X-Forwarded-Uri contains an encoded fragment suffix",
2852− method: http.MethodGet,
2853− url: "/public/secret%23/endpoint",
2854− allowed: false,
2855− },
2856 {
2772 {
2857 name: "Route allowed",
2773 name: "Route allowed",
2858− method: http.MethodGet,
2774+ method: "GET",
2859 url: "/skip/auth/routes/get",
2775 url: "/skip/auth/routes/get",
2860 allowed: true,
2776 allowed: true,
2861 },
2777 },
@@ -2867,35 +2783,22 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2867 },
2783 },
2868 {
2784 {
2869 name: "Route denied with wrong path",
2785 name: "Route denied with wrong path",
2870− method: http.MethodGet,
2786+ method: "GET",
2871 url: "/skip/auth/routes/wrong/path",
2787 url: "/skip/auth/routes/wrong/path",
2872 allowed: false,
2788 allowed: false,
2873 },
2789 },
2874 {
2790 {
2875 name: "Route denied with wrong path and method",
2791 name: "Route denied with wrong path and method",
2876− method: http.MethodPost,
2792+ method: "POST",
2877 url: "/skip/auth/routes/wrong/path",
2793 url: "/skip/auth/routes/wrong/path",
2878 allowed: false,
2794 allowed: false,
2879 },
2795 },
2880− {
2881− name: "Route allowed with fragment-free path",
2882− method: http.MethodGet,
2883− url: "/foo/public/bar",
2884− allowed: true,
2885− },
2886− {
2887− name: "Route denied when X-Forwarded-Uri contains an encoded fragment suffix",
2888− method: http.MethodGet,
2889− url: "/foo/secret%23/bar",
2890− allowed: false,
2891− },
2892 }
2796 }
2893
2797
2894 for _, tc := range testCases {
2798 for _, tc := range testCases {
2895 t.Run(tc.name, func(t *testing.T) {
2799 t.Run(tc.name, func(t *testing.T) {
2896 req, err := http.NewRequest(tc.method, opts.ProxyPrefix+authOnlyPath, nil)
2800 req, err := http.NewRequest(tc.method, opts.ProxyPrefix+authOnlyPath, nil)
2897 req.Header.Set("X-Forwarded-Uri", tc.url)
2801 req.Header.Set("X-Forwarded-Uri", tc.url)
2898− req.RemoteAddr = "127.0.0.1:4180"
2899 assert.NoError(t, err)
2802 assert.NoError(t, err)
2900
2803
2901 rw := httptest.NewRecorder()
2804 rw := httptest.NewRecorder()
@@ -2910,43 +2813,6 @@ func TestAllowedRequestWithForwardedUriHeader(t *testing.T) {
2910 }
2813 }
2911}
2814}
2912
2815
2913−func TestAllowedRequestWithForwardedUriHeaderRequiresTrustedProxy(t *testing.T) {
2914− upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2915− w.WriteHeader(200)
2916− }))
2917− t.Cleanup(upstreamServer.Close)
2918−
2919− opts := baseTestOptions()
2920− opts.ReverseProxy = true
2921− opts.TrustedProxyIPs = []string{"127.0.0.1/32"}
2922− opts.UpstreamServers = options.UpstreamConfig{
2923− Upstreams: []options.Upstream{
2924− {
2925− ID: upstreamServer.URL,
2926− Path: "/",
2927− URI: upstreamServer.URL,
2928− },
2929− },
2930− }
2931− opts.SkipAuthRegex = []string{"^/skip/auth/regex$"}
2932−
2933− err := validation.Validate(opts)
2934− assert.NoError(t, err)
2935−
2936− proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
2937− assert.NoError(t, err)
2938−
2939− req, err := http.NewRequest(http.MethodGet, opts.ProxyPrefix+authOnlyPath, nil)
2940− assert.NoError(t, err)
2941− req.RemoteAddr = "192.0.2.10:4180"
2942− req.Header.Set("X-Forwarded-Uri", "/skip/auth/regex")
2943−
2944− rw := httptest.NewRecorder()
2945− proxy.ServeHTTP(rw, req)
2946−
2947− assert.Equal(t, 401, rw.Code)
2948−}
2949−
2950func TestAllowedRequestNegateWithoutMethod(t *testing.T) {
2816func TestAllowedRequestNegateWithoutMethod(t *testing.T) {
2951 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2817 upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2952 w.WriteHeader(200)
2818 w.WriteHeader(200)
@@ -2986,37 +2852,37 @@ func TestAllowedRequestNegateWithoutMethod(t *testing.T) {
2986 }{
2852 }{
2987 {
2853 {
2988 name: "Some static file allowed",
2854 name: "Some static file allowed",
2989− method: http.MethodGet,
2855+ method: "GET",
2990 url: "/static/file.txt",
2856 url: "/static/file.txt",
2991 allowed: true,
2857 allowed: true,
2992 },
2858 },
2993 {
2859 {
2994 name: "POST to contact form allowed",
2860 name: "POST to contact form allowed",
2995− method: http.MethodPost,
2861+ method: "POST",
2996 url: "/contact",
2862 url: "/contact",
2997 allowed: true,
2863 allowed: true,
2998 },
2864 },
2999 {
2865 {
3000 name: "Regex POST allowed",
2866 name: "Regex POST allowed",
3001− method: http.MethodPost,
2867+ method: "POST",
3002 url: "/api/public-entity",
2868 url: "/api/public-entity",
3003 allowed: true,
2869 allowed: true,
3004 },
2870 },
3005 {
2871 {
3006 name: "Regex POST with trailing slash allowed",
2872 name: "Regex POST with trailing slash allowed",
3007− method: http.MethodPost,
2873+ method: "POST",
3008 url: "/api/public-entity/",
2874 url: "/api/public-entity/",
3009 allowed: true,
2875 allowed: true,
3010 },
2876 },
3011 {
2877 {
3012 name: "Regex GET api route denied",
2878 name: "Regex GET api route denied",
3013− method: http.MethodGet,
2879+ method: "GET",
3014 url: "/api/users",
2880 url: "/api/users",
3015 allowed: false,
2881 allowed: false,
3016 },
2882 },
3017 {
2883 {
3018 name: "Regex POST api route denied",
2884 name: "Regex POST api route denied",
3019− method: http.MethodPost,
2885+ method: "POST",
3020 url: "/api/users",
2886 url: "/api/users",
3021 allowed: false,
2887 allowed: false,
3022 },
2888 },
@@ -3086,37 +2952,37 @@ func TestAllowedRequestNegateWithMethod(t *testing.T) {
3086 }{
2952 }{
3087 {
2953 {
3088 name: "Some static file allowed",
2954 name: "Some static file allowed",
3089− method: http.MethodGet,
2955+ method: "GET",
3090 url: "/static/file.txt",
2956 url: "/static/file.txt",
3091 allowed: true,
2957 allowed: true,
3092 },
2958 },
3093 {
2959 {
3094 name: "POST to contact form not allowed",
2960 name: "POST to contact form not allowed",
3095− method: http.MethodPost,
2961+ method: "POST",
3096 url: "/contact",
2962 url: "/contact",
3097 allowed: false,
2963 allowed: false,
3098 },
2964 },
3099 {
2965 {
3100 name: "Regex POST allowed",
2966 name: "Regex POST allowed",
3101− method: http.MethodPost,
2967+ method: "POST",
3102 url: "/api/public-entity",
2968 url: "/api/public-entity",
3103 allowed: true,
2969 allowed: true,
3104 },
2970 },
3105 {
2971 {
3106 name: "Regex POST with trailing slash allowed",
2972 name: "Regex POST with trailing slash allowed",
3107− method: http.MethodPost,
2973+ method: "POST",
3108 url: "/api/public-entity/",
2974 url: "/api/public-entity/",
3109 allowed: true,
2975 allowed: true,
3110 },
2976 },
3111 {
2977 {
3112 name: "Regex GET api route denied",
2978 name: "Regex GET api route denied",
3113− method: http.MethodGet,
2979+ method: "GET",
3114 url: "/api/users",
2980 url: "/api/users",
3115 allowed: false,
2981 allowed: false,
3116 },
2982 },
3117 {
2983 {
3118 name: "Regex POST api route denied",
2984 name: "Regex POST api route denied",
3119− method: http.MethodPost,
2985+ method: "POST",
3120 url: "/api/users",
2986 url: "/api/users",
3121 allowed: false,
2987 allowed: false,
3122 },
2988 },
@@ -3256,7 +3122,7 @@ func TestProxyAllowedGroups(t *testing.T) {
3256 t.Fatal(err)
3122 t.Fatal(err)
3257 }
3123 }
3258
3124
3259− test.req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/%s", tt.querystring), nil)
3125+ test.req, _ = http.NewRequest("GET", fmt.Sprintf("/%s", tt.querystring), nil)
3260
3126
3261 test.req.Header.Add("accept", applicationJSON)
3127 test.req.Header.Add("accept", applicationJSON)
3262 err = test.SaveSession(session)
3128 err = test.SaveSession(session)
@@ -3400,7 +3266,7 @@ func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
3400 {
3266 {
3401 name: "UserWithGroupSkipAuthPreflight",
3267 name: "UserWithGroupSkipAuthPreflight",
3402 groups: []string{"a", "c"},
3268 groups: []string{"a", "c"},
3403− method: http.MethodOptions,
3269+ method: "OPTIONS",
3404 ip: "1.2.3.5:43670",
3270 ip: "1.2.3.5:43670",
3405 withSession: true,
3271 withSession: true,
3406 expectedStatusCode: http.StatusAccepted,
3272 expectedStatusCode: http.StatusAccepted,
@@ -3408,7 +3274,7 @@ func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
3408 {
3274 {
3409 name: "UserWithGroupTrustedIp",
3275 name: "UserWithGroupTrustedIp",
3410 groups: []string{"a", "c"},
3276 groups: []string{"a", "c"},
3411− method: http.MethodGet,
3277+ method: "GET",
3412 ip: "1.2.3.4:43670",
3278 ip: "1.2.3.4:43670",
3413 withSession: true,
3279 withSession: true,
3414 expectedStatusCode: http.StatusAccepted,
3280 expectedStatusCode: http.StatusAccepted,
@@ -3416,7 +3282,7 @@ func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
3416 {
3282 {
3417 name: "UserWithoutGroupSkipAuthPreflight",
3283 name: "UserWithoutGroupSkipAuthPreflight",
3418 groups: []string{"c"},
3284 groups: []string{"c"},
3419− method: http.MethodOptions,
3285+ method: "OPTIONS",
3420 ip: "1.2.3.5:43670",
3286 ip: "1.2.3.5:43670",
3421 withSession: true,
3287 withSession: true,
3422 expectedStatusCode: http.StatusForbidden,
3288 expectedStatusCode: http.StatusForbidden,
@@ -3424,21 +3290,21 @@ func TestAuthOnlyAllowedGroupsWithSkipMethods(t *testing.T) {
3424 {
3290 {
3425 name: "UserWithoutGroupTrustedIp",
3291 name: "UserWithoutGroupTrustedIp",
3426 groups: []string{"c"},
3292 groups: []string{"c"},
3427− method: http.MethodGet,
3293+ method: "GET",
3428 ip: "1.2.3.4:43670",
3294 ip: "1.2.3.4:43670",
3429 withSession: true,
3295 withSession: true,
3430 expectedStatusCode: http.StatusForbidden,
3296 expectedStatusCode: http.StatusForbidden,
3431 },
3297 },
3432 {
3298 {
3433 name: "UserWithoutSessionSkipAuthPreflight",
3299 name: "UserWithoutSessionSkipAuthPreflight",
3434− method: http.MethodOptions,
3300+ method: "OPTIONS",
3435 ip: "1.2.3.5:43670",
3301 ip: "1.2.3.5:43670",
3436 withSession: false,
3302 withSession: false,
3437 expectedStatusCode: http.StatusAccepted,
3303 expectedStatusCode: http.StatusAccepted,
3438 },
3304 },
3439 {
3305 {
3440 name: "UserWithoutSessionTrustedIp",
3306 name: "UserWithoutSessionTrustedIp",
3441− method: http.MethodGet,
3307+ method: "GET",
3442 ip: "1.2.3.4:43670",
3308 ip: "1.2.3.4:43670",
3443 withSession: false,
3309 withSession: false,
3444 expectedStatusCode: http.StatusAccepted,
3310 expectedStatusCode: http.StatusAccepted,
@@ -3538,24 +3404,6 @@ func TestAuthOnlyAllowedEmailDomains(t *testing.T) {
3538 querystring: "?allowed_email_domains=a.b.c.example.com,*.c.example.com",
3404 querystring: "?allowed_email_domains=a.b.c.example.com,*.c.example.com",
3539 expectedStatusCode: http.StatusAccepted,
3405 expectedStatusCode: http.StatusAccepted,
3540 },
3406 },
3541− {
3542− name: "UserWithMultipleAtSignsExactDomain",
3543− email: "attacker@evil.com@example.com",
3544− querystring: "?allowed_email_domains=example.com",
3545− expectedStatusCode: http.StatusForbidden,
3546− },
3547− {
3548− name: "UserWithMultipleAtSignsWildcardDomain",
3549− email: "attacker@evil.com@foo.example.com",
3550− querystring: "?allowed_email_domains=*.example.com",
3551− expectedStatusCode: http.StatusForbidden,
3552− },
3553− {
3554− name: "UserWithMultipleAtSignsDotPrefixedDomain",
3555− email: "attacker@evil.com@foo.example.com",
3556− querystring: "?allowed_email_domains=.example.com",
3557− expectedStatusCode: http.StatusForbidden,
3558− },
3559 }
3407 }
3560
3408
3561 for _, tc := range testCases {
3409 for _, tc := range testCases {
@@ -3790,14 +3638,14 @@ func TestIdTokenPlaceholderInSignOut(t *testing.T) {
3790
3638
3791 // Save the required session
3639 // Save the required session
3792 rw := httptest.NewRecorder()
3640 rw := httptest.NewRecorder()
3793− req, _ := http.NewRequest(http.MethodGet, "/", nil)
3641+ req, _ := http.NewRequest("GET", "/", nil)
3794 err = proxy.sessionStore.Save(rw, req, session)
3642 err = proxy.sessionStore.Save(rw, req, session)
3795 assert.NoError(t, err)
3643 assert.NoError(t, err)
3796
3644
3797 rw = httptest.NewRecorder()
3645 rw = httptest.NewRecorder()
3798
3646
3799 rdUrl := url.QueryEscape("https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com/")
3647 rdUrl := url.QueryEscape("https://my-oidc-provider.example.com/sign_out_page?id_token_hint={id_token}&post_logout_redirect_uri=https://my-app.example.com/")
3800− req, _ = http.NewRequest(http.MethodGet, "/oauth2/sign_out?rd="+rdUrl, nil)
3648+ req, _ = http.NewRequest("GET", "/oauth2/sign_out?rd="+rdUrl, nil)
3801 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
3649 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
3802 RequestID: "11111111-2222-4333-8444-555555555555",
3650 RequestID: "11111111-2222-4333-8444-555555555555",
3803 Session: session,
3651 Session: session,
pkg/apis/middleware/scope.go+1−48
@@ -2,12 +2,9 @@ package middleware
2
2
3import (
3import (
4 "context"
4 "context"
5− "net"
6 "net/http"
5 "net/http"
7− "strings"
8
6
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
7 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
10− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
11)
8)
12
9
13type scopeKey string
10type scopeKey string
@@ -21,13 +18,9 @@ const RequestScopeKey scopeKey = "request-scope"
21// within the chain.
18// within the chain.
22type RequestScope struct {
19type RequestScope struct {
23 // ReverseProxy tracks whether OAuth2-Proxy is operating in reverse proxy
20 // ReverseProxy tracks whether OAuth2-Proxy is operating in reverse proxy
24− // mode and if request `X-Forwarded-*` headers may be trusted
21+ // mode and if request `X-Forwarded-*` headers should be trusted
25 ReverseProxy bool
22 ReverseProxy bool
26
23
27− // TrustedProxies tracks which direct callers are allowed to supply
28− // forwarded headers when ReverseProxy mode is enabled.
29− TrustedProxies *ip.NetSet
30−
31 // RequestID is set to the request's `X-Request-Id` header if set.
24 // RequestID is set to the request's `X-Request-Id` header if set.
32 // Otherwise a random UUID is set.
25 // Otherwise a random UUID is set.
33 RequestID string
26 RequestID string
@@ -65,43 +58,3 @@ func AddRequestScope(req *http.Request, scope *RequestScope) *http.Request {
65 ctx := context.WithValue(req.Context(), RequestScopeKey, scope)
58 ctx := context.WithValue(req.Context(), RequestScopeKey, scope)
66 return req.WithContext(ctx)
59 return req.WithContext(ctx)
67}
60}
68−
69−// CanTrustForwardedHeaders returns whether forwarded headers should be
70−// processed for this request.
71−func (s *RequestScope) CanTrustForwardedHeaders(req *http.Request) bool {
72− if s == nil || req == nil || !s.ReverseProxy || s.TrustedProxies == nil {
73− return false
74− }
75−
76− if isUnixSocketRemoteAddr(req.RemoteAddr) {
77− return true
78− }
79−
80− remoteIP := parseRemoteAddrIP(req.RemoteAddr)
81− if remoteIP == nil {
82− return false
83− }
84−
85− return s.TrustedProxies.Has(remoteIP)
86−}
87−
88−func parseRemoteAddrIP(remoteAddr string) net.IP {
89− if remoteAddr == "" {
90− return nil
91− }
92−
93− if ip := net.ParseIP(remoteAddr); ip != nil {
94− return ip
95− }
96−
97− host, _, err := net.SplitHostPort(remoteAddr)
98− if err != nil {
99− return nil
100− }
101−
102− return net.ParseIP(host)
103−}
104−
105−func isUnixSocketRemoteAddr(remoteAddr string) bool {
106− return remoteAddr == "@" || strings.HasPrefix(remoteAddr, "/")
107−}
pkg/apis/middleware/scope_test.go−34
@@ -4,7 +4,6 @@ import (
4 "net/http"
4 "net/http"
5
5
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
7− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
8 . "github.com/onsi/ginkgo/v2"
7 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/gomega"
8 . "github.com/onsi/gomega"
10)
9)
@@ -54,37 +53,4 @@ var _ = Describe("Scope Suite", func() {
54 })
53 })
55 })
54 })
56 })
55 })
57−
58− Context("CanTrustForwardedHeaders", func() {
59− var request *http.Request
60− var scope *middleware.RequestScope
61−
62− BeforeEach(func() {
63− var err error
64− request, err = http.NewRequest("", "http://127.0.0.1/", nil)
65− Expect(err).ToNot(HaveOccurred())
66−
67− trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
68− Expect(err).ToNot(HaveOccurred())
69− scope = &middleware.RequestScope{
70− ReverseProxy: true,
71− TrustedProxies: trustedProxies,
72− }
73− })
74−
75− It("returns true for a trusted remote address", func() {
76− request.RemoteAddr = "127.0.0.1:4180"
77− Expect(scope.CanTrustForwardedHeaders(request)).To(BeTrue())
78− })
79−
80− It("returns false for an untrusted remote address", func() {
81− request.RemoteAddr = "192.0.2.10:4180"
82− Expect(scope.CanTrustForwardedHeaders(request)).To(BeFalse())
83− })
84−
85− It("returns true for unix socket callers", func() {
86− request.RemoteAddr = "@"
87− Expect(scope.CanTrustForwardedHeaders(request)).To(BeTrue())
88− })
89− })
90})
56})
pkg/apis/options/load.go+1−1
@@ -145,7 +145,7 @@ func loadAndSubstituteEnvs(configFileName string) ([]byte, error) {
145func registerFlags(v *viper.Viper, prefix string, flagSet *pflag.FlagSet, options interface{}) error {
145func registerFlags(v *viper.Viper, prefix string, flagSet *pflag.FlagSet, options interface{}) error {
146 val := reflect.ValueOf(options)
146 val := reflect.ValueOf(options)
147 var typ reflect.Type
147 var typ reflect.Type
148− if val.Kind() == reflect.Pointer {
148+ if val.Kind() == reflect.Ptr {
149 typ = val.Elem().Type()
149 typ = val.Elem().Type()
150 } else {
150 } else {
151 typ = val.Type()
151 typ = val.Type()
pkg/apis/options/load_test.go+2−2
@@ -282,7 +282,7 @@ var _ = Describe("Load", func() {
282 Entry("with an invalid config file", &testOptionsTableInput{
282 Entry("with an invalid config file", &testOptionsTableInput{
283 configFile: []byte(`slice_option = foo`),
283 configFile: []byte(`slice_option = foo`),
284 flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
284 flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
285− expectedErr: fmt.Errorf("unable to load config file: While parsing config: toml: expected keyword \"false\""),
285+ expectedErr: fmt.Errorf("unable to load config file: While parsing config: toml: expected 'false'"),
286 expectedOutput: &TestOptions{},
286 expectedOutput: &TestOptions{},
287 }),
287 }),
288 Entry("with an invalid flagset", &testOptionsTableInput{
288 Entry("with an invalid flagset", &testOptionsTableInput{
@@ -329,7 +329,7 @@ var _ = Describe("Load", func() {
329 Entry("with an unknown option in the config file", &testOptionsTableInput{
329 Entry("with an unknown option in the config file", &testOptionsTableInput{
330 configFile: []byte(`unknown_option="foo"`),
330 configFile: []byte(`unknown_option="foo"`),
331 flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
331 flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
332− expectedErr: fmt.Errorf("error unmarshalling config: decoding failed due to the following error(s):\n\n'options.TestOptions' has invalid keys: unknown_option"),
332+ expectedErr: fmt.Errorf("error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_option"),
333 // Viper will unmarshal before returning the error, so this is the default output
333 // Viper will unmarshal before returning the error, so this is the default output
334 expectedOutput: &TestOptions{
334 expectedOutput: &TestOptions{
335 StringOption: "default",
335 StringOption: "default",
pkg/apis/options/options.go−2
@@ -24,7 +24,6 @@ type Options struct {
24 ReadyPath string `flag:"ready-path" cfg:"ready_path"`
24 ReadyPath string `flag:"ready-path" cfg:"ready_path"`
25 ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
25 ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
26 RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
26 RealClientIPHeader string `flag:"real-client-ip-header" cfg:"real_client_ip_header"`
27− TrustedProxyIPs []string `flag:"trusted-proxy-ip" cfg:"trusted_proxy_ips"`
28 TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
27 TrustedIPs []string `flag:"trusted-ip" cfg:"trusted_ips"`
29 ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
28 ForceHTTPS bool `flag:"force-https" cfg:"force_https"`
30 RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
29 RawRedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
@@ -120,7 +119,6 @@ func NewFlagSet() *pflag.FlagSet {
120
119
121 flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted")
120 flagSet.Bool("reverse-proxy", false, "are we running behind a reverse proxy, controls whether headers like X-Real-Ip are accepted")
122 flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, X-ProxyUser-IP, X-Envoy-External-Address, or CF-Connecting-IP)")
121 flagSet.String("real-client-ip-header", "X-Real-IP", "Header used to determine the real IP of the client (one of: X-Forwarded-For, X-Real-IP, X-ProxyUser-IP, X-Envoy-External-Address, or CF-Connecting-IP)")
123− flagSet.StringSlice("trusted-proxy-ip", []string{}, "list of IPs or CIDR ranges that are allowed to set X-Forwarded-* headers when --reverse-proxy is enabled. Defaults to trusting all IPs for backwards compatibility; configure this to your reverse proxy addresses to prevent header spoofing.")
124 flagSet.StringSlice("trusted-ip", []string{}, "list of IPs or CIDR ranges to allow to bypass authentication. WARNING: trusting by IP has inherent security flaws, read the configuration documentation for more information.")
122 flagSet.StringSlice("trusted-ip", []string{}, "list of IPs or CIDR ranges to allow to bypass authentication. WARNING: trusting by IP has inherent security flaws, read the configuration documentation for more information.")
125 flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
123 flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
126 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
124 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
pkg/apis/options/providers.go−6
@@ -151,9 +151,6 @@ const (
151 // ADFSProvider is the provider type for ADFS
151 // ADFSProvider is the provider type for ADFS
152 ADFSProvider ProviderType = "adfs"
152 ADFSProvider ProviderType = "adfs"
153
153
154− // AtlassianProvider is the provider type for Atlassian Cloud
155− AtlassianProvider ProviderType = "atlassian"
156−
157 // AzureProvider is the provider type for Azure
154 // AzureProvider is the provider type for Azure
158 AzureProvider ProviderType = "azure"
155 AzureProvider ProviderType = "azure"
159
156
@@ -169,9 +166,6 @@ const (
169 // DigitalOceanProvider is the provider type for DigitalOcean
166 // DigitalOceanProvider is the provider type for DigitalOcean
170 DigitalOceanProvider ProviderType = "digitalocean"
167 DigitalOceanProvider ProviderType = "digitalocean"
171
168
172− // DTTSSOProvider is the provider type for the DTT SSO
173− DTTSSOProvider ProviderType = "dtt-sso"
174−
175 // FacebookProvider is the provider type for Facebook
169 // FacebookProvider is the provider type for Facebook
176 FacebookProvider ProviderType = "facebook"
170 FacebookProvider ProviderType = "facebook"
177
171
pkg/apis/options/upstreams.go+5−7
@@ -62,14 +62,12 @@ type Upstream struct {
62
62
63 // Path is used to map requests to the upstream server.
63 // Path is used to map requests to the upstream server.
64 // The closest match will take precedence and all Paths must be unique.
64 // The closest match will take precedence and all Paths must be unique.
65− // Without RewriteTarget, Path follows gorilla/mux route syntax.
65+ // Path can also take a pattern when used with RewriteTarget.
66+ // Path segments can be captured and matched using regular experessions.
66 // Eg:
67 // Eg:
67− // - `/foo`: Match only the explicit path `/foo`
68+ // - `^/foo$`: Match only the explicit path `/foo`
68− // - `/bar/`: Match any path prefixed with `/bar/`
69+ // - `^/bar/$`: Match any path prefixed with `/bar/`
69− // - `/baz/{path:.*}`: Match any path prefixed with `/baz/`
70− // When RewriteTarget is set, Path is treated as a regular expression.
71− // Eg: `^/baz/(.*)$` matches any path prefixed with `/baz` and captures the
72− // remaining path for use with RewriteTarget.
70+ // - `^/baz/(.*)$`: Match any path prefixed with `/baz` and capture the remaining path for use with RewriteTarget
73 Path string `yaml:"path,omitempty"`
71 Path string `yaml:"path,omitempty"`
74
72
75 // RewriteTarget allows users to rewrite the request path before it is sent to
73 // RewriteTarget allows users to rewrite the request path before it is sent to
pkg/app/redirect/director_test.go+2−33
@@ -4,7 +4,6 @@ import (
4 "net/http"
4 "net/http"
5
5
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
6 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
7− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
8 . "github.com/onsi/ginkgo/v2"
7 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/gomega"
8 . "github.com/onsi/gomega"
10)
9)
@@ -34,16 +33,9 @@ var _ = Describe("Director Suite", func() {
34 req.Header.Add(header, value)
33 req.Header.Add(header, value)
35 }
34 }
36 }
35 }
37− scope := &middleware.RequestScope{
36+ req = middleware.AddRequestScope(req, &middleware.RequestScope{
38 ReverseProxy: in.reverseProxy,
37 ReverseProxy: in.reverseProxy,
39− }
38+ })
40− if in.reverseProxy {
41− req.RemoteAddr = "127.0.0.1:4180"
42− trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
43− Expect(err).ToNot(HaveOccurred())
44− scope.TrustedProxies = trustedProxies
45− }
46− req = middleware.AddRequestScope(req, scope)
47
39
48 redirect, err := appDirector.GetRedirect(req)
40 redirect, err := appDirector.GetRedirect(req)
49 Expect(err).ToNot(HaveOccurred())
41 Expect(err).ToNot(HaveOccurred())
@@ -182,27 +174,4 @@ var _ = Describe("Director Suite", func() {
182 expectedRedirect: "https://a-service.example.com/foo/bar",
174 expectedRedirect: "https://a-service.example.com/foo/bar",
183 }),
175 }),
184 )
176 )
185−
186− It("ignores forwarded headers from an untrusted remote address", func() {
187− appDirector := NewAppDirector(AppDirectorOpts{
188− ProxyPrefix: testProxyPrefix,
189− Validator: testValidator(true),
190− })
191−
192− req, _ := http.NewRequest("GET", "https://oauth.example.com/foo?bar", nil)
193− req.RemoteAddr = "192.0.2.10:4180"
194− req.Header.Add("X-Forwarded-Proto", "https")
195− req.Header.Add("X-Forwarded-Host", "a-service.example.com")
196− req.Header.Add("X-Forwarded-Uri", fooBar)
197− trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
198− Expect(err).ToNot(HaveOccurred())
199− req = middleware.AddRequestScope(req, &middleware.RequestScope{
200− ReverseProxy: true,
201− TrustedProxies: trustedProxies,
202− })
203−
204− redirect, err := appDirector.GetRedirect(req)
205− Expect(err).ToNot(HaveOccurred())
206− Expect(redirect).To(Equal("/foo?bar"))
207− })
208})
177})
pkg/authentication/hmacauth/hmacauth_test.go+1−1
@@ -286,7 +286,7 @@ func TestSendAuthenticatedPostRequestToServer(t *testing.T) {
286 upstream := httptest.NewServer(
286 upstream := httptest.NewServer(
287 http.HandlerFunc(authenticator.Authenticate))
287 http.HandlerFunc(authenticator.Authenticate))
288
288
289− req, err := http.NewRequest(http.MethodPost, upstream.URL+"/foo/bar",
289+ req, err := http.NewRequest("POST", upstream.URL+"/foo/bar",
290 io.NopCloser(&fakeNetConn{reqBody: payload}))
290 io.NopCloser(&fakeNetConn{reqBody: payload}))
291 if err != nil {
291 if err != nil {
292 panic(err)
292 panic(err)
pkg/cookies/cookies_test.go+1−7
@@ -6,7 +6,6 @@ import (
6 "time"
6 "time"
7
7
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
10 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/ginkgo/v2"
11 . "github.com/onsi/gomega"
10 . "github.com/onsi/gomega"
12)
11)
@@ -31,13 +30,8 @@ var _ = Describe("Cookie Tests", func() {
31
30
32 if in.xForwardedHost != "" {
31 if in.xForwardedHost != "" {
33 req.Header.Add("X-Forwarded-Host", in.xForwardedHost)
32 req.Header.Add("X-Forwarded-Host", in.xForwardedHost)
34− req.RemoteAddr = "127.0.0.1:4180"
35− trustedProxies, err := ip.ParseNetSet([]string{"127.0.0.1"})
36− Expect(err).ToNot(HaveOccurred())
37−
38 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
33 req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
39− ReverseProxy: true,
34+ ReverseProxy: true,
40− TrustedProxies: trustedProxies,
41 })
35 })
42 }
36 }
43
37
pkg/ip/parse_ip_net.go−16
@@ -1,7 +1,6 @@
1package ip
1package ip
2
2
3import (
3import (
4− "fmt"
5 "net"
4 "net"
6 "strings"
5 "strings"
7)
6)
@@ -38,18 +37,3 @@ func ParseIPNet(s string) *net.IPNet {
38 return ipNet
37 return ipNet
39 }
38 }
40}
39}
41−
42−func ParseNetSet(ipStrs []string) (*NetSet, error) {
43− netSet := NewNetSet()
44−
45− for _, ipStr := range ipStrs {
46− ipNet := ParseIPNet(ipStr)
47− if ipNet == nil {
48− return nil, fmt.Errorf("could not parse IP network (%s)", ipStr)
49− }
50−
51− netSet.AddIPNet(*ipNet)
52− }
53−
54− return netSet, nil
55−}
pkg/ip/parse_ip_net_test.godeleted−118
@@ -1,118 +0,0 @@
1−package ip
2−
3−import (
4− "net"
5− "testing"
6−
7− "github.com/stretchr/testify/assert"
8−)
9−
10−func TestParseIPNet(t *testing.T) {
11− tests := []struct {
12− name string
13− input string
14− expectedIP net.IP
15− expectedMask net.IPMask
16− }{
17− {
18− name: "ipv4 address",
19− input: "127.0.0.1",
20− expectedIP: net.ParseIP("127.0.0.1"),
21− expectedMask: net.CIDRMask(32, 32),
22− },
23− {
24− name: "ipv6 address",
25− input: "::1",
26− expectedIP: net.ParseIP("::1"),
27− expectedMask: net.CIDRMask(128, 128),
28− },
29− {
30− name: "ipv4 cidr",
31− input: "10.0.0.0/24",
32− expectedIP: net.ParseIP("10.0.0.0"),
33− expectedMask: net.CIDRMask(24, 32),
34− },
35− {
36− name: "ipv6 cidr",
37− input: "2001:db8::/64",
38− expectedIP: net.ParseIP("2001:db8::"),
39− expectedMask: net.CIDRMask(64, 128),
40− },
41− }
42−
43− for _, test := range tests {
44− t.Run(test.name, func(t *testing.T) {
45− ipNet := ParseIPNet(test.input)
46−
47− assert.NotNil(t, ipNet)
48− if ipNet == nil {
49− return
50− }
51−
52− assert.True(t, test.expectedIP.Equal(ipNet.IP))
53− assert.Equal(t, test.expectedMask, ipNet.Mask)
54− })
55− }
56−}
57−
58−func TestParseIPNetRejectsInvalidNetworks(t *testing.T) {
59− tests := []struct {
60− name string
61− input string
62− }{
63− {
64− name: "invalid ip",
65− input: "not-an-ip",
66− },
67− {
68− name: "ipv4 cidr with host bits set",
69− input: "10.0.0.1/24",
70− },
71− {
72− name: "ipv6 cidr with host bits set",
73− input: "2001:db8::1/64",
74− },
75− {
76− name: "invalid cidr mask",
77− input: "10.0.0.0/33",
78− },
79− }
80−
81− for _, test := range tests {
82− t.Run(test.name, func(t *testing.T) {
83− assert.Nil(t, ParseIPNet(test.input))
84− })
85− }
86−}
87−
88−func TestParseNetSet(t *testing.T) {
89− netSet, err := ParseNetSet([]string{
90− "127.0.0.1",
91− "10.0.0.0/24",
92− "::1",
93− "2001:db8::/64",
94− })
95−
96− assert.NoError(t, err)
97− assert.NotNil(t, netSet)
98− if netSet == nil {
99− return
100− }
101−
102− assert.True(t, netSet.Has(net.ParseIP("127.0.0.1")))
103− assert.True(t, netSet.Has(net.ParseIP("10.0.0.55")))
104− assert.True(t, netSet.Has(net.ParseIP("::1")))
105− assert.True(t, netSet.Has(net.ParseIP("2001:db8::abcd")))
106−
107− assert.False(t, netSet.Has(net.ParseIP("127.0.0.2")))
108− assert.False(t, netSet.Has(net.ParseIP("10.0.1.1")))
109− assert.False(t, netSet.Has(net.ParseIP("::2")))
110− assert.False(t, netSet.Has(net.ParseIP("2001:db9::1")))
111−}
112−
113−func TestParseNetSetReturnsErrorForInvalidNetwork(t *testing.T) {
114− netSet, err := ParseNetSet([]string{"127.0.0.1", "10.0.0.1/24"})
115−
116− assert.Nil(t, netSet)
117− assert.EqualError(t, err, "could not parse IP network (10.0.0.1/24)")
118−}
pkg/middleware/healthcheck.go+4−7
@@ -43,13 +43,10 @@ func healthCheck(paths, userAgents []string, next http.Handler) http.Handler {
43
43
44func isHealthCheckRequest(paths, userAgents map[string]struct{}, req *http.Request) bool {
44func isHealthCheckRequest(paths, userAgents map[string]struct{}, req *http.Request) bool {
45 if _, ok := paths[req.URL.EscapedPath()]; ok {
45 if _, ok := paths[req.URL.EscapedPath()]; ok {
46− if len(userAgents) == 0 {
47− return true
46+ return true
48− }
47+ }
49−
50− if _, ok := userAgents[req.Header.Get("User-Agent")]; ok {
48+ if _, ok := userAgents[req.Header.Get("User-Agent")]; ok {
51− return true
49+ return true
52− }
53 }
50 }
54 return false
51 return false
55}
52}
pkg/middleware/healthcheck_test.go+9−31
@@ -45,16 +45,6 @@ var _ = Describe("HealthCheck suite", func() {
45 healthCheckPaths: []string{"/ping"},
45 healthCheckPaths: []string{"/ping"},
46 healthCheckUserAgents: []string{"hc/1.0"},
46 healthCheckUserAgents: []string{"hc/1.0"},
47 requestString: "http://example.com/ping",
47 requestString: "http://example.com/ping",
48− headers: map[string]string{
49− "User-Agent": "hc/1.0",
50− },
51− expectedStatus: 200,
52− expectedBody: "OK",
53− }),
54− Entry("when requesting the healthcheck path with no health check user agents configured", &requestTableInput{
55− healthCheckPaths: []string{"/ping"},
56− healthCheckUserAgents: []string{},
57− requestString: "http://example.com/ping",
58 headers: map[string]string{},
48 headers: map[string]string{},
59 expectedStatus: 200,
49 expectedStatus: 200,
60 expectedBody: "OK",
50 expectedBody: "OK",
@@ -95,25 +85,15 @@ var _ = Describe("HealthCheck suite", func() {
95 expectedStatus: 404,
85 expectedStatus: 404,
96 expectedBody: "404 page not found\n",
86 expectedBody: "404 page not found\n",
97 }),
87 }),
98− Entry("with a request from the health check user agent on a non-healthcheck path", &requestTableInput{
88+ Entry("with a request from the health check user agent", &requestTableInput{
99 healthCheckPaths: []string{"/ping"},
89 healthCheckPaths: []string{"/ping"},
100 healthCheckUserAgents: []string{"hc/1.0"},
90 healthCheckUserAgents: []string{"hc/1.0"},
101 requestString: "http://example.com/abc",
91 requestString: "http://example.com/abc",
102 headers: map[string]string{
92 headers: map[string]string{
103 "User-Agent": "hc/1.0",
93 "User-Agent": "hc/1.0",
104 },
94 },
105− expectedStatus: 404,
95+ expectedStatus: 200,
106− expectedBody: "404 page not found\n",
107− }),
108− Entry("when an auth_request endpoint receives the configured health check user agent", &requestTableInput{
109− healthCheckPaths: []string{"/ping"},
110− healthCheckUserAgents: []string{"GoogleHC/1.0"},
111− requestString: "http://example.com/oauth2/auth",
112− headers: map[string]string{
113− "User-Agent": "GoogleHC/1.0",
114− },
115− expectedStatus: 404,
116− expectedBody: "404 page not found\n",
96+ expectedBody: "OK",
117 }),
97 }),
118 Entry("when a blank string is configured as a health check agent and a request has no user agent", &requestTableInput{
98 Entry("when a blank string is configured as a health check agent and a request has no user agent", &requestTableInput{
119 healthCheckPaths: []string{"/ping"},
99 healthCheckPaths: []string{"/ping"},
@@ -127,11 +107,9 @@ var _ = Describe("HealthCheck suite", func() {
127 healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
107 healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
128 healthCheckUserAgents: []string{"hc/1.0"},
108 healthCheckUserAgents: []string{"hc/1.0"},
129 requestString: "http://example.com/readiness_check",
109 requestString: "http://example.com/readiness_check",
130− headers: map[string]string{
110+ headers: map[string]string{},
131− "User-Agent": "hc/1.0",
132− },
133− expectedStatus: 200,
111+ expectedStatus: 200,
134− expectedBody: "OK",
112+ expectedBody: "OK",
135 }),
113 }),
136 Entry("with multiple paths, request none of the healthcheck paths", &requestTableInput{
114 Entry("with multiple paths, request none of the healthcheck paths", &requestTableInput{
137 healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
115 healthCheckPaths: []string{"/ping", "/liveness_check", "/readiness_check"},
@@ -143,15 +121,15 @@ var _ = Describe("HealthCheck suite", func() {
143 expectedStatus: 404,
121 expectedStatus: 404,
144 expectedBody: "404 page not found\n",
122 expectedBody: "404 page not found\n",
145 }),
123 }),
146− Entry("with multiple user agents, request from a health check user agent on a non-healthcheck path", &requestTableInput{
124+ Entry("with multiple user agents, request from a health check user agent", &requestTableInput{
147 healthCheckPaths: []string{"/ping"},
125 healthCheckPaths: []string{"/ping"},
148 healthCheckUserAgents: []string{"hc/1.0", "GoogleHC/1.0"},
126 healthCheckUserAgents: []string{"hc/1.0", "GoogleHC/1.0"},
149 requestString: "http://example.com/abc",
127 requestString: "http://example.com/abc",
150 headers: map[string]string{
128 headers: map[string]string{
151 "User-Agent": "GoogleHC/1.0",
129 "User-Agent": "GoogleHC/1.0",
152 },
130 },
153− expectedStatus: 404,
131+ expectedStatus: 200,
154− expectedBody: "404 page not found\n",
132+ expectedBody: "OK",
155 }),
133 }),
156 Entry("with multiple user agents, request from none of the health check user agents", &requestTableInput{
134 Entry("with multiple user agents, request from none of the health check user agents", &requestTableInput{
157 healthCheckPaths: []string{"/ping"},
135 healthCheckPaths: []string{"/ping"},
pkg/middleware/redirect_to_https_test.go−15
@@ -6,7 +6,6 @@ import (
6 "net/http/httptest"
6 "net/http/httptest"
7
7
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
10 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/ginkgo/v2"
11 . "github.com/onsi/gomega"
10 . "github.com/onsi/gomega"
12)
11)
@@ -40,10 +39,6 @@ var _ = Describe("RedirectToHTTPS suite", func() {
40 scope := &middlewareapi.RequestScope{
39 scope := &middlewareapi.RequestScope{
41 ReverseProxy: in.reverseProxy,
40 ReverseProxy: in.reverseProxy,
42 }
41 }
43− if in.reverseProxy {
44− req.RemoteAddr = "127.0.0.1:4180"
45− scope.TrustedProxies = newRedirectTrustedProxySet("127.0.0.1")
46− }
47 req = middlewareapi.AddRequestScope(req, scope)
42 req = middlewareapi.AddRequestScope(req, scope)
48
43
49 rw := httptest.NewRecorder()
44 rw := httptest.NewRecorder()
@@ -212,13 +207,3 @@ var _ = Describe("RedirectToHTTPS suite", func() {
212 }),
207 }),
213 )
208 )
214})
209})
215−
216−func newRedirectTrustedProxySet(cidrs ...string) *ip.NetSet {
217− set := ip.NewNetSet()
218− for _, cidr := range cidrs {
219− ipNet := ip.ParseIPNet(cidr)
220− Expect(ipNet).ToNot(BeNil())
221− set.AddIPNet(*ipNet)
222− }
223− return set
224−}
pkg/middleware/scope.go+3−5
@@ -6,16 +6,14 @@ import (
6 "github.com/google/uuid"
6 "github.com/google/uuid"
7 "github.com/justinas/alice"
7 "github.com/justinas/alice"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
10)
9)
11
10
12−func NewScope(reverseProxy bool, idHeader string, trustedProxies *ip.NetSet) alice.Constructor {
11+func NewScope(reverseProxy bool, idHeader string) alice.Constructor {
13 return func(next http.Handler) http.Handler {
12 return func(next http.Handler) http.Handler {
14 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
13 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
15 scope := &middlewareapi.RequestScope{
14 scope := &middlewareapi.RequestScope{
16− ReverseProxy: reverseProxy,
15+ ReverseProxy: reverseProxy,
17− TrustedProxies: trustedProxies,
18− RequestID: genRequestID(req, idHeader),
16+ RequestID: genRequestID(req, idHeader),
19 }
17 }
20 req = middlewareapi.AddRequestScope(req, scope)
18 req = middlewareapi.AddRequestScope(req, scope)
21 next.ServeHTTP(rw, req)
19 next.ServeHTTP(rw, req)
pkg/middleware/scope_test.go+4−18
@@ -6,7 +6,6 @@ import (
6
6
7 "github.com/google/uuid"
7 "github.com/google/uuid"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
10 . "github.com/onsi/ginkgo/v2"
9 . "github.com/onsi/ginkgo/v2"
11 . "github.com/onsi/gomega"
10 . "github.com/onsi/gomega"
12)
11)
@@ -33,7 +32,7 @@ var _ = Describe("Scope Suite", func() {
33
32
34 Context("ReverseProxy is false", func() {
33 Context("ReverseProxy is false", func() {
35 BeforeEach(func() {
34 BeforeEach(func() {
36− handler := NewScope(false, testRequestHeader, nil)(
35+ handler := NewScope(false, testRequestHeader)(
37 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38 nextRequest = r
37 nextRequest = r
39 w.WriteHeader(200)
38 w.WriteHeader(200)
@@ -61,15 +60,8 @@ var _ = Describe("Scope Suite", func() {
61 })
60 })
62
61
63 Context("ReverseProxy is true", func() {
62 Context("ReverseProxy is true", func() {
64− var trustedProxies *ip.NetSet
65−
66 BeforeEach(func() {
63 BeforeEach(func() {
67− var err error
68−
69− trustedProxies, err = ip.ParseNetSet([]string{"127.0.0.1"})
70− Expect(err).ToNot(HaveOccurred())
71−
72− handler := NewScope(true, testRequestHeader, trustedProxies)(
64+ handler := NewScope(true, testRequestHeader)(
73 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74 nextRequest = r
66 nextRequest = r
75 w.WriteHeader(200)
67 w.WriteHeader(200)
@@ -82,18 +74,12 @@ var _ = Describe("Scope Suite", func() {
82 Expect(scope).ToNot(BeNil())
74 Expect(scope).ToNot(BeNil())
83 Expect(scope.ReverseProxy).To(BeTrue())
75 Expect(scope.ReverseProxy).To(BeTrue())
84 })
76 })
85−
86− It("stores the trusted proxies on the scope", func() {
87− scope := middlewareapi.GetRequestScope(nextRequest)
88− Expect(scope).ToNot(BeNil())
89− Expect(scope.TrustedProxies).To(BeIdenticalTo(trustedProxies))
90− })
91 })
77 })
92
78
93 Context("Request ID header is present", func() {
79 Context("Request ID header is present", func() {
94 BeforeEach(func() {
80 BeforeEach(func() {
95 request.Header.Add(testRequestHeader, testRequestID)
81 request.Header.Add(testRequestHeader, testRequestID)
96− handler := NewScope(false, testRequestHeader, nil)(
82+ handler := NewScope(false, testRequestHeader)(
97 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
83 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98 nextRequest = r
84 nextRequest = r
99 w.WriteHeader(200)
85 w.WriteHeader(200)
@@ -111,7 +97,7 @@ var _ = Describe("Scope Suite", func() {
111 BeforeEach(func() {
97 BeforeEach(func() {
112 uuid.SetRand(mockRand{})
98 uuid.SetRand(mockRand{})
113
99
114− handler := NewScope(true, testRequestHeader, nil)(
100+ handler := NewScope(true, testRequestHeader)(
115 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
101 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
116 nextRequest = r
102 nextRequest = r
117 w.WriteHeader(200)
103 w.WriteHeader(200)
pkg/middleware/stored_session.go+2−45
@@ -5,7 +5,6 @@ import (
5 "errors"
5 "errors"
6 "fmt"
6 "fmt"
7 "net/http"
7 "net/http"
8− "strings"
9 "time"
8 "time"
10
9
11 "github.com/justinas/alice"
10 "github.com/justinas/alice"
@@ -32,33 +31,6 @@ const (
32 sessionRefreshRetryPeriod = 10 * time.Millisecond
31 sessionRefreshRetryPeriod = 10 * time.Millisecond
33)
32)
34
33
35−// isFatalRefreshError checks if a refresh error indicates a revoked or
36−// non-existent session that should be immediately invalidated.
37−// Fatal errors indicate the session is no longer valid at the provider level.
38−// Non-fatal errors (network issues, timeouts) should not invalidate the session.
39−//
40−// Only checks standard OAuth2 error codes (RFC 6749 Section 5.2).
41−// Does NOT check error_description strings as they are optional and provider-specific.
42−func isFatalRefreshError(err error) bool {
43− if err == nil {
44− return false
45− }
46−
47− // Only check standard OAuth2 error codes (RFC 6749 Section 5.2)
48− // Do NOT check error_description strings as they are optional and provider-specific
49− fatalErrors := []string{
50− "invalid_grant", // refresh token revoked, expired, or session terminated
51− "invalid_client", // client credentials no longer valid
52− }
53−
54− for _, fe := range fatalErrors {
55− if strings.Contains(err.Error(), fe) {
56− return true
57− }
58− }
59− return false
60−}
61−
62// StoredSessionLoaderOptions contains all of the requirements to construct
34// StoredSessionLoaderOptions contains all of the requirements to construct
63// a stored session loader.
35// a stored session loader.
64// All options must be provided.
36// All options must be provided.
@@ -216,24 +188,9 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req
216 // We are holding the lock and the session needs a refresh
188 // We are holding the lock and the session needs a refresh
217 logger.Printf("Refreshing session - User: %s; SessionAge: %s", session.User, session.Age())
189 logger.Printf("Refreshing session - User: %s; SessionAge: %s", session.User, session.Age())
218 if err := s.refreshSession(rw, req, session); err != nil {
190 if err := s.refreshSession(rw, req, session); err != nil {
191+ // If a preemptive refresh fails, we still keep the session
192+ // if validateSession succeeds.
219 logger.Errorf("Unable to refresh session: %v", err)
193 logger.Errorf("Unable to refresh session: %v", err)
220−
221− // Check if this is a fatal error that indicates the session is revoked
222− // or no longer valid at the provider level
223− if isFatalRefreshError(err) {
224− logger.Printf("Fatal refresh error detected (session revoked or invalid), clearing session for user: %s", session.User)
225−
226− // Clear the session from storage (Redis) and remove the cookie
227− if err := s.store.Clear(rw, req); err != nil {
228− logger.Errorf("failed clearing session: %v", err)
229− }
230−
231− // Return error immediately to force re-authentication
232− return fmt.Errorf("session invalidated due to fatal refresh error: %w", err)
233− }
234−
235− // For non-fatal errors (network issues, timeouts), keep the session
236− // and let validateSession determine if it's still usable
237 }
194 }
238
195
239 // Validate all sessions after any Redeem/Refresh operation (fail or success)
196 // Validate all sessions after any Redeem/Refresh operation (fail or success)
pkg/middleware/stored_session_test.go−51
@@ -7,7 +7,6 @@ import (
7 "net/http"
7 "net/http"
8 "net/http/httptest"
8 "net/http/httptest"
9 "sync"
9 "sync"
10− "testing"
11 "time"
10 "time"
12
11
13 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
12 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
@@ -802,53 +801,3 @@ func (f *fakeSessionStore) Clear(rw http.ResponseWriter, req *http.Request) erro
802func (f *fakeSessionStore) VerifyConnection(_ context.Context) error {
801func (f *fakeSessionStore) VerifyConnection(_ context.Context) error {
803 return nil
802 return nil
804}
803}
805−
806−// TestIsFatalRefreshError tests the isFatalRefreshError function to ensure
807−// it correctly identifies fatal OAuth2 errors that should invalidate a session.
808−func TestIsFatalRefreshError(t *testing.T) {
809− tests := []struct {
810− name string
811− err error
812− expected bool
813− }{
814− {
815− name: "nil error",
816− err: nil,
817− expected: false,
818− },
819− {
820− name: "invalid_grant error",
821− err: fmt.Errorf("failed to get token: oauth2: \"invalid_grant\" \"Session not active\""),
822− expected: true,
823− },
824− {
825− name: "invalid_client error",
826− err: fmt.Errorf("invalid_client: client not found"),
827− expected: true,
828− },
829− {
830− name: "network timeout - not fatal",
831− err: fmt.Errorf("Post \"https://keycloak/token\": dial tcp: connect: connection refused"),
832− expected: false,
833− },
834− {
835− name: "server error - not fatal",
836− err: fmt.Errorf("unexpected status code 500"),
837− expected: false,
838− },
839− {
840− name: "generic refresh error - not fatal",
841− err: fmt.Errorf("error refreshing tokens: context deadline exceeded"),
842− expected: false,
843− },
844− }
845−
846− for _, tt := range tests {
847− t.Run(tt.name, func(t *testing.T) {
848− result := isFatalRefreshError(tt.err)
849− if result != tt.expected {
850− t.Errorf("isFatalRefreshError(%v) = %v, want %v", tt.err, result, tt.expected)
851− }
852− })
853− }
854−}
pkg/requests/builder_test.go+15−15
@@ -31,7 +31,7 @@ var _ = Describe("Builder suite", func() {
31
31
32 Context("with a basic request", func() {
32 Context("with a basic request", func() {
33 assertSuccessfulRequest(getBuilder, testHTTPRequest{
33 assertSuccessfulRequest(getBuilder, testHTTPRequest{
34− Method: http.MethodGet,
34+ Method: "GET",
35 Header: baseHeaders,
35 Header: baseHeaders,
36 Body: []byte{},
36 Body: []byte{},
37 RequestURI: "/json/path",
37 RequestURI: "/json/path",
@@ -52,7 +52,7 @@ var _ = Describe("Builder suite", func() {
52 })
52 })
53
53
54 assertSuccessfulRequest(getBuilder, testHTTPRequest{
54 assertSuccessfulRequest(getBuilder, testHTTPRequest{
55− Method: http.MethodGet,
55+ Method: "GET",
56 Header: baseHeaders,
56 Header: baseHeaders,
57 Body: []byte{},
57 Body: []byte{},
58 RequestURI: "/json/path",
58 RequestURI: "/json/path",
@@ -78,7 +78,7 @@ var _ = Describe("Builder suite", func() {
78 })
78 })
79
79
80 assertSuccessfulRequest(getBuilder, testHTTPRequest{
80 assertSuccessfulRequest(getBuilder, testHTTPRequest{
81− Method: http.MethodGet,
81+ Method: "GET",
82 Header: header,
82 Header: header,
83 Body: []byte(body),
83 Body: []byte(body),
84 RequestURI: "/json/path",
84 RequestURI: "/json/path",
@@ -93,11 +93,11 @@ var _ = Describe("Builder suite", func() {
93
93
94 BeforeEach(func() {
94 BeforeEach(func() {
95 buf := bytes.NewBuffer([]byte(body))
95 buf := bytes.NewBuffer([]byte(body))
96− b = b.WithMethod(http.MethodPost).WithBody(buf)
96+ b = b.WithMethod("POST").WithBody(buf)
97 })
97 })
98
98
99 assertSuccessfulRequest(getBuilder, testHTTPRequest{
99 assertSuccessfulRequest(getBuilder, testHTTPRequest{
100− Method: http.MethodPost,
100+ Method: "POST",
101 Header: header,
101 Header: header,
102 Body: []byte(body),
102 Body: []byte(body),
103 RequestURI: "/json/path",
103 RequestURI: "/json/path",
@@ -109,11 +109,11 @@ var _ = Describe("Builder suite", func() {
109 header.Set("Content-Length", "0")
109 header.Set("Content-Length", "0")
110
110
111 BeforeEach(func() {
111 BeforeEach(func() {
112− b = b.WithMethod(http.MethodPost)
112+ b = b.WithMethod("POST")
113 })
113 })
114
114
115 assertSuccessfulRequest(getBuilder, testHTTPRequest{
115 assertSuccessfulRequest(getBuilder, testHTTPRequest{
116− Method: http.MethodPost,
116+ Method: "POST",
117 Header: header,
117 Header: header,
118 Body: []byte{},
118 Body: []byte{},
119 RequestURI: "/json/path",
119 RequestURI: "/json/path",
@@ -122,11 +122,11 @@ var _ = Describe("Builder suite", func() {
122
122
123 Context("OPTIONS", func() {
123 Context("OPTIONS", func() {
124 BeforeEach(func() {
124 BeforeEach(func() {
125− b = b.WithMethod(http.MethodOptions)
125+ b = b.WithMethod("OPTIONS")
126 })
126 })
127
127
128 assertSuccessfulRequest(getBuilder, testHTTPRequest{
128 assertSuccessfulRequest(getBuilder, testHTTPRequest{
129− Method: http.MethodOptions,
129+ Method: "OPTIONS",
130 Header: baseHeaders,
130 Header: baseHeaders,
131 Body: []byte{},
131 Body: []byte{},
132 RequestURI: "/json/path",
132 RequestURI: "/json/path",
@@ -152,7 +152,7 @@ var _ = Describe("Builder suite", func() {
152 })
152 })
153
153
154 assertSuccessfulRequest(getBuilder, testHTTPRequest{
154 assertSuccessfulRequest(getBuilder, testHTTPRequest{
155− Method: http.MethodGet,
155+ Method: "GET",
156 Header: header,
156 Header: header,
157 Body: []byte{},
157 Body: []byte{},
158 RequestURI: "/json/path",
158 RequestURI: "/json/path",
@@ -170,7 +170,7 @@ var _ = Describe("Builder suite", func() {
170 })
170 })
171
171
172 assertSuccessfulRequest(getBuilder, testHTTPRequest{
172 assertSuccessfulRequest(getBuilder, testHTTPRequest{
173− Method: http.MethodGet,
173+ Method: "GET",
174 Header: replacementHeaders,
174 Header: replacementHeaders,
175 Body: []byte{},
175 Body: []byte{},
176 RequestURI: "/json/path",
176 RequestURI: "/json/path",
@@ -190,7 +190,7 @@ var _ = Describe("Builder suite", func() {
190 })
190 })
191
191
192 assertSuccessfulRequest(getBuilder, testHTTPRequest{
192 assertSuccessfulRequest(getBuilder, testHTTPRequest{
193− Method: http.MethodGet,
193+ Method: "GET",
194 Header: replacementHeaders,
194 Header: replacementHeaders,
195 Body: []byte{},
195 Body: []byte{},
196 RequestURI: "/json/path",
196 RequestURI: "/json/path",
@@ -205,7 +205,7 @@ var _ = Describe("Builder suite", func() {
205 })
205 })
206
206
207 assertSuccessfulRequest(getBuilder, testHTTPRequest{
207 assertSuccessfulRequest(getBuilder, testHTTPRequest{
208− Method: http.MethodGet,
208+ Method: "GET",
209 Header: header,
209 Header: header,
210 Body: []byte{},
210 Body: []byte{},
211 RequestURI: "/json/path",
211 RequestURI: "/json/path",
@@ -219,12 +219,12 @@ var _ = Describe("Builder suite", func() {
219 result := b.Do()
219 result := b.Do()
220 Expect(result.Error()).ToNot(HaveOccurred())
220 Expect(result.Error()).ToNot(HaveOccurred())
221
221
222− b.WithMethod(http.MethodPost)
222+ b.WithMethod("POST")
223 })
223 })
224
224
225 Context("should not redo the request", func() {
225 Context("should not redo the request", func() {
226 assertSuccessfulRequest(getBuilder, testHTTPRequest{
226 assertSuccessfulRequest(getBuilder, testHTTPRequest{
227− Method: http.MethodGet,
227+ Method: "GET",
228 Header: baseHeaders,
228 Header: baseHeaders,
229 Body: []byte{},
229 Body: []byte{},
230 RequestURI: "/json/path",
230 RequestURI: "/json/path",
pkg/requests/util/util.go+15−28
@@ -15,30 +15,30 @@ const (
15)
15)
16
16
17// GetRequestProto returns the request scheme or X-Forwarded-Proto if present
17// GetRequestProto returns the request scheme or X-Forwarded-Proto if present
18−// and the request came from a trusted reverse proxy.
18+// and the request is proxied.
19func GetRequestProto(req *http.Request) string {
19func GetRequestProto(req *http.Request) string {
20 proto := req.Header.Get(XForwardedProto)
20 proto := req.Header.Get(XForwardedProto)
21− if !CanTrustForwardedHeaders(req) || proto == "" {
21+ if !IsProxied(req) || proto == "" {
22 proto = req.URL.Scheme
22 proto = req.URL.Scheme
23 }
23 }
24 return proto
24 return proto
25}
25}
26
26
27// GetRequestHost returns the request host header or X-Forwarded-Host if
27// GetRequestHost returns the request host header or X-Forwarded-Host if
28−// present and the request came from a trusted reverse proxy.
28+// present and the request is proxied.
29func GetRequestHost(req *http.Request) string {
29func GetRequestHost(req *http.Request) string {
30 host := req.Header.Get(XForwardedHost)
30 host := req.Header.Get(XForwardedHost)
31− if !CanTrustForwardedHeaders(req) || host == "" {
31+ if !IsProxied(req) || host == "" {
32 host = req.Host
32 host = req.Host
33 }
33 }
34 return host
34 return host
35}
35}
36
36
37// GetRequestURI return the request URI or X-Forwarded-Uri if present and the
37// GetRequestURI return the request URI or X-Forwarded-Uri if present and the
38−// request came from a trusted reverse proxy.
38+// request is proxied.
39func GetRequestURI(req *http.Request) string {
39func GetRequestURI(req *http.Request) string {
40 uri := req.Header.Get(XForwardedURI)
40 uri := req.Header.Get(XForwardedURI)
41− if !CanTrustForwardedHeaders(req) || uri == "" {
41+ if !IsProxied(req) || uri == "" {
42 // Use RequestURI to preserve ?query
42 // Use RequestURI to preserve ?query
43 uri = req.URL.RequestURI()
43 uri = req.URL.RequestURI()
44 }
44 }
@@ -46,29 +46,17 @@ func GetRequestURI(req *http.Request) string {
46}
46}
47
47
48// GetRequestPath returns the request URI or X-Forwarded-Uri if present and the
48// GetRequestPath returns the request URI or X-Forwarded-Uri if present and the
49−// request came from a trusted reverse proxy but always strips the query
49+// request is proxied but always strips the query parameters and only returns
50−// parameters and fragment suffixes and only returns the pure path.
50+// the pure path
51func GetRequestPath(req *http.Request) string {
51func GetRequestPath(req *http.Request) string {
52− uri := stripRequestFragment(GetRequestURI(req))
52+ uri := GetRequestURI(req)
53
53
54 // Parse URI and return only the path component
54 // Parse URI and return only the path component
55 if parsedURL, err := url.Parse(uri); err == nil {
55 if parsedURL, err := url.Parse(uri); err == nil {
56− return stripRequestFragment(parsedURL.Path)
56+ return parsedURL.Path
57 }
57 }
58
58
59 // Fallback: strip query parameters manually
59 // Fallback: strip query parameters manually
60− return stripRequestQuery(uri)
61−}
62−
63−func stripRequestFragment(uri string) string {
64− if idx := strings.Index(uri, "#"); idx != -1 {
65− return uri[:idx]
66− }
67−
68− return uri
69−}
70−
71−func stripRequestQuery(uri string) string {
72 if idx := strings.Index(uri, "?"); idx != -1 {
60 if idx := strings.Index(uri, "?"); idx != -1 {
73 return uri[:idx]
61 return uri[:idx]
74 }
62 }
@@ -76,18 +64,17 @@ func stripRequestQuery(uri string) string {
76 return uri
64 return uri
77}
65}
78
66
79−// CanTrustForwardedHeaders determines if forwarded headers should be processed
67+// IsProxied determines if a request was from a proxy based on the RequestScope
80−// based on the RequestScope and the direct caller's address.
68+// ReverseProxy tracker.
81−func CanTrustForwardedHeaders(req *http.Request) bool {
69+func IsProxied(req *http.Request) bool {
82 scope := middlewareapi.GetRequestScope(req)
70 scope := middlewareapi.GetRequestScope(req)
83 if scope == nil {
71 if scope == nil {
84 return false
72 return false
85 }
73 }
86−
74+ return scope.ReverseProxy
87− return scope.CanTrustForwardedHeaders(req)
88}
75}
89
76
90func IsForwardedRequest(req *http.Request) bool {
77func IsForwardedRequest(req *http.Request) bool {
91− return CanTrustForwardedHeaders(req) &&
78+ return IsProxied(req) &&
92 req.Host != GetRequestHost(req)
79 req.Host != GetRequestHost(req)
93}
80}
pkg/requests/util/util_test.go+12−74
@@ -6,7 +6,6 @@ import (
6 "net/http/httptest"
6 "net/http/httptest"
7
7
8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
8 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
11 . "github.com/onsi/ginkgo/v2"
10 . "github.com/onsi/ginkgo/v2"
12 . "github.com/onsi/gomega"
11 . "github.com/onsi/gomega"
@@ -20,13 +19,8 @@ var _ = Describe("Util Suite", func() {
20 uriNoQueryParams = "/test/endpoint"
19 uriNoQueryParams = "/test/endpoint"
21 )
20 )
22 var req *http.Request
21 var req *http.Request
23− var trustedProxies *ip.NetSet
24
22
25 BeforeEach(func() {
23 BeforeEach(func() {
26− var err error
27− trustedProxies, err = ip.ParseNetSet([]string{"127.0.0.1"})
28− Expect(err).ToNot(HaveOccurred())
29−
30 req = httptest.NewRequest(
24 req = httptest.NewRequest(
31 http.MethodGet,
25 http.MethodGet,
32 fmt.Sprintf("%s://%s%s", proto, host, uriWithQueryParams),
26 fmt.Sprintf("%s://%s%s", proto, host, uriWithQueryParams),
@@ -35,7 +29,7 @@ var _ = Describe("Util Suite", func() {
35 })
29 })
36
30
37 Context("GetRequestHost", func() {
31 Context("GetRequestHost", func() {
38− Context("trusted forwarded headers are disabled", func() {
32+ Context("IsProxied is false", func() {
39 BeforeEach(func() {
33 BeforeEach(func() {
40 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
34 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
41 })
35 })
@@ -50,12 +44,10 @@ var _ = Describe("Util Suite", func() {
50 })
44 })
51 })
45 })
52
46
53− Context("trusted forwarded headers are enabled", func() {
47+ Context("IsProxied is true", func() {
54 BeforeEach(func() {
48 BeforeEach(func() {
55− req.RemoteAddr = "127.0.0.1:4180"
56 req = middleware.AddRequestScope(req, &middleware.RequestScope{
49 req = middleware.AddRequestScope(req, &middleware.RequestScope{
57− ReverseProxy: true,
50+ ReverseProxy: true,
58− TrustedProxies: trustedProxies,
59 })
51 })
60 })
52 })
61
53
@@ -71,7 +63,7 @@ var _ = Describe("Util Suite", func() {
71 })
63 })
72
64
73 Context("GetRequestProto", func() {
65 Context("GetRequestProto", func() {
74− Context("trusted forwarded headers are disabled", func() {
66+ Context("IsProxied is false", func() {
75 BeforeEach(func() {
67 BeforeEach(func() {
76 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
68 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
77 })
69 })
@@ -86,12 +78,10 @@ var _ = Describe("Util Suite", func() {
86 })
78 })
87 })
79 })
88
80
89− Context("trusted forwarded headers are enabled", func() {
81+ Context("IsProxied is true", func() {
90 BeforeEach(func() {
82 BeforeEach(func() {
91− req.RemoteAddr = "127.0.0.1:4180"
92 req = middleware.AddRequestScope(req, &middleware.RequestScope{
83 req = middleware.AddRequestScope(req, &middleware.RequestScope{
93− ReverseProxy: true,
84+ ReverseProxy: true,
94− TrustedProxies: trustedProxies,
95 })
85 })
96 })
86 })
97
87
@@ -107,7 +97,7 @@ var _ = Describe("Util Suite", func() {
107 })
97 })
108
98
109 Context("GetRequestURI", func() {
99 Context("GetRequestURI", func() {
110− Context("trusted forwarded headers are disabled", func() {
100+ Context("IsProxied is false", func() {
111 BeforeEach(func() {
101 BeforeEach(func() {
112 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
102 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
113 })
103 })
@@ -122,12 +112,10 @@ var _ = Describe("Util Suite", func() {
122 })
112 })
123 })
113 })
124
114
125− Context("trusted forwarded headers are enabled", func() {
115+ Context("IsProxied is true", func() {
126 BeforeEach(func() {
116 BeforeEach(func() {
127− req.RemoteAddr = "127.0.0.1:4180"
128 req = middleware.AddRequestScope(req, &middleware.RequestScope{
117 req = middleware.AddRequestScope(req, &middleware.RequestScope{
129− ReverseProxy: true,
118+ ReverseProxy: true,
130− TrustedProxies: trustedProxies,
131 })
119 })
132 })
120 })
133
121
@@ -143,7 +131,7 @@ var _ = Describe("Util Suite", func() {
143 })
131 })
144
132
145 Context("GetRequestPath", func() {
133 Context("GetRequestPath", func() {
146− Context("trusted forwarded headers are disabled", func() {
134+ Context("IsProxied is false", func() {
147 BeforeEach(func() {
135 BeforeEach(func() {
148 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
136 req = middleware.AddRequestScope(req, &middleware.RequestScope{})
149 })
137 })
@@ -152,35 +140,16 @@ var _ = Describe("Util Suite", func() {
152 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
140 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
153 })
141 })
154
142
155− It("drops fragment content from a parsed request path", func() {
156− // Simulate net/http ParseRequestURI preserving '#' in URL.Path.
157− req.URL.Path = "/foo/secret#/bar"
158− req.URL.RawPath = "/foo/secret%23/bar"
159− Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
160− })
161−
162− It("drops fragment-like suffixes from encoded number signs", func() {
163− req = httptest.NewRequest(
164− http.MethodGet,
165− fmt.Sprintf("%s://%s/foo/secret%%23/bar?query=param", proto, host),
166− nil,
167− )
168− req = middleware.AddRequestScope(req, &middleware.RequestScope{})
169− Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
170− })
171−
172 It("ignores X-Forwarded-Uri and returns the URI (without query params)", func() {
143 It("ignores X-Forwarded-Uri and returns the URI (without query params)", func() {
173 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
144 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
174 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
145 Expect(util.GetRequestPath(req)).To(Equal(uriNoQueryParams))
175 })
146 })
176 })
147 })
177
148
178− Context("trusted forwarded headers are enabled", func() {
149+ Context("IsProxied is true", func() {
179 BeforeEach(func() {
150 BeforeEach(func() {
180− req.RemoteAddr = "127.0.0.1:4180"
181 req = middleware.AddRequestScope(req, &middleware.RequestScope{
151 req = middleware.AddRequestScope(req, &middleware.RequestScope{
182− ReverseProxy: true,
152+ ReverseProxy: true,
183− TrustedProxies: trustedProxies,
184 })
153 })
185 })
154 })
186
155
@@ -192,37 +161,6 @@ var _ = Describe("Util Suite", func() {
192 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
161 req.Header.Add("X-Forwarded-Uri", "/some/other/path?query=param")
193 Expect(util.GetRequestPath(req)).To(Equal("/some/other/path"))
162 Expect(util.GetRequestPath(req)).To(Equal("/some/other/path"))
194 })
163 })
195−
196− It("drops fragment-like suffixes from the X-Forwarded-Uri", func() {
197− req.Header.Add("X-Forwarded-Uri", "/foo/secret%23/bar?query=param")
198− Expect(util.GetRequestPath(req)).To(Equal("/foo/secret"))
199− })
200− })
201− })
202−
203− Context("CanTrustForwardedHeaders", func() {
204− It("returns false when no scope is present", func() {
205− Expect(util.CanTrustForwardedHeaders(req)).To(BeFalse())
206− })
207−
208− It("returns true when the remote address is trusted", func() {
209− req.RemoteAddr = "127.0.0.1:4180"
210− req = middleware.AddRequestScope(req, &middleware.RequestScope{
211− ReverseProxy: true,
212− TrustedProxies: trustedProxies,
213− })
214−
215− Expect(util.CanTrustForwardedHeaders(req)).To(BeTrue())
216− })
217−
218− It("returns false when the remote address is untrusted", func() {
219− req.RemoteAddr = "192.0.2.10:4180"
220− req = middleware.AddRequestScope(req, &middleware.RequestScope{
221− ReverseProxy: true,
222− TrustedProxies: trustedProxies,
223− })
224−
225− Expect(util.CanTrustForwardedHeaders(req)).To(BeFalse())
226 })
164 })
227 })
165 })
228})
166})
pkg/upstream/http.go+16−55
@@ -123,7 +123,7 @@ func (t *unixRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
123// The proxy should render an error page if there are failures connecting to the
123// The proxy should render an error page if there are failures connecting to the
124// upstream server.
124// upstream server.
125func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
125func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
126− proxy := newSingleHostReverseProxy(target)
126+ proxy := httputil.NewSingleHostReverseProxy(target)
127
127
128 // Inherit default transport options from Go's stdlib
128 // Inherit default transport options from Go's stdlib
129 transport := http.DefaultTransport.(*http.Transport).Clone()
129 transport := http.DefaultTransport.(*http.Transport).Clone()
@@ -155,7 +155,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
155 }
155 }
156
156
157 // Ensure we always pass the original request path
157 // Ensure we always pass the original request path
158− setProxyRewrite(proxy)
158+ setProxyDirector(proxy)
159
159
160 // TODO (@tuunit) - this should be inverted or get a better name in the future to set the upstream host header
160 // TODO (@tuunit) - this should be inverted or get a better name in the future to set the upstream host header
161 // only if PassHostHeader is explicitly set to true. Currently this would be a breaking change.
161 // only if PassHostHeader is explicitly set to true. Currently this would be a breaking change.
@@ -179,71 +179,32 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
179 return proxy
179 return proxy
180}
180}
181
181
182−func newSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
183− return &httputil.ReverseProxy{
184− Rewrite: func(proxyReq *httputil.ProxyRequest) {
185− proxyReq.SetURL(target)
186− proxyReq.Out.Host = proxyReq.In.Host
187− setProxyForwardingHeaders(proxyReq)
188− },
189− }
190−}
191−
192−// setProxyUpstreamHostHeader sets the proxy.Rewrite so that upstream requests
182+// setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
193// receive a host header matching the target URL.
183// receive a host header matching the target URL.
194func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
184func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
195− rewrite := proxy.Rewrite
185+ director := proxy.Director
196− proxy.Rewrite = func(proxyReq *httputil.ProxyRequest) {
186+ proxy.Director = func(req *http.Request) {
197− rewrite(proxyReq)
187+ director(req)
198− proxyReq.Out.Host = target.Host
188+ req.Host = target.Host
199 }
189 }
200}
190}
201
191
202−// setProxyRewrite sets the proxy.Rewrite so that request URIs are escaped
192+// setProxyDirector sets the proxy.Director so that request URIs are escaped
203// when proxying to usptream servers.
193// when proxying to usptream servers.
204−func setProxyRewrite(proxy *httputil.ReverseProxy) {
194+func setProxyDirector(proxy *httputil.ReverseProxy) {
205− rewrite := proxy.Rewrite
195+ director := proxy.Director
206− proxy.Rewrite = func(proxyReq *httputil.ProxyRequest) {
196+ proxy.Director = func(req *http.Request) {
207− rewrite(proxyReq)
197+ director(req)
208 // use RequestURI so that we aren't unescaping encoded slashes in the request path
198 // use RequestURI so that we aren't unescaping encoded slashes in the request path
209− proxyReq.Out.URL.Opaque = proxyReq.In.RequestURI
199+ req.URL.Opaque = req.RequestURI
210− proxyReq.Out.URL.RawQuery = ""
200+ req.URL.RawQuery = ""
211− proxyReq.Out.URL.ForceQuery = false
201+ req.URL.ForceQuery = false
212− }
213−}
214−
215−func setProxyForwardingHeaders(proxyReq *httputil.ProxyRequest) {
216− // TODO (@tuunit): Preserve the legacy Director-based forwarding header behavior
217− // for backwards compatibility. Harden this with saner defaults and/or
218− // explicit flags in the future.
219− for _, header := range []string{"Forwarded", "X-Forwarded-Host", "X-Forwarded-Proto"} {
220− if values, ok := proxyReq.In.Header[header]; ok {
221− proxyReq.Out.Header[header] = append([]string(nil), values...)
222− }
223− }
224−
225− prior, ok := proxyReq.In.Header["X-Forwarded-For"]
226− if ok {
227− proxyReq.Out.Header["X-Forwarded-For"] = append([]string(nil), prior...)
228− }
229−
230− clientIP, _, err := net.SplitHostPort(proxyReq.In.RemoteAddr)
231− if err != nil {
232− return
233− }
234−
235− omit := ok && prior == nil
236− if len(prior) > 0 {
237− clientIP = strings.Join(prior, ", ") + ", " + clientIP
238− }
239− if !omit {
240− proxyReq.Out.Header.Set("X-Forwarded-For", clientIP)
241 }
202 }
242}
203}
243
204
244// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
205// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
245func newWebSocketReverseProxy(u *url.URL, skipTLSVerify *bool, passHostHeader *bool) http.Handler {
206func newWebSocketReverseProxy(u *url.URL, skipTLSVerify *bool, passHostHeader *bool) http.Handler {
246− wsProxy := newSingleHostReverseProxy(u)
207+ wsProxy := httputil.NewSingleHostReverseProxy(u)
247
208
248 // Inherit default transport options from Go's stdlib
209 // Inherit default transport options from Go's stdlib
249 transport := http.DefaultTransport.(*http.Transport).Clone()
210 transport := http.DefaultTransport.(*http.Transport).Clone()
pkg/upstream/http_test.go+3−57
@@ -361,12 +361,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
361 return http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
361 return http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
362 proxy, ok := h.(*httputil.ReverseProxy)
362 proxy, ok := h.(*httputil.ReverseProxy)
363 Expect(ok).To(BeTrue())
363 Expect(ok).To(BeTrue())
364− outReq := req.Clone(req.Context())
364+ proxy.Director(req)
365− proxy.Rewrite(&httputil.ProxyRequest{
366− In: req,
367− Out: outReq,
368− })
369− req.Host = outReq.Host
370 })
365 })
371 }
366 }
372 httpUpstream.handler = requestInterceptor(httpUpstream.handler)
367 httpUpstream.handler = requestInterceptor(httpUpstream.handler)
@@ -375,54 +370,6 @@ var _ = Describe("HTTP Upstream Suite", func() {
375 Expect(req.Host).To(Equal(strings.TrimPrefix(serverAddr, "http://")))
370 Expect(req.Host).To(Equal(strings.TrimPrefix(serverAddr, "http://")))
376 })
371 })
377
372
378− It("ServeHTTP preserves forwarding headers when using Rewrite", func() {
379− req := httptest.NewRequest("", "http://example.localhost/foo", nil)
380− req.RemoteAddr = "192.0.2.10:1234"
381− req.Header.Set("Forwarded", "for=192.0.2.1;proto=https;host=example.localhost")
382− req.Header.Set("X-Forwarded-Host", "forwarded.example.localhost")
383− req.Header.Set("X-Forwarded-Proto", "https")
384− req.Header.Set("X-Forwarded-For", "192.0.2.1")
385− req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
386− rw := httptest.NewRecorder()
387−
388− upstream := options.Upstream{
389− ID: "preserveForwardedHeaders",
390− PassHostHeader: ptr.To(true),
391− ProxyWebSockets: ptr.To(false),
392− InsecureSkipTLSVerify: ptr.To(false),
393− FlushInterval: &defaultFlushInterval,
394− Timeout: &defaultTimeout,
395− }
396−
397− u, err := url.Parse(serverAddr)
398− Expect(err).ToNot(HaveOccurred())
399−
400− handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
401− httpUpstream, ok := handler.(*httpUpstreamProxy)
402− Expect(ok).To(BeTrue())
403−
404− requestInterceptor := func(h http.Handler) http.Handler {
405− return http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
406− proxy, ok := h.(*httputil.ReverseProxy)
407− Expect(ok).To(BeTrue())
408−
409− outReq := req.Clone(req.Context())
410− proxy.Rewrite(&httputil.ProxyRequest{
411− In: req,
412− Out: outReq,
413− })
414−
415− Expect(outReq.Header.Values("Forwarded")).To(Equal([]string{"for=192.0.2.1;proto=https;host=example.localhost"}))
416− Expect(outReq.Header.Values("X-Forwarded-Host")).To(Equal([]string{"forwarded.example.localhost"}))
417− Expect(outReq.Header.Values("X-Forwarded-Proto")).To(Equal([]string{"https"}))
418− Expect(outReq.Header.Values("X-Forwarded-For")).To(Equal([]string{"192.0.2.1, 192.0.2.10"}))
419− })
420− }
421− httpUpstream.handler = requestInterceptor(httpUpstream.handler)
422−
423− httpUpstream.ServeHTTP(rw, req)
424− })
425−
426 type newUpstreamTableInput struct {
373 type newUpstreamTableInput struct {
427 proxyWebSockets bool
374 proxyWebSockets bool
428 flushInterval time.Duration
375 flushInterval time.Duration
@@ -458,7 +405,6 @@ var _ = Describe("HTTP Upstream Suite", func() {
458
405
459 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
406 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
460 Expect(ok).To(BeTrue())
407 Expect(ok).To(BeTrue())
461− Expect(proxy.Rewrite).ToNot(BeNil())
462 Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
408 Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
463 transport, ok := proxy.Transport.(*http.Transport)
409 transport, ok := proxy.Transport.(*http.Transport)
464 Expect(ok).To(BeTrue())
410 Expect(ok).To(BeTrue())
@@ -552,7 +498,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
552
498
553 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
499 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
554
500
555− proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id", nil)(handler))
501+ proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
556 })
502 })
557
503
558 AfterEach(func() {
504 AfterEach(func() {
@@ -603,7 +549,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
603 Expect(err).ToNot(HaveOccurred())
549 Expect(err).ToNot(HaveOccurred())
604
550
605 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
551 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
606− noPassHostServer := httptest.NewServer(middleware.NewScope(false, "X-Request-Id", nil)(handler))
552+ noPassHostServer := httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
607 defer noPassHostServer.Close()
553 defer noPassHostServer.Close()
608
554
609 origin := "http://example.localhost"
555 origin := "http://example.localhost"
pkg/upstream/proxy_test.go+1−1
@@ -79,7 +79,7 @@ var _ = Describe("Proxy Suite", func() {
79 {
79 {
80 ID: "bad-http-backend",
80 ID: "bad-http-backend",
81 Path: "/bad-http/",
81 Path: "/bad-http/",
82− URI: invalidServer,
82+ URI: "http://::1",
83 },
83 },
84 {
84 {
85 ID: "single-path-backend",
85 ID: "single-path-backend",
pkg/upstream/upstream_suite_test.go+1−1
@@ -24,7 +24,7 @@ var (
24 serverAddr string
24 serverAddr string
25 unixServer *httptest.Server
25 unixServer *httptest.Server
26 unixServerAddr string
26 unixServerAddr string
27− invalidServer = "http://127.0.0.1:1"
27+ invalidServer = "http://::1"
28)
28)
29
29
30func TestUpstreamSuite(t *testing.T) {
30func TestUpstreamSuite(t *testing.T) {
pkg/validation/allowlist.go−12
@@ -16,7 +16,6 @@ func validateAllowlists(o *options.Options) []string {
16
16
17 msgs = append(msgs, validateAuthRoutes(o)...)
17 msgs = append(msgs, validateAuthRoutes(o)...)
18 msgs = append(msgs, validateAuthRegexes(o)...)
18 msgs = append(msgs, validateAuthRegexes(o)...)
19− msgs = append(msgs, validateTrustedProxyIPs(o)...)
20 msgs = append(msgs, validateTrustedIPs(o)...)
19 msgs = append(msgs, validateTrustedIPs(o)...)
21
20
22 if len(o.TrustedIPs) > 0 && o.ReverseProxy {
21 if len(o.TrustedIPs) > 0 && o.ReverseProxy {
@@ -29,17 +28,6 @@ func validateAllowlists(o *options.Options) []string {
29 return msgs
28 return msgs
30}
29}
31
30
32−// validateTrustedProxyIPs validates IP/CIDRs for trusted reverse proxies.
33−func validateTrustedProxyIPs(o *options.Options) []string {
34− msgs := []string{}
35− for i, ipStr := range o.TrustedProxyIPs {
36− if ip.ParseIPNet(ipStr) == nil {
37− msgs = append(msgs, fmt.Sprintf("trusted_proxy_ips[%d] (%s) could not be recognized", i, ipStr))
38− }
39− }
40− return msgs
41−}
42−
43// validateAuthRoutes validates method=path routes passed with options.SkipAuthRoutes
31// validateAuthRoutes validates method=path routes passed with options.SkipAuthRoutes
44func validateAuthRoutes(o *options.Options) []string {
32func validateAuthRoutes(o *options.Options) []string {
45 msgs := []string{}
33 msgs := []string{}
pkg/validation/allowlist_test.go−30
@@ -23,11 +23,6 @@ var _ = Describe("Allowlist", func() {
23 errStrings []string
23 errStrings []string
24 }
24 }
25
25
26− type validateTrustedProxyIPsTableInput struct {
27− trustedProxyIPs []string
28− errStrings []string
29− }
30−
31 DescribeTable("validateRoutes",
26 DescribeTable("validateRoutes",
32 func(r *validateRoutesTableInput) {
27 func(r *validateRoutesTableInput) {
33 opts := &options.Options{
28 opts := &options.Options{
@@ -126,29 +121,4 @@ var _ = Describe("Allowlist", func() {
126 },
121 },
127 }),
122 }),
128 )
123 )
129−
130− DescribeTable("validateTrustedProxyIPs",
131− func(t *validateTrustedProxyIPsTableInput) {
132− opts := &options.Options{
133− TrustedProxyIPs: t.trustedProxyIPs,
134− }
135− Expect(validateTrustedProxyIPs(opts)).To(ConsistOf(t.errStrings))
136− },
137− Entry("Valid trusted proxy IPs", &validateTrustedProxyIPsTableInput{
138− trustedProxyIPs: []string{
139− "127.0.0.1",
140− "10.32.0.1/32",
141− "::1",
142− "2a12:105:ee7:9234:0:0:0:0/64",
143− },
144− errStrings: []string{},
145− }),
146− Entry("Invalid trusted proxy IPs", &validateTrustedProxyIPsTableInput{
147− trustedProxyIPs: []string{"[::1]", "alkwlkbn/32"},
148− errStrings: []string{
149− "trusted_proxy_ips[0] ([::1]) could not be recognized",
150− "trusted_proxy_ips[1] (alkwlkbn/32) could not be recognized",
151− },
152− }),
153− )
154})
124})
pkg/validation/sessions_test.go+2−2
@@ -193,8 +193,8 @@ var _ = Describe("Sessions", func() {
193 unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
193 unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
194 unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
194 unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
195 unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
195 unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: redis: nil"
196− refusedSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: dial tcp 127.0.0.1:65535: connect: connection refused"
196+ refusedSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
197− refusedSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: dial tcp 127.0.0.1:65535: connect: connection refused"
197+ refusedSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels specified in configuration are unreachable: context deadline exceeded"
198 )
198 )
199
199
200 type redisStoreTableInput struct {
200 type redisStoreTableInput struct {
providers/atlassian-cloud.godeleted−99
@@ -1,99 +0,0 @@
1−package providers
2−
3−import (
4− "context"
5− "errors"
6− "net/url"
7−
8− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
10−)
11−
12−// AtlassianProvider represents an Atlassian Cloud based Identity Provider
13−type AtlassianProvider struct {
14− *ProviderData
15−}
16−
17−var _ Provider = (*AtlassianProvider)(nil)
18−
19−const (
20− atlassianProviderName = "Atlassian"
21− atlassianDefaultScope = "read:me"
22− atlassianPrompt = "consent"
23− atlassianAudience = "api.atlassian.com"
24−)
25−
26−var (
27− atlassianDefaultLoginURL = &url.URL{
28− Scheme: "https",
29− Host: "auth.atlassian.com",
30− Path: "/authorize",
31− }
32−
33− atlassianDefaultRedeemURL = &url.URL{
34− Scheme: "https",
35− Host: "auth.atlassian.com",
36− Path: "/oauth/token",
37− }
38−
39− // atlassianDefaultProfileURL returns the signed-in user's profile.
40− atlassianDefaultProfileURL = &url.URL{
41− Scheme: "https",
42− Host: "api.atlassian.com",
43− Path: "/me",
44− }
45−)
46−
47−// NewAtlassianProvider initiates a new AtlassianProvider
48−func NewAtlassianProvider(p *ProviderData) *AtlassianProvider {
49− p.setProviderDefaults(providerDefaults{
50− name: atlassianProviderName,
51− loginURL: atlassianDefaultLoginURL,
52− redeemURL: atlassianDefaultRedeemURL,
53− profileURL: atlassianDefaultProfileURL,
54− validateURL: atlassianDefaultProfileURL,
55− scope: atlassianDefaultScope,
56− })
57− return &AtlassianProvider{ProviderData: p}
58−}
59−
60−// GetLoginURL adds the audience and prompt parameters Atlassian requires.
61−func (p *AtlassianProvider) GetLoginURL(redirectURI, state, _ string, extraParams url.Values) string {
62− params := url.Values{}
63− for k, v := range extraParams {
64− params[k] = v
65− }
66− params.Del("approval_prompt")
67− if params.Get("prompt") == "" {
68− params.Set("prompt", atlassianPrompt)
69− }
70− params.Set("audience", atlassianAudience)
71−
72− loginURL := makeLoginURL(p.ProviderData, redirectURI, state, params)
73− return loginURL.String()
74−}
75−
76−// EnrichSession sets the session's email from the Atlassian profile.
77−func (p *AtlassianProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
78− var profile struct {
79− Email string `json:"email"`
80− }
81− err := requests.New(p.ProfileURL.String()).
82− WithContext(ctx).
83− WithHeaders(makeOIDCHeader(s.AccessToken)).
84− Do().
85− UnmarshalInto(&profile)
86− if err != nil {
87− return err
88− }
89− if profile.Email == "" {
90− return errors.New("no email in Atlassian profile")
91− }
92− s.Email = profile.Email
93− return nil
94−}
95−
96−// ValidateSession validates the AccessToken
97−func (p *AtlassianProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
98− return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken))
99−}
providers/atlassian-cloud_test.godeleted−93
@@ -1,93 +0,0 @@
1−package providers
2−
3−import (
4− "context"
5− "net/http"
6− "net/http/httptest"
7− "net/url"
8− "testing"
9−
10− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
11− . "github.com/onsi/gomega"
12−)
13−
14−func testAtlassianBackend(status int, body string) *httptest.Server {
15− return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16− if r.URL.Path != "/me" || r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
17− w.WriteHeader(http.StatusUnauthorized)
18− return
19− }
20− w.WriteHeader(status)
21− _, _ = w.Write([]byte(body))
22− }))
23−}
24−
25−func testAtlassianProvider(backend *httptest.Server) *AtlassianProvider {
26− p := NewAtlassianProvider(&ProviderData{})
27− u, _ := url.Parse(backend.URL)
28− updateURL(p.ProfileURL, u.Host)
29− updateURL(p.ValidateURL, u.Host)
30− return p
31−}
32−
33−func TestNewAtlassianProvider(t *testing.T) {
34− g := NewWithT(t)
35−
36− providerData := NewAtlassianProvider(&ProviderData{}).Data()
37− g.Expect(providerData.ProviderName).To(Equal("Atlassian"))
38− g.Expect(providerData.LoginURL.String()).To(Equal("https://auth.atlassian.com/authorize"))
39− g.Expect(providerData.RedeemURL.String()).To(Equal("https://auth.atlassian.com/oauth/token"))
40− g.Expect(providerData.ProfileURL.String()).To(Equal("https://api.atlassian.com/me"))
41− g.Expect(providerData.ValidateURL.String()).To(Equal("https://api.atlassian.com/me"))
42− g.Expect(providerData.Scope).To(Equal("read:me"))
43−}
44−
45−func TestAtlassianProviderGetLoginURL(t *testing.T) {
46− g := NewWithT(t)
47− p := NewAtlassianProvider(&ProviderData{ClientID: "client"})
48−
49− loginURL, err := url.Parse(p.GetLoginURL("https://proxy/oauth2/callback", "state", "",
50− url.Values{"approval_prompt": {"force"}}))
51− g.Expect(err).ToNot(HaveOccurred())
52−
53− q := loginURL.Query()
54− g.Expect(q.Get("audience")).To(Equal("api.atlassian.com"))
55− g.Expect(q.Get("prompt")).To(Equal("consent"))
56− g.Expect(q.Has("approval_prompt")).To(BeFalse())
57− g.Expect(q.Get("scope")).To(Equal("read:me"))
58−
59− loginURL, err = url.Parse(p.GetLoginURL("https://proxy/oauth2/callback", "state", "",
60− url.Values{"prompt": {"login"}}))
61− g.Expect(err).ToNot(HaveOccurred())
62− g.Expect(loginURL.Query().Get("prompt")).To(Equal("login"))
63−}
64−
65−func TestAtlassianProviderEnrichSession(t *testing.T) {
66− g := NewWithT(t)
67− b := testAtlassianBackend(http.StatusOK, `{"account_id":"abc","email":"user@example.com"}`)
68− defer b.Close()
69− p := testAtlassianProvider(b)
70−
71− session := CreateAuthorizedSession()
72− g.Expect(p.EnrichSession(context.Background(), session)).To(Succeed())
73− g.Expect(session.Email).To(Equal("user@example.com"))
74−}
75−
76−func TestAtlassianProviderEnrichSessionWithoutEmail(t *testing.T) {
77− g := NewWithT(t)
78− b := testAtlassianBackend(http.StatusOK, `{"account_id":"abc"}`)
79− defer b.Close()
80− p := testAtlassianProvider(b)
81−
82− g.Expect(p.EnrichSession(context.Background(), CreateAuthorizedSession())).ToNot(Succeed())
83−}
84−
85−func TestAtlassianProviderValidateSession(t *testing.T) {
86− g := NewWithT(t)
87− b := testAtlassianBackend(http.StatusOK, `{"email":"user@example.com"}`)
88− defer b.Close()
89− p := testAtlassianProvider(b)
90−
91− g.Expect(p.ValidateSession(context.Background(), CreateAuthorizedSession())).To(BeTrue())
92− g.Expect(p.ValidateSession(context.Background(), &sessions.SessionState{AccessToken: "wrong"})).To(BeFalse())
93−}
providers/azure.go+2−2
@@ -166,7 +166,7 @@ func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code, codeVerif
166
166
167 err = requests.New(p.RedeemURL.String()).
167 err = requests.New(p.RedeemURL.String()).
168 WithContext(ctx).
168 WithContext(ctx).
169− WithMethod(http.MethodPost).
169+ WithMethod("POST").
170 WithBody(bytes.NewBufferString(params.Encode())).
170 WithBody(bytes.NewBufferString(params.Encode())).
171 SetHeader("Content-Type", "application/x-www-form-urlencoded").
171 SetHeader("Content-Type", "application/x-www-form-urlencoded").
172 Do().
172 Do().
@@ -334,7 +334,7 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
334
334
335 err = requests.New(p.RedeemURL.String()).
335 err = requests.New(p.RedeemURL.String()).
336 WithContext(ctx).
336 WithContext(ctx).
337− WithMethod(http.MethodPost).
337+ WithMethod("POST").
338 WithBody(bytes.NewBufferString(params.Encode())).
338 WithBody(bytes.NewBufferString(params.Encode())).
339 SetHeader("Content-Type", "application/x-www-form-urlencoded").
339 SetHeader("Content-Type", "application/x-www-form-urlencoded").
340 Do().
340 Do().
providers/dtt_sso.godeleted−202
@@ -1,202 +0,0 @@
1−package providers
2−
3−import (
4− "bytes"
5− "context"
6− "encoding/base64"
7− "errors"
8− "fmt"
9− "net/http"
10− "net/url"
11− "time"
12−
13− "github.com/golang-jwt/jwt/v5"
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
16−)
17−
18−// DTTSSOProvider signs users in with the DTT SSO (auth-server).
19−type DTTSSOProvider struct {
20− *ProviderData
21−}
22−
23−var _ Provider = (*DTTSSOProvider)(nil)
24−
25−const (
26− dttSSOProviderName = "DTT SSO"
27− dttSSODefaultScope = "openid"
28−)
29−
30−var (
31− dttSSODefaultLoginURL = &url.URL{
32− Scheme: "https",
33− Host: "auth.thebestagent.pro",
34− Path: "/oauth/authorize",
35− }
36−
37− dttSSODefaultRedeemURL = &url.URL{
38− Scheme: "https",
39− Host: "auth.thebestagent.pro",
40− Path: "/oauth/token",
41− }
42−
43− dttSSODefaultValidateURL = &url.URL{
44− Scheme: "https",
45− Host: "auth.thebestagent.pro",
46− Path: "/oauth/check_token",
47− }
48−)
49−
50−// NewDTTSSOProvider initiates a new DTTSSOProvider
51−func NewDTTSSOProvider(p *ProviderData) *DTTSSOProvider {
52− p.setProviderDefaults(providerDefaults{
53− name: dttSSOProviderName,
54− loginURL: dttSSODefaultLoginURL,
55− redeemURL: dttSSODefaultRedeemURL,
56− profileURL: nil,
57− validateURL: dttSSODefaultValidateURL,
58− scope: dttSSODefaultScope,
59− })
60− return &DTTSSOProvider{ProviderData: p}
61−}
62−
63−// dttSSOTokenResponse is the SSO's /oauth/token response.
64−type dttSSOTokenResponse struct {
65− AccessToken string `json:"access_token"`
66− RefreshToken string `json:"refresh_token"`
67− ExpiresIn int64 `json:"expires_in"`
68− IDToken string `json:"id_token"`
69−}
70−
71−// dttSSOClaims holds the id_token claims the provider uses.
72−type dttSSOClaims struct {
73− jwt.RegisteredClaims
74− Email string `json:"email"`
75− Role string `json:"role"`
76− UserID string `json:"user_id"`
77−}
78−
79−// Redeem exchanges the authorization code for a session.
80−func (p *DTTSSOProvider) Redeem(ctx context.Context, redirectURL, code, _ string) (*sessions.SessionState, error) {
81− if code == "" {
82− return nil, ErrMissingCode
83− }
84− return p.requestTokens(ctx, url.Values{
85− "grant_type": {"authorization_code"},
86− "code": {code},
87− "redirect_uri": {redirectURL},
88− })
89−}
90−
91−// RefreshSession renews the tokens and re-reads the email and role.
92−func (p *DTTSSOProvider) RefreshSession(ctx context.Context, s *sessions.SessionState) (bool, error) {
93− if s == nil || s.RefreshToken == "" {
94− return false, nil
95− }
96− refreshed, err := p.requestTokens(ctx, url.Values{
97− "grant_type": {"refresh_token"},
98− "refresh_token": {s.RefreshToken},
99− })
100− if err != nil {
101− return false, fmt.Errorf("unable to redeem refresh token: %v", err)
102− }
103− s.AccessToken = refreshed.AccessToken
104− s.RefreshToken = refreshed.RefreshToken
105− s.IDToken = refreshed.IDToken
106− s.Email = refreshed.Email
107− s.User = refreshed.User
108− s.Groups = refreshed.Groups
109− s.CreatedAt = refreshed.CreatedAt
110− s.ExpiresOn = refreshed.ExpiresOn
111− return true, nil
112−}
113−
114−// ValidateSession checks the access token with the SSO's check_token endpoint.
115−func (p *DTTSSOProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
116− return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken))
117−}
118−
119−// requestTokens calls the token endpoint and builds a session from the verified id_token.
120−//
121−// The SSO compares the Basic credentials byte for byte, so they are sent unescaped
122−// rather than through golang.org/x/oauth2, which URL-escapes them.
123−func (p *DTTSSOProvider) requestTokens(ctx context.Context, params url.Values) (*sessions.SessionState, error) {
124− clientSecret, err := p.GetClientSecret()
125− if err != nil {
126− return nil, err
127− }
128− credentials := base64.StdEncoding.EncodeToString([]byte(p.ClientID + ":" + clientSecret))
129−
130− result := requests.New(p.RedeemURL.String()).
131− WithContext(ctx).
132− WithMethod(http.MethodPost).
133− WithBody(bytes.NewBufferString(params.Encode())).
134− SetHeader("Content-Type", "application/x-www-form-urlencoded").
135− SetHeader("Authorization", "Basic "+credentials).
136− Do()
137− if result.Error() != nil {
138− return nil, result.Error()
139− }
140− if result.StatusCode() != http.StatusOK {
141− return nil, fmt.Errorf("token request failed: status %d - %s", result.StatusCode(), result.Body())
142− }
143−
144− var token dttSSOTokenResponse
145− if err := result.UnmarshalInto(&token); err != nil {
146− return nil, err
147− }
148− if token.AccessToken == "" {
149− return nil, errors.New("token response carried no access_token")
150− }
151− claims, err := p.verifyIDToken(token.IDToken, clientSecret)
152− if err != nil {
153− return nil, err
154− }
155−
156− s := &sessions.SessionState{
157− AccessToken: token.AccessToken,
158− RefreshToken: token.RefreshToken,
159− IDToken: token.IDToken,
160− Email: claims.Email,
161− User: claims.UserID,
162− }
163− if claims.Role != "" {
164− s.Groups = []string{claims.Role}
165− }
166− s.CreatedAtNow()
167− if token.ExpiresIn > 0 {
168− s.ExpiresIn(time.Duration(token.ExpiresIn) * time.Second)
169− }
170− return s, nil
171−}
172−
173−// verifyIDToken checks the id_token's HS256 signature (keyed on the client secret),
174−// issuer, audience and expiry.
175−//
176−// go-jose is not used: it rejects HMAC keys under 32 bytes, and SSO client secrets
177−// can be shorter.
178−func (p *DTTSSOProvider) verifyIDToken(raw, clientSecret string) (*dttSSOClaims, error) {
179− if raw == "" {
180− return nil, errors.New("token response carried no id_token; the scope must be exactly \"openid\"")
181− }
182− var claims dttSSOClaims
183− _, err := jwt.ParseWithClaims(raw, &claims,
184− func(*jwt.Token) (any, error) { return []byte(clientSecret), nil },
185− jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
186− jwt.WithIssuer(p.issuer()),
187− jwt.WithAudience(p.ClientID),
188− jwt.WithExpirationRequired(),
189− )
190− if err != nil {
191− return nil, fmt.Errorf("verifying id_token: %v", err)
192− }
193− if claims.Email == "" {
194− return nil, errors.New("id_token carries no email")
195− }
196− return &claims, nil
197−}
198−
199−// issuer is the SSO's site host, the origin of the login URL.
200−func (p *DTTSSOProvider) issuer() string {
201− return p.LoginURL.Scheme + "://" + p.LoginURL.Host
202−}
providers/dtt_sso_test.godeleted−237
@@ -1,237 +0,0 @@
1−package providers
2−
3−import (
4− "context"
5− "encoding/base64"
6− "encoding/json"
7− "net/http"
8− "net/http/httptest"
9− "net/url"
10− "testing"
11− "time"
12−
13− "github.com/golang-jwt/jwt/v5"
14− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15− . "github.com/onsi/gomega"
16−)
17−
18−const (
19− dttSSOTestClientID = "redash-gate"
20− // Shorter than 32 bytes, and contains characters golang.org/x/oauth2 would
21− // URL-escape in Basic auth.
22− dttSSOTestSecret = "s3cr+t/abc=="
23−)
24−
25−// dttSSOTestServer mimics the SSO's token and check_token endpoints.
26−type dttSSOTestServer struct {
27− *httptest.Server
28− claims map[string]interface{}
29− signingKey []byte
30− lastForm url.Values
31− validTokens map[string]bool
32− tokenCounter int
33−}
34−
35−func newDTTSSOTestServer() *dttSSOTestServer {
36− s := &dttSSOTestServer{
37− signingKey: []byte(dttSSOTestSecret),
38− validTokens: map[string]bool{},
39− }
40− s.Server = httptest.NewServer(http.HandlerFunc(s.handle))
41− u, _ := url.Parse(s.URL)
42− s.claims = map[string]interface{}{
43− "iss": "http://" + u.Host,
44− "aud": dttSSOTestClientID,
45− "sub": "0b5c1e1a-1111-2222-3333-444455556666",
46− "user_id": "0b5c1e1a-1111-2222-3333-444455556666",
47− "email": "admin@example.com",
48− "role": "dtt_admin",
49− "exp": time.Now().Add(time.Hour).Unix(),
50− }
51− return s
52−}
53−
54−func (s *dttSSOTestServer) handle(w http.ResponseWriter, r *http.Request) {
55− switch r.URL.Path {
56− case "/oauth/token":
57− want := "Basic " + base64.StdEncoding.EncodeToString([]byte(dttSSOTestClientID+":"+dttSSOTestSecret))
58− if r.Header.Get("Authorization") != want {
59− w.WriteHeader(http.StatusUnauthorized)
60− return
61− }
62− _ = r.ParseForm()
63− s.lastForm = r.PostForm
64− if r.PostForm.Get("grant_type") == "refresh_token" && !s.validTokens["refresh-"+r.PostForm.Get("refresh_token")] {
65− w.WriteHeader(http.StatusBadRequest)
66− _, _ = w.Write([]byte(`{"error":"invalid_grant","error_description":"Invalid refresh token"}`))
67− return
68− }
69− s.tokenCounter++
70− access := "access-" + string(rune('0'+s.tokenCounter))
71− refresh := "refresh-" + string(rune('0'+s.tokenCounter))
72− s.validTokens[access] = true
73− s.validTokens["refresh-"+refresh] = true
74− w.Header().Set("Content-Type", "application/json")
75− _ = json.NewEncoder(w).Encode(map[string]interface{}{
76− "access_token": access,
77− "token_type": "bearer",
78− "expires_in": 3600,
79− "refresh_token": refresh,
80− "id_token": s.idToken(),
81− "scope": "openid",
82− })
83− case "/oauth/check_token":
84− token := r.Header.Get("Authorization")
85− if len(token) > len("Bearer ") && s.validTokens[token[len("Bearer "):]] {
86− _, _ = w.Write([]byte(`{"user_id":"0b5c1e1a-1111-2222-3333-444455556666","real_user_id":null,"issued_by":"redash-gate"}`))
87− return
88− }
89− w.WriteHeader(http.StatusUnauthorized)
90− _, _ = w.Write([]byte("Invalid access token"))
91− default:
92− w.WriteHeader(http.StatusNotFound)
93− }
94−}
95−
96−func (s *dttSSOTestServer) idToken() string {
97− if s.claims == nil {
98− return ""
99− }
100− raw, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(s.claims)).SignedString(s.signingKey)
101− if err != nil {
102− panic(err)
103− }
104− return raw
105−}
106−
107−func testDTTSSOProvider(server *dttSSOTestServer) *DTTSSOProvider {
108− p := NewDTTSSOProvider(&ProviderData{ClientID: dttSSOTestClientID, ClientSecret: dttSSOTestSecret})
109− u, _ := url.Parse(server.URL)
110− updateURL(p.LoginURL, u.Host)
111− updateURL(p.RedeemURL, u.Host)
112− updateURL(p.ValidateURL, u.Host)
113− return p
114−}
115−
116−func TestNewDTTSSOProvider(t *testing.T) {
117− g := NewWithT(t)
118−
119− providerData := NewDTTSSOProvider(&ProviderData{}).Data()
120− g.Expect(providerData.ProviderName).To(Equal("DTT SSO"))
121− g.Expect(providerData.LoginURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/authorize"))
122− g.Expect(providerData.RedeemURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/token"))
123− g.Expect(providerData.ValidateURL.String()).To(Equal("https://auth.thebestagent.pro/oauth/check_token"))
124− g.Expect(providerData.Scope).To(Equal("openid"))
125−}
126−
127−func TestDTTSSOProviderRedeem(t *testing.T) {
128− g := NewWithT(t)
129− server := newDTTSSOTestServer()
130− defer server.Close()
131− p := testDTTSSOProvider(server)
132−
133− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
134− g.Expect(err).ToNot(HaveOccurred())
135− g.Expect(s.Email).To(Equal("admin@example.com"))
136− g.Expect(s.User).To(Equal("0b5c1e1a-1111-2222-3333-444455556666"))
137− g.Expect(s.Groups).To(Equal([]string{"dtt_admin"}))
138− g.Expect(s.AccessToken).To(Equal("access-1"))
139− g.Expect(s.RefreshToken).To(Equal("refresh-1"))
140− g.Expect(s.IDToken).ToNot(BeEmpty())
141− g.Expect(s.ExpiresOn).ToNot(BeNil())
142− g.Expect(*s.ExpiresOn).To(BeTemporally("~", time.Now().Add(time.Hour), time.Minute))
143−
144− g.Expect(server.lastForm.Get("grant_type")).To(Equal("authorization_code"))
145− g.Expect(server.lastForm.Get("code")).To(Equal("the-code"))
146− g.Expect(server.lastForm.Get("redirect_uri")).To(Equal("https://redash.example.com/oauth2/callback"))
147−}
148−
149−func TestDTTSSOProviderRedeemRejectsBadIDTokens(t *testing.T) {
150− testCases := map[string]func(s *dttSSOTestServer){
151− "signed with another secret": func(s *dttSSOTestServer) { s.signingKey = []byte("some-other-client-secret") },
152− "other audience": func(s *dttSSOTestServer) { s.claims["aud"] = "frappe-gate" },
153− "other issuer": func(s *dttSSOTestServer) { s.claims["iss"] = "https://evil.example.com" },
154− "expired": func(s *dttSSOTestServer) { s.claims["exp"] = time.Now().Add(-time.Minute).Unix() },
155− "no email": func(s *dttSSOTestServer) { delete(s.claims, "email") },
156− "no id_token": func(s *dttSSOTestServer) { s.claims = nil },
157− }
158− for name, tamper := range testCases {
159− t.Run(name, func(t *testing.T) {
160− g := NewWithT(t)
161− server := newDTTSSOTestServer()
162− defer server.Close()
163− tamper(server)
164−
165− _, err := testDTTSSOProvider(server).Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
166− g.Expect(err).To(HaveOccurred())
167− })
168− }
169−}
170−
171−func TestDTTSSOProviderRedeemRejectsUnsignedIDToken(t *testing.T) {
172− g := NewWithT(t)
173− server := newDTTSSOTestServer()
174− defer server.Close()
175− p := testDTTSSOProvider(server)
176−
177− header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`))
178− payload, _ := json.Marshal(server.claims)
179− unsigned := header + "." + base64.RawURLEncoding.EncodeToString(payload) + "."
180−
181− _, err := p.verifyIDToken(unsigned, dttSSOTestSecret)
182− g.Expect(err).To(HaveOccurred())
183−}
184−
185−func TestDTTSSOProviderRefreshSession(t *testing.T) {
186− g := NewWithT(t)
187− server := newDTTSSOTestServer()
188− defer server.Close()
189− p := testDTTSSOProvider(server)
190−
191− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
192− g.Expect(err).ToNot(HaveOccurred())
193− nonce := []byte("keep-me")
194− s.Nonce = nonce
195−
196− server.claims["role"] = "agent"
197− server.claims["email"] = "renamed@example.com"
198− refreshed, err := p.RefreshSession(context.Background(), s)
199− g.Expect(err).ToNot(HaveOccurred())
200− g.Expect(refreshed).To(BeTrue())
201− g.Expect(s.AccessToken).To(Equal("access-2"))
202− g.Expect(s.RefreshToken).To(Equal("refresh-2"))
203− g.Expect(s.Groups).To(Equal([]string{"agent"}))
204− g.Expect(s.Email).To(Equal("renamed@example.com"))
205− g.Expect(s.Nonce).To(Equal(nonce))
206− g.Expect(server.lastForm.Get("grant_type")).To(Equal("refresh_token"))
207− g.Expect(server.lastForm.Get("refresh_token")).To(Equal("refresh-1"))
208−}
209−
210−func TestDTTSSOProviderRefreshSessionFailures(t *testing.T) {
211− g := NewWithT(t)
212− server := newDTTSSOTestServer()
213− defer server.Close()
214− p := testDTTSSOProvider(server)
215−
216− refreshed, err := p.RefreshSession(context.Background(), &sessions.SessionState{})
217− g.Expect(err).ToNot(HaveOccurred())
218− g.Expect(refreshed).To(BeFalse())
219−
220− refreshed, err = p.RefreshSession(context.Background(), &sessions.SessionState{RefreshToken: "revoked"})
221− g.Expect(err).To(HaveOccurred())
222− g.Expect(refreshed).To(BeFalse())
223−}
224−
225−func TestDTTSSOProviderValidateSession(t *testing.T) {
226− g := NewWithT(t)
227− server := newDTTSSOTestServer()
228− defer server.Close()
229− p := testDTTSSOProvider(server)
230−
231− s, err := p.Redeem(context.Background(), "https://redash.example.com/oauth2/callback", "the-code", "")
232− g.Expect(err).ToNot(HaveOccurred())
233− g.Expect(p.ValidateSession(context.Background(), s)).To(BeTrue())
234−
235− delete(server.validTokens, s.AccessToken)
236− g.Expect(p.ValidateSession(context.Background(), s)).To(BeFalse())
237−}
providers/google.go+2−2
@@ -219,7 +219,7 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code, codeVeri
219
219
220 err = requests.New(p.RedeemURL.String()).
220 err = requests.New(p.RedeemURL.String()).
221 WithContext(ctx).
221 WithContext(ctx).
222− WithMethod(http.MethodPost).
222+ WithMethod("POST").
223 WithBody(bytes.NewBufferString(params.Encode())).
223 WithBody(bytes.NewBufferString(params.Encode())).
224 SetHeader("Content-Type", "application/x-www-form-urlencoded").
224 SetHeader("Content-Type", "application/x-www-form-urlencoded").
225 Do().
225 Do().
@@ -543,7 +543,7 @@ func (p *GoogleProvider) redeemRefreshToken(ctx context.Context, s *sessions.Ses
543
543
544 err = requests.New(p.RedeemURL.String()).
544 err = requests.New(p.RedeemURL.String()).
545 WithContext(ctx).
545 WithContext(ctx).
546− WithMethod(http.MethodPost).
546+ WithMethod("POST").
547 WithBody(bytes.NewBufferString(params.Encode())).
547 WithBody(bytes.NewBufferString(params.Encode())).
548 SetHeader("Content-Type", "application/x-www-form-urlencoded").
548 SetHeader("Content-Type", "application/x-www-form-urlencoded").
549 Do().
549 Do().
providers/logingov.go+1−2
@@ -8,7 +8,6 @@ import (
8 "errors"
8 "errors"
9 "fmt"
9 "fmt"
10 "math/big"
10 "math/big"
11− "net/http"
12 "net/url"
11 "net/url"
13 "os"
12 "os"
14 "time"
13 "time"
@@ -238,7 +237,7 @@ func (p *LoginGovProvider) Redeem(ctx context.Context, _, code, codeVerifier str
238 }
237 }
239 err = requests.New(p.RedeemURL.String()).
238 err = requests.New(p.RedeemURL.String()).
240 WithContext(ctx).
239 WithContext(ctx).
241− WithMethod(http.MethodPost).
240+ WithMethod("POST").
242 WithBody(bytes.NewBufferString(params.Encode())).
241 WithBody(bytes.NewBufferString(params.Encode())).
243 SetHeader("Content-Type", "application/x-www-form-urlencoded").
242 SetHeader("Content-Type", "application/x-www-form-urlencoded").
244 Do().
243 Do().
providers/ms_entra_id.go+1−2
@@ -5,7 +5,6 @@ import (
5 "context"
5 "context"
6 "encoding/json"
6 "encoding/json"
7 "fmt"
7 "fmt"
8− "net/http"
9 "net/url"
8 "net/url"
10 "os"
9 "os"
11 "regexp"
10 "regexp"
@@ -305,7 +304,7 @@ func (p *MicrosoftEntraIDProvider) checkTenantMatchesTenantList(tenant string, a
305func (p *MicrosoftEntraIDProvider) fetchToken(ctx context.Context, params url.Values) (*oauth2.Token, error) {
304func (p *MicrosoftEntraIDProvider) fetchToken(ctx context.Context, params url.Values) (*oauth2.Token, error) {
306 resp := requests.New(p.RedeemURL.String()).
305 resp := requests.New(p.RedeemURL.String()).
307 WithContext(ctx).
306 WithContext(ctx).
308− WithMethod(http.MethodPost).
307+ WithMethod("POST").
309 WithBody(bytes.NewBufferString(params.Encode())).
308 WithBody(bytes.NewBufferString(params.Encode())).
310 SetHeader("Content-Type", "application/x-www-form-urlencoded").
309 SetHeader("Content-Type", "application/x-www-form-urlencoded").
311 Do()
310 Do()
providers/provider_default.go+1−2
@@ -5,7 +5,6 @@ import (
5 "context"
5 "context"
6 "errors"
6 "errors"
7 "fmt"
7 "fmt"
8− "net/http"
9 "net/url"
8 "net/url"
10
9
11 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
@@ -72,7 +71,7 @@ func (p *ProviderData) Redeem(ctx context.Context, redirectURL, code, codeVerifi
72
71
73 result := requests.New(p.RedeemURL.String()).
72 result := requests.New(p.RedeemURL.String()).
74 WithContext(ctx).
73 WithContext(ctx).
75− WithMethod(http.MethodPost).
74+ WithMethod("POST").
76 WithBody(bytes.NewBufferString(params.Encode())).
75 WithBody(bytes.NewBufferString(params.Encode())).
77 SetHeader("Content-Type", "application/x-www-form-urlencoded").
76 SetHeader("Content-Type", "application/x-www-form-urlencoded").
78 Do()
77 Do()
providers/providers.go+3−7
@@ -40,8 +40,6 @@ func NewProvider(providerConfig options.Provider) (Provider, error) {
40 switch providerConfig.Type {
40 switch providerConfig.Type {
41 case options.ADFSProvider:
41 case options.ADFSProvider:
42 return NewADFSProvider(providerData, providerConfig), nil
42 return NewADFSProvider(providerData, providerConfig), nil
43− case options.AtlassianProvider:
44− return NewAtlassianProvider(providerData), nil
45 case options.AzureProvider:
43 case options.AzureProvider:
46 return NewAzureProvider(providerData, providerConfig.AzureConfig), nil
44 return NewAzureProvider(providerData, providerConfig.AzureConfig), nil
47 case options.MicrosoftEntraIDProvider:
45 case options.MicrosoftEntraIDProvider:
@@ -52,8 +50,6 @@ func NewProvider(providerConfig options.Provider) (Provider, error) {
52 return NewCIDAASProvider(providerData, providerConfig), nil
50 return NewCIDAASProvider(providerData, providerConfig), nil
53 case options.DigitalOceanProvider:
51 case options.DigitalOceanProvider:
54 return NewDigitalOceanProvider(providerData), nil
52 return NewDigitalOceanProvider(providerData), nil
55− case options.DTTSSOProvider:
56− return NewDTTSSOProvider(providerData), nil
57 case options.FacebookProvider:
53 case options.FacebookProvider:
58 return NewFacebookProvider(providerData), nil
54 return NewFacebookProvider(providerData), nil
59 case options.GitHubProvider:
55 case options.GitHubProvider:
@@ -193,9 +189,9 @@ func parseCodeChallengeMethod(providerConfig options.Provider) string {
193
189
194func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bool, error) {
190func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bool, error) {
195 switch providerType {
191 switch providerType {
196− case options.AtlassianProvider, options.BitbucketProvider, options.DigitalOceanProvider, options.DTTSSOProvider,
192+ case options.BitbucketProvider, options.DigitalOceanProvider, options.FacebookProvider, options.GitHubProvider,
197− options.FacebookProvider, options.GitHubProvider, options.GoogleProvider, options.KeycloakProvider,
193+ options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider,
198− options.LinkedInProvider, options.LoginGovProvider, options.NextCloudProvider, options.SourceHutProvider:
194+ options.NextCloudProvider, options.SourceHutProvider:
199 return false, nil
195 return false, nil
200 case options.OIDCProvider, options.ADFSProvider, options.AzureProvider, options.CidaasProvider,
196 case options.OIDCProvider, options.ADFSProvider, options.AzureProvider, options.CidaasProvider,
201 options.GitLabProvider, options.KeycloakOIDCProvider, options.MicrosoftEntraIDProvider:
197 options.GitLabProvider, options.KeycloakOIDCProvider, options.MicrosoftEntraIDProvider:
providers/srht.go+1−2
@@ -4,7 +4,6 @@ import (
4 "bytes"
4 "bytes"
5 "context"
5 "context"
6 "fmt"
6 "fmt"
7− "net/http"
8 "net/url"
7 "net/url"
9
8
10 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
9 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
@@ -76,7 +75,7 @@ func NewSourceHutProvider(p *ProviderData) *SourceHutProvider {
76func (p *SourceHutProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
75func (p *SourceHutProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
77 json, err := requests.New(p.ProfileURL.String()).
76 json, err := requests.New(p.ProfileURL.String()).
78 WithContext(ctx).
77 WithContext(ctx).
79− WithMethod(http.MethodPost).
78+ WithMethod("POST").
80 SetHeader("Content-Type", "application/json").
79 SetHeader("Content-Type", "application/json").
81 SetHeader("Authorization", "Bearer "+s.AccessToken).
80 SetHeader("Authorization", "Bearer "+s.AccessToken).
82 WithBody(bytes.NewBufferString(`{"query": "{ me { username, email } }"}`)).
81 WithBody(bytes.NewBufferString(`{"query": "{ me { username, email } }"}`)).
validator.go+1−4
@@ -110,10 +110,6 @@ func NewValidator(domains []string, usersFile string) func(string) bool {
110
110
111// isEmailValidWithDomains checks if the authenticated email is validated against the provided domain
111// isEmailValidWithDomains checks if the authenticated email is validated against the provided domain
112func isEmailValidWithDomains(email string, allowedDomains []string) bool {
112func isEmailValidWithDomains(email string, allowedDomains []string) bool {
113− if strings.Count(email, "@") != 1 {
114− return false
115− }
116−
117 for _, domain := range allowedDomains {
113 for _, domain := range allowedDomains {
118 // allow if the domain is perfect suffix match with the email
114 // allow if the domain is perfect suffix match with the email
119 if strings.HasSuffix(email, "@"+domain) {
115 if strings.HasSuffix(email, "@"+domain) {
@@ -123,6 +119,7 @@ func isEmailValidWithDomains(email string, allowedDomains []string) bool {
123 // allow if the domain is prefixed with . or *. and
119 // allow if the domain is prefixed with . or *. and
124 // the last element (split on @) has the suffix as the domain
120 // the last element (split on @) has the suffix as the domain
125 atoms := strings.Split(email, "@")
121 atoms := strings.Split(email, "@")
122+
126 if (strings.HasPrefix(domain, ".") && strings.HasSuffix(atoms[len(atoms)-1], domain)) ||
123 if (strings.HasPrefix(domain, ".") && strings.HasSuffix(atoms[len(atoms)-1], domain)) ||
127 (strings.HasPrefix(domain, "*.") && strings.HasSuffix(atoms[len(atoms)-1], domain[1:])) {
124 (strings.HasPrefix(domain, "*.") && strings.HasSuffix(atoms[len(atoms)-1], domain[1:])) {
128 return true
125 return true
validator_test.go−21
@@ -404,27 +404,6 @@ func TestValidatorCases(t *testing.T) {
404 allowedDomains: []string{"*.company.com"},
404 allowedDomains: []string{"*.company.com"},
405 expectedAuthZ: false,
405 expectedAuthZ: false,
406 },
406 },
407− {
408− name: "CheckThatTwoAtSignsIsInvalid",
409− email: "attacker@evil.com@company.com",
410− allowedEmails: []string(nil),
411− allowedDomains: []string{"company.com"},
412− expectedAuthZ: false,
413− },
414− {
415− name: "CheckThatTwoAtSignsIsInvalidEvenWithDotPrefix",
416− email: "attacker@evil.com@company.com",
417− allowedEmails: []string(nil),
418− allowedDomains: []string{".company.com"},
419− expectedAuthZ: false,
420− },
421− {
422− name: "CheckThatTwoAtSignsIsInvalidEvenWithWildcardPrefix",
423− email: "attacker@evil.com@foo.company.com",
424− allowedEmails: []string(nil),
425− allowedDomains: []string{"*.company.com"},
426− expectedAuthZ: false,
427− },
428 }
407 }
429
408
430 for _, tc := range testCases {
409 for _, tc := range testCases {