2018-07-04 07:13:30 +00:00
package specconv
import (
"os"
"sort"
"strings"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
// ToRootless converts spec to be compatible with "rootless" runc.
// * Adds userns (Note: since we are already in userns, ideally we should not need to do this. runc-side issue is tracked at https://github.com/opencontainers/runc/issues/1837)
// * Fix up mount flags (same as above)
// * Replace /sys with bind-mount (FIXME: we don't need to do this if netns is unshared)
func ToRootless ( spec * specs . Spec ) error {
if ! system . RunningInUserNS ( ) {
return errors . New ( "needs to be in user namespace" )
}
uidMap , err := user . CurrentProcessUIDMap ( )
if err != nil && ! os . IsNotExist ( err ) {
return err
}
gidMap , err := user . CurrentProcessUIDMap ( )
if err != nil && ! os . IsNotExist ( err ) {
return err
}
return toRootless ( spec , uidMap , gidMap )
}
// toRootless was forked from github.com/opencontainers/runc/libcontainer/specconv
func toRootless ( spec * specs . Spec , uidMap , gidMap [ ] user . IDMap ) error {
if err := configureUserNS ( spec , uidMap , gidMap ) ; err != nil {
return err
}
if err := configureMounts ( spec ) ; err != nil {
return err
}
// Remove cgroup settings.
spec . Linux . Resources = nil
spec . Linux . CgroupsPath = ""
return nil
}
// configureUserNS add suserns and the current ID map to the spec.
// Since we are already in userns, ideally we should not need to add userns.
// However, currently rootless runc always requires userns to be added.
// https://github.com/opencontainers/runc/issues/1837
func configureUserNS ( spec * specs . Spec , uidMap , gidMap [ ] user . IDMap ) error {
spec . Linux . Namespaces = append ( spec . Linux . Namespaces , specs . LinuxNamespace {
Type : specs . UserNamespace ,
} )
sort . Slice ( uidMap , func ( i , j int ) bool { return uidMap [ i ] . ID < uidMap [ j ] . ID } )
uNextContainerID := int64 ( 0 )
for _ , u := range uidMap {
spec . Linux . UIDMappings = append ( spec . Linux . UIDMappings ,
specs . LinuxIDMapping {
HostID : uint32 ( u . ID ) ,
ContainerID : uint32 ( uNextContainerID ) ,
Size : uint32 ( u . Count ) ,
} )
2018-07-06 12:18:17 +00:00
uNextContainerID += int64 ( u . Count )
2018-07-04 07:13:30 +00:00
}
sort . Slice ( gidMap , func ( i , j int ) bool { return gidMap [ i ] . ID < gidMap [ j ] . ID } )
gNextContainerID := int64 ( 0 )
for _ , g := range gidMap {
spec . Linux . GIDMappings = append ( spec . Linux . GIDMappings ,
specs . LinuxIDMapping {
HostID : uint32 ( g . ID ) ,
ContainerID : uint32 ( gNextContainerID ) ,
Size : uint32 ( g . Count ) ,
} )
2018-07-06 12:18:17 +00:00
gNextContainerID += int64 ( g . Count )
2018-07-04 07:13:30 +00:00
}
return nil
}
func configureMounts ( spec * specs . Spec ) error {
var mounts [ ] specs . Mount
for _ , mount := range spec . Mounts {
// Ignore all mounts that are under /sys, because we add /sys later.
if strings . HasPrefix ( mount . Destination , "/sys" ) {
continue
}
// Remove all gid= and uid= mappings.
// Since we are already in userns, ideally we should not need to do this.
// https://github.com/opencontainers/runc/issues/1837
var options [ ] string
for _ , option := range mount . Options {
if ! strings . HasPrefix ( option , "gid=" ) && ! strings . HasPrefix ( option , "uid=" ) {
options = append ( options , option )
}
}
mount . Options = options
mounts = append ( mounts , mount )
}
// Add the sysfs mount as an rbind, because we can't mount /sys unless we have netns.
// TODO: keep original /sys mount when we have netns.
mounts = append ( mounts , specs . Mount {
Source : "/sys" ,
Destination : "/sys" ,
Type : "none" ,
Options : [ ] string { "rbind" , "nosuid" , "noexec" , "nodev" , "ro" } ,
} )
spec . Mounts = mounts
return nil
}