@marcel91200 / caddy Mirror · read-only / commits / f297bc0a
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 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 }