1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
package namespace
import (
"bufio"
"fmt"
"os"
"os/user"
"strings"
)
// Requirement defines a system check and the hint to provide if it fails.
type Requirement struct {
Name string
Check func() (bool, string)
Hint string
}
// Preflight handles system requirement checks and health diagnostics.
type Preflight struct {
FS FileSystem
}
// NewPreflight creates a new preflight checker with the provided filesystem.
func NewPreflight(fs FileSystem) *Preflight {
return &Preflight{FS: fs}
}
// CheckSystemRequirements verifies the absolute minimum requirements
// that must be met for wg-wrap to function.
func (p *Preflight) CheckSystemRequirements() error {
// The only absolute hard requirement that isn't better caught by a syscall
// is the existence and accessibility of the TUN device.
if _, err := p.FS.Stat("/dev/net/tun"); os.IsNotExist(err) {
return fmt.Errorf("critical requirement missing: /dev/net/tun not found. Please ensure the tun module is loaded (sudo modprobe tun)")
}
f, err := p.FS.Open("/dev/net/tun")
if err != nil {
return fmt.Errorf("critical requirement missing: /dev/net/tun is not accessible: %v", err)
}
_ = f.Close()
return nil
}
// CheckSystemRequirements is the global wrapper for the preflight check.
func CheckSystemRequirements() error {
return (&Preflight{FS: DefaultFS}).CheckSystemRequirements()
}
// RunHealthCheck performs a comprehensive set of diagnostics to help users
// configure their system for rootless network namespaces.
func (p *Preflight) RunHealthCheck(uid int, username string) []HealthResult {
isRoot := uid == 0
checks := []Requirement{
{
Name: "TUN Device Accessibility",
Check: func() (bool, string) {
if _, err := p.FS.Stat("/dev/net/tun"); os.IsNotExist(err) {
return false, "/dev/net/tun does not exist"
}
f, err := p.FS.Open("/dev/net/tun")
if err != nil {
return false, fmt.Sprintf("cannot open /dev/net/tun: %v", err)
}
_ = f.Close()
return true, ""
},
Hint: "Ensure the TUN module is loaded (sudo modprobe tun) and that your user has permissions to access /dev/net/tun",
},
{
Name: "Unprivileged User Namespaces (Debian/Ubuntu)",
Check: func() (bool, string) {
data, err := p.FS.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
if err != nil {
return true, "Not applicable (file not found)"
}
if strings.TrimSpace(string(data)) == "0" {
return false, "unprivileged_userns_clone is disabled"
}
return true, ""
},
Hint: "Enable unprivileged user namespaces by running: sudo sysctl -w kernel.unprivileged_userns_clone=1",
},
{
Name: "User Namespace Limit",
Check: func() (bool, string) {
data, err := p.FS.ReadFile("/proc/sys/user/max_user_namespaces")
if err != nil {
return true, "Not applicable (file not found)"
}
if strings.TrimSpace(string(data)) == "0" {
return false, "max_user_namespaces is set to 0"
}
return true, ""
},
Hint: "Increase the user namespace limit: sudo sysctl -w user.max_user_namespaces=1024",
},
{
Name: "SubUID/SubGID Mappings",
Check: func() (bool, string) {
if isRoot {
return true, "Bypassed (Running as root)"
}
if username == "" {
return false, "Could not determine current username"
}
data, err := p.FS.ReadFile("/etc/subuid")
if err != nil {
return false, fmt.Sprintf("failed to read /etc/subuid: %v", err)
}
scanner := bufio.NewScanner(strings.NewReader(string(data)))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, username+":") {
return true, ""
}
}
return false, fmt.Sprintf("no subuid mapping found for user %s", username)
},
Hint: "Configure subuid and subgid for your user. Example: sudo usermod --add-subuids 100000-60000 --add-subgids 100000-60000 $USER",
},
}
var results []HealthResult
for _, req := range checks {
ok, msg := req.Check()
results = append(results, HealthResult{
Name: req.Name,
Passed: ok,
Message: msg,
Hint: req.Hint,
})
}
return results
}
// RunHealthCheck is the global wrapper for the health check.
func RunHealthCheck() []HealthResult {
u, _ := user.Current()
username := ""
if u != nil {
username = u.Username
}
return (&Preflight{FS: DefaultFS}).RunHealthCheck(os.Getuid(), username)
}
// HealthResult describes the outcome of a single health check.
type HealthResult struct {
Name string
Passed bool
Message string
Hint string
}
|