github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/runtime/engines/oci/prepare.go (about)

     1  // Copyright (c) 2018-2019, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package oci
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  
    12  	"github.com/containerd/cgroups"
    13  	"github.com/sylabs/singularity/internal/pkg/sylog"
    14  	"github.com/sylabs/singularity/pkg/ociruntime"
    15  
    16  	"github.com/kr/pty"
    17  	specs "github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/sylabs/singularity/internal/pkg/runtime/engines/config/starter"
    19  	"github.com/sylabs/singularity/pkg/util/capabilities"
    20  )
    21  
    22  // make master/slave as global variable to avoid GC close file descriptor
    23  var (
    24  	master *os.File
    25  	slave  *os.File
    26  )
    27  
    28  func (e *EngineOperations) checkCapabilities() error {
    29  	for _, cap := range e.EngineConfig.OciConfig.Process.Capabilities.Permitted {
    30  		if _, ok := capabilities.Map[cap]; !ok {
    31  			return fmt.Errorf("Unrecognized capabilities %s", cap)
    32  		}
    33  	}
    34  	for _, cap := range e.EngineConfig.OciConfig.Process.Capabilities.Effective {
    35  		if _, ok := capabilities.Map[cap]; !ok {
    36  			return fmt.Errorf("Unrecognized capabilities %s", cap)
    37  		}
    38  	}
    39  	for _, cap := range e.EngineConfig.OciConfig.Process.Capabilities.Inheritable {
    40  		if _, ok := capabilities.Map[cap]; !ok {
    41  			return fmt.Errorf("Unrecognized capabilities %s", cap)
    42  		}
    43  	}
    44  	for _, cap := range e.EngineConfig.OciConfig.Process.Capabilities.Bounding {
    45  		if _, ok := capabilities.Map[cap]; !ok {
    46  			return fmt.Errorf("Unrecognized capabilities %s", cap)
    47  		}
    48  	}
    49  	for _, cap := range e.EngineConfig.OciConfig.Process.Capabilities.Ambient {
    50  		if _, ok := capabilities.Map[cap]; !ok {
    51  			return fmt.Errorf("Unrecognized capabilities %s", cap)
    52  		}
    53  	}
    54  	return nil
    55  }
    56  
    57  // PrepareConfig checks and prepares the runtime engine config
    58  func (e *EngineOperations) PrepareConfig(starterConfig *starter.Config) error {
    59  	if e.CommonConfig.EngineName != Name {
    60  		return fmt.Errorf("incorrect engine")
    61  	}
    62  
    63  	if starterConfig.GetIsSUID() {
    64  		return fmt.Errorf("SUID workflow disabled by administrator")
    65  	}
    66  
    67  	if e.EngineConfig.OciConfig.Process == nil {
    68  		return fmt.Errorf("empty OCI process configuration")
    69  	}
    70  
    71  	if e.EngineConfig.OciConfig.Linux == nil {
    72  		return fmt.Errorf("empty OCI linux configuration")
    73  	}
    74  
    75  	// reset state config that could be passed to engine
    76  	e.EngineConfig.State = ociruntime.State{}
    77  
    78  	var gids []int
    79  
    80  	uid := int(e.EngineConfig.OciConfig.Process.User.UID)
    81  	gid := e.EngineConfig.OciConfig.Process.User.GID
    82  
    83  	gids = append(gids, int(gid))
    84  	for _, g := range e.EngineConfig.OciConfig.Process.User.AdditionalGids {
    85  		gids = append(gids, int(g))
    86  	}
    87  
    88  	starterConfig.SetTargetUID(uid)
    89  	starterConfig.SetTargetGID(gids)
    90  
    91  	if !e.EngineConfig.Exec {
    92  		starterConfig.SetInstance(true)
    93  	}
    94  
    95  	userNS := false
    96  	for _, ns := range e.EngineConfig.OciConfig.Linux.Namespaces {
    97  		if ns.Type == specs.UserNamespace {
    98  			userNS = true
    99  			break
   100  		}
   101  	}
   102  
   103  	starterConfig.SetNsFlagsFromSpec(e.EngineConfig.OciConfig.Linux.Namespaces)
   104  	if err := starterConfig.SetNsPathFromSpec(e.EngineConfig.OciConfig.Linux.Namespaces); err != nil {
   105  		return err
   106  	}
   107  
   108  	if userNS {
   109  		if len(e.EngineConfig.OciConfig.Linux.UIDMappings) == 0 {
   110  			return fmt.Errorf("user namespace invoked without uid mapping")
   111  		}
   112  		if len(e.EngineConfig.OciConfig.Linux.GIDMappings) == 0 {
   113  			return fmt.Errorf("user namespace invoked without gid mapping")
   114  		}
   115  		if err := starterConfig.AddUIDMappings(e.EngineConfig.OciConfig.Linux.UIDMappings); err != nil {
   116  			return err
   117  		}
   118  		if err := starterConfig.AddGIDMappings(e.EngineConfig.OciConfig.Linux.GIDMappings); err != nil {
   119  			return err
   120  		}
   121  	}
   122  
   123  	if e.EngineConfig.OciConfig.Linux.RootfsPropagation != "" {
   124  		starterConfig.SetMountPropagation(e.EngineConfig.OciConfig.Linux.RootfsPropagation)
   125  	} else {
   126  		starterConfig.SetMountPropagation("private")
   127  	}
   128  
   129  	starterConfig.SetNoNewPrivs(e.EngineConfig.OciConfig.Process.NoNewPrivileges)
   130  
   131  	if e.EngineConfig.OciConfig.Process.Capabilities != nil {
   132  		if err := e.checkCapabilities(); err != nil {
   133  			return err
   134  		}
   135  
   136  		// force cap_sys_admin for seccomp and no_new_priv flag
   137  		caps := append(e.EngineConfig.OciConfig.Process.Capabilities.Effective, "CAP_SYS_ADMIN")
   138  		starterConfig.SetCapabilities(capabilities.Effective, caps)
   139  
   140  		caps = append(e.EngineConfig.OciConfig.Process.Capabilities.Permitted, "CAP_SYS_ADMIN")
   141  		starterConfig.SetCapabilities(capabilities.Permitted, caps)
   142  
   143  		starterConfig.SetCapabilities(capabilities.Inheritable, e.EngineConfig.OciConfig.Process.Capabilities.Inheritable)
   144  		starterConfig.SetCapabilities(capabilities.Bounding, e.EngineConfig.OciConfig.Process.Capabilities.Bounding)
   145  		starterConfig.SetCapabilities(capabilities.Ambient, e.EngineConfig.OciConfig.Process.Capabilities.Ambient)
   146  	}
   147  
   148  	e.EngineConfig.MasterPts = -1
   149  	e.EngineConfig.SlavePts = -1
   150  	e.EngineConfig.OutputStreams = [2]int{-1, -1}
   151  	e.EngineConfig.ErrorStreams = [2]int{-1, -1}
   152  	e.EngineConfig.InputStreams = [2]int{-1, -1}
   153  
   154  	if e.EngineConfig.GetLogFormat() == "" {
   155  		sylog.Debugf("No log format specified, setting kubernetes log format by default")
   156  		e.EngineConfig.SetLogFormat("kubernetes")
   157  	}
   158  
   159  	if !e.EngineConfig.Exec {
   160  		if e.EngineConfig.OciConfig.Process.Terminal {
   161  			var err error
   162  
   163  			master, slave, err = pty.Open()
   164  			if err != nil {
   165  				return err
   166  			}
   167  			consoleSize := e.EngineConfig.OciConfig.Process.ConsoleSize
   168  			if consoleSize != nil {
   169  				var size pty.Winsize
   170  
   171  				size.Cols = uint16(consoleSize.Width)
   172  				size.Rows = uint16(consoleSize.Height)
   173  				if err := pty.Setsize(slave, &size); err != nil {
   174  					return err
   175  				}
   176  			}
   177  			e.EngineConfig.MasterPts = int(master.Fd())
   178  			e.EngineConfig.SlavePts = int(slave.Fd())
   179  		} else {
   180  			r, w, err := os.Pipe()
   181  			if err != nil {
   182  				return err
   183  			}
   184  			e.EngineConfig.OutputStreams = [2]int{int(r.Fd()), int(w.Fd())}
   185  			r, w, err = os.Pipe()
   186  			if err != nil {
   187  				return err
   188  			}
   189  			e.EngineConfig.ErrorStreams = [2]int{int(r.Fd()), int(w.Fd())}
   190  			r, w, err = os.Pipe()
   191  			if err != nil {
   192  				return err
   193  			}
   194  			e.EngineConfig.InputStreams = [2]int{int(w.Fd()), int(r.Fd())}
   195  		}
   196  	} else {
   197  		starterConfig.SetJoinMount(true)
   198  		cPath := e.EngineConfig.OciConfig.Linux.CgroupsPath
   199  		if cPath == "" {
   200  			return nil
   201  		}
   202  
   203  		// add executed process to container cgroups
   204  		ppid := os.Getppid()
   205  		staticPath := cgroups.StaticPath(cPath)
   206  		control, err := cgroups.Load(cgroups.V1, staticPath)
   207  		if err != nil {
   208  			return fmt.Errorf("failed to load cgroups: %s", err)
   209  		}
   210  		if err := control.Add(cgroups.Process{Pid: ppid}); err != nil {
   211  			return fmt.Errorf("failed to add exec process to cgroups %s: %s", cPath, err)
   212  		}
   213  	}
   214  
   215  	return nil
   216  }