@marcel91200 / caddy Mirror · read-only / commits / ee7c92ec
Fork 0

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() {