Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 00:45 UTC
admin: Remove host checking for UDS (close #6832)
The consensus is that host enforcement on unix sockets is ineffective, frustrating, and confusing. (Unix sockets have their own OS-level permissions system.)
Matthew Holt committed
Apr 15, 2025 at 14:20 UTC
f297bc0a04dcab6c2585b47f3672d045c4f6b54b
2 files changed
+44
-50
admin.go
+38
-41
@@ -221,7 +221,8 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
221
if remote {
222
muxWrap.remoteControl = admin.Remote
223
} else {
224
- muxWrap.enforceHost = !addr.isWildcardInterface()
224
+ // see comment in allowedOrigins() as to why we disable the host check for unix/fd networks
225
+ muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
226
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
227
muxWrap.enforceOrigin = admin.EnforceOrigin
228
}
@@ -310,47 +311,43 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
311
for _, o := range admin.Origins {
312
uniqueOrigins[o] = struct{}{}
313
}
313
- if admin.Origins == nil {
314
+ // RFC 2616, Section 14.26:
315
+ // "A client MUST include a Host header field in all HTTP/1.1 request
316
+ // messages. If the requested URI does not include an Internet host
317
+ // name for the service being requested, then the Host header field MUST
318
+ // be given with an empty value."
319
+ //
320
+ // UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
321
+ // Understandable, but frustrating. See:
322
+ // https://github.com/golang/go/issues/60374
323
+ // See also the discussion here:
324
+ // https://github.com/golang/go/issues/61431
325
+ //
326
+ // We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
327
+ // in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
328
+ // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
329
+ // security checks, the infosec community assures me that it is secure to do
330
+ // so, because:
331
+ //
332
+ // 1) Browsers do not allow access to unix sockets
333
+ // 2) DNS is irrelevant to unix sockets
334
+ //
335
+ // If either of those two statements ever fail to hold true, it is not the
336
+ // fault of Caddy.
337
+ //
338
+ // Thus, we do not fill out allowed origins and do not enforce Host
339
+ // requirements for unix sockets. Enforcing it leads to confusion and
340
+ // frustration, when UDS have their own permissions from the OS.
341
+ // Enforcing host requirements here is effectively security theater,
342
+ // and a false sense of security.
343
+ //
344
+ // See also the discussion in #6832.
345
+ if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
346
if addr.isLoopback() {
315
- if addr.IsUnixNetwork() || addr.IsFdNetwork() {
316
- // RFC 2616, Section 14.26:
317
- // "A client MUST include a Host header field in all HTTP/1.1 request
318
- // messages. If the requested URI does not include an Internet host
319
- // name for the service being requested, then the Host header field MUST
320
- // be given with an empty value."
321
- //
322
- // UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
323
- // Understandable, but frustrating. See:
324
- // https://github.com/golang/go/issues/60374
325
- // See also the discussion here:
326
- // https://github.com/golang/go/issues/61431
327
- //
328
- // We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
329
- // in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
330
- // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
331
- // security checks, the infosec community assures me that it is secure to do
332
- // so, because:
333
- // 1) Browsers do not allow access to unix sockets
334
- // 2) DNS is irrelevant to unix sockets
335
- //
336
- // I am not quite ready to trust either of those external factors, so instead
337
- // of disabling Host/Origin checks, we now allow specific Host values when
338
- // accessing the admin endpoint over unix sockets. I definitely don't trust
339
- // DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
340
- // and IP shouldn't even be used, but if it is for some reason, I think we can
341
- // at least be reasonably assured that 127.0.0.1 and ::1 route to the local
342
- // machine, meaning that a hypothetical browser origin would have to be on the
343
- // local machine as well.
344
- uniqueOrigins[""] = struct{}{}
345
- uniqueOrigins["127.0.0.1"] = struct{}{}
346
- uniqueOrigins["::1"] = struct{}{}
347
- } else {
348
- uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
349
- uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
350
- uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
351
- }
352
- }
353
- if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
347
+ uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
348
+ uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
349
+ uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
350
+ } else {
351
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
352
}
353
}
admin_test.go
+6
-9
@@ -531,6 +531,7 @@ func TestAdminRouterProvisioning(t *testing.T) {
531
}
532
533
func TestAllowedOriginsUnixSocket(t *testing.T) {
534
+ // see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
535
tests := []struct {
536
name string
537
addr NetworkAddress
@@ -543,12 +544,8 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
544
Network: "unix",
545
Host: "/tmp/caddy.sock",
546
},
546
- origins: nil, // default origins
547
- expectOrigins: []string{
548
- "", // empty host as per RFC 2616
549
- "127.0.0.1",
550
- "::1",
551
- },
547
+ origins: nil, // default origins
548
+ expectOrigins: []string{},
549
},
550
{
551
name: "unix socket with custom origins",
@@ -578,7 +575,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
575
},
576
}
577
581
- for _, test := range tests {
578
+ for i, test := range tests {
579
t.Run(test.name, func(t *testing.T) {
580
admin := AdminConfig{
581
Origins: test.origins,
@@ -592,7 +589,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
589
}
590
591
if len(gotOrigins) != len(test.expectOrigins) {
595
- t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
592
+ t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins))
593
return
594
}
595
@@ -607,7 +604,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
604
}
605
606
if !reflect.DeepEqual(expectMap, gotMap) {
610
- t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
607
+ t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins)
608
}
609
})
610
}