Skip to content

Commit 6313c6d

Browse files
oktalzGopher Bot
authored andcommitted
MINOR: read CapBnd instead of CapEff for NET_BIND_SERVICE
Update the capability detection logic to read CapBnd (bounding set) rather than CapEff (effective set) from /proc/self/status. HAProxy is launched via haproxy_wrapper, which carries a file capability (setcap cap_net_bind_service=+ep). Because of this, it gains the NET_BIND_SERVICE capability at exec time regardless of the controller's effective set. This file capability is only limited by the container-wide bounding set. Therefore, checking CapBnd correctly predicts whether HAProxy will be able to bind to privileged ports.
1 parent a883241 commit 6313c6d

3 files changed

Lines changed: 26 additions & 13 deletions

File tree

.aspell.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,7 @@ allowed:
139139
- statx
140140
- patref
141141
- ccae
142+
- CapBnd
143+
- Bnd
144+
- capbnd
145+
- setcap

k8s/gate/caps/caps_linux.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ var (
3535
procSysctl = "/proc/sys/net/ipv4/ip_unprivileged_port_start"
3636
)
3737

38-
// Detect reads /proc to determine the effective bind capabilities of the
39-
// current process. On read errors it falls back to the conservative defaults
40-
// (minUnprivPort=1024, hasNetBind=false) and logs the failure.
38+
// Detect reads /proc to determine the bind capabilities available to HAProxy.
39+
// HAProxy is launched via haproxy_wrapper, which carries a file capability
40+
// (setcap cap_net_bind_service=+ep), so it gains NET_BIND_SERVICE at exec time
41+
// regardless of the controller's effective set. The file capability is bounded
42+
// only by the container-wide bounding set, which the controller shares, so we
43+
// read CapBnd (not CapEff) to predict whether HAProxy can bind privileged ports.
44+
// On read errors it falls back to the conservative defaults (minUnprivPort=1024,
45+
// hasNetBind=false) and logs the failure.
4146
func Detect(ctx context.Context, logger *slog.Logger) PortBinder {
4247
minPort, errMin := readUnprivPortStart(procSysctl)
4348
if errMin != nil {
@@ -56,7 +61,7 @@ func Detect(ctx context.Context, logger *slog.Logger) PortBinder {
5661
if logger != nil {
5762
logger.LogAttrs(
5863
ctx, slog.LevelWarn,
59-
"caps: could not read CapEff, assuming no NET_BIND_SERVICE",
64+
"caps: could not read CapBnd, assuming no NET_BIND_SERVICE",
6065
slog.String("error", errCap.Error()),
6166
)
6267
}
@@ -86,6 +91,10 @@ func readUnprivPortStart(path string) (uint16, error) {
8691
return uint16(v), nil
8792
}
8893

94+
// readNetBindService reports whether CAP_NET_BIND_SERVICE is in the bounding
95+
// set (CapBnd). The bounding set caps what a file capability (the +ep on
96+
// haproxy_wrapper) can raise into HAProxy's effective set, so it predicts
97+
// HAProxy's reach better than the controller's CapEff.
8998
func readNetBindService(path string) (bool, error) {
9099
f, err := os.Open(path)
91100
if err != nil {
@@ -96,18 +105,18 @@ func readNetBindService(path string) (bool, error) {
96105
scanner := bufio.NewScanner(f)
97106
for scanner.Scan() {
98107
line := scanner.Text()
99-
rest, ok := strings.CutPrefix(line, "CapEff:")
108+
rest, ok := strings.CutPrefix(line, "CapBnd:")
100109
if !ok {
101110
continue
102111
}
103112
bits, err := strconv.ParseUint(strings.TrimSpace(rest), 16, 64)
104113
if err != nil {
105-
return false, fmt.Errorf("parse CapEff: %w", err)
114+
return false, fmt.Errorf("parse CapBnd: %w", err)
106115
}
107116
return bits&(1<<unix.CAP_NET_BIND_SERVICE) != 0, nil
108117
}
109118
if err := scanner.Err(); err != nil {
110119
return false, err
111120
}
112-
return false, errors.New("CapEff line not found")
121+
return false, errors.New("CapBnd line not found")
113122
}

k8s/gate/caps/caps_linux_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,34 +78,34 @@ func TestReadNetBindService(t *testing.T) {
7878
{
7979
name: "only NET_BIND_SERVICE",
8080
content: `Name: hug
81-
CapEff: 0000000000000400
81+
CapBnd: 0000000000000400
8282
`,
8383
want: true,
8484
},
8585
{
8686
name: "no caps",
8787
content: `Name: hug
88-
CapEff: 0000000000000000
88+
CapBnd: 0000000000000000
8989
`,
9090
want: false,
9191
},
9292
{
9393
name: "full caps incl NET_BIND_SERVICE",
9494
content: `Name: hug
95-
CapEff: 00000000a80425fb
95+
CapBnd: 00000000a80425fb
9696
`,
9797
want: true,
9898
},
9999
{
100-
name: "missing CapEff",
100+
name: "missing CapBnd",
101101
content: `Name: hug
102102
CapPrm: 0000000000000400
103103
`,
104104
wantErr: true,
105105
},
106106
{
107107
name: "malformed hex",
108-
content: "CapEff:\tzzzz\n",
108+
content: "CapBnd:\tzzzz\n",
109109
wantErr: true,
110110
},
111111
}
@@ -151,7 +151,7 @@ func TestDetect_FromFiles(t *testing.T) {
151151
orig1, orig2 := procStatus, procSysctl
152152
t.Cleanup(func() { procStatus, procSysctl = orig1, orig2 })
153153

154-
procStatus = writeFile(t, dir, "status", "CapEff:\t0000000000000400\n")
154+
procStatus = writeFile(t, dir, "status", "CapBnd:\t0000000000000400\n")
155155
procSysctl = writeFile(t, dir, "sysctl", "1024\n")
156156

157157
b := Detect(context.Background(), nil)

0 commit comments

Comments
 (0)