-
Notifications
You must be signed in to change notification settings - Fork 937
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Container: BPF token support #15009
base: main
Are you sure you want to change the base?
Container: BPF token support #15009
Changes from 6 commits
0832aec
188276b
7bbd0f5
253082b
db9f6da
77eb3fa
ea4a5d1
d495777
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,15 @@ func HandleContainerHook(lxdPath string, projectName string, instanceRef string, | |
u := api.NewURL().Path("internal", "containers", instanceRef, "on"+hook) | ||
u.WithQuery("target", target) | ||
|
||
if hook == "starthost" { | ||
lxcPID := os.Getenv("LXC_PID") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we lose this validation and instead still call the starthost URL with a missing or blank lxc_pid query parameter, then let LXD validate that its missing, as the error will then be more accessible in the LXD logs rather than in the lxc debug log. |
||
if lxcPID == "" { | ||
return errors.New("starthost hook requires LXC_PID env variable set") | ||
} | ||
|
||
u.WithQuery("lxc_pid", lxcPID) | ||
} | ||
|
||
if projectName != "" { | ||
u.WithQuery("project", projectName) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1071,6 +1071,13 @@ func (d *lxc) initLXC(config bool) (*liblxc.Container, error) { | |
} | ||
} | ||
|
||
if shared.IsTrue(d.expandedConfig["security.delegate_bpf"]) { | ||
err = lxcSetConfigItem(cc, "lxc.hook.start-host", fmt.Sprintf("%s callhook %s %s %s starthost", d.state.OS.ExecPath, shared.VarPath(""), strconv.Quote(d.Project().Name), strconv.Quote(d.Name()))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like that |
||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// Memory limits | ||
if d.state.OS.CGInfo.Supports(cgroup.Memory, cg) { | ||
memory := d.expandedConfig["limits.memory"] | ||
|
@@ -2464,6 +2471,8 @@ func (d *lxc) OnHook(hookName string, args map[string]string) error { | |
switch hookName { | ||
case instance.HookStart: | ||
return d.onStart(args) | ||
case instance.HookStartHost: | ||
return d.onStartHost(args) | ||
case instance.HookStopNS: | ||
return d.onStopNS(args) | ||
case instance.HookStop: | ||
|
@@ -2522,6 +2531,94 @@ func (d *lxc) onStart(_ map[string]string) error { | |
return nil | ||
} | ||
|
||
// mountBpfFs mounts bpffs inside the container. | ||
func (d *lxc) mountBpfFs(pid int, bpffsParams map[string]string) error { | ||
if !d.state.OS.BPFToken { | ||
return fmt.Errorf("BPF Token mechanism is not supported by kernel running.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No full stops on log messages or errors please |
||
} | ||
|
||
pidFdNr, pidFd := seccomp.MakePidFd(pid, d.state) | ||
if pidFdNr >= 0 { | ||
defer func() { _ = pidFd.Close() }() | ||
} | ||
|
||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
defer cancel() | ||
|
||
d.logger.Debug("bpffs mount helper is being called", logger.Ctx{"pid": pid, "bpffsParams": bpffsParams}) | ||
stdout, err := shared.RunCommandInheritFds( | ||
ctx, | ||
[]*os.File{pidFd}, | ||
d.state.OS.ExecPath, | ||
"forkmount", | ||
"bpffs", | ||
"--", | ||
fmt.Sprint(pid), | ||
fmt.Sprint(pidFdNr), | ||
bpffsParams["mountpoint"], | ||
bpffsParams["delegate_cmds"], | ||
bpffsParams["delegate_maps"], | ||
bpffsParams["delegate_progs"], | ||
bpffsParams["delegate_attachs"]) | ||
if err != nil { | ||
d.logger.Error("bpffs mount helper has failed", logger.Ctx{"err": err, "stdout": stdout}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there potentially useful info in stdout we should be collecting and passing to the end user in err? |
||
return err | ||
} | ||
|
||
d.logger.Debug("bpffs mount helper has finished without error", logger.Ctx{"stdout": stdout}) | ||
|
||
return nil | ||
} | ||
|
||
// onStartHost implements the LXC start-host hook. | ||
func (d *lxc) onStartHost(args map[string]string) error { | ||
if shared.IsFalseOrEmpty(d.expandedConfig["security.delegate_bpf"]) { | ||
return nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets move the contents of his function from line 2580 into its own function, suggest That way we nicely structure the general |
||
} | ||
|
||
// Get the init PID | ||
pidStr, ok := args["LXC_PID"] | ||
if !ok { | ||
return fmt.Errorf("No LXC_PID parameter was provided to start-host hook") | ||
} | ||
|
||
pid, err := strconv.Atoi(pidStr) | ||
if err != nil { | ||
return fmt.Errorf("Invalid LXC_PID parameter was provided to start-host hook %q: %w", pidStr, err) | ||
} | ||
|
||
bpffsParams := map[string]string{ | ||
"delegate_cmds": "any", | ||
"delegate_maps": "any", | ||
"delegate_progs": "any", | ||
"delegate_attachs": "any", | ||
"mountpoint": "/sys/fs/bpf", | ||
} | ||
|
||
if d.expandedConfig["security.delegate_bpf.cmds"] != "" { | ||
bpffsParams["delegate_cmds"] = d.expandedConfig["security.delegate_bpf.cmds"] | ||
} | ||
|
||
if d.expandedConfig["security.delegate_bpf.maps"] != "" { | ||
bpffsParams["delegate_maps"] = d.expandedConfig["security.delegate_bpf.maps"] | ||
} | ||
|
||
if d.expandedConfig["security.delegate_bpf.progs"] != "" { | ||
bpffsParams["delegate_progs"] = d.expandedConfig["security.delegate_bpf.progs"] | ||
} | ||
|
||
if d.expandedConfig["security.delegate_bpf.attachs"] != "" { | ||
bpffsParams["delegate_attachs"] = d.expandedConfig["security.delegate_bpf.attachs"] | ||
} | ||
|
||
err = d.mountBpfFs(pid, bpffsParams) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// validateStartup checks any constraints that would prevent start up from succeeding under normal circumstances. | ||
func (d *lxc) validateStartup(statusCode api.StatusCode) error { | ||
err := d.common.validateStartup(statusCode) | ||
|
@@ -2539,6 +2636,10 @@ func (d *lxc) validateStartup(statusCode api.StatusCode) error { | |
return fmt.Errorf("Instance is protected from being started") | ||
} | ||
|
||
if shared.IsTrue(d.expandedConfig["security.delegate_bpf"]) && !d.state.OS.BPFToken { | ||
return fmt.Errorf("BPF Token mechanism is not supported by your kernel. Linux kernel 6.9+ is required to start this instance, or security.delegate_bpf option must be disabled") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this is container specific, suggest
container_bpf_delegation
, similar tocontainer_syscall_intercept_bpf_devices
that exists today.