2024-03-14 04:49:35 +00:00
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.19
2018-02-05 21:05:59 +00:00
package v2 // import "github.com/docker/docker/plugin/v2"
2016-12-12 23:05:53 +00:00
import (
"os"
"path/filepath"
2017-05-26 23:14:18 +00:00
"runtime"
2016-12-12 23:05:53 +00:00
"strings"
2024-03-14 04:49:35 +00:00
"github.com/containerd/containerd/pkg/userns"
2016-12-12 23:05:53 +00:00
"github.com/docker/docker/api/types"
2024-03-14 04:49:35 +00:00
"github.com/docker/docker/internal/rootless/mountopts"
"github.com/docker/docker/internal/sliceutil"
2016-12-12 23:05:53 +00:00
"github.com/docker/docker/oci"
2019-08-05 14:37:47 +00:00
specs "github.com/opencontainers/runtime-spec/specs-go"
2017-01-17 18:27:01 +00:00
"github.com/pkg/errors"
2016-12-12 23:05:53 +00:00
)
// InitSpec creates an OCI spec from the plugin's config.
func ( p * Plugin ) InitSpec ( execRoot string ) ( * specs . Spec , error ) {
s := oci . DefaultSpec ( )
2017-12-13 20:24:51 +00:00
2017-08-01 15:51:24 +00:00
s . Root = & specs . Root {
2016-12-12 23:05:53 +00:00
Path : p . Rootfs ,
Readonly : false , // TODO: all plugins should be readonly? settable in config?
}
userMounts := make ( map [ string ] struct { } , len ( p . PluginObj . Settings . Mounts ) )
for _ , m := range p . PluginObj . Settings . Mounts {
userMounts [ m . Destination ] = struct { } { }
}
execRoot = filepath . Join ( execRoot , p . PluginObj . ID )
2022-01-20 13:12:23 +00:00
if err := os . MkdirAll ( execRoot , 0 o700 ) ; err != nil {
2017-01-17 18:27:01 +00:00
return nil , errors . WithStack ( err )
2016-12-12 23:05:53 +00:00
}
2017-12-14 14:29:11 +00:00
if p . PluginObj . Config . PropagatedMount != "" {
pRoot := filepath . Join ( filepath . Dir ( p . Rootfs ) , "propagated-mount" )
s . Mounts = append ( s . Mounts , specs . Mount {
Source : pRoot ,
Destination : p . PluginObj . Config . PropagatedMount ,
Type : "bind" ,
Options : [ ] string { "rbind" , "rw" , "rshared" } ,
} )
s . Linux . RootfsPropagation = "rshared"
}
2016-12-12 23:05:53 +00:00
mounts := append ( p . PluginObj . Config . Mounts , types . PluginMount {
Source : & execRoot ,
Destination : defaultPluginRuntimeDestination ,
Type : "bind" ,
Options : [ ] string { "rbind" , "rshared" } ,
} )
if p . PluginObj . Config . Network . Type != "" {
// TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
if p . PluginObj . Config . Network . Type == "host" {
2017-04-27 21:52:47 +00:00
oci . RemoveNamespace ( & s , specs . LinuxNamespaceType ( "network" ) )
2016-12-12 23:05:53 +00:00
}
etcHosts := "/etc/hosts"
resolvConf := "/etc/resolv.conf"
mounts = append ( mounts ,
types . PluginMount {
Source : & etcHosts ,
Destination : etcHosts ,
Type : "bind" ,
Options : [ ] string { "rbind" , "ro" } ,
} ,
types . PluginMount {
Source : & resolvConf ,
Destination : resolvConf ,
Type : "bind" ,
Options : [ ] string { "rbind" , "ro" } ,
} )
}
2017-03-10 22:17:24 +00:00
if p . PluginObj . Config . PidHost {
2017-04-27 21:52:47 +00:00
oci . RemoveNamespace ( & s , specs . LinuxNamespaceType ( "pid" ) )
2017-03-10 22:17:24 +00:00
}
2016-12-12 23:05:53 +00:00
2017-03-08 02:26:09 +00:00
if p . PluginObj . Config . IpcHost {
2017-04-27 21:52:47 +00:00
oci . RemoveNamespace ( & s , specs . LinuxNamespaceType ( "ipc" ) )
2017-03-08 02:26:09 +00:00
}
2016-12-12 23:05:53 +00:00
for _ , mnt := range mounts {
m := specs . Mount {
Destination : mnt . Destination ,
Type : mnt . Type ,
Options : mnt . Options ,
}
if mnt . Source == nil {
return nil , errors . New ( "mount source is not specified" )
}
m . Source = * mnt . Source
s . Mounts = append ( s . Mounts , m )
}
for i , m := range s . Mounts {
if strings . HasPrefix ( m . Destination , "/dev/" ) {
if _ , ok := userMounts [ m . Destination ] ; ok {
s . Mounts = append ( s . Mounts [ : i ] , s . Mounts [ i + 1 : ] ... )
}
}
}
2017-01-10 19:00:57 +00:00
if p . PluginObj . Config . Linux . AllowAllDevices {
2017-04-27 21:52:47 +00:00
s . Linux . Resources . Devices = [ ] specs . LinuxDeviceCgroup { { Allow : true , Access : "rwm" } }
2016-12-12 23:05:53 +00:00
}
for _ , dev := range p . PluginObj . Settings . Devices {
path := * dev . Path
d , dPermissions , err := oci . DevicesFromPath ( path , path , "rwm" )
if err != nil {
2017-01-17 18:27:01 +00:00
return nil , errors . WithStack ( err )
2016-12-12 23:05:53 +00:00
}
s . Linux . Devices = append ( s . Linux . Devices , d ... )
s . Linux . Resources . Devices = append ( s . Linux . Resources . Devices , dPermissions ... )
}
envs := make ( [ ] string , 1 , len ( p . PluginObj . Settings . Env ) + 1 )
2022-10-07 21:57:09 +00:00
envs [ 0 ] = "PATH=" + oci . DefaultPathEnv ( runtime . GOOS )
2016-12-12 23:05:53 +00:00
envs = append ( envs , p . PluginObj . Settings . Env ... )
args := append ( p . PluginObj . Config . Entrypoint , p . PluginObj . Settings . Args ... )
cwd := p . PluginObj . Config . WorkDir
if len ( cwd ) == 0 {
cwd = "/"
}
s . Process . Terminal = false
s . Process . Args = args
s . Process . Cwd = cwd
s . Process . Env = envs
2017-04-27 21:52:47 +00:00
caps := s . Process . Capabilities
caps . Bounding = append ( caps . Bounding , p . PluginObj . Config . Linux . Capabilities ... )
caps . Permitted = append ( caps . Permitted , p . PluginObj . Config . Linux . Capabilities ... )
caps . Inheritable = append ( caps . Inheritable , p . PluginObj . Config . Linux . Capabilities ... )
caps . Effective = append ( caps . Effective , p . PluginObj . Config . Linux . Capabilities ... )
2016-12-12 23:05:53 +00:00
2017-12-13 20:24:51 +00:00
if p . modifyRuntimeSpec != nil {
p . modifyRuntimeSpec ( & s )
}
2024-03-14 04:49:35 +00:00
// Rootless mode requires modifying the mount flags
// https://github.com/moby/moby/issues/47248#issuecomment-1927776700
// https://github.com/moby/moby/pull/47558
if userns . RunningInUserNS ( ) {
for i := range s . Mounts {
m := & s . Mounts [ i ]
for _ , o := range m . Options {
switch o {
case "bind" , "rbind" :
if _ , err := os . Lstat ( m . Source ) ; err != nil {
if errors . Is ( err , os . ErrNotExist ) {
continue
}
return nil , err
}
// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
// bind-mounting "with options" will not fail with user namespaces, due to
// kernel restrictions that require user namespace mounts to preserve
// CL_UNPRIVILEGED locked flags.
unpriv , err := mountopts . UnprivilegedMountFlags ( m . Source )
if err != nil {
return nil , errors . Wrapf ( err , "failed to get unprivileged mount flags for %+v" , m )
}
m . Options = sliceutil . Dedup ( append ( m . Options , unpriv ... ) )
}
}
}
}
2016-12-12 23:05:53 +00:00
return & s , nil
}