From 4abdc2f35d7bba364f00951a303ad283a59e712e Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Tue, 18 Oct 2022 16:44:13 +0200 Subject: [PATCH 1/3] Add nvidia-ctk hook chmod command to set permissions This change adds an nvidia-ctk hook chmod command that can be used to update the permissions for paths in the container. This prepends the container root to the paths to allow these to be updated by runtime executables. Signed-off-by: Evan Lezar --- cmd/nvidia-ctk/hook/chmod/chmod.go | 132 +++++++++++++++++++++++++++++ cmd/nvidia-ctk/hook/hook.go | 2 + 2 files changed, 134 insertions(+) create mode 100644 cmd/nvidia-ctk/hook/chmod/chmod.go diff --git a/cmd/nvidia-ctk/hook/chmod/chmod.go b/cmd/nvidia-ctk/hook/chmod/chmod.go new file mode 100644 index 00000000..01567084 --- /dev/null +++ b/cmd/nvidia-ctk/hook/chmod/chmod.go @@ -0,0 +1,132 @@ +/** +# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package chmod + +import ( + "fmt" + "path/filepath" + "strings" + "syscall" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +type command struct { + logger *logrus.Logger +} + +type config struct { + paths cli.StringSlice + mode string + containerSpec string +} + +// NewCommand constructs a chmod command with the specified logger +func NewCommand(logger *logrus.Logger) *cli.Command { + c := command{ + logger: logger, + } + return c.build() +} + +// build the chmod command +func (m command) build() *cli.Command { + cfg := config{} + + // Create the 'chmod' command + c := cli.Command{ + Name: "chmod", + Usage: "Set the permissions of folders in the container by running chmod. The container root is prefixed to the specified paths.", + Before: func(c *cli.Context) error { + return validateFlags(c, &cfg) + }, + Action: func(c *cli.Context) error { + return m.run(c, &cfg) + }, + } + + c.Flags = []cli.Flag{ + &cli.StringSliceFlag{ + Name: "path", + Usage: "Specifiy a path to apply the specified mode to", + Destination: &cfg.paths, + }, + &cli.StringFlag{ + Name: "mode", + Usage: "Specify the file mode", + Destination: &cfg.mode, + }, + &cli.StringFlag{ + Name: "container-spec", + Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN", + Destination: &cfg.containerSpec, + }, + } + + return &c +} + +func validateFlags(c *cli.Context, cfg *config) error { + if strings.TrimSpace(cfg.mode) == "" { + return fmt.Errorf("a non-empty mode must be specified") + } + + for _, p := range cfg.paths.Value() { + if strings.TrimSpace(p) == "" { + return fmt.Errorf("paths must not be empty") + } + } + + return nil +} + +func (m command) run(c *cli.Context, cfg *config) error { + s, err := oci.LoadContainerState(cfg.containerSpec) + if err != nil { + return fmt.Errorf("failed to load container state: %v", err) + } + + containerRoot, err := s.GetContainerRoot() + if err != nil { + return fmt.Errorf("failed to determined container root: %v", err) + } + if containerRoot == "" { + return fmt.Errorf("empty container root detected") + } + + paths := m.getPaths(containerRoot, cfg.paths.Value()) + if len(paths) == 0 { + m.logger.Debugf("No paths specified; exiting") + return nil + } + + args := append([]string{"/bin/chmod", cfg.mode}, paths...) + + return syscall.Exec(args[0], args, nil) +} + +// getPaths updates the specified paths relative to the root. +func (m command) getPaths(root string, paths []string) []string { + var pathsInRoot []string + for _, f := range paths { + pathsInRoot = append(pathsInRoot, filepath.Join(root, f)) + } + + return pathsInRoot +} diff --git a/cmd/nvidia-ctk/hook/hook.go b/cmd/nvidia-ctk/hook/hook.go index feac8f5d..7deed271 100644 --- a/cmd/nvidia-ctk/hook/hook.go +++ b/cmd/nvidia-ctk/hook/hook.go @@ -17,6 +17,7 @@ package hook import ( + chmod "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/chmod" symlinks "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/create-symlinks" ldcache "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/update-ldcache" "github.com/sirupsen/logrus" @@ -46,6 +47,7 @@ func (m hookCommand) build() *cli.Command { hook.Subcommands = []*cli.Command{ ldcache.NewCommand(m.logger), symlinks.NewCommand(m.logger), + chmod.NewCommand(m.logger), } return &hook From ae18c5d8477781d02c661a6d8436b07ea995ede9 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Fri, 21 Oct 2022 13:29:21 +0200 Subject: [PATCH 2/3] Include chmod hook for device subfolders in CDI spec generation This change generates one or more createContainer hooks for ensuring that subfolders in /dev have the required permissions in the container. As an example, a user requires read permissions to the /dev/nvidia-caps in addition to including the specific caps devices under this folder. Signed-off-by: Evan Lezar --- .../info/generate-cdi/generate-cdi.go | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/cmd/nvidia-ctk/info/generate-cdi/generate-cdi.go b/cmd/nvidia-ctk/info/generate-cdi/generate-cdi.go index 7ef290b4..fb262a85 100644 --- a/cmd/nvidia-ctk/info/generate-cdi/generate-cdi.go +++ b/cmd/nvidia-ctk/info/generate-cdi/generate-cdi.go @@ -26,6 +26,7 @@ import ( "github.com/NVIDIA/nvidia-container-toolkit/internal/discover" "github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache" "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup" + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" specs "github.com/container-orchestrated-devices/container-device-interface/specs-go" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -212,7 +213,12 @@ func (m command) generateSpec() (*specs.Spec, error) { ldcacheUpdateHook := m.generateUpdateLdCacheHook(libraries) - spec.ContainerEdits.Hooks = []*specs.Hook{ldcacheUpdateHook} + deviceFolderPermissionHooks, err := m.generateDeviceFolderPermissionHooks(ldcacheUpdateHook.Path, allDeviceNodes) + if err != nil { + return nil, fmt.Errorf("failed to generated permission hooks for device nodes: %v", err) + } + + spec.ContainerEdits.Hooks = append([]*specs.Hook{ldcacheUpdateHook}, deviceFolderPermissionHooks...) return &spec, nil } @@ -384,3 +390,49 @@ func (m command) generateUpdateLdCacheHook(libraries []string) *specs.Hook { Args: hook.Args, } } + +func (m command) generateDeviceFolderPermissionHooks(nvidiaCTKPath string, deviceNodes []*specs.DeviceNode) ([]*specs.Hook, error) { + var deviceFolders []string + seen := make(map[string]bool) + + for _, dn := range deviceNodes { + if !strings.HasPrefix(dn.Path, "/dev") { + m.logger.Warningf("Skipping unexpected device folder path for device %v", dn.Path) + continue + } + for df := filepath.Dir(dn.Path); df != "/dev"; df = filepath.Dir(df) { + if seen[df] { + continue + } + deviceFolders = append(deviceFolders, df) + seen[df] = true + } + } + + foldersByMode := make(map[string][]string) + for _, p := range deviceFolders { + info, err := os.Stat(p) + if err != nil { + return nil, fmt.Errorf("failed to get info for path %v: %v", p, err) + } + mode := fmt.Sprintf("%o", info.Mode().Perm()) + foldersByMode[mode] = append(foldersByMode[mode], p) + } + + var hooks []*specs.Hook + for mode, folders := range foldersByMode { + args := []string{filepath.Base(nvidiaCTKPath), "hook", "chmod", "--mode", mode} + for _, folder := range folders { + args = append(args, "--path", folder) + } + hook := specs.Hook{ + HookName: cdi.CreateContainerHook, + Path: nvidiaCTKPath, + Args: args, + } + + hooks = append(hooks, &hook) + } + + return hooks, nil +} From 523fc57ab432dfa8671abf7f5977bed9e700e639 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Wed, 26 Oct 2022 16:24:11 +0200 Subject: [PATCH 3/3] Use an Executable Locator to lookup chmod Signed-off-by: Evan Lezar --- cmd/nvidia-ctk/hook/chmod/chmod.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/nvidia-ctk/hook/chmod/chmod.go b/cmd/nvidia-ctk/hook/chmod/chmod.go index 01567084..cad6edfa 100644 --- a/cmd/nvidia-ctk/hook/chmod/chmod.go +++ b/cmd/nvidia-ctk/hook/chmod/chmod.go @@ -22,6 +22,7 @@ import ( "strings" "syscall" + "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup" "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -116,9 +117,16 @@ func (m command) run(c *cli.Context, cfg *config) error { return nil } - args := append([]string{"/bin/chmod", cfg.mode}, paths...) + locator := lookup.NewExecutableLocator(m.logger, "") + targets, err := locator.Locate("chmod") + if err != nil { + return fmt.Errorf("failed to locate chmod: %v", err) + } + chmodPath := targets[0] - return syscall.Exec(args[0], args, nil) + args := append([]string{filepath.Base(chmodPath), cfg.mode}, paths...) + + return syscall.Exec(chmodPath, args, nil) } // getPaths updates the specified paths relative to the root.