Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 09:15 UTC
reverseproxy: Mask the WS close message when we're the client (#5199)
* reverseproxy: Mask the WS close message when we're the client * weakrand * Bump golangci-lint version so path ignores work on Windows * gofmt * ugh, gofmt everything, I guess
Francis Lavoie committed
Nov 14, 2022 at 11:38 UTC
ee7c92ec9b57c671c9091ff993b1a24251020c25
20 files changed
+209
-123
.github/workflows/lint.yml
+1
-1
@@ -34,7 +34,7 @@ jobs:
34
- name: golangci-lint
35
uses: golangci/golangci-lint-action@v3
36
with:
37
- version: v1.47
37
+ version: v1.50
38
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
39
args: --timeout 10m
40
# Optional: show only new issues if it's a pull request. The default value is `false`.
.golangci.yml
+4
@@ -96,3 +96,7 @@ issues:
96
text: "G404" # G404: Insecure random number source (rand)
97
linters:
98
- gosec
99
+ - path: modules/caddyhttp/reverseproxy/streaming.go
100
+ text: "G404" # G404: Insecure random number source (rand)
101
+ linters:
102
+ - gosec
caddyconfig/httpcaddyfile/pkiapp.go
+17
-17
@@ -26,23 +26,23 @@ func init() {
26
27
// parsePKIApp parses the global log option. Syntax:
28
//
29
-// pki {
30
-// ca [<id>] {
31
-// name <name>
32
-// root_cn <name>
33
-// intermediate_cn <name>
34
-// root {
35
-// cert <path>
36
-// key <path>
37
-// format <format>
38
-// }
39
-// intermediate {
40
-// cert <path>
41
-// key <path>
42
-// format <format>
43
-// }
44
-// }
45
-// }
29
+// pki {
30
+// ca [<id>] {
31
+// name <name>
32
+// root_cn <name>
33
+// intermediate_cn <name>
34
+// root {
35
+// cert <path>
36
+// key <path>
37
+// format <format>
38
+// }
39
+// intermediate {
40
+// cert <path>
41
+// key <path>
42
+// format <format>
43
+// }
44
+// }
45
+// }
46
//
47
// When the CA ID is unspecified, 'local' is assumed.
48
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
cmd/caddy/main.go
+4
-4
@@ -19,10 +19,10 @@
19
// There is no need to modify the Caddy source code to customize your
20
// builds. You can easily build a custom Caddy with these simple steps:
21
//
22
-// 1. Copy this file (main.go) into a new folder
23
-// 2. Edit the imports below to include the modules you want plugged in
24
-// 3. Run `go mod init caddy`
25
-// 4. Run `go install` or `go build` - you now have a custom binary!
22
+// 1. Copy this file (main.go) into a new folder
23
+// 2. Edit the imports below to include the modules you want plugged in
24
+// 3. Run `go mod init caddy`
25
+// 4. Run `go install` or `go build` - you now have a custom binary!
26
//
27
// Or you can use xcaddy which does it all for you as a command:
28
// https://github.com/caddyserver/xcaddy
modules/caddyhttp/caddyauth/caddyfile.go
+4
-4
@@ -27,10 +27,10 @@ func init() {
27
28
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
29
//
30
-// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
31
-// <username> <hashed_password_base64> [<salt_base64>]
32
-// ...
33
-// }
30
+// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
31
+// <username> <hashed_password_base64> [<salt_base64>]
32
+// ...
33
+// }
34
//
35
// If no hash algorithm is supplied, bcrypt will be assumed.
36
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
modules/caddyhttp/encode/caddyfile.go
+12
-12
@@ -39,18 +39,18 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
39
40
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
41
//
42
-// encode [<matcher>] <formats...> {
43
-// gzip [<level>]
44
-// zstd
45
-// minimum_length <length>
46
-// # response matcher block
47
-// match {
48
-// status <code...>
49
-// header <field> [<value>]
50
-// }
51
-// # or response matcher single line syntax
52
-// match [header <field> [<value>]] | [status <code...>]
53
-// }
42
+// encode [<matcher>] <formats...> {
43
+// gzip [<level>]
44
+// zstd
45
+// minimum_length <length>
46
+// # response matcher block
47
+// match {
48
+// status <code...>
49
+// header <field> [<value>]
50
+// }
51
+// # or response matcher single line syntax
52
+// match [header <field> [<value>]] | [status <code...>]
53
+// }
54
//
55
// Specifying the formats on the first line will use those formats' defaults.
56
func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
modules/caddyhttp/push/caddyfile.go
+7
-7
@@ -26,13 +26,13 @@ func init() {
26
27
// parseCaddyfile sets up the push handler. Syntax:
28
//
29
-// push [<matcher>] [<resource>] {
30
-// [GET|HEAD] <resource>
31
-// headers {
32
-// [+]<field> [<value|regexp> [<replacement>]]
33
-// -<field>
34
-// }
35
-// }
29
+// push [<matcher>] [<resource>] {
30
+// [GET|HEAD] <resource>
31
+// headers {
32
+// [+]<field> [<value|regexp> [<replacement>]]
33
+// -<field>
34
+// }
35
+// }
36
//
37
// A single resource can be specified inline without opening a
38
// block for the most common/simple case. Or, a block can be
modules/caddyhttp/push/link.go
+3
-3
@@ -29,9 +29,9 @@ type linkResource struct {
29
//
30
// Accepted formats are:
31
//
32
-// Link: <resource>; as=script
33
-// Link: <resource>; as=script,<resource>; as=style
34
-// Link: <resource>;<resource2>
32
+// Link: <resource>; as=script
33
+// Link: <resource>; as=script,<resource>; as=style
34
+// Link: <resource>;<resource2>
35
//
36
// where <resource> begins with a forward slash (/).
37
func parseLinkHeader(header string) []linkResource {
modules/caddyhttp/push/link_test.go
+1
-1
@@ -4,7 +4,7 @@
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
-// http://www.apache.org/licenses/LICENSE-2.0
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
modules/caddyhttp/responsematchers.go
+5
-6
@@ -58,15 +58,14 @@ func (rm ResponseMatcher) matchStatusCode(statusCode int) bool {
58
59
// ParseNamedResponseMatcher parses the tokens of a named response matcher.
60
//
61
-// @name {
62
-// header <field> [<value>]
63
-// status <code...>
64
-// }
61
+// @name {
62
+// header <field> [<value>]
63
+// status <code...>
64
+// }
65
//
66
// Or, single line syntax:
67
//
68
-// @name [header <field> [<value>]] | [status <code...>]
69
-//
68
+// @name [header <field> [<value>]] | [status <code...>]
69
func ParseNamedResponseMatcher(d *caddyfile.Dispenser, matchers map[string]ResponseMatcher) error {
70
for d.Next() {
71
definitionName := d.Val()
modules/caddyhttp/reverseproxy/selectionpolicies.go
+2
-1
@@ -418,7 +418,8 @@ func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Request, w http
418
}
419
420
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
421
-// lb_policy cookie [<name> [<secret>]]
421
+//
422
+// lb_policy cookie [<name> [<secret>]]
423
//
424
// By default name is `lb`
425
func (s *CookieHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
modules/caddyhttp/reverseproxy/streaming.go
+102
-16
@@ -20,12 +20,13 @@ package reverseproxy
20
21
import (
22
"context"
23
- "encoding/binary"
23
"io"
24
+ weakrand "math/rand"
25
"mime"
26
"net/http"
27
"sync"
28
"time"
29
+ "unsafe"
30
31
"go.uber.org/zap"
32
"golang.org/x/net/http/httpguts"
@@ -103,16 +104,19 @@ func (h Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrite
104
// with the backend, are both closed in the event of a server shutdown. This
105
// is done by registering them. We also try to gracefully close connections
106
// we recognize as websockets.
106
- gracefulClose := func(conn io.ReadWriteCloser) func() error {
107
+ // We need to make sure the client connection messages (i.e. to upstream)
108
+ // are masked, so we need to know whether the connection is considered the
109
+ // server or the client side of the proxy.
110
+ gracefulClose := func(conn io.ReadWriteCloser, isClient bool) func() error {
111
if isWebsocket(req) {
112
return func() error {
109
- return writeCloseControl(conn)
113
+ return writeCloseControl(conn, isClient)
114
}
115
}
116
return nil
117
}
114
- deleteFrontConn := h.registerConnection(conn, gracefulClose(conn))
115
- deleteBackConn := h.registerConnection(backConn, gracefulClose(backConn))
118
+ deleteFrontConn := h.registerConnection(conn, gracefulClose(conn, false))
119
+ deleteBackConn := h.registerConnection(backConn, gracefulClose(backConn, true))
120
defer deleteFrontConn()
121
defer deleteBackConn()
122
@@ -248,27 +252,108 @@ func (h *Handler) registerConnection(conn io.ReadWriteCloser, gracefulClose func
252
// writeCloseControl sends a best-effort Close control message to the given
253
// WebSocket connection. Thanks to @pascaldekloe who provided inspiration
254
// from his simple implementation of this I was able to learn from at:
251
-// github.com/pascaldekloe/websocket.
252
-func writeCloseControl(conn io.Writer) error {
255
+// github.com/pascaldekloe/websocket. Further work for handling masking
256
+// taken from github.com/gorilla/websocket.
257
+func writeCloseControl(conn io.Writer, isClient bool) error {
258
+ // Sources:
259
// https://github.com/pascaldekloe/websocket/blob/32050af67a5d/websocket.go#L119
260
+ // https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L413
261
262
+ // For now, we're not using a reason. We might later, though.
263
+ // The code handling the reason is left in
264
var reason string // max 123 bytes (control frame payload limit is 125; status code takes 2)
256
- const goingAway uint16 = 1001
265
258
- // TODO: we might need to ensure we are the exclusive writer by this point (io.Copy is stopped)?
259
- var writeBuf [127]byte
266
const closeMessage = 8
261
- const finalBit = 1 << 7
262
- writeBuf[0] = closeMessage | finalBit
263
- writeBuf[1] = byte(len(reason) + 2)
264
- binary.BigEndian.PutUint16(writeBuf[2:4], goingAway)
265
- copy(writeBuf[4:], reason)
267
+ const finalBit = 1 << 7 // Frame header byte 0 bits from Section 5.2 of RFC 6455
268
+ const maskBit = 1 << 7 // Frame header byte 1 bits from Section 5.2 of RFC 6455
269
+ const goingAwayUpper uint8 = 1001 >> 8
270
+ const goingAwayLower uint8 = 1001 & 0xff
271
+
272
+ b0 := byte(closeMessage) | finalBit
273
+ b1 := byte(len(reason) + 2)
274
+ if isClient {
275
+ b1 |= maskBit
276
+ }
277
+
278
+ buf := make([]byte, 0, 127)
279
+ buf = append(buf, b0, b1)
280
+ msgLength := 4 + len(reason)
281
+
282
+ // Both branches below append the "going away" code and reason
283
+ appendMessage := func(buf []byte) []byte {
284
+ buf = append(buf, goingAwayUpper, goingAwayLower)
285
+ buf = append(buf, []byte(reason)...)
286
+ return buf
287
+ }
288
+
289
+ // When we're the client, we need to mask the message as per
290
+ // https://www.rfc-editor.org/rfc/rfc6455#section-5.3
291
+ if isClient {
292
+ key := newMaskKey()
293
+ buf = append(buf, key[:]...)
294
+ msgLength += len(key)
295
+ buf = appendMessage(buf)
296
+ maskBytes(key, 0, buf[2+len(key):])
297
+ } else {
298
+ buf = appendMessage(buf)
299
+ }
300
301
// simply best-effort, but return error for logging purposes
268
- _, err := conn.Write(writeBuf[:4+len(reason)])
302
+ // TODO: we might need to ensure we are the exclusive writer by this point (io.Copy is stopped)?
303
+ _, err := conn.Write(buf[:msgLength])
304
return err
305
}
306
307
+// Copied from https://github.com/gorilla/websocket/blob/v1.5.0/mask.go
308
+func maskBytes(key [4]byte, pos int, b []byte) int {
309
+ // Mask one byte at a time for small buffers.
310
+ if len(b) < 2*wordSize {
311
+ for i := range b {
312
+ b[i] ^= key[pos&3]
313
+ pos++
314
+ }
315
+ return pos & 3
316
+ }
317
+
318
+ // Mask one byte at a time to word boundary.
319
+ if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
320
+ n = wordSize - n
321
+ for i := range b[:n] {
322
+ b[i] ^= key[pos&3]
323
+ pos++
324
+ }
325
+ b = b[n:]
326
+ }
327
+
328
+ // Create aligned word size key.
329
+ var k [wordSize]byte
330
+ for i := range k {
331
+ k[i] = key[(pos+i)&3]
332
+ }
333
+ kw := *(*uintptr)(unsafe.Pointer(&k))
334
+
335
+ // Mask one word at a time.
336
+ n := (len(b) / wordSize) * wordSize
337
+ for i := 0; i < n; i += wordSize {
338
+ *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
339
+ }
340
+
341
+ // Mask one byte at a time for remaining bytes.
342
+ b = b[n:]
343
+ for i := range b {
344
+ b[i] ^= key[pos&3]
345
+ pos++
346
+ }
347
+
348
+ return pos & 3
349
+}
350
+
351
+// Copied from https://github.com/gorilla/websocket/blob/v1.5.0/conn.go#L184
352
+func newMaskKey() [4]byte {
353
+ n := weakrand.Uint32()
354
+ return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
355
+}
356
+
357
// isWebsocket returns true if r looks to be an upgrade request for WebSockets.
358
// It is a fairly naive check.
359
func isWebsocket(r *http.Request) bool {
@@ -364,3 +449,4 @@ var streamingBufPool = sync.Pool{
449
}
450
451
const defaultBufferSize = 32 * 1024
452
+const wordSize = int(unsafe.Sizeof(uintptr(0)))
modules/caddyhttp/rewrite/caddyfile.go
+6
-7
@@ -34,7 +34,7 @@ func init() {
34
35
// parseCaddyfileRewrite sets up a basic rewrite handler from Caddyfile tokens. Syntax:
36
//
37
-// rewrite [<matcher>] <to>
37
+// rewrite [<matcher>] <to>
38
//
39
// Only URI components which are given in <to> will be set in the resulting URI.
40
// See the docs for the rewrite handler for more information.
@@ -54,8 +54,7 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
54
55
// parseCaddyfileMethod sets up a basic method rewrite handler from Caddyfile tokens. Syntax:
56
//
57
-// method [<matcher>] <method>
58
-//
57
+// method [<matcher>] <method>
58
func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
59
var rewr Rewrite
60
for h.Next() {
@@ -73,7 +72,7 @@ func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
72
// parseCaddyfileURI sets up a handler for manipulating (but not "rewriting") the
73
// URI from Caddyfile tokens. Syntax:
74
//
76
-// uri [<matcher>] strip_prefix|strip_suffix|replace|path_regexp <target> [<replacement> [<limit>]]
75
+// uri [<matcher>] strip_prefix|strip_suffix|replace|path_regexp <target> [<replacement> [<limit>]]
76
//
77
// If strip_prefix or strip_suffix are used, then <target> will be stripped
78
// only if it is the beginning or the end, respectively, of the URI path. If
@@ -147,9 +146,9 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
146
147
// parseCaddyfileHandlePath parses the handle_path directive. Syntax:
148
//
150
-// handle_path [<matcher>] {
151
-// <directives...>
152
-// }
149
+// handle_path [<matcher>] {
150
+// <directives...>
151
+// }
152
//
153
// Only path matchers (with a `/` prefix) are supported as this is a shortcut
154
// for the handle directive with a strip_prefix rewrite.
modules/caddyhttp/staticerror.go
+3
-3
@@ -53,9 +53,9 @@ func (StaticError) CaddyModule() caddy.ModuleInfo {
53
54
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
55
//
56
-// error [<matcher>] <status>|<message> [<status>] {
57
-// message <text>
58
-// }
56
+// error [<matcher>] <status>|<message> [<status>] {
57
+// message <text>
58
+// }
59
//
60
// If there is just one argument (other than the matcher), it is considered
61
// to be a status code if it's a valid positive integer of 3 digits.
modules/caddyhttp/templates/caddyfile.go
+5
-6
@@ -25,12 +25,11 @@ func init() {
25
26
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
27
//
28
-// templates [<matcher>] {
29
-// mime <types...>
30
-// between <open_delim> <close_delim>
31
-// root <path>
32
-// }
33
-//
28
+// templates [<matcher>] {
29
+// mime <types...>
30
+// between <open_delim> <close_delim>
31
+// root <path>
32
+// }
33
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
34
t := new(Templates)
35
for h.Next() {
modules/caddyhttp/templates/templates.go
+11
-11
@@ -152,10 +152,10 @@ func init() {
152
//
153
// Accesses the current HTTP request, which has various fields, including:
154
//
155
-// - `.Method` - the method
156
-// - `.URL` - the URL, which in turn has component fields (Scheme, Host, Path, etc.)
157
-// - `.Header` - the header fields
158
-// - `.Host` - the Host or :authority header of the request
155
+// - `.Method` - the method
156
+// - `.URL` - the URL, which in turn has component fields (Scheme, Host, Path, etc.)
157
+// - `.Header` - the header fields
158
+// - `.Host` - the Host or :authority header of the request
159
//
160
// ```
161
// {{.Req.Header.Get "User-Agent"}}
@@ -221,15 +221,16 @@ func init() {
221
// ---
222
// ```
223
//
224
-//
224
// **JSON** is simply `{` and `}`:
225
//
226
// ```
228
-// {
229
-// "template": "blog",
230
-// "title": "Blog Homepage",
231
-// "sitename": "A Caddy site"
232
-// }
227
+//
228
+// {
229
+// "template": "blog",
230
+// "title": "Blog Homepage",
231
+// "sitename": "A Caddy site"
232
+// }
233
+//
234
// ```
235
//
236
// The resulting front matter will be made available like so:
@@ -237,7 +238,6 @@ func init() {
238
// - `.Meta` to access the metadata fields, for example: `{{$parsed.Meta.title}}`
239
// - `.Body` to access the body after the front matter, for example: `{{markdown $parsed.Body}}`
240
//
240
-//
241
// ##### `stripHTML`
242
//
243
// Removes HTML from a string.
modules/caddypki/acmeserver/caddyfile.go
+3
-4
@@ -25,10 +25,9 @@ func init() {
25
26
// parseACMEServer sets up an ACME server handler from Caddyfile tokens.
27
//
28
-// acme_server [<matcher>] {
29
-// ca <id>
30
-// }
31
-//
28
+// acme_server [<matcher>] {
29
+// ca <id>
30
+// }
31
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
32
if !h.Next() {
33
return nil, h.ArgErr()
modules/logging/filewriter.go
+8
-8
@@ -131,14 +131,14 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
131
132
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
133
//
134
-// file <filename> {
135
-// roll_disabled
136
-// roll_size <size>
137
-// roll_uncompressed
138
-// roll_local_time
139
-// roll_keep <num>
140
-// roll_keep_for <days>
141
-// }
134
+// file <filename> {
135
+// roll_disabled
136
+// roll_size <size>
137
+// roll_uncompressed
138
+// roll_local_time
139
+// roll_keep <num>
140
+// roll_keep_for <days>
141
+// }
142
//
143
// The roll_size value has megabyte resolution.
144
// Fractional values are rounded up to the next whole megabyte (MiB).
modules/logging/filterencoder.go
+8
-8
@@ -98,14 +98,14 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
98
99
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
100
//
101
-// filter {
102
-// wrap <another encoder>
103
-// fields {
104
-// <field> <filter> {
105
-// <filter options>
106
-// }
107
-// }
108
-// }
101
+// filter {
102
+// wrap <another encoder>
103
+// fields {
104
+// <field> <filter> {
105
+// <filter options>
106
+// }
107
+// }
108
+// }
109
func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
110
for d.Next() {
111
for d.NextBlock(0) {
modules/logging/netwriter.go
+3
-4
@@ -102,10 +102,9 @@ func (nw NetWriter) OpenWriter() (io.WriteCloser, error) {
102
103
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
104
//
105
-// net <address> {
106
-// dial_timeout <duration>
107
-// }
108
-//
105
+// net <address> {
106
+// dial_timeout <duration>
107
+// }
108
func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
109
for d.Next() {
110
if !d.NextArg() {