@marcel91200 / caddy Mirror · read-only
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 10, 23:45 UTC

master
go 129 lines 4.14 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
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
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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //go:build linux
16
17 package internal
18
19 import (
20 "errors"
21 "fmt"
22 "net"
23 "runtime"
24 "strconv"
25 "strings"
26
27 "golang.org/x/sys/unix"
28 )
29
30 // WithBindCapability calls listen, which should bind to address on
31 // network. If that fails with EACCES for a TCP or UDP port, and
32 // CAP_NET_BIND_SERVICE is in the permitted set but not the effective
33 // set, it raises the capability and calls listen again, lowering the
34 // capability afterwards.
35 //
36 // Retrying on EACCES, instead of checking the port up front, means the
37 // capability is only raised when the kernel actually requires it, which
38 // depends on net.ipv4.ip_unprivileged_port_start (often 0 in containers,
39 // and possibly above 1024).
40 //
41 // This allows the caddy binary to be granted the capability with
42 // `setcap cap_net_bind_service=+p` instead of `=+ep`. With the effective
43 // bit set, the kernel refuses to execute the binary at all when the
44 // capability can't be granted (for example, in containers run with
45 // `--cap-drop ALL`, or systemd units with an empty CapabilityBoundingSet),
46 // even if Caddy never binds a privileged port. See "Safety checking for
47 // capability-dumb binaries" in capabilities(7).
48 func WithBindCapability(network, address string, listen func() (any, error)) (any, error) {
49 ln, err := listen()
50 if !errors.Is(err, unix.EACCES) || !isPortBind(network, address) {
51 return ln, err
52 }
53 if raise, capErr := shouldRaiseBindCapability(); capErr != nil || !raise {
54 return ln, err
55 }
56
57 // capabilities are per-thread, so raise it on a locked OS thread
58 // and bind from there; the goroutine exits without unlocking, so
59 // the runtime terminates the thread instead of reusing it with
60 // the raised capability (or wedges it, if it is the main thread,
61 // which is why we also lower it again after binding)
62 type result struct {
63 ln any
64 err error
65 }
66 ch := make(chan result, 1)
67 go func() {
68 runtime.LockOSThread()
69 if capErr := setBindCapability(true); capErr != nil {
70 ch <- result{err: fmt.Errorf("%w (raising CAP_NET_BIND_SERVICE: %v)", err, capErr)}
71 return
72 }
73 ln, err := listen()
74 _ = setBindCapability(false)
75 ch <- result{ln, err}
76 }()
77 r := <-ch
78 return r.ln, r.err
79 }
80
81 // isPortBind reports whether address is a TCP or UDP
82 // address with a nonzero port.
83 func isPortBind(network, address string) bool {
84 if !strings.HasPrefix(network, "tcp") && !strings.HasPrefix(network, "udp") {
85 return false
86 }
87 _, portStr, err := net.SplitHostPort(address)
88 if err != nil {
89 return false
90 }
91 port, err := strconv.ParseUint(portStr, 10, 16)
92 return err == nil && port > 0
93 }
94
95 const bindCapabilityMask = 1 << unix.CAP_NET_BIND_SERVICE
96
97 // shouldRaiseBindCapability reports whether CAP_NET_BIND_SERVICE is
98 // permitted but not effective for the calling thread.
99 func shouldRaiseBindCapability() (bool, error) {
100 _, data, err := getCapabilities()
101 if err != nil {
102 return false, err
103 }
104 return data[0].Permitted&bindCapabilityMask != 0 &&
105 data[0].Effective&bindCapabilityMask == 0, nil
106 }
107
108 // setBindCapability adds or removes CAP_NET_BIND_SERVICE in the
109 // effective set of the calling thread. To add it, it must already
110 // be permitted.
111 func setBindCapability(effective bool) error {
112 hdr, data, err := getCapabilities()
113 if err != nil {
114 return err
115 }
116 if effective {
117 data[0].Effective |= bindCapabilityMask
118 } else {
119 data[0].Effective &^= bindCapabilityMask
120 }
121 return unix.Capset(&hdr, &data[0])
122 }
123
124 func getCapabilities() (unix.CapUserHeader, [2]unix.CapUserData, error) {
125 hdr := unix.CapUserHeader{Version: unix.LINUX_CAPABILITY_VERSION_3}
126 var data [2]unix.CapUserData
127 err := unix.Capget(&hdr, &data[0])
128 return hdr, data, err
129 }