git.iko.soy/typeable/oauth2-proxy
on master help sign in
Request ID Logging (#1087)

* Add RequestID to the RequestScope * Expose RequestID to auth & request loggers * Use the RequestID in templated HTML pages * Allow customizing the RequestID header * Document new Request ID support * Add more cases to scope/requestID tests * Split Get vs Generate RequestID funtionality * Add {{.RequestID}} to the request logger tests * Move RequestID management to RequestScope * Use HTML escape instead of sanitization for Request ID rendering

commitc1267bb92dbc14368e200a61906e9f562fc882fc authorNick Meves <nick.meves@greenhouse.io> dateSun 21 Mar 2021, 11:20 (6 years ago) parent4d9de06
23 files changed+274−72
CHANGELOG.md+3−2 docs/docs/configuration/overview.md+8−5 go.mod+1 go.sum+2−13 oauthproxy.go+10−3 pkg/apis/middleware/scope.go+4 pkg/apis/options/logging.go+3 pkg/app/pagewriter/error.html+8−1 pkg/app/pagewriter/error_page.go+31−7 pkg/app/pagewriter/error_page_test.go+53−9 pkg/app/pagewriter/pagewriter.go+2−2 pkg/app/pagewriter/pagewriter_suite_test.go+2 pkg/app/pagewriter/pagewriter_test.go+16−4 pkg/app/pagewriter/sign_in_page.go+9−2 pkg/app/pagewriter/sign_in_page_test.go+12−4 pkg/app/pagewriter/templates_test.go+2 pkg/logger/logger.go+9−2 pkg/middleware/request_logger_test.go+8−5 pkg/middleware/scope.go+14−1 pkg/middleware/scope_test.go+66−8 pkg/middleware/stored_session_test.go+1 pkg/requests/util/util.go+9−3 pkg/upstream/http_test.go+1−1
CHANGELOG.md+3−2
@@ -4,9 +4,9 @@
44
55## Important Notes
66
7−
87- [#1103](https://github.com/oauth2-proxy/oauth2-proxy/pull/1103) Upstream request signatures via `--signature-key` is
98 deprecated. Support will be removed completely in v8.0.0.
9+- [1087](https://github.com/oauth2-proxy/oauth2-proxy/pull/1087) The default logging templates have been updated to include {{.RequestID}}
1010
1111## Breaking Changes
1212
@@ -14,7 +14,8 @@
1414
1515- [#1045](https://github.com/oauth2-proxy/oauth2-proxy/pull/1045) Ensure redirect URI always has a scheme (@JoelSpeed)
1616- [#1103](https://github.com/oauth2-proxy/oauth2-proxy/pull/1103) Deprecate upstream request signatures (@NickMeves)
17−- [#914](https://github.com/oauth2-proxy/oauth2-proxy/pull/914) Extract email from id_token for azure provider when oidc is configured
17+- [1087](https://github.com/oauth2-proxy/oauth2-proxy/pull/1087) Support Request ID in logging (@NickMeves)
18+- [#914](https://github.com/oauth2-proxy/oauth2-proxy/pull/914) Extract email from id_token for azure provider when oidc is configured (@weinong)
1819- [#1047](https://github.com/oauth2-proxy/oauth2-proxy/pull/1047) Refactor HTTP Server and add ServerGroup to handle graceful shutdown of multiple servers (@JoelSpeed)
1920- [#1070](https://github.com/oauth2-proxy/oauth2-proxy/pull/1070) Refactor logging middleware to middleware package (@NickMeves)
2021- [#1064](https://github.com/oauth2-proxy/oauth2-proxy/pull/1064) Add support for setting groups on session when using basic auth (@stefansedich)
docs/docs/configuration/overview.md+8−5
@@ -108,6 +108,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
108108| `--redis-sentinel-connection-urls` | string \| list | List of Redis sentinel connection URLs (e.g. `redis://HOST[:PORT]`). Used in conjunction with `--redis-use-sentinel` | |
109109| `--redis-use-cluster` | bool | Connect to redis cluster. Must set `--redis-cluster-connection-urls` to use this feature | false |
110110| `--redis-use-sentinel` | bool | Connect to redis via sentinels. Must set `--redis-sentinel-master-name` and `--redis-sentinel-connection-urls` to use this feature | false |
111+| `--request-id-header` | string | Request header to use as the request ID in logging | X-Request-Id |
111112| `--request-logging` | bool | Log requests | true |
112113| `--request-logging-format` | string | Template for request log lines | see [Logging Configuration](#logging-configuration) |
113114| `--resource` | string | The resource that is protected (Azure AD only) | |
@@ -184,7 +185,7 @@ Logging of requests to the `/ping` endpoint (or using `--ping-user-agent`) can b
184185Authentication logs are logs which are guaranteed to contain a username or email address of a user attempting to authenticate. These logs are output by default in the below format:
185186
186187```
187−<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] [<STATUS>] <MESSAGE>
188+<REMOTE_ADDRESS> - <REQUEST ID> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] [<STATUS>] <MESSAGE>
188189```
189190
190191The status block will contain one of the below strings:
@@ -197,7 +198,7 @@ If you require a different format than that, you can configure it with the `--au
197198The default format is configured as follows:
198199
199200```
200−{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}
201+{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}
201202```
202203
203204Available variables for auth logging:
@@ -206,26 +207,27 @@ Available variables for auth logging:
206207| --- | --- | --- |
207208| Client | 74.125.224.72 | The client/remote IP address. Will use the X-Real-IP header it if exists & reverse-proxy is set to true. |
208209| Host | domain.com | The value of the Host header. |
210+| Message | Authenticated via OAuth2 | The details of the auth attempt. |
209211| Protocol | HTTP/1.0 | The request protocol. |
212+| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f | The request ID pulled from the `--request-id-header`. Random UUID if empty |
210213| RequestMethod | GET | The request method. |
211214| Timestamp | 19/Mar/2015:17:20:19 -0400 | The date and time of the logging event. |
212215| UserAgent | - | The full user agent as reported by the requesting client. |
213216| Username | username@email.com | The email or username of the auth request. |
214217| Status | AuthSuccess | The status of the auth request. See above for details. |
215−| Message | Authenticated via OAuth2 | The details of the auth attempt. |
216218
217219### Request Log Format
218220HTTP request logs will output by default in the below format:
219221
220222```
221−<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
223+<REMOTE_ADDRESS> - <REQUEST ID> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
222224```
223225
224226If you require a different format than that, you can configure it with the `--request-logging-format` flag.
225227The default format is configured as follows:
226228
227229```
228−{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
230+{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
229231```
230232
231233Available variables for request logging:
@@ -236,6 +238,7 @@ Available variables for request logging:
236238| Host | domain.com | The value of the Host header. |
237239| Protocol | HTTP/1.0 | The request protocol. |
238240| RequestDuration | 0.001 | The time in seconds that a request took to process. |
241+| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f | The request ID pulled from the `--request-id-header`. Random UUID if empty |
239242| RequestMethod | GET | The request method. |
240243| RequestURI | "/oauth2/auth" | The URI path of the request. |
241244| ResponseSize | 12 | The size in bytes of the response. |
go.mod+1
@@ -13,6 +13,7 @@ require (
1313 github.com/fsnotify/fsnotify v1.4.9
1414 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
1515 github.com/go-redis/redis/v8 v8.2.3
16+ github.com/google/uuid v1.2.0
1617 github.com/justinas/alice v1.2.0
1718 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
1819 github.com/mitchellh/mapstructure v1.1.2
go.sum+2−13
@@ -39,7 +39,6 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
3939github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
4040github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
4141github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
42−github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
4342github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
4443github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4544github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -103,7 +102,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
103102github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
104103github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
105104github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
106−github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
107105github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
108106github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
109107github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
@@ -149,7 +147,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
149147github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
150148github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
151149github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
152−github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
153150github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
154151github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
155152github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
@@ -171,8 +168,9 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
171168github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
172169github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
173170github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
174−github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
175171github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
172+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
173+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
176174github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
177175github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
178176github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -204,7 +202,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
204202github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
205203github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
206204github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
207−github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
208205github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
209206github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
210207github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
@@ -335,7 +332,6 @@ github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAm
335332github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
336333github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
337334github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
338−github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
339335github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
340336github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
341337github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
@@ -345,14 +341,12 @@ github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66Id
345341github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
346342github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
347343github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
348−github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
349344github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
350345github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
351346github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
352347github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
353348github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
354349github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
355−github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
356350github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
357351github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
358352github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
@@ -361,7 +355,6 @@ github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tysp
361355github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
362356github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
363357github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
364−github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
365358github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
366359github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
367360github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
@@ -396,7 +389,6 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9
396389github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
397390github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
398391github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
399−github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
400392github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
401393github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
402394github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -437,7 +429,6 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
437429go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
438430go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
439431go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
440−go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
441432go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
442433go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
443434go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -453,7 +444,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
453444go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
454445golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
455446golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
456−golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
457447golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
458448golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
459449golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -506,7 +496,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
506496golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
507497golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508498golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
509−golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
510499golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
511500golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
512501golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
oauthproxy.go+10−3
@@ -289,7 +289,7 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
289289// the OAuth2 Proxy authentication logic kicks in.
290290// For example forcing HTTPS or health checks.
291291func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
292− chain := alice.New(middleware.NewScope(opts.ReverseProxy))
292+ chain := alice.New(middleware.NewScope(opts.ReverseProxy, opts.Logging.RequestIDHeader))
293293
294294 if opts.ForceHTTPS {
295295 _, httpsPort, err := net.SplitHostPort(opts.Server.SecureBindAddress)
@@ -596,7 +596,14 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code i
596596 redirectURL = "/"
597597 }
598598
599− p.pageWriter.WriteErrorPage(rw, code, redirectURL, appError, messages...)
599+ scope := middlewareapi.GetRequestScope(req)
600+ p.pageWriter.WriteErrorPage(rw, pagewriter.ErrorPageOpts{
601+ Status: code,
602+ RedirectURL: redirectURL,
603+ RequestID: scope.RequestID,
604+ AppError: appError,
605+ Messages: messages,
606+ })
600607}
601608
602609// IsAllowedRequest is used to check if auth should be skipped for this request
@@ -657,7 +664,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
657664 redirectURL = "/"
658665 }
659666
660− p.pageWriter.WriteSignInPage(rw, redirectURL)
667+ p.pageWriter.WriteSignInPage(rw, req, redirectURL)
661668}
662669
663670// ManualSignIn handles basic auth logins to the proxy
pkg/apis/middleware/scope.go+4
@@ -21,6 +21,10 @@ type RequestScope struct {
2121 // mode and if request `X-Forwarded-*` headers should be trusted
2222 ReverseProxy bool
2323
24+ // RequestID is set to the request's `X-Request-Id` header if set.
25+ // Otherwise a random UUID is set.
26+ RequestID string
27+
2428 // Session details the authenticated users information (if it exists).
2529 Session *sessions.SessionState
2630
pkg/apis/options/logging.go+3
@@ -17,6 +17,7 @@ type Logging struct {
1717 ExcludePaths []string `flag:"exclude-logging-path" cfg:"exclude_logging_paths"`
1818 LocalTime bool `flag:"logging-local-time" cfg:"logging_local_time"`
1919 SilencePing bool `flag:"silence-ping-logging" cfg:"silence_ping_logging"`
20+ RequestIDHeader string `flag:"request-id-header" cfg:"request_id_header"`
2021 File LogFileOptions `cfg:",squash"`
2122}
2223
@@ -43,6 +44,7 @@ func loggingFlagSet() *pflag.FlagSet {
4344 flagSet.StringSlice("exclude-logging-path", []string{}, "Exclude logging requests to paths (eg: '/path1,/path2,/path3')")
4445 flagSet.Bool("logging-local-time", true, "If the time in log files and backup filenames are local or UTC time")
4546 flagSet.Bool("silence-ping-logging", false, "Disable logging of requests to ping endpoint")
47+ flagSet.String("request-id-header", "X-Request-Id", "Request header to use as the request ID")
4648
4749 flagSet.String("logging-filename", "", "File to log requests to, empty for stdout")
4850 flagSet.Int("logging-max-size", 100, "Maximum size in megabytes of the log file before rotation")
@@ -59,6 +61,7 @@ func loggingDefaults() Logging {
5961 ExcludePaths: nil,
6062 LocalTime: true,
6163 SilencePing: false,
64+ RequestIDHeader: "X-Request-Id",
6265 AuthEnabled: true,
6366 AuthFormat: logger.DefaultAuthLoggingFormat,
6467 RequestEnabled: true,
pkg/app/pagewriter/error.html+8−1
@@ -47,7 +47,7 @@
4747 <h1 class="subtitle is-1">{{.Title}}</h1>
4848 </div>
4949
50− {{ if .Message }}
50+ {{ if or .Message .RequestID }}
5151 <div id="more-info" class="block card is-fullwidth is-shadowless">
5252 <header class="card-header is-shadowless">
5353 <p class="card-header-title">More Info</p>
@@ -56,9 +56,16 @@
5656 </a>
5757 </header>
5858 <div class="card-content has-text-left is-hidden">
59+ {{ if .Message }}
5960 <div class="content">
6061 {{.Message}}
6162 </div>
63+ {{ end }}
64+ {{ if .RequestID }}
65+ <div class="content">
66+ Request ID: {{.RequestID}}
67+ </div>
68+ {{ end }}
6269 </div>
6370 </div>
6471 {{ end }}
pkg/app/pagewriter/error_page.go+31−7
@@ -5,6 +5,7 @@ import (
55 "html/template"
66 "net/http"
77
8+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
89 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
910)
1011
@@ -37,11 +38,25 @@ type errorPageWriter struct {
3738 debug bool
3839}
3940
41+// ErrorPageOpts bundles up all the content needed to write the Error Page
42+type ErrorPageOpts struct {
43+ // HTTP status code
44+ Status int
45+ // Redirect URL for "Go back" and "Sign in" buttons
46+ RedirectURL string
47+ // The UUID of the request
48+ RequestID string
49+ // App Error shown in debug mode
50+ AppError string
51+ // Generic error messages shown in non-debug mode
52+ Messages []interface{}
53+}
54+
4055// WriteErrorPage writes an error page to the given response writer.
4156// It uses the passed redirectURL to give users the option to go back to where
4257// they originally came from or try signing in again.
43−func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{}) {
44− rw.WriteHeader(status)
58+func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, opts ErrorPageOpts) {
59+ rw.WriteHeader(opts.Status)
4560
4661 // We allow unescaped template.HTML since it is user configured options
4762 /* #nosec G203 */
@@ -51,14 +66,16 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, red
5166 ProxyPrefix string
5267 StatusCode int
5368 Redirect string
69+ RequestID string
5470 Footer template.HTML
5571 Version string
5672 }{
57− Title: http.StatusText(status),
58− Message: e.getMessage(status, appError, messages...),
73+ Title: http.StatusText(opts.Status),
74+ Message: e.getMessage(opts.Status, opts.AppError, opts.Messages...),
5975 ProxyPrefix: e.proxyPrefix,
60− StatusCode: status,
61− Redirect: redirectURL,
76+ StatusCode: opts.Status,
77+ Redirect: opts.RedirectURL,
78+ RequestID: opts.RequestID,
6279 Footer: template.HTML(e.footer),
6380 Version: e.version,
6481 }
@@ -74,7 +91,14 @@ func (e *errorPageWriter) WriteErrorPage(rw http.ResponseWriter, status int, red
7491// It is expected to always render a bad gateway error.
7592func (e *errorPageWriter) ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error) {
7693 logger.Errorf("Error proxying to upstream server: %v", proxyErr)
77− e.WriteErrorPage(rw, http.StatusBadGateway, "", proxyErr.Error(), "There was a problem connecting to the upstream server.")
94+ scope := middlewareapi.GetRequestScope(req)
95+ e.WriteErrorPage(rw, ErrorPageOpts{
96+ Status: http.StatusBadGateway,
97+ RedirectURL: "", // The user is already logged in and has hit an upstream error. Makes no sense to redirect in this case.
98+ RequestID: scope.RequestID,
99+ AppError: proxyErr.Error(),
100+ Messages: []interface{}{"There was a problem connecting to the upstream server."},
101+ })
78102}
79103
80104// getMessage creates the message for the template parameters.
pkg/app/pagewriter/error_page_test.go+53−9
@@ -6,6 +6,7 @@ import (
66 "io/ioutil"
77 "net/http/httptest"
88
9+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
910 . "github.com/onsi/ginkgo"
1011 . "github.com/onsi/gomega"
1112)
@@ -14,7 +15,7 @@ var _ = Describe("Error Page Writer", func() {
1415 var errorPage *errorPageWriter
1516
1617 BeforeEach(func() {
17− tmpl, err := template.New("").Parse("{{.Title}} {{.Message}} {{.ProxyPrefix}} {{.StatusCode}} {{.Redirect}} {{.Footer}} {{.Version}}")
18+ tmpl, err := template.New("").Parse("{{.Title}} {{.Message}} {{.ProxyPrefix}} {{.StatusCode}} {{.Redirect}} {{.RequestID}} {{.Footer}} {{.Version}}")
1819 Expect(err).ToNot(HaveOccurred())
1920
2021 errorPage = &errorPageWriter{
@@ -28,41 +29,77 @@ var _ = Describe("Error Page Writer", func() {
2829 Context("WriteErrorPage", func() {
2930 It("Writes the template to the response writer", func() {
3031 recorder := httptest.NewRecorder()
31− errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied")
32+ errorPage.WriteErrorPage(recorder, ErrorPageOpts{
33+ Status: 403,
34+ RedirectURL: "/redirect",
35+ RequestID: testRequestID,
36+ AppError: "Access Denied",
37+ })
3238
3339 body, err := ioutil.ReadAll(recorder.Result().Body)
3440 Expect(err).ToNot(HaveOccurred())
35− Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test"))
41+ Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
3642 })
3743
3844 It("With a different code, uses the stock message for the correct code", func() {
3945 recorder := httptest.NewRecorder()
40− errorPage.WriteErrorPage(recorder, 500, "/redirect", "Access Denied")
46+ errorPage.WriteErrorPage(recorder, ErrorPageOpts{
47+ Status: 500,
48+ RedirectURL: "/redirect",
49+ RequestID: testRequestID,
50+ AppError: "Access Denied",
51+ })
4152
4253 body, err := ioutil.ReadAll(recorder.Result().Body)
4354 Expect(err).ToNot(HaveOccurred())
44− Expect(string(body)).To(Equal("Internal Server Error Oops! Something went wrong. For more information contact your server administrator. /prefix/ 500 /redirect Custom Footer Text v0.0.0-test"))
55+ Expect(string(body)).To(Equal("Internal Server Error Oops! Something went wrong. For more information contact your server administrator. /prefix/ 500 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
4556 })
4657
4758 It("With a message override, uses the message", func() {
4859 recorder := httptest.NewRecorder()
49− errorPage.WriteErrorPage(recorder, 403, "/redirect", "Access Denied", "An extra message: %s", "with more context.")
60+ errorPage.WriteErrorPage(recorder, ErrorPageOpts{
61+ Status: 403,
62+ RedirectURL: "/redirect",
63+ RequestID: testRequestID,
64+ AppError: "Access Denied",
65+ Messages: []interface{}{
66+ "An extra message: %s",
67+ "with more context.",
68+ },
69+ })
5070
5171 body, err := ioutil.ReadAll(recorder.Result().Body)
5272 Expect(err).ToNot(HaveOccurred())
53− Expect(string(body)).To(Equal("Forbidden An extra message: with more context. /prefix/ 403 /redirect Custom Footer Text v0.0.0-test"))
73+ Expect(string(body)).To(Equal("Forbidden An extra message: with more context. /prefix/ 403 /redirect 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
74+ })
75+
76+ It("Sanitizes malicious user input", func() {
77+ recorder := httptest.NewRecorder()
78+ errorPage.WriteErrorPage(recorder, ErrorPageOpts{
79+ Status: 403,
80+ RedirectURL: "/redirect",
81+ RequestID: "<script>alert(1)</script>",
82+ AppError: "Access Denied",
83+ })
84+
85+ body, err := ioutil.ReadAll(recorder.Result().Body)
86+ Expect(err).ToNot(HaveOccurred())
87+ Expect(string(body)).To(Equal("Forbidden You do not have permission to access this resource. /prefix/ 403 /redirect &lt;script&gt;alert(1)&lt;/script&gt; Custom Footer Text v0.0.0-test"))
5488 })
5589 })
5690
5791 Context("ProxyErrorHandler", func() {
5892 It("Writes a bad gateway error the response writer", func() {
5993 req := httptest.NewRequest("", "/bad-gateway", nil)
94+ req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
95+ RequestID: testRequestID,
96+ })
6097 recorder := httptest.NewRecorder()
6198 errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error"))
6299
63100 body, err := ioutil.ReadAll(recorder.Result().Body)
64101 Expect(err).ToNot(HaveOccurred())
65− Expect(string(body)).To(Equal("Bad Gateway There was a problem connecting to the upstream server. /prefix/ 502 Custom Footer Text v0.0.0-test"))
102+ Expect(string(body)).To(Equal("Bad Gateway There was a problem connecting to the upstream server. /prefix/ 502 11111111-2222-4333-8444-555555555555 Custom Footer Text v0.0.0-test"))
66103 })
67104 })
68105
@@ -78,7 +115,11 @@ var _ = Describe("Error Page Writer", func() {
78115 Context("WriteErrorPage", func() {
79116 It("Writes the detailed error in place of the message", func() {
80117 recorder := httptest.NewRecorder()
81− errorPage.WriteErrorPage(recorder, 403, "/redirect", "Debug error")
118+ errorPage.WriteErrorPage(recorder, ErrorPageOpts{
119+ Status: 403,
120+ RedirectURL: "/redirect",
121+ AppError: "Debug error",
122+ })
82123
83124 body, err := ioutil.ReadAll(recorder.Result().Body)
84125 Expect(err).ToNot(HaveOccurred())
@@ -89,6 +130,9 @@ var _ = Describe("Error Page Writer", func() {
89130 Context("ProxyErrorHandler", func() {
90131 It("Writes a bad gateway error the response writer", func() {
91132 req := httptest.NewRequest("", "/bad-gateway", nil)
133+ req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
134+ RequestID: testRequestID,
135+ })
92136 recorder := httptest.NewRecorder()
93137 errorPage.ProxyErrorHandler(recorder, req, errors.New("some upstream error"))
94138
pkg/app/pagewriter/pagewriter.go+2−2
@@ -10,8 +10,8 @@ import (
1010// It can also be used to write errors for the http.ReverseProxy used in the
1111// upstream package.
1212type Writer interface {
13− WriteSignInPage(rw http.ResponseWriter, redirectURL string)
14− WriteErrorPage(rw http.ResponseWriter, status int, redirectURL string, appError string, messages ...interface{})
13+ WriteSignInPage(rw http.ResponseWriter, req *http.Request, redirectURL string)
14+ WriteErrorPage(rw http.ResponseWriter, opts ErrorPageOpts)
1515 ProxyErrorHandler(rw http.ResponseWriter, req *http.Request, proxyErr error)
1616}
1717
pkg/app/pagewriter/pagewriter_suite_test.go+2
@@ -8,6 +8,8 @@ import (
88 . "github.com/onsi/gomega"
99)
1010
11+const testRequestID = "11111111-2222-4333-8444-555555555555"
12+
1113func TestOptionsSuite(t *testing.T) {
1214 logger.SetOutput(GinkgoWriter)
1315 logger.SetErrOutput(GinkgoWriter)
pkg/app/pagewriter/pagewriter_test.go+16−4
@@ -2,6 +2,7 @@ package pagewriter
22
33import (
44 "io/ioutil"
5+ "net/http"
56 "net/http/httptest"
67 "os"
78 "path/filepath"
@@ -14,6 +15,7 @@ var _ = Describe("Writer", func() {
1415 Context("NewWriter", func() {
1516 var writer Writer
1617 var opts Opts
18+ var request *http.Request
1719
1820 BeforeEach(func() {
1921 opts = Opts{
@@ -26,6 +28,8 @@ var _ = Describe("Writer", func() {
2628 ProviderName: "<ProviderName>",
2729 SignInMessage: "<SignInMessage>",
2830 }
31+
32+ request = httptest.NewRequest("", "http://127.0.0.1/", nil)
2933 })
3034
3135 Context("With no custom templates", func() {
@@ -37,7 +41,11 @@ var _ = Describe("Writer", func() {
3741
3842 It("Writes the default error template", func() {
3943 recorder := httptest.NewRecorder()
40− writer.WriteErrorPage(recorder, 500, "/redirect", "Some debug error")
44+ writer.WriteErrorPage(recorder, ErrorPageOpts{
45+ Status: 500,
46+ RedirectURL: "/redirect",
47+ AppError: "Some debug error",
48+ })
4149
4250 body, err := ioutil.ReadAll(recorder.Result().Body)
4351 Expect(err).ToNot(HaveOccurred())
@@ -46,7 +54,7 @@ var _ = Describe("Writer", func() {
4654
4755 It("Writes the default sign in template", func() {
4856 recorder := httptest.NewRecorder()
49− writer.WriteSignInPage(recorder, "/redirect")
57+ writer.WriteSignInPage(recorder, request, "/redirect")
5058
5159 body, err := ioutil.ReadAll(recorder.Result().Body)
5260 Expect(err).ToNot(HaveOccurred())
@@ -80,7 +88,11 @@ var _ = Describe("Writer", func() {
8088
8189 It("Writes the custom error template", func() {
8290 recorder := httptest.NewRecorder()
83− writer.WriteErrorPage(recorder, 500, "/redirect", "Some debug error")
91+ writer.WriteErrorPage(recorder, ErrorPageOpts{
92+ Status: 500,
93+ RedirectURL: "/redirect",
94+ AppError: "Some debug error",
95+ })
8496
8597 body, err := ioutil.ReadAll(recorder.Result().Body)
8698 Expect(err).ToNot(HaveOccurred())
@@ -89,7 +101,7 @@ var _ = Describe("Writer", func() {
89101
90102 It("Writes the custom sign in template", func() {
91103 recorder := httptest.NewRecorder()
92− writer.WriteSignInPage(recorder, "/redirect")
104+ writer.WriteSignInPage(recorder, request, "/redirect")
93105
94106 body, err := ioutil.ReadAll(recorder.Result().Body)
95107 Expect(err).ToNot(HaveOccurred())
pkg/app/pagewriter/sign_in_page.go+9−2
@@ -13,6 +13,7 @@ import (
1313 "html/template"
1414 "net/http"
1515
16+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1617 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1718)
1819
@@ -53,7 +54,7 @@ type signInPageWriter struct {
5354
5455// WriteSignInPage writes the sign-in page to the given response writer.
5556// It uses the redirectURL to be able to set the final destination for the user post login.
56−func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL string) {
57+func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, req *http.Request, redirectURL string) {
5758 // We allow unescaped template.HTML since it is user configured options
5859 /* #nosec G203 */
5960 t := struct {
@@ -79,7 +80,13 @@ func (s *signInPageWriter) WriteSignInPage(rw http.ResponseWriter, redirectURL s
7980 err := s.template.Execute(rw, t)
8081 if err != nil {
8182 logger.Printf("Error rendering sign-in template: %v", err)
82− s.errorPageWriter.WriteErrorPage(rw, http.StatusInternalServerError, redirectURL, err.Error())
83+ scope := middlewareapi.GetRequestScope(req)
84+ s.errorPageWriter.WriteErrorPage(rw, ErrorPageOpts{
85+ Status: http.StatusInternalServerError,
86+ RedirectURL: redirectURL,
87+ RequestID: scope.RequestID,
88+ AppError: err.Error(),
89+ })
8390 }
8491}
8592
pkg/app/pagewriter/sign_in_page_test.go+12−4
@@ -5,11 +5,13 @@ import (
55 "fmt"
66 "html/template"
77 "io/ioutil"
8+ "net/http"
89 "net/http/httptest"
910 "os"
1011 "path/filepath"
1112 "strings"
1213
14+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1315 . "github.com/onsi/ginkgo"
1416 . "github.com/onsi/ginkgo/extensions/table"
1517 . "github.com/onsi/gomega"
@@ -18,10 +20,11 @@ import (
1820var _ = Describe("SignIn Page", func() {
1921
2022 Context("SignIn Page Writer", func() {
23+ var request *http.Request
2124 var signInPage *signInPageWriter
2225
2326 BeforeEach(func() {
24− errorTmpl, err := template.New("").Parse("{{.Title}}")
27+ errorTmpl, err := template.New("").Parse("{{.Title}} | {{.RequestID}}")
2528 Expect(err).ToNot(HaveOccurred())
2629 errorPage := &errorPageWriter{
2730 template: errorTmpl,
@@ -41,12 +44,17 @@ var _ = Describe("SignIn Page", func() {
4144 displayLoginForm: true,
4245 logoData: "Logo Data",
4346 }
47+
48+ request = httptest.NewRequest("", "http://127.0.0.1/", nil)
49+ request = middlewareapi.AddRequestScope(request, &middlewareapi.RequestScope{
50+ RequestID: testRequestID,
51+ })
4452 })
4553
4654 Context("WriteSignInPage", func() {
4755 It("Writes the template to the response writer", func() {
4856 recorder := httptest.NewRecorder()
49− signInPage.WriteSignInPage(recorder, "/redirect")
57+ signInPage.WriteSignInPage(recorder, request, "/redirect")
5058
5159 body, err := ioutil.ReadAll(recorder.Result().Body)
5260 Expect(err).ToNot(HaveOccurred())
@@ -60,11 +68,11 @@ var _ = Describe("SignIn Page", func() {
6068 signInPage.template = tmpl
6169
6270 recorder := httptest.NewRecorder()
63− signInPage.WriteSignInPage(recorder, "/redirect")
71+ signInPage.WriteSignInPage(recorder, request, "/redirect")
6472
6573 body, err := ioutil.ReadAll(recorder.Result().Body)
6674 Expect(err).ToNot(HaveOccurred())
67− Expect(string(body)).To(Equal("Internal Server Error"))
75+ Expect(string(body)).To(Equal(fmt.Sprintf("Internal Server Error | %s", testRequestID)))
6876 })
6977 })
7078 })
pkg/app/pagewriter/templates_test.go+2
@@ -51,6 +51,7 @@ var _ = Describe("Templates", func() {
5151 StatusCode int
5252 Title string
5353 Message string
54+ RequestID string
5455
5556 // For custom templates
5657 TestString string
@@ -67,6 +68,7 @@ var _ = Describe("Templates", func() {
6768 StatusCode: 404,
6869 Title: "<title>",
6970 Message: "<message>",
71+ RequestID: "<request-id>",
7072
7173 TestString: "Testing",
7274 }
pkg/logger/logger.go+9−2
@@ -12,6 +12,7 @@ import (
1212 "text/template"
1313 "time"
1414
15+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
1516 requestutil "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
1617)
1718
@@ -25,9 +26,9 @@ const (
2526 // DefaultStandardLoggingFormat defines the default standard log format
2627 DefaultStandardLoggingFormat = "[{{.Timestamp}}] [{{.File}}] {{.Message}}"
2728 // DefaultAuthLoggingFormat defines the default auth log format
28− DefaultAuthLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
29+ DefaultAuthLoggingFormat = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}"
2930 // DefaultRequestLoggingFormat defines the default request log format
30− DefaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
31+ DefaultRequestLoggingFormat = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
3132
3233 // AuthSuccess indicates that an auth attempt has succeeded explicitly
3334 AuthSuccess AuthStatus = "AuthSuccess"
@@ -63,6 +64,7 @@ type authLogMessageData struct {
6364 Client,
6465 Host,
6566 Protocol,
67+ RequestID,
6668 RequestMethod,
6769 Timestamp,
6870 UserAgent,
@@ -75,6 +77,7 @@ type reqLogMessageData struct {
7577 Client,
7678 Host,
7779 Protocol,
80+ RequestID,
7881 RequestDuration,
7982 RequestMethod,
8083 RequestURI,
@@ -195,10 +198,12 @@ func (l *Logger) PrintAuthf(username string, req *http.Request, status AuthStatu
195198 l.mu.Lock()
196199 defer l.mu.Unlock()
197200
201+ scope := middlewareapi.GetRequestScope(req)
198202 err := l.authTemplate.Execute(l.writer, authLogMessageData{
199203 Client: client,
200204 Host: requestutil.GetRequestHost(req),
201205 Protocol: req.Proto,
206+ RequestID: scope.RequestID,
202207 RequestMethod: req.Method,
203208 Timestamp: FormatTimestamp(now),
204209 UserAgent: fmt.Sprintf("%q", req.UserAgent()),
@@ -249,10 +254,12 @@ func (l *Logger) PrintReq(username, upstream string, req *http.Request, url url.
249254 l.mu.Lock()
250255 defer l.mu.Unlock()
251256
257+ scope := middlewareapi.GetRequestScope(req)
252258 err := l.reqTemplate.Execute(l.writer, reqLogMessageData{
253259 Client: client,
254260 Host: requestutil.GetRequestHost(req),
255261 Protocol: req.Proto,
262+ RequestID: scope.RequestID,
256263 RequestDuration: fmt.Sprintf("%0.3f", duration),
257264 RequestMethod: req.Method,
258265 RequestURI: fmt.Sprintf("%q", url.RequestURI()),
pkg/middleware/request_logger_test.go+8−5
@@ -13,7 +13,7 @@ import (
1313 . "github.com/onsi/gomega"
1414)
1515
16−const RequestLoggingFormatWithoutTime = "{{.Client}} - {{.Username}} [TIMELESS] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
16+const RequestLoggingFormatWithoutTime = "{{.Client}} - {{.RequestID}} - {{.Username}} [TIMELESS] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
1717
1818var _ = Describe("Request logger suite", func() {
1919 type requestLoggerTableInput struct {
@@ -37,7 +37,10 @@ var _ = Describe("Request logger suite", func() {
3737 req.RemoteAddr = "127.0.0.1"
3838 req.Host = "test-server"
3939
40− scope := &middlewareapi.RequestScope{Session: in.Session}
40+ scope := &middlewareapi.RequestScope{
41+ RequestID: "11111111-2222-4333-8444-555555555555",
42+ Session: in.Session,
43+ }
4144 req = middlewareapi.AddRequestScope(req, scope)
4245
4346 handler := NewRequestLogger()(testUpstreamHandler(in.Upstream))
@@ -47,7 +50,7 @@ var _ = Describe("Request logger suite", func() {
4750 },
4851 Entry("standard request", &requestLoggerTableInput{
4952 Format: RequestLoggingFormatWithoutTime,
50− ExpectedLogMessage: "127.0.0.1 - standard.user [TIMELESS] test-server GET standard \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
53+ ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - standard.user [TIMELESS] test-server GET standard \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
5154 Path: "/foo/bar",
5255 ExcludePaths: []string{},
5356 Upstream: "standard",
@@ -55,7 +58,7 @@ var _ = Describe("Request logger suite", func() {
5558 }),
5659 Entry("with unrelated path excluded", &requestLoggerTableInput{
5760 Format: RequestLoggingFormatWithoutTime,
58− ExpectedLogMessage: "127.0.0.1 - unrelated.exclusion [TIMELESS] test-server GET unrelated \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
61+ ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - unrelated.exclusion [TIMELESS] test-server GET unrelated \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n",
5962 Path: "/foo/bar",
6063 ExcludePaths: []string{"/ping"},
6164 Upstream: "unrelated",
@@ -69,7 +72,7 @@ var _ = Describe("Request logger suite", func() {
6972 }),
7073 Entry("ping path", &requestLoggerTableInput{
7174 Format: RequestLoggingFormatWithoutTime,
72− ExpectedLogMessage: "127.0.0.1 - mr.ping [TIMELESS] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n",
75+ ExpectedLogMessage: "127.0.0.1 - 11111111-2222-4333-8444-555555555555 - mr.ping [TIMELESS] test-server GET - \"/ping\" HTTP/1.1 \"\" 200 4 0.000\n",
7376 Path: "/ping",
7477 ExcludePaths: []string{},
7578 Upstream: "",
pkg/middleware/scope.go+14−1
@@ -3,18 +3,31 @@ package middleware
33import (
44 "net/http"
55
6+ "github.com/google/uuid"
67 "github.com/justinas/alice"
78 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
89)
910
10−func NewScope(reverseProxy bool) alice.Constructor {
11+func NewScope(reverseProxy bool, idHeader string) alice.Constructor {
1112 return func(next http.Handler) http.Handler {
1213 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
1314 scope := &middlewareapi.RequestScope{
1415 ReverseProxy: reverseProxy,
16+ RequestID: genRequestID(req, idHeader),
1517 }
1618 req = middlewareapi.AddRequestScope(req, scope)
1719 next.ServeHTTP(rw, req)
1820 })
1921 }
2022}
23+
24+// genRequestID sets a request-wide ID for use in logging or error pages.
25+// If a RequestID header is set, it uses that. Otherwise, it generates a random
26+// UUID for the lifespan of the request.
27+func genRequestID(req *http.Request, idHeader string) string {
28+ rid := req.Header.Get(idHeader)
29+ if rid != "" {
30+ return rid
31+ }
32+ return uuid.New().String()
33+}
pkg/middleware/scope_test.go+66−8
@@ -4,11 +4,19 @@ import (
44 "net/http"
55 "net/http/httptest"
66
7+ "github.com/google/uuid"
78 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
89 . "github.com/onsi/ginkgo"
910 . "github.com/onsi/gomega"
1011)
1112
13+const (
14+ testRequestHeader = "X-Request-Id"
15+ testRequestID = "11111111-2222-4333-8444-555555555555"
16+ // mockRand io.Reader below counts bytes from 0-255 in order
17+ testRandomUUID = "00010203-0405-4607-8809-0a0b0c0d0e0f"
18+)
19+
1220var _ = Describe("Scope Suite", func() {
1321 Context("NewScope", func() {
1422 var request, nextRequest *http.Request
@@ -24,10 +32,11 @@ var _ = Describe("Scope Suite", func() {
2432
2533 Context("ReverseProxy is false", func() {
2634 BeforeEach(func() {
27− handler := NewScope(false)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
28− nextRequest = r
29− w.WriteHeader(200)
30− }))
35+ handler := NewScope(false, testRequestHeader)(
36+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37+ nextRequest = r
38+ w.WriteHeader(200)
39+ }))
3140 handler.ServeHTTP(rw, request)
3241 })
3342
@@ -52,10 +61,11 @@ var _ = Describe("Scope Suite", func() {
5261
5362 Context("ReverseProxy is true", func() {
5463 BeforeEach(func() {
55− handler := NewScope(true)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
56− nextRequest = r
57− w.WriteHeader(200)
58− }))
64+ handler := NewScope(true, testRequestHeader)(
65+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66+ nextRequest = r
67+ w.WriteHeader(200)
68+ }))
5969 handler.ServeHTTP(rw, request)
6070 })
6171
@@ -65,5 +75,53 @@ var _ = Describe("Scope Suite", func() {
6575 Expect(scope.ReverseProxy).To(BeTrue())
6676 })
6777 })
78+
79+ Context("Request ID header is present", func() {
80+ BeforeEach(func() {
81+ request.Header.Add(testRequestHeader, testRequestID)
82+ handler := NewScope(false, testRequestHeader)(
83+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
84+ nextRequest = r
85+ w.WriteHeader(200)
86+ }))
87+ handler.ServeHTTP(rw, request)
88+ })
89+
90+ It("sets the RequestID using the request", func() {
91+ scope := middlewareapi.GetRequestScope(nextRequest)
92+ Expect(scope.RequestID).To(Equal(testRequestID))
93+ })
94+ })
95+
96+ Context("Request ID header is missing", func() {
97+ BeforeEach(func() {
98+ uuid.SetRand(mockRand{})
99+
100+ handler := NewScope(true, testRequestHeader)(
101+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102+ nextRequest = r
103+ w.WriteHeader(200)
104+ }))
105+ handler.ServeHTTP(rw, request)
106+ })
107+
108+ AfterEach(func() {
109+ uuid.SetRand(nil)
110+ })
111+
112+ It("sets the RequestID using a random UUID", func() {
113+ scope := middlewareapi.GetRequestScope(nextRequest)
114+ Expect(scope.RequestID).To(Equal(testRandomUUID))
115+ })
116+ })
68117 })
69118})
119+
120+type mockRand struct{}
121+
122+func (mockRand) Read(p []byte) (int, error) {
123+ for i := range p {
124+ p[i] = byte(i % 256)
125+ }
126+ return len(p), nil
127+}
pkg/middleware/stored_session_test.go+1
@@ -401,6 +401,7 @@ var _ = Describe("Stored Session Suite", func() {
401401 }
402402
403403 req := httptest.NewRequest("", "/", nil)
404+ req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{})
404405 refreshed, err := s.refreshSessionWithProvider(nil, req, in.session)
405406 if in.expectedErr != nil {
406407 Expect(err).To(MatchError(in.expectedErr))
pkg/requests/util/util.go+9−3
@@ -6,10 +6,16 @@ import (
66 middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
77)
88
9+const (
10+ XForwardedProto = "X-Forwarded-Proto"
11+ XForwardedHost = "X-Forwarded-Host"
12+ XForwardedURI = "X-Forwarded-Uri"
13+)
14+
915// GetRequestProto returns the request scheme or X-Forwarded-Proto if present
1016// and the request is proxied.
1117func GetRequestProto(req *http.Request) string {
12− proto := req.Header.Get("X-Forwarded-Proto")
18+ proto := req.Header.Get(XForwardedProto)
1319 if !IsProxied(req) || proto == "" {
1420 proto = req.URL.Scheme
1521 }
@@ -19,7 +25,7 @@ func GetRequestProto(req *http.Request) string {
1925// GetRequestHost returns the request host header or X-Forwarded-Host if
2026// present and the request is proxied.
2127func GetRequestHost(req *http.Request) string {
22− host := req.Header.Get("X-Forwarded-Host")
28+ host := req.Header.Get(XForwardedHost)
2329 if !IsProxied(req) || host == "" {
2430 host = req.Host
2531 }
@@ -29,7 +35,7 @@ func GetRequestHost(req *http.Request) string {
2935// GetRequestURI return the request URI or X-Forwarded-Uri if present and the
3036// request is proxied.
3137func GetRequestURI(req *http.Request) string {
32− uri := req.Header.Get("X-Forwarded-Uri")
38+ uri := req.Header.Get(XForwardedURI)
3339 if !IsProxied(req) || uri == "" {
3440 // Use RequestURI to preserve ?query
3541 uri = req.URL.RequestURI()
pkg/upstream/http_test.go+1−1
@@ -390,7 +390,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
390390
391391 handler := newHTTPUpstreamProxy(upstream, u, nil, nil)
392392
393− proxyServer = httptest.NewServer(middleware.NewScope(false)(handler))
393+ proxyServer = httptest.NewServer(middleware.NewScope(false, "X-Request-Id")(handler))
394394 })
395395
396396 AfterEach(func() {