2018-06-29 22:00:38 +08:00
// +build linux
package buildah
import (
2019-04-26 03:39:49 +08:00
"bytes"
"encoding/json"
2018-06-29 22:00:38 +08:00
"fmt"
2019-04-26 03:39:49 +08:00
"io"
"io/ioutil"
"net"
2018-06-29 22:00:38 +08:00
"os"
2019-04-26 03:39:49 +08:00
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
2021-09-14 21:43:42 +08:00
"sync/atomic"
2019-04-26 03:39:49 +08:00
"syscall"
"time"
"github.com/containers/buildah/bind"
"github.com/containers/buildah/chroot"
2020-10-31 17:58:59 +08:00
"github.com/containers/buildah/copier"
2021-02-07 06:49:40 +08:00
"github.com/containers/buildah/define"
2021-10-18 13:51:51 +08:00
"github.com/containers/buildah/internal"
internalParse "github.com/containers/buildah/internal/parse"
internalUtil "github.com/containers/buildah/internal/util"
2019-04-29 21:41:18 +08:00
"github.com/containers/buildah/pkg/overlay"
2021-09-28 19:20:36 +08:00
"github.com/containers/buildah/pkg/parse"
2021-07-26 14:07:23 +08:00
"github.com/containers/buildah/pkg/sshagent"
2019-04-26 03:39:49 +08:00
"github.com/containers/buildah/util"
2022-01-06 04:36:49 +08:00
"github.com/containers/common/libnetwork/network"
nettypes "github.com/containers/common/libnetwork/types"
2020-02-19 03:50:08 +08:00
"github.com/containers/common/pkg/capabilities"
2021-12-02 03:08:25 +08:00
"github.com/containers/common/pkg/cgroups"
2021-01-25 22:32:56 +08:00
"github.com/containers/common/pkg/chown"
2020-02-08 01:54:18 +08:00
"github.com/containers/common/pkg/config"
2020-11-21 04:46:20 +08:00
"github.com/containers/common/pkg/subscriptions"
2021-10-18 13:51:51 +08:00
imagetypes "github.com/containers/image/v5/types"
2022-01-06 04:36:49 +08:00
"github.com/containers/storage"
2019-04-26 03:39:49 +08:00
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/stringid"
2020-03-31 21:56:18 +08:00
"github.com/containers/storage/pkg/unshare"
2021-10-18 13:51:51 +08:00
storagetypes "github.com/containers/storage/types"
2019-04-26 03:39:49 +08:00
"github.com/docker/go-units"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go"
2019-09-07 03:07:18 +08:00
spec "github.com/opencontainers/runtime-spec/specs-go"
2019-04-26 03:39:49 +08:00
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
2021-08-25 03:30:13 +08:00
"golang.org/x/term"
2018-06-29 22:00:38 +08:00
)
2020-09-24 23:40:57 +08:00
// ContainerDevices is an alias for a slice of github.com/opencontainers/runc/libcontainer/configs.Device structures.
2021-02-07 06:49:40 +08:00
type ContainerDevices define . ContainerDevices
2020-09-24 23:40:57 +08:00
2018-06-29 22:00:38 +08:00
func setChildProcess ( ) error {
if err := unix . Prctl ( unix . PR_SET_CHILD_SUBREAPER , uintptr ( 1 ) , 0 , 0 , 0 ) ; err != nil {
fmt . Fprintf ( os . Stderr , "prctl(PR_SET_CHILD_SUBREAPER, 1): %v\n" , err )
return err
}
return nil
}
2019-04-26 03:39:49 +08:00
// Run runs the specified command in the container's root filesystem.
func ( b * Builder ) Run ( command [ ] string , options RunOptions ) error {
2021-02-07 06:49:40 +08:00
p , err := ioutil . TempDir ( "" , define . Package )
2019-04-26 03:39:49 +08:00
if err != nil {
2020-10-15 17:16:50 +08:00
return err
2019-04-26 03:39:49 +08:00
}
// On some hosts like AH, /tmp is a symlink and we need an
// absolute path.
path , err := filepath . EvalSymlinks ( p )
if err != nil {
2020-10-15 17:16:50 +08:00
return err
2019-04-26 03:39:49 +08:00
}
logrus . Debugf ( "using %q to hold bundle data" , path )
defer func ( ) {
if err2 := os . RemoveAll ( path ) ; err2 != nil {
2021-05-08 01:38:44 +08:00
options . Logger . Error ( err2 )
2019-04-26 03:39:49 +08:00
}
} ( )
gp , err := generate . New ( "linux" )
if err != nil {
return errors . Wrapf ( err , "error generating new 'linux' runtime spec" )
}
g := & gp
isolation := options . Isolation
2021-02-07 06:49:40 +08:00
if isolation == define . IsolationDefault {
2019-04-26 03:39:49 +08:00
isolation = b . Isolation
2021-02-07 06:49:40 +08:00
if isolation == define . IsolationDefault {
isolation = define . IsolationOCI
2019-04-26 03:39:49 +08:00
}
}
if err := checkAndOverrideIsolationOptions ( isolation , & options ) ; err != nil {
return err
}
2020-08-27 23:33:35 +08:00
// hardwire the environment to match docker build to avoid subtle and hard-to-debug differences due to containers.conf
b . configureEnvironment ( g , options , [ ] string { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" } )
2019-04-26 03:39:49 +08:00
if b . CommonBuildOpts == nil {
return errors . Errorf ( "Invalid format on container you must recreate the container" )
}
if err := addCommonOptsToSpec ( b . CommonBuildOpts , g ) ; err != nil {
return err
}
if options . WorkingDir != "" {
g . SetProcessCwd ( options . WorkingDir )
} else if b . WorkDir ( ) != "" {
g . SetProcessCwd ( b . WorkDir ( ) )
}
setupSelinux ( g , b . ProcessLabel , b . MountLabel )
mountPoint , err := b . Mount ( b . MountLabel )
if err != nil {
return errors . Wrapf ( err , "error mounting container %q" , b . ContainerID )
}
defer func ( ) {
if err := b . Unmount ( ) ; err != nil {
2021-05-08 01:38:44 +08:00
options . Logger . Errorf ( "error unmounting container: %v" , err )
2019-04-26 03:39:49 +08:00
}
} ( )
g . SetRootPath ( mountPoint )
if len ( command ) > 0 {
command = runLookupPath ( g , command )
g . SetProcessArgs ( command )
} else {
g . SetProcessArgs ( nil )
}
2019-09-07 03:07:18 +08:00
for _ , d := range b . Devices {
sDev := spec . LinuxDevice {
Type : string ( d . Type ) ,
Path : d . Path ,
Major : d . Major ,
Minor : d . Minor ,
FileMode : & d . FileMode ,
UID : & d . Uid ,
GID : & d . Gid ,
}
g . AddDevice ( sDev )
2020-07-06 17:58:10 +08:00
g . AddLinuxResourcesDevice ( true , string ( d . Type ) , & d . Major , & d . Minor , string ( d . Permissions ) )
2019-09-07 03:07:18 +08:00
}
2019-04-26 03:39:49 +08:00
setupMaskedPaths ( g )
setupReadOnlyPaths ( g )
setupTerminal ( g , options . Terminal , options . TerminalSize )
2021-10-27 17:13:35 +08:00
configureNetwork , configureNetworks , err := b . configureNamespaces ( g , & options )
2019-04-26 03:39:49 +08:00
if err != nil {
return err
}
2019-05-16 06:40:15 +08:00
homeDir , err := b . configureUIDGID ( g , mountPoint , options )
if err != nil {
2019-04-26 03:39:49 +08:00
return err
}
g . SetProcessApparmorProfile ( b . CommonBuildOpts . ApparmorProfile )
// Now grab the spec from the generator. Set the generator to nil so that future contributors
// will quickly be able to tell that they're supposed to be modifying the spec directly from here.
spec := g . Config
g = nil
// Set the seccomp configuration using the specified profile name. Some syscalls are
// allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot),
// so we sorted out the capabilities lists first.
if err = setupSeccomp ( spec , b . CommonBuildOpts . SeccompProfilePath ) ; err != nil {
return err
}
// Figure out who owns files that will appear to be owned by UID/GID 0 in the container.
rootUID , rootGID , err := util . GetHostRootIDs ( spec )
if err != nil {
return err
}
rootIDPair := & idtools . IDPair { UID : int ( rootUID ) , GID : int ( rootGID ) }
2020-10-31 17:58:59 +08:00
mode := os . FileMode ( 0755 )
coptions := copier . MkdirOptions {
ChownNew : rootIDPair ,
ChmodNew : & mode ,
}
2020-11-06 01:46:21 +08:00
if err := copier . Mkdir ( mountPoint , filepath . Join ( mountPoint , spec . Process . Cwd ) , coptions ) ; err != nil {
2020-10-31 17:58:59 +08:00
return err
}
2019-04-26 03:39:49 +08:00
bindFiles := make ( map [ string ] string )
namespaceOptions := append ( b . NamespaceOptions , options . NamespaceOptions ... )
volumes := b . Volumes ( )
if ! contains ( volumes , "/etc/hosts" ) {
hostFile , err := b . generateHosts ( path , spec . Hostname , b . CommonBuildOpts . AddHost , rootIDPair )
if err != nil {
return err
}
2020-07-23 05:06:01 +08:00
// Only bind /etc/hosts if there's a network
2021-02-07 06:49:40 +08:00
if options . ConfigureNetwork != define . NetworkDisabled {
2020-07-23 05:06:01 +08:00
bindFiles [ "/etc/hosts" ] = hostFile
}
2019-04-26 03:39:49 +08:00
}
2019-06-07 03:44:23 +08:00
if ! ( contains ( volumes , "/etc/resolv.conf" ) || ( len ( b . CommonBuildOpts . DNSServers ) == 1 && strings . ToLower ( b . CommonBuildOpts . DNSServers [ 0 ] ) == "none" ) ) {
2021-08-04 18:31:13 +08:00
resolvFile , err := b . addResolvConf ( path , rootIDPair , b . CommonBuildOpts . DNSServers , b . CommonBuildOpts . DNSSearch , b . CommonBuildOpts . DNSOptions , namespaceOptions )
2019-04-26 03:39:49 +08:00
if err != nil {
return err
}
2020-07-23 05:06:01 +08:00
// Only bind /etc/resolv.conf if there's a network
2021-02-07 06:49:40 +08:00
if options . ConfigureNetwork != define . NetworkDisabled {
2020-07-23 05:06:01 +08:00
bindFiles [ "/etc/resolv.conf" ] = resolvFile
}
2019-04-26 03:39:49 +08:00
}
2019-09-06 04:54:40 +08:00
// Empty file, so no need to recreate if it exists
if _ , ok := bindFiles [ "/run/.containerenv" ] ; ! ok {
containerenvPath := filepath . Join ( path , "/run/.containerenv" )
2020-10-15 17:16:50 +08:00
if err = os . MkdirAll ( filepath . Dir ( containerenvPath ) , 0755 ) ; err != nil {
2019-09-06 04:54:40 +08:00
return err
}
2020-11-24 08:07:50 +08:00
rootless := 0
if unshare . IsRootless ( ) {
rootless = 1
}
// Populate the .containerenv with container information
containerenv := fmt . Sprintf ( ` \
engine = "buildah-%s"
name = % q
id = % q
image = % q
imageid = % q
rootless = % d
2021-02-07 06:49:40 +08:00
` , define . Version , b . Container , b . ContainerID , b . FromImage , b . FromImageID , rootless )
2020-11-24 08:07:50 +08:00
if err = ioutils . AtomicWriteFile ( containerenvPath , [ ] byte ( containerenv ) , 0755 ) ; err != nil {
2019-09-06 04:54:40 +08:00
return err
}
if err := label . Relabel ( containerenvPath , b . MountLabel , false ) ; err != nil {
2020-10-15 17:16:50 +08:00
return err
2019-09-06 04:54:40 +08:00
}
bindFiles [ "/run/.containerenv" ] = containerenvPath
}
2021-10-18 13:51:51 +08:00
runArtifacts , err := b . setupMounts ( options . SystemContext , mountPoint , spec , path , options . Mounts , bindFiles , volumes , b . CommonBuildOpts . Volumes , b . CommonBuildOpts . ShmSize , namespaceOptions , options . Secrets , options . SSHSources , options . RunMounts , options . ContextDir , options . StageMountPoints )
2019-04-26 03:39:49 +08:00
if err != nil {
return errors . Wrapf ( err , "error resolving mountpoints for container %q" , b . ContainerID )
}
2021-07-26 14:07:23 +08:00
if runArtifacts . SSHAuthSock != "" {
sshenv := "SSH_AUTH_SOCK=" + runArtifacts . SSHAuthSock
spec . Process . Env = append ( spec . Process . Env , sshenv )
}
2021-04-17 06:21:31 +08:00
2021-10-18 13:51:51 +08:00
// following run was called from `buildah run`
// and some images were mounted for this run
// add them to cleanup artifacts
if len ( options . ExternalImageMounts ) > 0 {
runArtifacts . MountedImages = append ( runArtifacts . MountedImages , options . ExternalImageMounts ... )
}
2021-04-17 06:21:31 +08:00
defer func ( ) {
2021-10-18 13:51:51 +08:00
if err := b . cleanupRunMounts ( options . SystemContext , mountPoint , runArtifacts ) ; err != nil {
options . Logger . Errorf ( "unable to cleanup run mounts %v" , err )
2021-04-17 06:21:31 +08:00
}
} ( )
2019-04-29 21:41:18 +08:00
defer b . cleanupTempVolumes ( )
2019-04-26 03:39:49 +08:00
switch isolation {
2021-02-07 06:49:40 +08:00
case define . IsolationOCI :
2019-04-26 03:39:49 +08:00
var moreCreateArgs [ ] string
if options . NoPivot {
moreCreateArgs = [ ] string { "--no-pivot" }
} else {
moreCreateArgs = nil
}
2021-02-07 06:49:40 +08:00
err = b . runUsingRuntimeSubproc ( isolation , options , configureNetwork , configureNetworks , moreCreateArgs , spec , mountPoint , path , define . Package + "-" + filepath . Base ( path ) )
2019-04-26 03:39:49 +08:00
case IsolationChroot :
2019-05-16 06:40:15 +08:00
err = chroot . RunUsingChroot ( spec , path , homeDir , options . Stdin , options . Stdout , options . Stderr )
2019-04-26 03:39:49 +08:00
case IsolationOCIRootless :
moreCreateArgs := [ ] string { "--no-new-keyring" }
if options . NoPivot {
moreCreateArgs = append ( moreCreateArgs , "--no-pivot" )
}
2019-07-16 16:47:48 +08:00
if err := setupRootlessSpecChanges ( spec , path , b . CommonBuildOpts . ShmSize ) ; err != nil {
2019-04-26 03:39:49 +08:00
return err
}
2021-02-07 06:49:40 +08:00
err = b . runUsingRuntimeSubproc ( isolation , options , configureNetwork , configureNetworks , moreCreateArgs , spec , mountPoint , path , define . Package + "-" + filepath . Base ( path ) )
2019-04-26 03:39:49 +08:00
default :
err = errors . Errorf ( "don't know how to run this command" )
}
return err
}
2021-02-07 06:49:40 +08:00
func addCommonOptsToSpec ( commonOpts * define . CommonBuildOptions , g * generate . Generator ) error {
2019-04-26 03:39:49 +08:00
// Resources - CPU
if commonOpts . CPUPeriod != 0 {
g . SetLinuxResourcesCPUPeriod ( commonOpts . CPUPeriod )
}
if commonOpts . CPUQuota != 0 {
g . SetLinuxResourcesCPUQuota ( commonOpts . CPUQuota )
}
if commonOpts . CPUShares != 0 {
g . SetLinuxResourcesCPUShares ( commonOpts . CPUShares )
}
if commonOpts . CPUSetCPUs != "" {
g . SetLinuxResourcesCPUCpus ( commonOpts . CPUSetCPUs )
}
if commonOpts . CPUSetMems != "" {
g . SetLinuxResourcesCPUMems ( commonOpts . CPUSetMems )
}
// Resources - Memory
if commonOpts . Memory != 0 {
g . SetLinuxResourcesMemoryLimit ( commonOpts . Memory )
}
if commonOpts . MemorySwap != 0 {
g . SetLinuxResourcesMemorySwap ( commonOpts . MemorySwap )
}
// cgroup membership
if commonOpts . CgroupParent != "" {
g . SetLinuxCgroupsPath ( commonOpts . CgroupParent )
}
2020-02-08 01:54:18 +08:00
defaultContainerConfig , err := config . Default ( )
if err != nil {
return errors . Wrapf ( err , "failed to get container config" )
}
2019-04-26 03:39:49 +08:00
// Other process resource limits
2020-02-08 01:54:18 +08:00
if err := addRlimits ( commonOpts . Ulimit , g , defaultContainerConfig . Containers . DefaultUlimits ) ; err != nil {
2019-04-26 03:39:49 +08:00
return err
}
logrus . Debugf ( "Resources: %#v" , commonOpts )
return nil
}
2019-07-25 22:10:03 +08:00
func runSetupBuiltinVolumes ( mountLabel , mountPoint , containerDir string , builtinVolumes [ ] string , rootUID , rootGID int ) ( [ ] specs . Mount , error ) {
2019-04-26 03:39:49 +08:00
var mounts [ ] specs . Mount
hostOwner := idtools . IDPair { UID : rootUID , GID : rootGID }
// Add temporary copies of the contents of volume locations at the
// volume locations, unless we already have something there.
for _ , volume := range builtinVolumes {
2021-04-28 03:12:25 +08:00
volumePath := filepath . Join ( containerDir , "buildah-volumes" , digest . Canonical . FromString ( volume ) . Hex ( ) )
2019-04-26 03:39:49 +08:00
initializeVolume := false
2021-04-28 03:12:25 +08:00
// If we need to, create the directory that we'll use to hold
// the volume contents. If we do need to create it, then we'll
// need to populate it, too, so make a note of that.
2019-04-26 03:39:49 +08:00
if _ , err := os . Stat ( volumePath ) ; err != nil {
if ! os . IsNotExist ( err ) {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
2021-04-28 03:12:25 +08:00
logrus . Debugf ( "setting up built-in volume path at %q for %q" , volumePath , volume )
2019-04-26 03:39:49 +08:00
if err = os . MkdirAll ( volumePath , 0755 ) ; err != nil {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
if err = label . Relabel ( volumePath , mountLabel , false ) ; err != nil {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
initializeVolume = true
}
2021-04-28 03:12:25 +08:00
// Make sure the volume exists in the rootfs and read its attributes.
createDirPerms := os . FileMode ( 0755 )
err := copier . Mkdir ( mountPoint , filepath . Join ( mountPoint , volume ) , copier . MkdirOptions {
ChownNew : & hostOwner ,
ChmodNew : & createDirPerms ,
} )
if err != nil {
return nil , errors . Wrapf ( err , "ensuring volume path %q" , filepath . Join ( mountPoint , volume ) )
2021-03-17 09:58:31 +08:00
}
2021-04-28 03:12:25 +08:00
srcPath , err := copier . Eval ( mountPoint , filepath . Join ( mountPoint , volume ) , copier . EvalOptions { } )
2019-04-26 03:39:49 +08:00
if err != nil {
2021-04-28 03:12:25 +08:00
return nil , errors . Wrapf ( err , "evaluating path %q" , srcPath )
}
stat , err := os . Stat ( srcPath )
if err != nil && ! os . IsNotExist ( err ) {
return nil , err
2019-04-26 03:39:49 +08:00
}
2021-04-28 03:12:25 +08:00
// If we need to populate the mounted volume's contents with
// content from the rootfs, set it up now.
2019-04-26 03:39:49 +08:00
if initializeVolume {
if err = os . Chmod ( volumePath , stat . Mode ( ) . Perm ( ) ) ; err != nil {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
if err = os . Chown ( volumePath , int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Uid ) , int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Gid ) ) ; err != nil {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
2021-04-28 03:12:25 +08:00
logrus . Debugf ( "populating directory %q for volume %q using contents of %q" , volumePath , volume , srcPath )
2019-07-25 22:10:03 +08:00
if err = extractWithTar ( mountPoint , srcPath , volumePath ) ; err != nil && ! os . IsNotExist ( errors . Cause ( err ) ) {
2019-04-26 03:39:49 +08:00
return nil , errors . Wrapf ( err , "error populating directory %q for volume %q using contents of %q" , volumePath , volume , srcPath )
}
}
// Add the bind mount.
mounts = append ( mounts , specs . Mount {
Source : volumePath ,
Destination : volume ,
Type : "bind" ,
Options : [ ] string { "bind" } ,
} )
}
return mounts , nil
}
2021-10-18 13:51:51 +08:00
func ( b * Builder ) setupMounts ( context * imagetypes . SystemContext , mountPoint string , spec * specs . Spec , bundlePath string , optionMounts [ ] specs . Mount , bindFiles map [ string ] string , builtinVolumes , volumeMounts [ ] string , shmSize string , namespaceOptions define . NamespaceOptions , secrets map [ string ] define . Secret , sshSources map [ string ] * sshagent . Source , runFileMounts [ ] string , contextDir string , stageMountPoints map [ string ] internal . StageMountDetails ) ( * runMountArtifacts , error ) {
2019-04-26 03:39:49 +08:00
// Start building a new list of mounts.
var mounts [ ] specs . Mount
haveMount := func ( destination string ) bool {
for _ , mount := range mounts {
if mount . Destination == destination {
// Already have something to mount there.
return true
}
}
return false
}
ipc := namespaceOptions . Find ( string ( specs . IPCNamespace ) )
hostIPC := ipc == nil || ipc . Host
net := namespaceOptions . Find ( string ( specs . NetworkNamespace ) )
hostNetwork := net == nil || net . Host
user := namespaceOptions . Find ( string ( specs . UserNamespace ) )
2019-08-09 21:22:38 +08:00
hostUser := ( user == nil || user . Host ) && ! unshare . IsRootless ( )
2019-04-26 03:39:49 +08:00
// Copy mounts from the generated list.
mountCgroups := true
specMounts := [ ] specs . Mount { }
for _ , specMount := range spec . Mounts {
// Override some of the mounts from the generated list if we're doing different things with namespaces.
if specMount . Destination == "/dev/shm" {
2020-06-19 06:03:43 +08:00
specMount . Options = [ ] string { "nosuid" , "noexec" , "nodev" , "mode=1777" }
if shmSize != "" {
specMount . Options = append ( specMount . Options , "size=" + shmSize )
}
2019-04-26 03:39:49 +08:00
if hostIPC && ! hostUser {
if _ , err := os . Stat ( "/dev/shm" ) ; err != nil && os . IsNotExist ( err ) {
logrus . Debugf ( "/dev/shm is not present, not binding into container" )
continue
}
specMount = specs . Mount {
Source : "/dev/shm" ,
Type : "bind" ,
Destination : "/dev/shm" ,
Options : [ ] string { bind . NoBindOption , "rbind" , "nosuid" , "noexec" , "nodev" } ,
}
}
}
if specMount . Destination == "/dev/mqueue" {
if hostIPC && ! hostUser {
if _ , err := os . Stat ( "/dev/mqueue" ) ; err != nil && os . IsNotExist ( err ) {
logrus . Debugf ( "/dev/mqueue is not present, not binding into container" )
continue
}
specMount = specs . Mount {
Source : "/dev/mqueue" ,
Type : "bind" ,
Destination : "/dev/mqueue" ,
Options : [ ] string { bind . NoBindOption , "rbind" , "nosuid" , "noexec" , "nodev" } ,
}
}
}
if specMount . Destination == "/sys" {
if hostNetwork && ! hostUser {
mountCgroups = false
if _ , err := os . Stat ( "/sys" ) ; err != nil && os . IsNotExist ( err ) {
logrus . Debugf ( "/sys is not present, not binding into container" )
continue
}
specMount = specs . Mount {
Source : "/sys" ,
Type : "bind" ,
Destination : "/sys" ,
Options : [ ] string { bind . NoBindOption , "rbind" , "nosuid" , "noexec" , "nodev" , "ro" } ,
}
}
}
specMounts = append ( specMounts , specMount )
}
// Add a mount for the cgroups filesystem, unless we're already
// recursively bind mounting all of /sys, in which case we shouldn't
// bother with it.
sysfsMount := [ ] specs . Mount { }
if mountCgroups {
sysfsMount = [ ] specs . Mount { {
Destination : "/sys/fs/cgroup" ,
Type : "cgroup" ,
Source : "cgroup" ,
Options : [ ] string { bind . NoBindOption , "nosuid" , "noexec" , "nodev" , "relatime" , "ro" } ,
} }
}
// Get the list of files we need to bind into the container.
2019-07-16 16:47:48 +08:00
bindFileMounts := runSetupBoundFiles ( bundlePath , bindFiles )
2019-04-26 03:39:49 +08:00
// After this point we need to know the per-container persistent storage directory.
cdir , err := b . store . ContainerDirectory ( b . ContainerID )
if err != nil {
2021-04-17 06:21:31 +08:00
return nil , errors . Wrapf ( err , "error determining work directory for container %q" , b . ContainerID )
2019-04-26 03:39:49 +08:00
}
2020-12-22 00:19:56 +08:00
// Figure out which UID and GID to tell the subscriptions package to use
2019-04-26 03:39:49 +08:00
// for files that it creates.
rootUID , rootGID , err := util . GetHostRootIDs ( spec )
if err != nil {
2021-04-17 06:21:31 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
2021-09-28 19:20:36 +08:00
// Get host UID and GID of the container process.
processUID , processGID , err := util . GetHostIDs ( spec . Linux . UIDMappings , spec . Linux . GIDMappings , spec . Process . User . UID , spec . Process . User . GID )
if err != nil {
return nil , err
}
2020-12-22 00:19:56 +08:00
// Get the list of subscriptions mounts.
2021-04-21 04:02:27 +08:00
subscriptionMounts := subscriptions . MountsWithUIDGID ( b . MountLabel , cdir , b . DefaultMountsFilePath , mountPoint , int ( rootUID ) , int ( rootGID ) , unshare . IsRootless ( ) , false )
2019-04-26 03:39:49 +08:00
2021-04-28 03:12:25 +08:00
// Get the list of mounts that are just for this Run() call.
2021-07-26 14:07:23 +08:00
// TODO: acui: de-spaghettify run mounts
2021-10-18 13:51:51 +08:00
runMounts , mountArtifacts , err := b . runSetupRunMounts ( context , runFileMounts , secrets , stageMountPoints , sshSources , cdir , contextDir , spec . Linux . UIDMappings , spec . Linux . GIDMappings , int ( rootUID ) , int ( rootGID ) , int ( processUID ) , int ( processGID ) )
2021-04-17 06:21:31 +08:00
if err != nil {
return nil , err
}
2019-04-26 03:39:49 +08:00
// Add temporary copies of the contents of volume locations at the
// volume locations, unless we already have something there.
2019-07-25 22:10:03 +08:00
builtins , err := runSetupBuiltinVolumes ( b . MountLabel , mountPoint , cdir , builtinVolumes , int ( rootUID ) , int ( rootGID ) )
2019-04-26 03:39:49 +08:00
if err != nil {
2021-04-17 06:21:31 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
2020-11-18 22:50:53 +08:00
2019-04-26 03:39:49 +08:00
// Get the list of explicitly-specified volume mounts.
2020-11-18 22:50:53 +08:00
volumes , err := b . runSetupVolumeMounts ( spec . Linux . MountLabel , volumeMounts , optionMounts , int ( rootUID ) , int ( rootGID ) , int ( processUID ) , int ( processGID ) )
2019-04-26 03:39:49 +08:00
if err != nil {
2021-04-17 06:21:31 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
2021-09-19 19:40:11 +08:00
// prepare list of mount destinations which can be cleaned up safely.
// we can clean bindFiles, subscriptionMounts and specMounts
// everything other than these might have users content
mountArtifacts . RunMountTargets = append ( append ( append ( mountArtifacts . RunMountTargets , cleanableDestinationListFromMounts ( bindFileMounts ) ... ) , cleanableDestinationListFromMounts ( subscriptionMounts ) ... ) , cleanableDestinationListFromMounts ( specMounts ) ... )
2021-04-17 06:21:31 +08:00
allMounts := util . SortMounts ( append ( append ( append ( append ( append ( append ( volumes , builtins ... ) , runMounts ... ) , subscriptionMounts ... ) , bindFileMounts ... ) , specMounts ... ) , sysfsMount ... ) )
2019-04-26 03:39:49 +08:00
// Add them all, in the preferred order, except where they conflict with something that was previously added.
2021-03-31 18:57:18 +08:00
for _ , mount := range allMounts {
2019-04-26 03:39:49 +08:00
if haveMount ( mount . Destination ) {
// Already mounting something there, no need to bother with this one.
continue
}
// Add the mount.
mounts = append ( mounts , mount )
}
// Set the list in the spec.
spec . Mounts = mounts
2021-07-26 14:07:23 +08:00
return mountArtifacts , nil
2019-04-26 03:39:49 +08:00
}
2021-09-19 19:40:11 +08:00
// Destinations which can be cleaned up after every RUN
func cleanableDestinationListFromMounts ( mounts [ ] spec . Mount ) [ ] string {
mountDest := [ ] string { }
for _ , mount := range mounts {
// Add all destination to mountArtifacts so that they can be cleaned up later
if mount . Destination != "" {
// we dont want to remove destinations with /etc, /dev, /sys, /proc as rootfs already contains these files
// and unionfs will create a `whiteout` i.e `.wh` files on removal of overlapping files from these directories.
// everything other than these will be cleanedup
if ! strings . HasPrefix ( mount . Destination , "/etc" ) && ! strings . HasPrefix ( mount . Destination , "/dev" ) && ! strings . HasPrefix ( mount . Destination , "/sys" ) && ! strings . HasPrefix ( mount . Destination , "/proc" ) {
mountDest = append ( mountDest , mount . Destination )
}
}
}
return mountDest
}
2021-08-04 18:31:13 +08:00
// addResolvConf copies files from host and sets them up to bind mount into container
func ( b * Builder ) addResolvConf ( rdir string , chownOpts * idtools . IDPair , dnsServers , dnsSearch , dnsOptions [ ] string , namespaceOptions define . NamespaceOptions ) ( string , error ) {
resolvConf := "/etc/resolv.conf"
stat , err := os . Stat ( resolvConf )
2019-04-26 03:39:49 +08:00
if err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
2021-08-04 18:31:13 +08:00
contents , err := ioutil . ReadFile ( resolvConf )
// resolv.conf doesn't have to exists
if err != nil && ! os . IsNotExist ( err ) {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
2021-08-04 18:31:13 +08:00
netns := false
ns := namespaceOptions . Find ( string ( spec . NetworkNamespace ) )
if ns != nil && ! ns . Host {
netns = true
}
nameservers := resolvconf . GetNameservers ( contents , types . IPv4 )
// check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver
if len ( nameservers ) == 1 && nameservers [ 0 ] == "127.0.0.53" && netns {
// read the actual resolv.conf file for systemd-resolved
resolvedContents , err := ioutil . ReadFile ( "/run/systemd/resolve/resolv.conf" )
if err != nil {
if ! os . IsNotExist ( err ) {
return "" , errors . Wrapf ( err , "detected that systemd-resolved is in use, but could not locate real resolv.conf" )
}
} else {
contents = resolvedContents
}
}
// Ensure that the container's /etc/resolv.conf is compatible with its
// network configuration.
if netns {
// FIXME handle IPv6
resolve , err := resolvconf . FilterResolvDNS ( contents , true )
if err != nil {
return "" , errors . Wrapf ( err , "error parsing host resolv.conf" )
}
contents = resolve . Content
}
2019-04-26 03:39:49 +08:00
search := resolvconf . GetSearchDomains ( contents )
2021-08-04 18:31:13 +08:00
nameservers = resolvconf . GetNameservers ( contents , types . IP )
2019-04-26 03:39:49 +08:00
options := resolvconf . GetOptions ( contents )
2020-02-08 01:54:18 +08:00
defaultContainerConfig , err := config . Default ( )
if err != nil {
return "" , errors . Wrapf ( err , "failed to get container config" )
}
dnsSearch = append ( defaultContainerConfig . Containers . DNSSearches , dnsSearch ... )
2019-04-26 03:39:49 +08:00
if len ( dnsSearch ) > 0 {
search = dnsSearch
}
2019-06-24 18:24:33 +08:00
if b . Isolation == IsolationOCIRootless {
if ns != nil && ! ns . Host && ns . Path == "" {
// if we are using slirp4netns, also add the built-in DNS server.
logrus . Debugf ( "adding slirp4netns 10.0.2.3 built-in DNS server" )
nameservers = append ( [ ] string { "10.0.2.3" } , nameservers ... )
}
}
2020-02-08 01:54:18 +08:00
dnsServers = append ( defaultContainerConfig . Containers . DNSServers , dnsServers ... )
2019-04-26 03:39:49 +08:00
if len ( dnsServers ) != 0 {
dns , err := getDNSIP ( dnsServers )
if err != nil {
return "" , errors . Wrapf ( err , "error getting dns servers" )
}
nameservers = [ ] string { }
for _ , server := range dns {
nameservers = append ( nameservers , server . String ( ) )
}
}
2020-02-08 01:54:18 +08:00
dnsOptions = append ( defaultContainerConfig . Containers . DNSOptions , dnsOptions ... )
2019-04-26 03:39:49 +08:00
if len ( dnsOptions ) != 0 {
options = dnsOptions
}
2021-08-04 18:31:13 +08:00
cfile := filepath . Join ( rdir , filepath . Base ( resolvConf ) )
2019-04-26 03:39:49 +08:00
if _ , err = resolvconf . Build ( cfile , nameservers , search , options ) ; err != nil {
return "" , errors . Wrapf ( err , "error building resolv.conf for container %s" , b . ContainerID )
}
uid := int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Uid )
gid := int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Gid )
if chownOpts != nil {
uid = chownOpts . UID
gid = chownOpts . GID
}
if err = os . Chown ( cfile , uid , gid ) ; err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
if err := label . Relabel ( cfile , b . MountLabel , false ) ; err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
return cfile , nil
}
// generateHosts creates a containers hosts file
func ( b * Builder ) generateHosts ( rdir , hostname string , addHosts [ ] string , chownOpts * idtools . IDPair ) ( string , error ) {
hostPath := "/etc/hosts"
stat , err := os . Stat ( hostPath )
if err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
hosts := bytes . NewBufferString ( "# Generated by Buildah\n" )
orig , err := ioutil . ReadFile ( hostPath )
if err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
hosts . Write ( orig )
for _ , host := range addHosts {
// verify the host format
values := strings . SplitN ( host , ":" , 2 )
if len ( values ) != 2 {
return "" , errors . Errorf ( "unable to parse host entry %q: incorrect format" , host )
}
if values [ 0 ] == "" {
return "" , errors . Errorf ( "hostname in host entry %q is empty" , host )
}
if values [ 1 ] == "" {
return "" , errors . Errorf ( "IP address in host entry %q is empty" , host )
}
hosts . Write ( [ ] byte ( fmt . Sprintf ( "%s\t%s\n" , values [ 1 ] , values [ 0 ] ) ) )
}
2021-10-13 03:33:07 +08:00
hosts . Write ( [ ] byte ( fmt . Sprintf ( "127.0.0.1 %s %s\n" , b . Container , hostname ) ) )
hosts . Write ( [ ] byte ( fmt . Sprintf ( "::1 %s %s\n" , b . Container , hostname ) ) )
2019-04-26 03:39:49 +08:00
2021-11-22 22:27:17 +08:00
if ip := util . LocalIP ( ) ; ip != "" {
2021-10-13 03:33:07 +08:00
hosts . Write ( [ ] byte ( fmt . Sprintf ( "%s %s\n" , ip , "host.containers.internal" ) ) )
2019-04-26 03:39:49 +08:00
}
2021-10-13 03:33:07 +08:00
2019-04-26 03:39:49 +08:00
cfile := filepath . Join ( rdir , filepath . Base ( hostPath ) )
if err = ioutils . AtomicWriteFile ( cfile , hosts . Bytes ( ) , stat . Mode ( ) . Perm ( ) ) ; err != nil {
return "" , errors . Wrapf ( err , "error writing /etc/hosts into the container" )
}
uid := int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Uid )
gid := int ( stat . Sys ( ) . ( * syscall . Stat_t ) . Gid )
if chownOpts != nil {
uid = chownOpts . UID
gid = chownOpts . GID
}
if err = os . Chown ( cfile , uid , gid ) ; err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
if err := label . Relabel ( cfile , b . MountLabel , false ) ; err != nil {
2020-10-15 17:16:50 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
return cfile , nil
}
func setupTerminal ( g * generate . Generator , terminalPolicy TerminalPolicy , terminalSize * specs . Box ) {
switch terminalPolicy {
case DefaultTerminal :
2021-08-25 03:30:13 +08:00
onTerminal := term . IsTerminal ( unix . Stdin ) && term . IsTerminal ( unix . Stdout ) && term . IsTerminal ( unix . Stderr )
2019-04-26 03:39:49 +08:00
if onTerminal {
logrus . Debugf ( "stdio is a terminal, defaulting to using a terminal" )
} else {
logrus . Debugf ( "stdio is not a terminal, defaulting to not using a terminal" )
}
g . SetProcessTerminal ( onTerminal )
case WithTerminal :
g . SetProcessTerminal ( true )
case WithoutTerminal :
g . SetProcessTerminal ( false )
}
if terminalSize != nil {
g . SetProcessConsoleSize ( terminalSize . Width , terminalSize . Height )
}
}
2022-01-05 23:00:04 +08:00
func runUsingRuntime ( options RunOptions , configureNetwork bool , moreCreateArgs [ ] string , spec * specs . Spec , bundlePath , containerName string ,
containerCreateW io . WriteCloser , containerStartR io . ReadCloser ) ( wstatus unix . WaitStatus , err error ) {
2021-08-06 17:12:05 +08:00
if options . Logger == nil {
options . Logger = logrus . StandardLogger ( )
}
2019-04-26 03:39:49 +08:00
// Lock the caller to a single OS-level thread.
runtime . LockOSThread ( )
// Set up bind mounts for things that a namespaced user might not be able to get to directly.
unmountAll , err := bind . SetupIntermediateMountNamespace ( spec , bundlePath )
if unmountAll != nil {
defer func ( ) {
if err := unmountAll ( ) ; err != nil {
2021-05-08 01:38:44 +08:00
options . Logger . Error ( err )
2019-04-26 03:39:49 +08:00
}
} ( )
}
if err != nil {
return 1 , err
}
// Write the runtime configuration.
specbytes , err := json . Marshal ( spec )
if err != nil {
return 1 , errors . Wrapf ( err , "error encoding configuration %#v as json" , spec )
}
if err = ioutils . AtomicWriteFile ( filepath . Join ( bundlePath , "config.json" ) , specbytes , 0600 ) ; err != nil {
2020-10-15 17:16:50 +08:00
return 1 , errors . Wrapf ( err , "error storing runtime configuration" )
2019-04-26 03:39:49 +08:00
}
logrus . Debugf ( "config = %v" , string ( specbytes ) )
// Decide which runtime to use.
runtime := options . Runtime
if runtime == "" {
runtime = util . Runtime ( )
2020-02-23 03:39:48 +08:00
localRuntime := util . FindLocalRuntime ( runtime )
if localRuntime != "" {
runtime = localRuntime
}
2019-04-26 03:39:49 +08:00
}
// Default to just passing down our stdio.
getCreateStdio := func ( ) ( io . ReadCloser , io . WriteCloser , io . WriteCloser ) {
return os . Stdin , os . Stdout , os . Stderr
}
// Figure out how we're doing stdio handling, and create pipes and sockets.
var stdio sync . WaitGroup
var consoleListener * net . UnixListener
var errorFds , closeBeforeReadingErrorFds [ ] int
stdioPipe := make ( [ ] [ ] int , 3 )
copyConsole := false
copyPipes := false
finishCopy := make ( [ ] int , 2 )
if err = unix . Pipe ( finishCopy ) ; err != nil {
return 1 , errors . Wrapf ( err , "error creating pipe for notifying to stop stdio" )
}
finishedCopy := make ( chan struct { } )
2020-03-10 21:43:54 +08:00
var pargs [ ] string
2019-04-26 03:39:49 +08:00
if spec . Process != nil {
2020-03-10 21:43:54 +08:00
pargs = spec . Process . Args
2019-04-26 03:39:49 +08:00
if spec . Process . Terminal {
copyConsole = true
// Create a listening socket for accepting the container's terminal's PTY master.
socketPath := filepath . Join ( bundlePath , "console.sock" )
consoleListener , err = net . ListenUnix ( "unix" , & net . UnixAddr { Name : socketPath , Net : "unix" } )
if err != nil {
return 1 , errors . Wrapf ( err , "error creating socket %q to receive terminal descriptor" , consoleListener . Addr ( ) )
}
// Add console socket arguments.
moreCreateArgs = append ( moreCreateArgs , "--console-socket" , socketPath )
} else {
copyPipes = true
// Figure out who should own the pipes.
uid , gid , err := util . GetHostRootIDs ( spec )
if err != nil {
return 1 , err
}
// Create stdio pipes.
if stdioPipe , err = runMakeStdioPipe ( int ( uid ) , int ( gid ) ) ; err != nil {
return 1 , err
}
2021-11-16 23:56:04 +08:00
if err = runLabelStdioPipes ( stdioPipe , spec . Process . SelinuxLabel , spec . Linux . MountLabel ) ; err != nil {
return 1 , err
}
2019-04-26 03:39:49 +08:00
errorFds = [ ] int { stdioPipe [ unix . Stdout ] [ 0 ] , stdioPipe [ unix . Stderr ] [ 0 ] }
closeBeforeReadingErrorFds = [ ] int { stdioPipe [ unix . Stdout ] [ 1 ] , stdioPipe [ unix . Stderr ] [ 1 ] }
// Set stdio to our pipes.
getCreateStdio = func ( ) ( io . ReadCloser , io . WriteCloser , io . WriteCloser ) {
stdin := os . NewFile ( uintptr ( stdioPipe [ unix . Stdin ] [ 0 ] ) , "/dev/stdin" )
stdout := os . NewFile ( uintptr ( stdioPipe [ unix . Stdout ] [ 1 ] ) , "/dev/stdout" )
stderr := os . NewFile ( uintptr ( stdioPipe [ unix . Stderr ] [ 1 ] ) , "/dev/stderr" )
return stdin , stdout , stderr
}
}
} else {
if options . Quiet {
// Discard stdout.
getCreateStdio = func ( ) ( io . ReadCloser , io . WriteCloser , io . WriteCloser ) {
return os . Stdin , nil , os . Stderr
}
}
}
// Build the commands that we'll execute.
pidFile := filepath . Join ( bundlePath , "pid" )
args := append ( append ( append ( options . Args , "create" , "--bundle" , bundlePath , "--pid-file" , pidFile ) , moreCreateArgs ... ) , containerName )
create := exec . Command ( runtime , args ... )
create . Dir = bundlePath
stdin , stdout , stderr := getCreateStdio ( )
create . Stdin , create . Stdout , create . Stderr = stdin , stdout , stderr
if create . SysProcAttr == nil {
create . SysProcAttr = & syscall . SysProcAttr { }
}
args = append ( options . Args , "start" , containerName )
start := exec . Command ( runtime , args ... )
start . Dir = bundlePath
start . Stderr = os . Stderr
args = append ( options . Args , "kill" , containerName )
kill := exec . Command ( runtime , args ... )
kill . Dir = bundlePath
kill . Stderr = os . Stderr
args = append ( options . Args , "delete" , containerName )
del := exec . Command ( runtime , args ... )
del . Dir = bundlePath
del . Stderr = os . Stderr
// Actually create the container.
logrus . Debugf ( "Running %q" , create . Args )
err = create . Run ( )
if err != nil {
2021-05-08 01:38:44 +08:00
return 1 , errors . Wrapf ( err , "error from %s creating container for %v: %s" , runtime , pargs , runCollectOutput ( options . Logger , errorFds , closeBeforeReadingErrorFds ) )
2019-04-26 03:39:49 +08:00
}
defer func ( ) {
err2 := del . Run ( )
if err2 != nil {
if err == nil {
err = errors . Wrapf ( err2 , "error deleting container" )
} else {
2021-05-08 01:38:44 +08:00
options . Logger . Infof ( "error from %s deleting container: %v" , runtime , err2 )
2019-04-26 03:39:49 +08:00
}
}
} ( )
// Make sure we read the container's exit status when it exits.
pidValue , err := ioutil . ReadFile ( pidFile )
if err != nil {
2020-10-15 17:16:50 +08:00
return 1 , err
2019-04-26 03:39:49 +08:00
}
pid , err := strconv . Atoi ( strings . TrimSpace ( string ( pidValue ) ) )
if err != nil {
return 1 , errors . Wrapf ( err , "error parsing pid %s as a number" , string ( pidValue ) )
}
2021-09-14 21:43:42 +08:00
var stopped uint32
2019-04-26 03:39:49 +08:00
var reaping sync . WaitGroup
reaping . Add ( 1 )
go func ( ) {
defer reaping . Done ( )
var err error
_ , err = unix . Wait4 ( pid , & wstatus , 0 , nil )
if err != nil {
wstatus = 0
2021-05-08 01:38:44 +08:00
options . Logger . Errorf ( "error waiting for container child process %d: %v\n" , pid , err )
2019-04-26 03:39:49 +08:00
}
2021-09-14 21:43:42 +08:00
atomic . StoreUint32 ( & stopped , 1 )
2019-04-26 03:39:49 +08:00
} ( )
if configureNetwork {
2022-01-05 23:00:04 +08:00
if _ , err := containerCreateW . Write ( [ ] byte { 1 } ) ; err != nil {
2019-04-26 03:39:49 +08:00
return 1 , err
}
2022-01-05 23:00:04 +08:00
containerCreateW . Close ( )
logrus . Debug ( "waiting for parent start message" )
b := make ( [ ] byte , 1 )
if _ , err := containerStartR . Read ( b ) ; err != nil {
return 1 , errors . Wrap ( err , "did not get container start message from parent" )
}
containerStartR . Close ( )
2019-04-26 03:39:49 +08:00
}
if copyPipes {
// We don't need the ends of the pipes that belong to the container.
stdin . Close ( )
if stdout != nil {
stdout . Close ( )
}
stderr . Close ( )
}
// Handle stdio for the container in the background.
stdio . Add ( 1 )
2021-05-08 01:38:44 +08:00
go runCopyStdio ( options . Logger , & stdio , copyPipes , stdioPipe , copyConsole , consoleListener , finishCopy , finishedCopy , spec )
2019-04-26 03:39:49 +08:00
// Start the container.
logrus . Debugf ( "Running %q" , start . Args )
err = start . Run ( )
if err != nil {
2021-04-28 23:04:38 +08:00
return 1 , errors . Wrapf ( err , "error from %s starting container" , runtime )
2019-04-26 03:39:49 +08:00
}
defer func ( ) {
2021-09-14 21:43:42 +08:00
if atomic . LoadUint32 ( & stopped ) == 0 {
2020-07-02 22:29:37 +08:00
if err2 := kill . Run ( ) ; err2 != nil {
2021-05-08 01:38:44 +08:00
options . Logger . Infof ( "error from %s stopping container: %v" , runtime , err2 )
2019-04-26 03:39:49 +08:00
}
}
} ( )
// Wait for the container to exit.
for {
now := time . Now ( )
var state specs . State
args = append ( options . Args , "state" , containerName )
stat := exec . Command ( runtime , args ... )
stat . Dir = bundlePath
stat . Stderr = os . Stderr
2020-08-21 20:11:55 +08:00
stateOutput , err := stat . Output ( )
if err != nil {
2021-09-14 21:43:42 +08:00
if atomic . LoadUint32 ( & stopped ) != 0 {
2021-05-07 19:37:44 +08:00
// container exited
break
}
2021-04-28 23:04:38 +08:00
return 1 , errors . Wrapf ( err , "error reading container state from %s (got output: %q)" , runtime , string ( stateOutput ) )
2019-04-26 03:39:49 +08:00
}
if err = json . Unmarshal ( stateOutput , & state ) ; err != nil {
2021-04-28 23:04:38 +08:00
return 1 , errors . Wrapf ( err , "error parsing container state %q from %s" , string ( stateOutput ) , runtime )
2019-04-26 03:39:49 +08:00
}
switch state . Status {
case "running" :
case "stopped" :
2021-09-14 21:43:42 +08:00
atomic . StoreUint32 ( & stopped , 1 )
2019-04-26 03:39:49 +08:00
default :
return 1 , errors . Errorf ( "container status unexpectedly changed to %q" , state . Status )
}
2021-09-14 21:43:42 +08:00
if atomic . LoadUint32 ( & stopped ) != 0 {
2019-04-26 03:39:49 +08:00
break
}
select {
case <- finishedCopy :
2021-09-14 21:43:42 +08:00
atomic . StoreUint32 ( & stopped , 1 )
2019-04-26 03:39:49 +08:00
case <- time . After ( time . Until ( now . Add ( 100 * time . Millisecond ) ) ) :
continue
}
2021-09-14 21:43:42 +08:00
if atomic . LoadUint32 ( & stopped ) != 0 {
2019-04-26 03:39:49 +08:00
break
}
}
// Close the writing end of the stop-handling-stdio notification pipe.
unix . Close ( finishCopy [ 1 ] )
// Wait for the stdio copy goroutine to flush.
stdio . Wait ( )
// Wait until we finish reading the exit status.
reaping . Wait ( )
return wstatus , nil
}
2021-05-08 01:38:44 +08:00
func runCollectOutput ( logger * logrus . Logger , fds , closeBeforeReadingFds [ ] int ) string { //nolint:interfacer
2019-04-26 03:39:49 +08:00
for _ , fd := range closeBeforeReadingFds {
unix . Close ( fd )
}
var b bytes . Buffer
buf := make ( [ ] byte , 8192 )
for _ , fd := range fds {
nread , err := unix . Read ( fd , buf )
if err != nil {
if errno , isErrno := err . ( syscall . Errno ) ; isErrno {
switch errno {
default :
2021-05-08 01:38:44 +08:00
logger . Errorf ( "error reading from pipe %d: %v" , fd , err )
2019-04-26 03:39:49 +08:00
case syscall . EINTR , syscall . EAGAIN :
}
} else {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "unable to wait for data from pipe %d: %v" , fd , err )
2019-04-26 03:39:49 +08:00
}
continue
}
for nread > 0 {
r := buf [ : nread ]
if nwritten , err := b . Write ( r ) ; err != nil || nwritten != len ( r ) {
if nwritten != len ( r ) {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "error buffering data from pipe %d: %v" , fd , err )
2019-04-26 03:39:49 +08:00
break
}
}
nread , err = unix . Read ( fd , buf )
if err != nil {
if errno , isErrno := err . ( syscall . Errno ) ; isErrno {
switch errno {
default :
2021-05-08 01:38:44 +08:00
logger . Errorf ( "error reading from pipe %d: %v" , fd , err )
2019-04-26 03:39:49 +08:00
case syscall . EINTR , syscall . EAGAIN :
}
} else {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "unable to wait for data from pipe %d: %v" , fd , err )
2019-04-26 03:39:49 +08:00
}
break
}
}
}
return b . String ( )
}
func setupRootlessNetwork ( pid int ) ( teardown func ( ) , err error ) {
slirp4netns , err := exec . LookPath ( "slirp4netns" )
if err != nil {
2020-10-15 17:16:50 +08:00
return nil , err
2019-04-26 03:39:49 +08:00
}
rootlessSlirpSyncR , rootlessSlirpSyncW , err := os . Pipe ( )
if err != nil {
return nil , errors . Wrapf ( err , "cannot create slirp4netns sync pipe" )
}
defer rootlessSlirpSyncR . Close ( )
// Be sure there are no fds inherited to slirp4netns except the sync pipe
files , err := ioutil . ReadDir ( "/proc/self/fd" )
if err != nil {
return nil , errors . Wrapf ( err , "cannot list open fds" )
}
for _ , f := range files {
fd , err := strconv . Atoi ( f . Name ( ) )
if err != nil {
return nil , errors . Wrapf ( err , "cannot parse fd" )
}
if fd == int ( rootlessSlirpSyncW . Fd ( ) ) {
continue
}
unix . CloseOnExec ( fd )
}
2021-09-28 05:25:06 +08:00
cmd := exec . Command ( slirp4netns , "--mtu" , "65520" , "-r" , "3" , "-c" , strconv . Itoa ( pid ) , "tap0" )
2019-04-26 03:39:49 +08:00
cmd . Stdin , cmd . Stdout , cmd . Stderr = nil , nil , nil
cmd . ExtraFiles = [ ] * os . File { rootlessSlirpSyncW }
err = cmd . Start ( )
rootlessSlirpSyncW . Close ( )
if err != nil {
return nil , errors . Wrapf ( err , "cannot start slirp4netns" )
}
b := make ( [ ] byte , 1 )
for {
if err := rootlessSlirpSyncR . SetDeadline ( time . Now ( ) . Add ( 1 * time . Second ) ) ; err != nil {
return nil , errors . Wrapf ( err , "error setting slirp4netns pipe timeout" )
}
if _ , err := rootlessSlirpSyncR . Read ( b ) ; err == nil {
break
} else {
if os . IsTimeout ( err ) {
// Check if the process is still running.
var status syscall . WaitStatus
_ , err := syscall . Wait4 ( cmd . Process . Pid , & status , syscall . WNOHANG , nil )
if err != nil {
return nil , errors . Wrapf ( err , "failed to read slirp4netns process status" )
}
if status . Exited ( ) || status . Signaled ( ) {
return nil , errors . New ( "slirp4netns failed" )
}
continue
}
return nil , errors . Wrapf ( err , "failed to read from slirp4netns sync pipe" )
}
}
return func ( ) {
2019-06-18 19:37:58 +08:00
cmd . Process . Kill ( ) // nolint:errcheck
cmd . Wait ( ) // nolint:errcheck
2019-04-26 03:39:49 +08:00
} , nil
}
2022-01-06 04:36:49 +08:00
func ( b * Builder ) runConfigureNetwork ( pid int , isolation define . Isolation , options RunOptions , configureNetworks [ ] string , containerName string ) ( teardown func ( ) , err error ) {
2019-04-26 03:39:49 +08:00
if isolation == IsolationOCIRootless {
if ns := options . NamespaceOptions . Find ( string ( specs . NetworkNamespace ) ) ; ns != nil && ! ns . Host && ns . Path == "" {
return setupRootlessNetwork ( pid )
}
}
2021-06-23 03:55:00 +08:00
2022-01-06 04:36:49 +08:00
if len ( configureNetworks ) == 0 {
configureNetworks = [ ] string { b . NetworkInterface . DefaultNetworkName ( ) }
2021-06-23 03:55:00 +08:00
}
2019-04-26 03:39:49 +08:00
// Make sure we can access the container's network namespace,
// even after it exits, to successfully tear down the
// interfaces. Ensure this by opening a handle to the network
// namespace, and using our copy to both configure and
// deconfigure it.
netns := fmt . Sprintf ( "/proc/%d/ns/net" , pid )
netFD , err := unix . Open ( netns , unix . O_RDONLY , 0 )
if err != nil {
2022-01-05 23:00:04 +08:00
return nil , errors . Wrapf ( err , "error opening network namespace" )
2019-04-26 03:39:49 +08:00
}
mynetns := fmt . Sprintf ( "/proc/%d/fd/%d" , unix . Getpid ( ) , netFD )
2022-01-06 04:36:49 +08:00
networks := make ( map [ string ] nettypes . PerNetworkOptions , len ( configureNetworks ) )
for i , network := range configureNetworks {
networks [ network ] = nettypes . PerNetworkOptions {
InterfaceName : fmt . Sprintf ( "eth%d" , i ) ,
2019-04-26 03:39:49 +08:00
}
}
2022-01-06 04:36:49 +08:00
opts := nettypes . NetworkOptions {
ContainerID : containerName ,
ContainerName : containerName ,
Networks : networks ,
}
_ , err = b . NetworkInterface . Setup ( mynetns , nettypes . SetupOptions { NetworkOptions : opts } )
if err != nil {
return nil , err
}
teardown = func ( ) {
err := b . NetworkInterface . Teardown ( mynetns , nettypes . TeardownOptions { NetworkOptions : opts } )
2019-04-26 03:39:49 +08:00
if err != nil {
2022-01-06 04:36:49 +08:00
options . Logger . Errorf ( "failed to cleanup network: %v" , err )
2019-04-26 03:39:49 +08:00
}
}
2022-01-06 04:36:49 +08:00
2019-04-26 03:39:49 +08:00
return teardown , nil
}
2021-06-24 22:49:20 +08:00
func setNonblock ( logger * logrus . Logger , fd int , description string , nonblocking bool ) ( bool , error ) { //nolint:interfacer
mask , err := unix . FcntlInt ( uintptr ( fd ) , unix . F_GETFL , 0 )
2019-06-12 18:19:28 +08:00
if err != nil {
2021-06-24 22:49:20 +08:00
return false , err
}
blocked := mask & unix . O_NONBLOCK == 0
if err := unix . SetNonblock ( fd , nonblocking ) ; err != nil {
2019-06-12 18:19:28 +08:00
if nonblocking {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "error setting %s to nonblocking: %v" , description , err )
2019-06-12 18:19:28 +08:00
} else {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "error setting descriptor %s blocking: %v" , description , err )
2019-06-12 18:19:28 +08:00
}
}
2021-06-24 22:49:20 +08:00
return blocked , err
2019-06-12 18:19:28 +08:00
}
2021-05-08 01:38:44 +08:00
func runCopyStdio ( logger * logrus . Logger , stdio * sync . WaitGroup , copyPipes bool , stdioPipe [ ] [ ] int , copyConsole bool , consoleListener * net . UnixListener , finishCopy [ ] int , finishedCopy chan struct { } , spec * specs . Spec ) {
2019-04-26 03:39:49 +08:00
defer func ( ) {
unix . Close ( finishCopy [ 0 ] )
if copyPipes {
unix . Close ( stdioPipe [ unix . Stdin ] [ 1 ] )
unix . Close ( stdioPipe [ unix . Stdout ] [ 0 ] )
unix . Close ( stdioPipe [ unix . Stderr ] [ 0 ] )
}
stdio . Done ( )
finishedCopy <- struct { } { }
} ( )
// Map describing where data on an incoming descriptor should go.
relayMap := make ( map [ int ] int )
// Map describing incoming and outgoing descriptors.
readDesc := make ( map [ int ] string )
writeDesc := make ( map [ int ] string )
// Buffers.
relayBuffer := make ( map [ int ] * bytes . Buffer )
// Set up the terminal descriptor or pipes for polling.
if copyConsole {
// Accept a connection over our listening socket.
2021-05-08 01:38:44 +08:00
fd , err := runAcceptTerminal ( logger , consoleListener , spec . Process . ConsoleSize )
2019-04-26 03:39:49 +08:00
if err != nil {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "%v" , err )
2019-04-26 03:39:49 +08:00
return
}
terminalFD := fd
// Input from our stdin, output from the terminal descriptor.
relayMap [ unix . Stdin ] = terminalFD
readDesc [ unix . Stdin ] = "stdin"
relayBuffer [ terminalFD ] = new ( bytes . Buffer )
writeDesc [ terminalFD ] = "container terminal input"
relayMap [ terminalFD ] = unix . Stdout
readDesc [ terminalFD ] = "container terminal output"
relayBuffer [ unix . Stdout ] = new ( bytes . Buffer )
writeDesc [ unix . Stdout ] = "output"
// Set our terminal's mode to raw, to pass handling of special
// terminal input to the terminal in the container.
2021-08-25 03:30:13 +08:00
if term . IsTerminal ( unix . Stdin ) {
if state , err := term . MakeRaw ( unix . Stdin ) ; err != nil {
2021-05-08 01:38:44 +08:00
logger . Warnf ( "error setting terminal state: %v" , err )
2019-04-26 03:39:49 +08:00
} else {
defer func ( ) {
2021-08-25 03:30:13 +08:00
if err = term . Restore ( unix . Stdin , state ) ; err != nil {
2021-05-08 01:38:44 +08:00
logger . Errorf ( "unable to restore terminal state: %v" , err )
2019-04-26 03:39:49 +08:00
}
} ( )
}
}
}
if copyPipes {
// Input from our stdin, output from the stdout and stderr pipes.
relayMap [ unix . Stdin ] = stdioPipe [ unix . Stdin ] [ 1 ]
readDesc [ unix . Stdin ] = "stdin"
relayBuffer [ stdioPipe [ unix . Stdin ] [ 1 ] ] = new ( bytes . Buffer )
writeDesc [ stdioPipe [ unix . Stdin ] [ 1 ] ] = "container stdin"
relayMap [ stdioPipe [ unix . Stdout ] [ 0 ] ] = unix . Stdout
readDesc [ stdioPipe [ unix . Stdout ] [ 0 ] ] = "container stdout"
relayBuffer [ unix . Stdout ] = new ( bytes . Buffer )
writeDesc [ unix . Stdout ] = "stdout"
relayMap [ stdioPipe [ unix . Stderr ] [ 0 ] ] = unix . Stderr
readDesc [ stdioPipe [ unix . Stderr ] [ 0 ] ] = "container stderr"
relayBuffer [ unix . Stderr ] = new ( bytes . Buffer )
writeDesc [ unix . Stderr ] = "stderr"
}
// Set our reading descriptors to non-blocking.
for rfd , wfd := range relayMap {
2021-06-24 22:49:20 +08:00
blocked , err := setNonblock ( logger , rfd , readDesc [ rfd ] , true )
if err != nil {
2019-04-26 03:39:49 +08:00
return
}
2021-06-24 22:49:20 +08:00
if blocked {
defer setNonblock ( logger , rfd , readDesc [ rfd ] , false ) // nolint:errcheck
}
2021-05-08 01:38:44 +08:00
setNonblock ( logger , wfd , writeDesc [ wfd ] , false ) // nolint:errcheck
2019-04-26 03:39:49 +08:00
}
2019-06-12 18:19:28 +08:00
2019-06-15 06:08:18 +08:00
if copyPipes {
2021-05-08 01:38:44 +08:00
setNonblock ( logger , stdioPipe [ unix . Stdin ] [ 1 ] , writeDesc [ stdioPipe [ unix . Stdin ] [ 1 ] ] , true ) // nolint:errcheck
2019-06-15 06:08:18 +08:00
}
2019-07-16 16:47:48 +08:00
runCopyStdioPassData ( copyPipes , stdioPipe , finishCopy , relayMap , relayBuffer , readDesc , writeDesc )
2019-06-15 06:08:18 +08:00
}
2019-06-12 18:19:28 +08:00
2020-03-02 20:30:26 +08:00
func canRetry ( err error ) bool {
if errno , isErrno := err . ( syscall . Errno ) ; isErrno {
return errno == syscall . EINTR || errno == syscall . EAGAIN
}
return false
}
2019-07-16 16:47:48 +08:00
func runCopyStdioPassData ( copyPipes bool , stdioPipe [ ] [ ] int , finishCopy [ ] int , relayMap map [ int ] int , relayBuffer map [ int ] * bytes . Buffer , readDesc map [ int ] string , writeDesc map [ int ] string ) {
2019-06-12 18:19:28 +08:00
closeStdin := false
2019-04-26 03:39:49 +08:00
// Pass data back and forth.
pollTimeout := - 1
for len ( relayMap ) > 0 {
// Start building the list of descriptors to poll.
pollFds := make ( [ ] unix . PollFd , 0 , len ( relayMap ) + 1 )
// Poll for a notification that we should stop handling stdio.
pollFds = append ( pollFds , unix . PollFd { Fd : int32 ( finishCopy [ 0 ] ) , Events : unix . POLLIN | unix . POLLHUP } )
// Poll on our reading descriptors.
for rfd := range relayMap {
pollFds = append ( pollFds , unix . PollFd { Fd : int32 ( rfd ) , Events : unix . POLLIN | unix . POLLHUP } )
}
buf := make ( [ ] byte , 8192 )
// Wait for new data from any input descriptor, or a notification that we're done.
_ , err := unix . Poll ( pollFds , pollTimeout )
if ! util . LogIfNotRetryable ( err , fmt . Sprintf ( "error waiting for stdio/terminal data to relay: %v" , err ) ) {
return
}
removes := make ( map [ int ] struct { } )
for _ , pollFd := range pollFds {
// If this descriptor's just been closed from the other end, mark it for
// removal from the set that we're checking for.
if pollFd . Revents & unix . POLLHUP == unix . POLLHUP {
removes [ int ( pollFd . Fd ) ] = struct { } { }
}
// If the descriptor was closed elsewhere, remove it from our list.
if pollFd . Revents & unix . POLLNVAL != 0 {
logrus . Debugf ( "error polling descriptor %s: closed?" , readDesc [ int ( pollFd . Fd ) ] )
removes [ int ( pollFd . Fd ) ] = struct { } { }
}
// If the POLLIN flag isn't set, then there's no data to be read from this descriptor.
if pollFd . Revents & unix . POLLIN == 0 {
continue
}
// Read whatever there is to be read.
readFD := int ( pollFd . Fd )
writeFD , needToRelay := relayMap [ readFD ]
if needToRelay {
n , err := unix . Read ( readFD , buf )
if ! util . LogIfNotRetryable ( err , fmt . Sprintf ( "unable to read %s data: %v" , readDesc [ readFD ] , err ) ) {
return
}
// If it's zero-length on our stdin and we're
// using pipes, it's an EOF, so close the stdin
// pipe's writing end.
2020-03-02 20:30:26 +08:00
if n == 0 && ! canRetry ( err ) && int ( pollFd . Fd ) == unix . Stdin {
2019-06-08 06:00:56 +08:00
removes [ int ( pollFd . Fd ) ] = struct { } { }
} else if n > 0 {
2019-04-26 03:39:49 +08:00
// Buffer the data in case we get blocked on where they need to go.
nwritten , err := relayBuffer [ writeFD ] . Write ( buf [ : n ] )
if err != nil {
logrus . Debugf ( "buffer: %v" , err )
continue
}
if nwritten != n {
logrus . Debugf ( "buffer: expected to buffer %d bytes, wrote %d" , n , nwritten )
continue
}
// If this is the last of the data we'll be able to read from this
// descriptor, read all that there is to read.
for pollFd . Revents & unix . POLLHUP == unix . POLLHUP {
nr , err := unix . Read ( readFD , buf )
util . LogIfUnexpectedWhileDraining ( err , fmt . Sprintf ( "read %s: %v" , readDesc [ readFD ] , err ) )
if nr <= 0 {
break
}
nwritten , err := relayBuffer [ writeFD ] . Write ( buf [ : nr ] )
if err != nil {
logrus . Debugf ( "buffer: %v" , err )
break
}
if nwritten != nr {
logrus . Debugf ( "buffer: expected to buffer %d bytes, wrote %d" , nr , nwritten )
break
}
}
}
}
}
// Try to drain the output buffers. Set the default timeout
// for the next poll() to 100ms if we still have data to write.
pollTimeout = - 1
for writeFD := range relayBuffer {
if relayBuffer [ writeFD ] . Len ( ) > 0 {
n , err := unix . Write ( writeFD , relayBuffer [ writeFD ] . Bytes ( ) )
if ! util . LogIfNotRetryable ( err , fmt . Sprintf ( "unable to write %s data: %v" , writeDesc [ writeFD ] , err ) ) {
return
}
if n > 0 {
relayBuffer [ writeFD ] . Next ( n )
}
2019-06-13 19:31:22 +08:00
if closeStdin && writeFD == stdioPipe [ unix . Stdin ] [ 1 ] && stdioPipe [ unix . Stdin ] [ 1 ] >= 0 && relayBuffer [ stdioPipe [ unix . Stdin ] [ 1 ] ] . Len ( ) == 0 {
2019-06-12 18:19:28 +08:00
logrus . Debugf ( "closing stdin" )
unix . Close ( stdioPipe [ unix . Stdin ] [ 1 ] )
stdioPipe [ unix . Stdin ] [ 1 ] = - 1
}
2019-04-26 03:39:49 +08:00
}
if relayBuffer [ writeFD ] . Len ( ) > 0 {
pollTimeout = 100
}
}
// Remove any descriptors which we don't need to poll any more from the poll descriptor list.
for remove := range removes {
2019-06-08 06:00:56 +08:00
if copyPipes && remove == unix . Stdin {
2019-06-12 18:19:28 +08:00
closeStdin = true
if relayBuffer [ stdioPipe [ unix . Stdin ] [ 1 ] ] . Len ( ) == 0 {
logrus . Debugf ( "closing stdin" )
unix . Close ( stdioPipe [ unix . Stdin ] [ 1 ] )
stdioPipe [ unix . Stdin ] [ 1 ] = - 1
}
2019-06-08 06:00:56 +08:00
}
2019-04-26 03:39:49 +08:00
delete ( relayMap , remove )
}
// If the we-can-return pipe had anything for us, we're done.
for _ , pollFd := range pollFds {
if int ( pollFd . Fd ) == finishCopy [ 0 ] && pollFd . Revents != 0 {
// The pipe is closed, indicating that we can stop now.
return
}
}
}
}
2021-05-08 01:38:44 +08:00
func runAcceptTerminal ( logger * logrus . Logger , consoleListener * net . UnixListener , terminalSize * specs . Box ) ( int , error ) {
2019-04-26 03:39:49 +08:00
defer consoleListener . Close ( )
c , err := consoleListener . AcceptUnix ( )
if err != nil {
return - 1 , errors . Wrapf ( err , "error accepting socket descriptor connection" )
}
defer c . Close ( )
// Expect a control message over our new connection.
b := make ( [ ] byte , 8192 )
oob := make ( [ ] byte , 8192 )
n , oobn , _ , _ , err := c . ReadMsgUnix ( b , oob )
if err != nil {
return - 1 , errors . Wrapf ( err , "error reading socket descriptor" )
}
if n > 0 {
logrus . Debugf ( "socket descriptor is for %q" , string ( b [ : n ] ) )
}
if oobn > len ( oob ) {
return - 1 , errors . Errorf ( "too much out-of-bounds data (%d bytes)" , oobn )
}
// Parse the control message.
scm , err := unix . ParseSocketControlMessage ( oob [ : oobn ] )
if err != nil {
return - 1 , errors . Wrapf ( err , "error parsing out-of-bound data as a socket control message" )
}
logrus . Debugf ( "control messages: %v" , scm )
// Expect to get a descriptor.
terminalFD := - 1
for i := range scm {
fds , err := unix . ParseUnixRights ( & scm [ i ] )
if err != nil {
return - 1 , errors . Wrapf ( err , "error parsing unix rights control message: %v" , & scm [ i ] )
}
logrus . Debugf ( "fds: %v" , fds )
if len ( fds ) == 0 {
continue
}
terminalFD = fds [ 0 ]
break
}
if terminalFD == - 1 {
return - 1 , errors . Errorf ( "unable to read terminal descriptor" )
}
// Set the pseudoterminal's size to the configured size, or our own.
winsize := & unix . Winsize { }
if terminalSize != nil {
// Use configured sizes.
winsize . Row = uint16 ( terminalSize . Height )
winsize . Col = uint16 ( terminalSize . Width )
} else {
2021-08-25 03:30:13 +08:00
if term . IsTerminal ( unix . Stdin ) {
2019-04-26 03:39:49 +08:00
// Use the size of our terminal.
if winsize , err = unix . IoctlGetWinsize ( unix . Stdin , unix . TIOCGWINSZ ) ; err != nil {
2021-05-08 01:38:44 +08:00
logger . Warnf ( "error reading size of controlling terminal: %v" , err )
2019-04-26 03:39:49 +08:00
winsize . Row = 0
winsize . Col = 0
}
}
}
if winsize . Row != 0 && winsize . Col != 0 {
if err = unix . IoctlSetWinsize ( terminalFD , unix . TIOCSWINSZ , winsize ) ; err != nil {
2021-05-08 01:38:44 +08:00
logger . Warnf ( "error setting size of container pseudoterminal: %v" , err )
2019-04-26 03:39:49 +08:00
}
// FIXME - if we're connected to a terminal, we should
// be passing the updated terminal size down when we
// receive a SIGWINCH.
}
return terminalFD , nil
}
// Create pipes to use for relaying stdio.
func runMakeStdioPipe ( uid , gid int ) ( [ ] [ ] int , error ) {
stdioPipe := make ( [ ] [ ] int , 3 )
for i := range stdioPipe {
stdioPipe [ i ] = make ( [ ] int , 2 )
if err := unix . Pipe ( stdioPipe [ i ] ) ; err != nil {
return nil , errors . Wrapf ( err , "error creating pipe for container FD %d" , i )
}
}
if err := unix . Fchown ( stdioPipe [ unix . Stdin ] [ 0 ] , uid , gid ) ; err != nil {
return nil , errors . Wrapf ( err , "error setting owner of stdin pipe descriptor" )
}
if err := unix . Fchown ( stdioPipe [ unix . Stdout ] [ 1 ] , uid , gid ) ; err != nil {
return nil , errors . Wrapf ( err , "error setting owner of stdout pipe descriptor" )
}
if err := unix . Fchown ( stdioPipe [ unix . Stderr ] [ 1 ] , uid , gid ) ; err != nil {
return nil , errors . Wrapf ( err , "error setting owner of stderr pipe descriptor" )
}
return stdioPipe , nil
}
func runUsingRuntimeMain ( ) {
var options runUsingRuntimeSubprocOptions
// Set logging.
if level := os . Getenv ( "LOGLEVEL" ) ; level != "" {
if ll , err := strconv . Atoi ( level ) ; err == nil {
logrus . SetLevel ( logrus . Level ( ll ) )
}
}
// Unpack our configuration.
confPipe := os . NewFile ( 3 , "confpipe" )
if confPipe == nil {
fmt . Fprintf ( os . Stderr , "error reading options pipe\n" )
os . Exit ( 1 )
}
defer confPipe . Close ( )
if err := json . NewDecoder ( confPipe ) . Decode ( & options ) ; err != nil {
fmt . Fprintf ( os . Stderr , "error decoding options: %v\n" , err )
os . Exit ( 1 )
}
// Set ourselves up to read the container's exit status. We're doing this in a child process
2019-08-23 00:45:36 +08:00
// so that we won't mess with the setting in a caller of the library.
2019-04-26 03:39:49 +08:00
if err := setChildProcess ( ) ; err != nil {
os . Exit ( 1 )
}
2020-05-01 05:32:57 +08:00
ospec := options . Spec
if ospec == nil {
fmt . Fprintf ( os . Stderr , "options spec not specified\n" )
os . Exit ( 1 )
2020-03-10 21:43:54 +08:00
}
2022-01-05 23:00:04 +08:00
// open the pipes used to communicate with the parent process
var containerCreateW * os . File
var containerStartR * os . File
if options . ConfigureNetwork {
containerCreateW = os . NewFile ( 4 , "containercreatepipe" )
if containerCreateW == nil {
fmt . Fprintf ( os . Stderr , "could not open fd 4\n" )
os . Exit ( 1 )
}
containerStartR = os . NewFile ( 5 , "containerstartpipe" )
if containerStartR == nil {
fmt . Fprintf ( os . Stderr , "could not open fd 5\n" )
os . Exit ( 1 )
}
}
2019-04-26 03:39:49 +08:00
// Run the container, start to finish.
2022-01-05 23:00:04 +08:00
status , err := runUsingRuntime ( options . Options , options . ConfigureNetwork , options . MoreCreateArgs , ospec , options . BundlePath , options . ContainerName , containerCreateW , containerStartR )
2019-04-26 03:39:49 +08:00
if err != nil {
fmt . Fprintf ( os . Stderr , "error running container: %v\n" , err )
os . Exit ( 1 )
}
// Pass the container's exit status back to the caller by exiting with the same status.
if status . Exited ( ) {
os . Exit ( status . ExitStatus ( ) )
} else if status . Signaled ( ) {
fmt . Fprintf ( os . Stderr , "container exited on %s\n" , status . Signal ( ) )
os . Exit ( 1 )
}
os . Exit ( 1 )
}
2021-05-08 01:38:44 +08:00
func setupNamespaces ( logger * logrus . Logger , g * generate . Generator , namespaceOptions define . NamespaceOptions , idmapOptions define . IDMappingOptions , policy define . NetworkConfigurationPolicy ) ( configureNetwork bool , configureNetworks [ ] string , configureUTS bool , err error ) {
2019-04-26 03:39:49 +08:00
// Set namespace options in the container configuration.
configureUserns := false
specifiedNetwork := false
for _ , namespaceOption := range namespaceOptions {
switch namespaceOption . Name {
case string ( specs . UserNamespace ) :
configureUserns = false
if ! namespaceOption . Host && namespaceOption . Path == "" {
configureUserns = true
}
case string ( specs . NetworkNamespace ) :
specifiedNetwork = true
configureNetwork = false
if ! namespaceOption . Host && ( namespaceOption . Path == "" || ! filepath . IsAbs ( namespaceOption . Path ) ) {
if namespaceOption . Path != "" && ! filepath . IsAbs ( namespaceOption . Path ) {
configureNetworks = strings . Split ( namespaceOption . Path , "," )
namespaceOption . Path = ""
}
2021-02-07 06:49:40 +08:00
configureNetwork = ( policy != define . NetworkDisabled )
2019-04-26 03:39:49 +08:00
}
case string ( specs . UTSNamespace ) :
configureUTS = false
if ! namespaceOption . Host && namespaceOption . Path == "" {
configureUTS = true
}
}
if namespaceOption . Host {
if err := g . RemoveLinuxNamespace ( namespaceOption . Name ) ; err != nil {
return false , nil , false , errors . Wrapf ( err , "error removing %q namespace for run" , namespaceOption . Name )
}
} else if err := g . AddOrReplaceLinuxNamespace ( namespaceOption . Name , namespaceOption . Path ) ; err != nil {
if namespaceOption . Path == "" {
return false , nil , false , errors . Wrapf ( err , "error adding new %q namespace for run" , namespaceOption . Name )
}
return false , nil , false , errors . Wrapf ( err , "error adding %q namespace %q for run" , namespaceOption . Name , namespaceOption . Path )
}
}
// If we've got mappings, we're going to have to create a user namespace.
if len ( idmapOptions . UIDMap ) > 0 || len ( idmapOptions . GIDMap ) > 0 || configureUserns {
2019-07-16 22:47:25 +08:00
if err := g . AddOrReplaceLinuxNamespace ( string ( specs . UserNamespace ) , "" ) ; err != nil {
2019-04-26 03:39:49 +08:00
return false , nil , false , errors . Wrapf ( err , "error adding new %q namespace for run" , string ( specs . UserNamespace ) )
}
hostUidmap , hostGidmap , err := unshare . GetHostIDMappings ( "" )
if err != nil {
return false , nil , false , err
}
for _ , m := range idmapOptions . UIDMap {
g . AddLinuxUIDMapping ( m . HostID , m . ContainerID , m . Size )
}
if len ( idmapOptions . UIDMap ) == 0 {
for _ , m := range hostUidmap {
g . AddLinuxUIDMapping ( m . ContainerID , m . ContainerID , m . Size )
}
}
for _ , m := range idmapOptions . GIDMap {
g . AddLinuxGIDMapping ( m . HostID , m . ContainerID , m . Size )
}
if len ( idmapOptions . GIDMap ) == 0 {
for _ , m := range hostGidmap {
g . AddLinuxGIDMapping ( m . ContainerID , m . ContainerID , m . Size )
}
}
if ! specifiedNetwork {
2019-07-16 22:47:25 +08:00
if err := g . AddOrReplaceLinuxNamespace ( string ( specs . NetworkNamespace ) , "" ) ; err != nil {
2019-04-26 03:39:49 +08:00
return false , nil , false , errors . Wrapf ( err , "error adding new %q namespace for run" , string ( specs . NetworkNamespace ) )
}
2021-02-07 06:49:40 +08:00
configureNetwork = ( policy != define . NetworkDisabled )
2019-04-26 03:39:49 +08:00
}
} else {
2019-07-16 22:47:25 +08:00
if err := g . RemoveLinuxNamespace ( string ( specs . UserNamespace ) ) ; err != nil {
2019-04-26 03:39:49 +08:00
return false , nil , false , errors . Wrapf ( err , "error removing %q namespace for run" , string ( specs . UserNamespace ) )
}
if ! specifiedNetwork {
2019-07-16 22:47:25 +08:00
if err := g . RemoveLinuxNamespace ( string ( specs . NetworkNamespace ) ) ; err != nil {
2019-04-26 03:39:49 +08:00
return false , nil , false , errors . Wrapf ( err , "error removing %q namespace for run" , string ( specs . NetworkNamespace ) )
}
}
}
2019-06-11 11:28:12 +08:00
if configureNetwork && ! unshare . IsRootless ( ) {
2021-03-02 02:07:58 +08:00
for name , val := range define . DefaultNetworkSysctl {
2019-05-28 18:25:39 +08:00
// Check that the sysctl we are adding is actually supported
// by the kernel
p := filepath . Join ( "/proc/sys" , strings . Replace ( name , "." , "/" , - 1 ) )
_ , err := os . Stat ( p )
if err != nil && ! os . IsNotExist ( err ) {
2020-10-15 17:16:50 +08:00
return false , nil , false , err
2019-05-28 18:25:39 +08:00
}
if err == nil {
g . AddLinuxSysctl ( name , val )
} else {
2021-05-08 01:38:44 +08:00
logger . Warnf ( "ignoring sysctl %s since %s doesn't exist" , name , p )
2019-05-28 18:25:39 +08:00
}
2019-04-26 03:39:49 +08:00
}
}
return configureNetwork , configureNetworks , configureUTS , nil
}
2021-10-27 17:13:35 +08:00
func ( b * Builder ) configureNamespaces ( g * generate . Generator , options * RunOptions ) ( bool , [ ] string , error ) {
2019-04-26 03:39:49 +08:00
defaultNamespaceOptions , err := DefaultNamespaceOptions ( )
if err != nil {
return false , nil , err
}
namespaceOptions := defaultNamespaceOptions
namespaceOptions . AddOrReplace ( b . NamespaceOptions ... )
namespaceOptions . AddOrReplace ( options . NamespaceOptions ... )
networkPolicy := options . ConfigureNetwork
2021-10-27 17:13:35 +08:00
//Nothing was specified explictily so network policy should be inherited from builder
2019-04-26 03:39:49 +08:00
if networkPolicy == NetworkDefault {
networkPolicy = b . ConfigureNetwork
2021-10-27 17:13:35 +08:00
// If builder policy was NetworkDisabled and
// we want to disable network for this run.
// reset options.ConfigureNetwork to NetworkDisabled
// since it will be treated as source of truth later.
if networkPolicy == NetworkDisabled {
options . ConfigureNetwork = networkPolicy
}
2019-04-26 03:39:49 +08:00
}
2021-05-08 01:38:44 +08:00
configureNetwork , configureNetworks , configureUTS , err := setupNamespaces ( options . Logger , g , namespaceOptions , b . IDMappingOptions , networkPolicy )
2019-04-26 03:39:49 +08:00
if err != nil {
return false , nil , err
}
if configureUTS {
if options . Hostname != "" {
g . SetHostname ( options . Hostname )
} else if b . Hostname ( ) != "" {
g . SetHostname ( b . Hostname ( ) )
} else {
g . SetHostname ( stringid . TruncateID ( b . ContainerID ) )
}
} else {
g . SetHostname ( "" )
}
found := false
spec := g . Config
for i := range spec . Process . Env {
if strings . HasPrefix ( spec . Process . Env [ i ] , "HOSTNAME=" ) {
found = true
break
}
}
if ! found {
spec . Process . Env = append ( spec . Process . Env , fmt . Sprintf ( "HOSTNAME=%s" , spec . Hostname ) )
}
return configureNetwork , configureNetworks , nil
}
2019-07-16 16:47:48 +08:00
func runSetupBoundFiles ( bundlePath string , bindFiles map [ string ] string ) ( mounts [ ] specs . Mount ) {
2019-04-26 03:39:49 +08:00
for dest , src := range bindFiles {
options := [ ] string { "rbind" }
if strings . HasPrefix ( src , bundlePath ) {
options = append ( options , bind . NoBindOption )
}
mounts = append ( mounts , specs . Mount {
Source : src ,
Destination : dest ,
Type : "bind" ,
Options : options ,
} )
}
2019-07-16 16:47:48 +08:00
return mounts
2019-04-26 03:39:49 +08:00
}
2020-02-08 01:54:18 +08:00
func addRlimits ( ulimit [ ] string , g * generate . Generator , defaultUlimits [ ] string ) error {
2019-04-26 03:39:49 +08:00
var (
ul * units . Ulimit
err error
)
2020-02-08 01:54:18 +08:00
ulimit = append ( defaultUlimits , ulimit ... )
2019-04-26 03:39:49 +08:00
for _ , u := range ulimit {
if ul , err = units . ParseUlimit ( u ) ; err != nil {
return errors . Wrapf ( err , "ulimit option %q requires name=SOFT:HARD, failed to be parsed" , u )
}
g . AddProcessRlimits ( "RLIMIT_" + strings . ToUpper ( ul . Name ) , uint64 ( ul . Hard ) , uint64 ( ul . Soft ) )
}
return nil
}
2019-04-29 21:41:18 +08:00
func ( b * Builder ) cleanupTempVolumes ( ) {
for tempVolume , val := range b . TempVolumes {
if val {
if err := overlay . RemoveTemp ( tempVolume ) ; err != nil {
2021-05-08 01:38:44 +08:00
b . Logger . Errorf ( err . Error ( ) )
2019-04-29 21:41:18 +08:00
}
b . TempVolumes [ tempVolume ] = false
}
}
}
2020-11-18 22:50:53 +08:00
func ( b * Builder ) runSetupVolumeMounts ( mountLabel string , volumeMounts [ ] string , optionMounts [ ] specs . Mount , rootUID , rootGID , processUID , processGID int ) ( mounts [ ] specs . Mount , Err error ) {
2019-05-30 04:34:17 +08:00
// Make sure the overlay directory is clean before running
containerDir , err := b . store . ContainerDirectory ( b . ContainerID )
if err != nil {
return nil , errors . Wrapf ( err , "error looking up container directory for %s" , b . ContainerID )
}
if err := overlay . CleanupContent ( containerDir ) ; err != nil {
return nil , errors . Wrapf ( err , "error cleaning up overlay content for %s" , b . ContainerID )
}
2019-06-20 02:17:11 +08:00
parseMount := func ( mountType , host , container string , options [ ] string ) ( specs . Mount , error ) {
2020-11-18 22:50:53 +08:00
var foundrw , foundro , foundz , foundZ , foundO , foundU bool
2019-04-26 03:39:49 +08:00
var rootProp string
for _ , opt := range options {
switch opt {
case "rw" :
foundrw = true
case "ro" :
foundro = true
case "z" :
foundz = true
case "Z" :
foundZ = true
2019-04-29 21:41:18 +08:00
case "O" :
foundO = true
2020-11-18 22:50:53 +08:00
case "U" :
foundU = true
2019-04-26 03:39:49 +08:00
case "private" , "rprivate" , "slave" , "rslave" , "shared" , "rshared" :
rootProp = opt
}
}
if ! foundrw && ! foundro {
options = append ( options , "rw" )
}
if foundz {
if err := label . Relabel ( host , mountLabel , true ) ; err != nil {
2020-10-15 17:16:50 +08:00
return specs . Mount { } , err
2019-04-26 03:39:49 +08:00
}
}
if foundZ {
if err := label . Relabel ( host , mountLabel , false ) ; err != nil {
2020-10-15 17:16:50 +08:00
return specs . Mount { } , err
2019-04-26 03:39:49 +08:00
}
}
2020-11-18 22:50:53 +08:00
if foundU {
2021-01-25 22:32:56 +08:00
if err := chown . ChangeHostPathOwnership ( host , true , processUID , processGID ) ; err != nil {
2020-11-18 22:50:53 +08:00
return specs . Mount { } , err
}
}
2019-04-29 21:41:18 +08:00
if foundO {
2019-12-13 23:12:32 +08:00
containerDir , err := b . store . ContainerDirectory ( b . ContainerID )
if err != nil {
return specs . Mount { } , err
}
contentDir , err := overlay . TempDir ( containerDir , rootUID , rootGID )
if err != nil {
return specs . Mount { } , errors . Wrapf ( err , "failed to create TempDir in the %s directory" , containerDir )
}
overlayMount , err := overlay . Mount ( contentDir , host , container , rootUID , rootGID , b . store . GraphOptions ( ) )
2019-04-29 21:41:18 +08:00
if err == nil {
b . TempVolumes [ contentDir ] = true
}
2020-11-18 22:50:53 +08:00
// If chown true, add correct ownership to the overlay temp directories.
if foundU {
2021-01-25 22:32:56 +08:00
if err := chown . ChangeHostPathOwnership ( contentDir , true , processUID , processGID ) ; err != nil {
2020-11-18 22:50:53 +08:00
return specs . Mount { } , err
}
}
2019-04-29 21:41:18 +08:00
return overlayMount , err
}
2019-04-26 03:39:49 +08:00
if rootProp == "" {
options = append ( options , "private" )
}
2019-06-20 02:17:11 +08:00
if mountType != "tmpfs" {
mountType = "bind"
options = append ( options , "rbind" )
}
2019-04-26 03:39:49 +08:00
return specs . Mount {
Destination : container ,
2019-06-20 02:17:11 +08:00
Type : mountType ,
2019-04-26 03:39:49 +08:00
Source : host ,
2019-06-20 02:17:11 +08:00
Options : options ,
2019-04-26 03:39:49 +08:00
} , nil
}
2019-04-29 21:41:18 +08:00
2019-04-26 03:39:49 +08:00
// Bind mount volumes specified for this particular Run() invocation
for _ , i := range optionMounts {
logrus . Debugf ( "setting up mounted volume at %q" , i . Destination )
2019-06-20 02:17:11 +08:00
mount , err := parseMount ( i . Type , i . Source , i . Destination , i . Options )
2019-04-26 03:39:49 +08:00
if err != nil {
return nil , err
}
mounts = append ( mounts , mount )
}
// Bind mount volumes given by the user when the container was created
for _ , i := range volumeMounts {
var options [ ] string
2021-10-11 11:26:35 +08:00
spliti := parse . SplitStringWithColonEscape ( i )
2019-04-26 03:39:49 +08:00
if len ( spliti ) > 2 {
options = strings . Split ( spliti [ 2 ] , "," )
}
options = append ( options , "rbind" )
2019-06-20 02:17:11 +08:00
mount , err := parseMount ( "bind" , spliti [ 0 ] , spliti [ 1 ] , options )
2019-04-26 03:39:49 +08:00
if err != nil {
return nil , err
}
mounts = append ( mounts , mount )
}
return mounts , nil
}
func setupMaskedPaths ( g * generate . Generator ) {
for _ , mp := range [ ] string {
"/proc/acpi" ,
"/proc/kcore" ,
"/proc/keys" ,
"/proc/latency_stats" ,
"/proc/timer_list" ,
"/proc/timer_stats" ,
"/proc/sched_debug" ,
"/proc/scsi" ,
"/sys/firmware" ,
2020-07-07 22:00:43 +08:00
"/sys/fs/selinux" ,
2020-07-14 04:18:32 +08:00
"/sys/dev" ,
2019-04-26 03:39:49 +08:00
} {
g . AddLinuxMaskedPaths ( mp )
}
}
func setupReadOnlyPaths ( g * generate . Generator ) {
for _ , rp := range [ ] string {
"/proc/asound" ,
"/proc/bus" ,
"/proc/fs" ,
"/proc/irq" ,
"/proc/sys" ,
"/proc/sysrq-trigger" ,
} {
g . AddLinuxReadonlyPaths ( rp )
}
}
func setupCapAdd ( g * generate . Generator , caps ... string ) error {
for _ , cap := range caps {
if err := g . AddProcessCapabilityBounding ( cap ) ; err != nil {
return errors . Wrapf ( err , "error adding %q to the bounding capability set" , cap )
}
if err := g . AddProcessCapabilityEffective ( cap ) ; err != nil {
return errors . Wrapf ( err , "error adding %q to the effective capability set" , cap )
}
if err := g . AddProcessCapabilityInheritable ( cap ) ; err != nil {
return errors . Wrapf ( err , "error adding %q to the inheritable capability set" , cap )
}
if err := g . AddProcessCapabilityPermitted ( cap ) ; err != nil {
return errors . Wrapf ( err , "error adding %q to the permitted capability set" , cap )
}
if err := g . AddProcessCapabilityAmbient ( cap ) ; err != nil {
return errors . Wrapf ( err , "error adding %q to the ambient capability set" , cap )
}
}
return nil
}
func setupCapDrop ( g * generate . Generator , caps ... string ) error {
for _ , cap := range caps {
if err := g . DropProcessCapabilityBounding ( cap ) ; err != nil {
return errors . Wrapf ( err , "error removing %q from the bounding capability set" , cap )
}
if err := g . DropProcessCapabilityEffective ( cap ) ; err != nil {
return errors . Wrapf ( err , "error removing %q from the effective capability set" , cap )
}
if err := g . DropProcessCapabilityInheritable ( cap ) ; err != nil {
return errors . Wrapf ( err , "error removing %q from the inheritable capability set" , cap )
}
if err := g . DropProcessCapabilityPermitted ( cap ) ; err != nil {
return errors . Wrapf ( err , "error removing %q from the permitted capability set" , cap )
}
if err := g . DropProcessCapabilityAmbient ( cap ) ; err != nil {
return errors . Wrapf ( err , "error removing %q from the ambient capability set" , cap )
}
}
return nil
}
2020-01-14 20:12:56 +08:00
func setupCapabilities ( g * generate . Generator , defaultCapabilities , adds , drops [ ] string ) error {
2019-04-26 03:39:49 +08:00
g . ClearProcessCapabilities ( )
2020-01-14 20:12:56 +08:00
if err := setupCapAdd ( g , defaultCapabilities ... ) ; err != nil {
2019-04-26 03:39:49 +08:00
return err
}
2020-01-14 20:12:56 +08:00
for _ , c := range adds {
if strings . ToLower ( c ) == "all" {
2020-02-19 03:50:08 +08:00
adds = capabilities . AllCapabilities ( )
2020-01-14 20:12:56 +08:00
break
}
2019-04-26 03:39:49 +08:00
}
2020-01-14 20:12:56 +08:00
for _ , c := range drops {
if strings . ToLower ( c ) == "all" {
g . ClearProcessCapabilities ( )
return nil
}
2019-04-26 03:39:49 +08:00
}
2020-01-14 20:12:56 +08:00
if err := setupCapAdd ( g , adds ... ) ; err != nil {
2019-04-26 03:39:49 +08:00
return err
}
2020-01-14 20:12:56 +08:00
return setupCapDrop ( g , drops ... )
2019-04-26 03:39:49 +08:00
}
// Search for a command that isn't given as an absolute path using the $PATH
// under the rootfs. We can't resolve absolute symbolic links without
// chroot()ing, which we may not be able to do, so just accept a link as a
// valid resolution.
func runLookupPath ( g * generate . Generator , command [ ] string ) [ ] string {
// Look for the configured $PATH.
spec := g . Config
envPath := ""
for i := range spec . Process . Env {
if strings . HasPrefix ( spec . Process . Env [ i ] , "PATH=" ) {
envPath = spec . Process . Env [ i ]
}
}
// If there is no configured $PATH, supply one.
if envPath == "" {
defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
envPath = "PATH=" + defaultPath
g . AddProcessEnv ( "PATH" , defaultPath )
}
// No command, nothing to do.
if len ( command ) == 0 {
return command
}
// Command is already an absolute path, use it as-is.
if filepath . IsAbs ( command [ 0 ] ) {
return command
}
// For each element in the PATH,
for _ , pathEntry := range filepath . SplitList ( envPath [ 5 : ] ) {
// if it's the empty string, it's ".", which is the Cwd,
if pathEntry == "" {
pathEntry = spec . Process . Cwd
}
// build the absolute path which it might be,
candidate := filepath . Join ( pathEntry , command [ 0 ] )
// check if it's there,
if fi , err := os . Lstat ( filepath . Join ( spec . Root . Path , candidate ) ) ; fi != nil && err == nil {
// and if it's not a directory, and either a symlink or executable,
if ! fi . IsDir ( ) && ( ( fi . Mode ( ) & os . ModeSymlink != 0 ) || ( fi . Mode ( ) & 0111 != 0 ) ) {
// use that.
return append ( [ ] string { candidate } , command [ 1 : ] ... )
}
}
}
return command
}
func getDNSIP ( dnsServers [ ] string ) ( dns [ ] net . IP , err error ) {
for _ , i := range dnsServers {
result := net . ParseIP ( i )
if result == nil {
return dns , errors . Errorf ( "invalid IP address %s" , i )
}
dns = append ( dns , result )
}
return dns , nil
}
2019-05-16 06:40:15 +08:00
func ( b * Builder ) configureUIDGID ( g * generate . Generator , mountPoint string , options RunOptions ) ( string , error ) {
2019-04-26 03:39:49 +08:00
// Set the user UID/GID/supplemental group list/capabilities lists.
2021-03-23 00:00:02 +08:00
user , homeDir , err := b . userForRun ( mountPoint , options . User )
2019-04-26 03:39:49 +08:00
if err != nil {
2019-05-16 06:40:15 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
2020-01-14 20:12:56 +08:00
if err := setupCapabilities ( g , b . Capabilities , options . AddCapabilities , options . DropCapabilities ) ; err != nil {
2019-05-16 06:40:15 +08:00
return "" , err
2019-04-26 03:39:49 +08:00
}
g . SetProcessUID ( user . UID )
g . SetProcessGID ( user . GID )
for _ , gid := range user . AdditionalGids {
g . AddProcessAdditionalGid ( gid )
}
// Remove capabilities if not running as root except Bounding set
if user . UID != 0 {
bounding := g . Config . Process . Capabilities . Bounding
g . ClearProcessCapabilities ( )
g . Config . Process . Capabilities . Bounding = bounding
}
2019-05-16 06:40:15 +08:00
return homeDir , nil
2019-04-26 03:39:49 +08:00
}
2020-02-08 01:54:18 +08:00
func ( b * Builder ) configureEnvironment ( g * generate . Generator , options RunOptions , defaultEnv [ ] string ) {
2019-04-26 03:39:49 +08:00
g . ClearProcessEnv ( )
2020-01-14 20:12:56 +08:00
2019-04-26 03:39:49 +08:00
if b . CommonBuildOpts . HTTPProxy {
2022-01-08 03:29:52 +08:00
for _ , envSpec := range config . ProxyEnv {
if envVal , ok := os . LookupEnv ( envSpec ) ; ok {
2019-04-26 03:39:49 +08:00
g . AddProcessEnv ( envSpec , envVal )
}
}
}
2020-07-14 04:49:32 +08:00
for _ , envSpec := range util . MergeEnv ( util . MergeEnv ( defaultEnv , b . Env ( ) ) , options . Env ) {
2019-04-26 03:39:49 +08:00
env := strings . SplitN ( envSpec , "=" , 2 )
if len ( env ) > 1 {
g . AddProcessEnv ( env [ 0 ] , env [ 1 ] )
}
}
}
2019-07-16 16:47:48 +08:00
func setupRootlessSpecChanges ( spec * specs . Spec , bundleDir string , shmSize string ) error {
2019-04-26 03:39:49 +08:00
emptyDir := filepath . Join ( bundleDir , "empty" )
if err := os . Mkdir ( emptyDir , 0 ) ; err != nil {
2020-10-15 17:16:50 +08:00
return err
2019-04-26 03:39:49 +08:00
}
2021-11-04 20:45:40 +08:00
// If the container has a network namespace, we can create a fresh /sys mount
for _ , ns := range spec . Linux . Namespaces {
if ns . Type == specs . NetworkNamespace {
return nil
}
}
2019-04-26 03:39:49 +08:00
// Replace /sys with a read-only bind mount.
mounts := [ ] specs . Mount {
{
Source : "/dev" ,
Destination : "/dev" ,
Type : "tmpfs" ,
Options : [ ] string { "private" , "strictatime" , "noexec" , "nosuid" , "mode=755" , "size=65536k" } ,
} ,
{
Source : "mqueue" ,
Destination : "/dev/mqueue" ,
Type : "mqueue" ,
Options : [ ] string { "private" , "nodev" , "noexec" , "nosuid" } ,
} ,
{
Source : "pts" ,
Destination : "/dev/pts" ,
Type : "devpts" ,
Options : [ ] string { "private" , "noexec" , "nosuid" , "newinstance" , "ptmxmode=0666" , "mode=0620" } ,
} ,
{
Source : "shm" ,
Destination : "/dev/shm" ,
Type : "tmpfs" ,
2019-05-16 21:16:57 +08:00
Options : [ ] string { "private" , "nodev" , "noexec" , "nosuid" , "mode=1777" , fmt . Sprintf ( "size=%s" , shmSize ) } ,
2019-04-26 03:39:49 +08:00
} ,
{
Source : "/proc" ,
Destination : "/proc" ,
Type : "proc" ,
Options : [ ] string { "private" , "nodev" , "noexec" , "nosuid" } ,
} ,
{
Source : "/sys" ,
Destination : "/sys" ,
Type : "bind" ,
Options : [ ] string { bind . NoBindOption , "rbind" , "private" , "nodev" , "noexec" , "nosuid" , "ro" } ,
} ,
}
2021-12-02 03:08:25 +08:00
cgroup2 , err := cgroups . IsCgroup2UnifiedMode ( )
if err != nil {
return err
}
if cgroup2 {
hasCgroupNs := false
for _ , ns := range spec . Linux . Namespaces {
if ns . Type == specs . CgroupNamespace {
hasCgroupNs = true
break
}
}
if hasCgroupNs {
mounts = append ( mounts , specs . Mount {
Destination : "/sys/fs/cgroup" ,
Type : "cgroup" ,
Source : "cgroup" ,
Options : [ ] string { "private" , "rw" } ,
} )
}
} else {
spec . Linux . Resources = nil
// Cover up /sys/fs/cgroup, if it exist in our source for /sys.
if _ , err := os . Stat ( "/sys/fs/cgroup" ) ; err == nil {
spec . Linux . MaskedPaths = append ( spec . Linux . MaskedPaths , "/sys/fs/cgroup" )
}
2019-04-26 03:39:49 +08:00
}
// Keep anything that isn't under /dev, /proc, or /sys.
for i := range spec . Mounts {
if spec . Mounts [ i ] . Destination == "/dev" || strings . HasPrefix ( spec . Mounts [ i ] . Destination , "/dev/" ) ||
spec . Mounts [ i ] . Destination == "/proc" || strings . HasPrefix ( spec . Mounts [ i ] . Destination , "/proc/" ) ||
spec . Mounts [ i ] . Destination == "/sys" || strings . HasPrefix ( spec . Mounts [ i ] . Destination , "/sys/" ) {
continue
}
mounts = append ( mounts , spec . Mounts [ i ] )
}
spec . Mounts = mounts
return nil
}
2021-02-07 06:49:40 +08:00
func ( b * Builder ) runUsingRuntimeSubproc ( isolation define . Isolation , options RunOptions , configureNetwork bool , configureNetworks , moreCreateArgs [ ] string , spec * specs . Spec , rootPath , bundlePath , containerName string ) ( err error ) {
2019-04-26 03:39:49 +08:00
var confwg sync . WaitGroup
config , conferr := json . Marshal ( runUsingRuntimeSubprocOptions {
2022-01-05 23:00:04 +08:00
Options : options ,
Spec : spec ,
RootPath : rootPath ,
BundlePath : bundlePath ,
ConfigureNetwork : configureNetwork ,
MoreCreateArgs : moreCreateArgs ,
ContainerName : containerName ,
Isolation : isolation ,
2019-04-26 03:39:49 +08:00
} )
if conferr != nil {
return errors . Wrapf ( conferr , "error encoding configuration for %q" , runUsingRuntimeCommand )
}
cmd := reexec . Command ( runUsingRuntimeCommand )
cmd . Dir = bundlePath
cmd . Stdin = options . Stdin
if cmd . Stdin == nil {
cmd . Stdin = os . Stdin
}
cmd . Stdout = options . Stdout
if cmd . Stdout == nil {
cmd . Stdout = os . Stdout
}
cmd . Stderr = options . Stderr
if cmd . Stderr == nil {
cmd . Stderr = os . Stderr
}
2020-07-14 04:49:32 +08:00
cmd . Env = util . MergeEnv ( os . Environ ( ) , [ ] string { fmt . Sprintf ( "LOGLEVEL=%d" , logrus . GetLevel ( ) ) } )
2019-04-26 03:39:49 +08:00
preader , pwriter , err := os . Pipe ( )
if err != nil {
return errors . Wrapf ( err , "error creating configuration pipe" )
}
confwg . Add ( 1 )
go func ( ) {
_ , conferr = io . Copy ( pwriter , bytes . NewReader ( config ) )
if conferr != nil {
conferr = errors . Wrapf ( conferr , "error while copying configuration down pipe to child process" )
}
confwg . Done ( )
} ( )
2022-01-05 23:00:04 +08:00
// create network configuration pipes
var containerCreateR , containerCreateW * os . File
var containerStartR , containerStartW * os . File
if configureNetwork {
containerCreateR , containerCreateW , err = os . Pipe ( )
if err != nil {
return errors . Wrapf ( err , "error creating container create pipe" )
}
defer containerCreateR . Close ( )
defer containerCreateW . Close ( )
containerStartR , containerStartW , err = os . Pipe ( )
if err != nil {
return errors . Wrapf ( err , "error creating container create pipe" )
}
defer containerStartR . Close ( )
defer containerStartW . Close ( )
cmd . ExtraFiles = [ ] * os . File { containerCreateW , containerStartR }
}
2019-04-26 03:39:49 +08:00
cmd . ExtraFiles = append ( [ ] * os . File { preader } , cmd . ExtraFiles ... )
defer preader . Close ( )
defer pwriter . Close ( )
2022-01-05 23:00:04 +08:00
if err := cmd . Start ( ) ; err != nil {
return errors . Wrapf ( err , "error while starting runtime" )
}
if configureNetwork {
if err := waitForSync ( containerCreateR ) ; err != nil {
2022-01-06 04:36:49 +08:00
// we do not want to return here since we want to capture the exit code from the child via cmd.Wait()
// close the pipes here so that the child will not hang forever
containerCreateR . Close ( )
containerStartW . Close ( )
logrus . Errorf ( "did not get container create message from subprocess: %v" , err )
} else {
pidFile := filepath . Join ( bundlePath , "pid" )
pidValue , err := ioutil . ReadFile ( pidFile )
if err != nil {
return err
}
pid , err := strconv . Atoi ( strings . TrimSpace ( string ( pidValue ) ) )
if err != nil {
return errors . Wrapf ( err , "error parsing pid %s as a number" , string ( pidValue ) )
}
2022-01-05 23:00:04 +08:00
2022-01-06 04:36:49 +08:00
teardown , err := b . runConfigureNetwork ( pid , isolation , options , configureNetworks , containerName )
if teardown != nil {
defer teardown ( )
}
if err != nil {
return err
}
2022-01-05 23:00:04 +08:00
2022-01-06 04:36:49 +08:00
logrus . Debug ( "network namespace successfully setup, send start message to child" )
_ , err = containerStartW . Write ( [ ] byte { 1 } )
if err != nil {
return err
}
2022-01-05 23:00:04 +08:00
}
}
if err := cmd . Wait ( ) ; err != nil {
return errors . Wrapf ( err , "error while running runtime" )
2019-04-26 03:39:49 +08:00
}
confwg . Wait ( )
if err == nil {
return conferr
}
if conferr != nil {
logrus . Debugf ( "%v" , conferr )
}
return err
}
2022-01-05 23:00:04 +08:00
// waitForSync waits for a maximum of 5 seconds to read something from the file
func waitForSync ( pipeR * os . File ) error {
if err := pipeR . SetDeadline ( time . Now ( ) . Add ( 5 * time . Second ) ) ; err != nil {
return err
}
b := make ( [ ] byte , 16 )
_ , err := pipeR . Read ( b )
return err
}
2021-02-07 06:49:40 +08:00
func checkAndOverrideIsolationOptions ( isolation define . Isolation , options * RunOptions ) error {
2019-04-26 03:39:49 +08:00
switch isolation {
case IsolationOCIRootless :
if ns := options . NamespaceOptions . Find ( string ( specs . IPCNamespace ) ) ; ns == nil || ns . Host {
logrus . Debugf ( "Forcing use of an IPC namespace." )
}
2021-02-07 06:49:40 +08:00
options . NamespaceOptions . AddOrReplace ( define . NamespaceOption { Name : string ( specs . IPCNamespace ) } )
2019-04-26 03:39:49 +08:00
_ , err := exec . LookPath ( "slirp4netns" )
hostNetworking := err != nil
networkNamespacePath := ""
if ns := options . NamespaceOptions . Find ( string ( specs . NetworkNamespace ) ) ; ns != nil {
hostNetworking = ns . Host
networkNamespacePath = ns . Path
if ! hostNetworking && networkNamespacePath != "" && ! filepath . IsAbs ( networkNamespacePath ) {
logrus . Debugf ( "Disabling network namespace configuration." )
networkNamespacePath = ""
}
}
2021-02-07 06:49:40 +08:00
options . NamespaceOptions . AddOrReplace ( define . NamespaceOption {
2019-04-26 03:39:49 +08:00
Name : string ( specs . NetworkNamespace ) ,
Host : hostNetworking ,
Path : networkNamespacePath ,
} )
if ns := options . NamespaceOptions . Find ( string ( specs . PIDNamespace ) ) ; ns == nil || ns . Host {
logrus . Debugf ( "Forcing use of a PID namespace." )
}
2021-02-07 06:49:40 +08:00
options . NamespaceOptions . AddOrReplace ( define . NamespaceOption { Name : string ( specs . PIDNamespace ) , Host : false } )
2019-04-26 03:39:49 +08:00
if ns := options . NamespaceOptions . Find ( string ( specs . UserNamespace ) ) ; ns == nil || ns . Host {
logrus . Debugf ( "Forcing use of a user namespace." )
}
2021-02-07 06:49:40 +08:00
options . NamespaceOptions . AddOrReplace ( define . NamespaceOption { Name : string ( specs . UserNamespace ) } )
2019-04-26 03:39:49 +08:00
case IsolationOCI :
pidns := options . NamespaceOptions . Find ( string ( specs . PIDNamespace ) )
userns := options . NamespaceOptions . Find ( string ( specs . UserNamespace ) )
2021-02-05 23:46:01 +08:00
if ( pidns != nil && pidns . Host ) && ( userns != nil && ! userns . Host ) {
2020-04-04 04:34:43 +08:00
return errors . Errorf ( "not allowed to mix host PID namespace with container user namespace" )
2019-04-26 03:39:49 +08:00
}
}
return nil
}
// DefaultNamespaceOptions returns the default namespace settings from the
// runtime-tools generator library.
2021-02-07 06:49:40 +08:00
func DefaultNamespaceOptions ( ) ( define . NamespaceOptions , error ) {
2021-11-19 03:09:29 +08:00
cfg , err := config . Default ( )
if err != nil {
return nil , errors . Wrapf ( err , "failed to get container config" )
}
2021-02-07 06:49:40 +08:00
options := define . NamespaceOptions {
2021-11-19 03:09:29 +08:00
{ Name : string ( specs . CgroupNamespace ) , Host : cfg . CgroupNS ( ) == "host" } ,
{ Name : string ( specs . IPCNamespace ) , Host : cfg . IPCNS ( ) == "host" } ,
2019-04-26 03:39:49 +08:00
{ Name : string ( specs . MountNamespace ) , Host : true } ,
2021-11-19 03:09:29 +08:00
{ Name : string ( specs . NetworkNamespace ) , Host : cfg . NetNS ( ) == "host" || cfg . NetNS ( ) == "container" } ,
{ Name : string ( specs . PIDNamespace ) , Host : cfg . PidNS ( ) == "host" } ,
2019-04-26 03:39:49 +08:00
{ Name : string ( specs . UserNamespace ) , Host : true } ,
2021-11-19 03:09:29 +08:00
{ Name : string ( specs . UTSNamespace ) , Host : cfg . UTSNS ( ) == "host" } ,
2019-04-26 03:39:49 +08:00
}
g , err := generate . New ( "linux" )
if err != nil {
return options , errors . Wrapf ( err , "error generating new 'linux' runtime spec" )
}
spec := g . Config
if spec . Linux != nil {
for _ , ns := range spec . Linux . Namespaces {
2021-02-07 06:49:40 +08:00
options . AddOrReplace ( define . NamespaceOption {
2019-04-26 03:39:49 +08:00
Name : string ( ns . Type ) ,
Path : ns . Path ,
} )
}
}
return options , nil
}
func contains ( volumes [ ] string , v string ) bool {
for _ , i := range volumes {
if i == v {
return true
}
}
return false
}
type runUsingRuntimeSubprocOptions struct {
2022-01-05 23:00:04 +08:00
Options RunOptions
Spec * specs . Spec
RootPath string
BundlePath string
ConfigureNetwork bool
MoreCreateArgs [ ] string
ContainerName string
Isolation define . Isolation
2019-04-26 03:39:49 +08:00
}
func init ( ) {
reexec . Register ( runUsingRuntimeCommand , runUsingRuntimeMain )
}
2021-04-17 06:21:31 +08:00
// runSetupRunMounts sets up mounts that exist only in this RUN, not in subsequent runs
2021-10-18 13:51:51 +08:00
func ( b * Builder ) runSetupRunMounts ( context * imagetypes . SystemContext , mounts [ ] string , secrets map [ string ] define . Secret , stageMountPoints map [ string ] internal . StageMountDetails , sshSources map [ string ] * sshagent . Source , containerWorkingDir string , contextDir string , uidmap [ ] spec . LinuxIDMapping , gidmap [ ] spec . LinuxIDMapping , rootUID int , rootGID int , processUID int , processGID int ) ( [ ] spec . Mount , * runMountArtifacts , error ) {
mountTargets := make ( [ ] string , 0 , 10 )
2021-10-22 04:14:05 +08:00
tmpFiles := make ( [ ] string , 0 , len ( mounts ) )
2021-10-18 13:51:51 +08:00
mountImages := make ( [ ] string , 0 , 10 )
2021-04-17 06:21:31 +08:00
finalMounts := make ( [ ] specs . Mount , 0 , len ( mounts ) )
2021-07-26 14:07:23 +08:00
agents := make ( [ ] * sshagent . AgentServer , 0 , len ( mounts ) )
sshCount := 0
defaultSSHSock := ""
tokens := [ ] string { }
2021-04-17 06:21:31 +08:00
for _ , mount := range mounts {
arr := strings . SplitN ( mount , "," , 2 )
kv := strings . Split ( arr [ 0 ] , "=" )
if len ( kv ) != 2 || kv [ 0 ] != "type" {
return nil , nil , errors . New ( "invalid mount type" )
}
2021-07-26 14:07:23 +08:00
if len ( arr ) == 2 {
tokens = strings . Split ( arr [ 1 ] , "," )
}
2021-04-17 06:21:31 +08:00
// For now, we only support type secret.
switch kv [ 1 ] {
case "secret" :
2021-10-22 04:14:05 +08:00
mount , envFile , err := getSecretMount ( tokens , secrets , b . MountLabel , containerWorkingDir , uidmap , gidmap )
2021-04-17 06:21:31 +08:00
if err != nil {
return nil , nil , err
}
if mount != nil {
finalMounts = append ( finalMounts , * mount )
mountTargets = append ( mountTargets , mount . Destination )
2021-10-22 04:14:05 +08:00
if envFile != "" {
tmpFiles = append ( tmpFiles , envFile )
}
2021-04-17 06:21:31 +08:00
}
2021-07-26 14:07:23 +08:00
case "ssh" :
2021-09-28 19:20:36 +08:00
mount , agent , err := b . getSSHMount ( tokens , sshCount , sshSources , b . MountLabel , uidmap , gidmap , b . ProcessLabel )
2021-07-26 14:07:23 +08:00
if err != nil {
return nil , nil , err
}
if mount != nil {
finalMounts = append ( finalMounts , * mount )
mountTargets = append ( mountTargets , mount . Destination )
agents = append ( agents , agent )
if sshCount == 0 {
defaultSSHSock = mount . Destination
}
// Count is needed as the default destination of the ssh sock inside the container is /run/buildkit/ssh_agent.{i}
sshCount ++
}
2021-09-28 19:20:36 +08:00
case "bind" :
2021-10-18 13:51:51 +08:00
mount , image , err := b . getBindMount ( context , tokens , contextDir , rootUID , rootGID , processUID , processGID , stageMountPoints )
2021-09-28 19:20:36 +08:00
if err != nil {
return nil , nil , err
}
finalMounts = append ( finalMounts , * mount )
mountTargets = append ( mountTargets , mount . Destination )
2021-10-18 13:51:51 +08:00
// only perform cleanup if image was mounted ignore everything else
if image != "" {
mountImages = append ( mountImages , image )
}
2021-10-07 16:37:29 +08:00
case "tmpfs" :
mount , err := b . getTmpfsMount ( tokens , rootUID , rootGID , processUID , processGID )
if err != nil {
return nil , nil , err
}
finalMounts = append ( finalMounts , * mount )
mountTargets = append ( mountTargets , mount . Destination )
2021-10-07 16:27:17 +08:00
case "cache" :
2021-10-18 13:51:51 +08:00
mount , err := b . getCacheMount ( tokens , rootUID , rootGID , processUID , processGID , stageMountPoints )
2021-10-07 16:27:17 +08:00
if err != nil {
return nil , nil , err
}
finalMounts = append ( finalMounts , * mount )
mountTargets = append ( mountTargets , mount . Destination )
2021-04-17 06:21:31 +08:00
default :
2021-07-26 14:07:23 +08:00
return nil , nil , errors . Errorf ( "invalid mount type %q" , kv [ 1 ] )
2021-04-17 06:21:31 +08:00
}
}
2021-07-26 14:07:23 +08:00
artifacts := & runMountArtifacts {
RunMountTargets : mountTargets ,
2021-10-22 04:14:05 +08:00
TmpFiles : tmpFiles ,
2021-07-26 14:07:23 +08:00
Agents : agents ,
2021-10-18 13:51:51 +08:00
MountedImages : mountImages ,
2021-07-26 14:07:23 +08:00
SSHAuthSock : defaultSSHSock ,
}
return finalMounts , artifacts , nil
2021-04-17 06:21:31 +08:00
}
2021-10-18 13:51:51 +08:00
func ( b * Builder ) getBindMount ( context * imagetypes . SystemContext , tokens [ ] string , contextDir string , rootUID , rootGID , processUID , processGID int , stageMountPoints map [ string ] internal . StageMountDetails ) ( * spec . Mount , string , error ) {
2021-09-28 19:20:36 +08:00
if contextDir == "" {
2021-10-18 13:51:51 +08:00
return nil , "" , errors . New ( "Context Directory for current run invocation is not configured" )
2021-09-28 19:20:36 +08:00
}
var optionMounts [ ] specs . Mount
2021-10-18 13:51:51 +08:00
mount , image , err := internalParse . GetBindMount ( context , tokens , contextDir , b . store , b . MountLabel , stageMountPoints )
2021-09-28 19:20:36 +08:00
if err != nil {
2021-10-18 13:51:51 +08:00
return nil , image , err
2021-09-28 19:20:36 +08:00
}
optionMounts = append ( optionMounts , mount )
volumes , err := b . runSetupVolumeMounts ( b . MountLabel , nil , optionMounts , rootUID , rootGID , processUID , processGID )
if err != nil {
2021-10-18 13:51:51 +08:00
return nil , image , err
2021-09-28 19:20:36 +08:00
}
2021-10-18 13:51:51 +08:00
return & volumes [ 0 ] , image , nil
2021-09-28 19:20:36 +08:00
}
2021-10-07 16:37:29 +08:00
func ( b * Builder ) getTmpfsMount ( tokens [ ] string , rootUID , rootGID , processUID , processGID int ) ( * spec . Mount , error ) {
var optionMounts [ ] specs . Mount
2021-10-18 13:51:51 +08:00
mount , err := internalParse . GetTmpfsMount ( tokens )
2021-10-07 16:37:29 +08:00
if err != nil {
return nil , err
}
optionMounts = append ( optionMounts , mount )
volumes , err := b . runSetupVolumeMounts ( b . MountLabel , nil , optionMounts , rootUID , rootGID , processUID , processGID )
if err != nil {
return nil , err
}
return & volumes [ 0 ] , nil
}
2021-10-18 13:51:51 +08:00
func ( b * Builder ) getCacheMount ( tokens [ ] string , rootUID , rootGID , processUID , processGID int , stageMountPoints map [ string ] internal . StageMountDetails ) ( * spec . Mount , error ) {
2021-10-07 16:27:17 +08:00
var optionMounts [ ] specs . Mount
2021-10-18 13:51:51 +08:00
mount , err := internalParse . GetCacheMount ( tokens , b . store , b . MountLabel , stageMountPoints )
2021-10-07 16:27:17 +08:00
if err != nil {
return nil , err
}
optionMounts = append ( optionMounts , mount )
volumes , err := b . runSetupVolumeMounts ( b . MountLabel , nil , optionMounts , rootUID , rootGID , processUID , processGID )
if err != nil {
return nil , err
}
return & volumes [ 0 ] , nil
}
2021-10-22 04:14:05 +08:00
func getSecretMount ( tokens [ ] string , secrets map [ string ] define . Secret , mountlabel string , containerWorkingDir string , uidmap [ ] spec . LinuxIDMapping , gidmap [ ] spec . LinuxIDMapping ) ( * spec . Mount , string , error ) {
2021-04-17 06:21:31 +08:00
errInvalidSyntax := errors . New ( "secret should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint" )
2021-07-26 14:07:23 +08:00
if len ( tokens ) == 0 {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-07-26 14:07:23 +08:00
}
2021-04-17 06:21:31 +08:00
var err error
var id , target string
var required bool
var uid , gid uint32
2021-10-13 03:21:02 +08:00
var mode uint32 = 0400
2021-04-17 06:21:31 +08:00
for _ , val := range tokens {
kv := strings . SplitN ( val , "=" , 2 )
switch kv [ 0 ] {
case "id" :
id = kv [ 1 ]
2021-07-23 02:46:05 +08:00
case "target" , "dst" , "destination" :
2021-04-17 06:21:31 +08:00
target = kv [ 1 ]
case "required" :
required , err = strconv . ParseBool ( kv [ 1 ] )
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
case "mode" :
mode64 , err := strconv . ParseUint ( kv [ 1 ] , 8 , 32 )
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
mode = uint32 ( mode64 )
case "uid" :
uid64 , err := strconv . ParseUint ( kv [ 1 ] , 10 , 32 )
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
uid = uint32 ( uid64 )
case "gid" :
gid64 , err := strconv . ParseUint ( kv [ 1 ] , 10 , 32 )
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
gid = uint32 ( gid64 )
default :
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
}
if id == "" {
2021-10-22 04:14:05 +08:00
return nil , "" , errInvalidSyntax
2021-04-17 06:21:31 +08:00
}
// Default location for secretis is /run/secrets/id
if target == "" {
target = "/run/secrets/" + id
}
2021-10-22 04:14:05 +08:00
secr , ok := secrets [ id ]
2021-04-17 06:21:31 +08:00
if ! ok {
if required {
2021-10-22 04:14:05 +08:00
return nil , "" , errors . Errorf ( "secret required but no secret with id %s found" , id )
2021-04-17 06:21:31 +08:00
}
2021-10-22 04:14:05 +08:00
return nil , "" , nil
2021-04-17 06:21:31 +08:00
}
2021-10-22 04:14:05 +08:00
var data [ ] byte
var envFile string
var ctrFileOnHost string
2021-04-17 06:21:31 +08:00
2021-10-22 04:14:05 +08:00
switch secr . SourceType {
case "env" :
data = [ ] byte ( os . Getenv ( secr . Source ) )
tmpFile , err := ioutil . TempFile ( "/dev/shm" , "buildah*" )
2021-04-17 06:21:31 +08:00
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
2021-10-22 04:14:05 +08:00
envFile = tmpFile . Name ( )
ctrFileOnHost = tmpFile . Name ( )
case "file" :
data , err = ioutil . ReadFile ( secr . Source )
if err != nil {
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
2021-10-22 04:14:05 +08:00
ctrFileOnHost = filepath . Join ( containerWorkingDir , "secrets" , id )
_ , err = os . Stat ( ctrFileOnHost )
if ! os . IsNotExist ( err ) {
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
2021-10-22 04:14:05 +08:00
default :
return nil , "" , errors . New ( "invalid source secret type" )
}
// Copy secrets to container working dir (or tmp dir if it's an env), since we need to chmod,
// chown and relabel it for the container user and we don't want to mess with the original file
2022-01-12 03:07:53 +08:00
if err := os . MkdirAll ( filepath . Dir ( ctrFileOnHost ) , 0755 ) ; err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
}
if err := ioutil . WriteFile ( ctrFileOnHost , data , 0644 ) ; err != nil {
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
if err := label . Relabel ( ctrFileOnHost , mountlabel , false ) ; err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
hostUID , hostGID , err := util . GetHostIDs ( uidmap , gidmap , uid , gid )
if err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
if err := os . Lchown ( ctrFileOnHost , int ( hostUID ) , int ( hostGID ) ) ; err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
if err := os . Chmod ( ctrFileOnHost , os . FileMode ( mode ) ) ; err != nil {
2021-10-22 04:14:05 +08:00
return nil , "" , err
2021-04-17 06:21:31 +08:00
}
newMount := specs . Mount {
Destination : target ,
Type : "bind" ,
Source : ctrFileOnHost ,
Options : [ ] string { "bind" , "rprivate" , "ro" } ,
}
2021-10-22 04:14:05 +08:00
return & newMount , envFile , nil
2021-04-17 06:21:31 +08:00
}
2021-07-26 14:07:23 +08:00
// getSSHMount parses the --mount type=ssh flag in the Containerfile, checks if there's an ssh source provided, and creates and starts an ssh-agent to be forwarded into the container
2021-09-25 03:06:08 +08:00
func ( b * Builder ) getSSHMount ( tokens [ ] string , count int , sshsources map [ string ] * sshagent . Source , mountlabel string , uidmap [ ] spec . LinuxIDMapping , gidmap [ ] spec . LinuxIDMapping , processLabel string ) ( * spec . Mount , * sshagent . AgentServer , error ) {
2021-07-26 14:07:23 +08:00
errInvalidSyntax := errors . New ( "ssh should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint" )
var err error
var id , target string
var required bool
var uid , gid uint32
var mode uint32 = 400
for _ , val := range tokens {
kv := strings . SplitN ( val , "=" , 2 )
if len ( kv ) < 2 {
return nil , nil , errInvalidSyntax
}
switch kv [ 0 ] {
case "id" :
id = kv [ 1 ]
case "target" , "dst" , "destination" :
target = kv [ 1 ]
case "required" :
required , err = strconv . ParseBool ( kv [ 1 ] )
if err != nil {
return nil , nil , errInvalidSyntax
}
case "mode" :
mode64 , err := strconv . ParseUint ( kv [ 1 ] , 8 , 32 )
if err != nil {
return nil , nil , errInvalidSyntax
}
mode = uint32 ( mode64 )
case "uid" :
uid64 , err := strconv . ParseUint ( kv [ 1 ] , 10 , 32 )
if err != nil {
return nil , nil , errInvalidSyntax
}
uid = uint32 ( uid64 )
case "gid" :
gid64 , err := strconv . ParseUint ( kv [ 1 ] , 10 , 32 )
if err != nil {
return nil , nil , errInvalidSyntax
}
gid = uint32 ( gid64 )
default :
return nil , nil , errInvalidSyntax
}
}
if id == "" {
id = "default"
}
// Default location for secretis is /run/buildkit/ssh_agent.{i}
if target == "" {
target = fmt . Sprintf ( "/run/buildkit/ssh_agent.%d" , count )
}
sshsource , ok := sshsources [ id ]
if ! ok {
if required {
return nil , nil , errors . Errorf ( "ssh required but no ssh with id %s found" , id )
}
return nil , nil , nil
}
// Create new agent from keys or socket
fwdAgent , err := sshagent . NewAgentServer ( sshsource )
if err != nil {
return nil , nil , err
}
// Start ssh server, and get the host sock we're mounting in the container
hostSock , err := fwdAgent . Serve ( processLabel )
if err != nil {
return nil , nil , err
}
if err := label . Relabel ( filepath . Dir ( hostSock ) , mountlabel , false ) ; err != nil {
if shutdownErr := fwdAgent . Shutdown ( ) ; shutdownErr != nil {
2021-09-25 03:06:08 +08:00
b . Logger . Errorf ( "error shutting down agent: %v" , shutdownErr )
2021-07-26 14:07:23 +08:00
}
return nil , nil , err
}
if err := label . Relabel ( hostSock , mountlabel , false ) ; err != nil {
if shutdownErr := fwdAgent . Shutdown ( ) ; shutdownErr != nil {
2021-09-25 03:06:08 +08:00
b . Logger . Errorf ( "error shutting down agent: %v" , shutdownErr )
2021-07-26 14:07:23 +08:00
}
return nil , nil , err
}
hostUID , hostGID , err := util . GetHostIDs ( uidmap , gidmap , uid , gid )
if err != nil {
if shutdownErr := fwdAgent . Shutdown ( ) ; shutdownErr != nil {
2021-09-25 03:06:08 +08:00
b . Logger . Errorf ( "error shutting down agent: %v" , shutdownErr )
2021-07-26 14:07:23 +08:00
}
return nil , nil , err
}
if err := os . Lchown ( hostSock , int ( hostUID ) , int ( hostGID ) ) ; err != nil {
if shutdownErr := fwdAgent . Shutdown ( ) ; shutdownErr != nil {
2021-09-25 03:06:08 +08:00
b . Logger . Errorf ( "error shutting down agent: %v" , shutdownErr )
2021-07-26 14:07:23 +08:00
}
return nil , nil , err
}
if err := os . Chmod ( hostSock , os . FileMode ( mode ) ) ; err != nil {
if shutdownErr := fwdAgent . Shutdown ( ) ; shutdownErr != nil {
2021-09-25 03:06:08 +08:00
b . Logger . Errorf ( "error shutting down agent: %v" , shutdownErr )
2021-07-26 14:07:23 +08:00
}
return nil , nil , err
}
newMount := specs . Mount {
Destination : target ,
Type : "bind" ,
Source : hostSock ,
Options : [ ] string { "bind" , "rprivate" , "ro" } ,
}
return & newMount , fwdAgent , nil
}
// cleanupRunMounts cleans up run mounts so they only appear in this run.
2021-10-18 13:51:51 +08:00
func ( b * Builder ) cleanupRunMounts ( context * imagetypes . SystemContext , mountpoint string , artifacts * runMountArtifacts ) error {
2021-07-26 14:07:23 +08:00
for _ , agent := range artifacts . Agents {
err := agent . Shutdown ( )
if err != nil {
return err
}
}
2021-10-18 13:51:51 +08:00
//cleanup any mounted images for this run
for _ , image := range artifacts . MountedImages {
if image != "" {
// if flow hits here some image was mounted for this run
i , err := internalUtil . LookupImage ( context , b . store , image )
if err == nil {
// silently try to unmount and do nothing
// if image is being used by something else
_ = i . Unmount ( false )
}
if errors . Cause ( err ) == storagetypes . ErrImageUnknown {
// Ignore only if ErrImageUnknown
// Reason: Image is already unmounted do nothing
continue
}
return err
}
}
2021-04-17 06:21:31 +08:00
opts := copier . RemoveOptions {
All : true ,
}
2021-07-26 14:07:23 +08:00
for _ , path := range artifacts . RunMountTargets {
2021-04-17 06:21:31 +08:00
err := copier . Remove ( mountpoint , path , opts )
if err != nil {
return err
}
}
2021-10-22 04:14:05 +08:00
var prevErr error
for _ , path := range artifacts . TmpFiles {
err := os . Remove ( path )
if ! os . IsNotExist ( err ) {
if prevErr != nil {
logrus . Error ( prevErr )
}
prevErr = err
}
}
return prevErr
2021-04-17 06:21:31 +08:00
}
2022-01-06 04:36:49 +08:00
// getNetworkInterface creates the network interface
func getNetworkInterface ( store storage . Store , cniConfDir , cniPluginPath string ) ( nettypes . ContainerNetwork , error ) {
conf , err := config . Default ( )
if err != nil {
return nil , err
}
// copy the config to not modify the default by accident
newconf := * conf
if len ( cniConfDir ) > 0 {
newconf . Network . NetworkConfigDir = cniConfDir
}
if len ( cniPluginPath ) > 0 {
plugins := strings . Split ( cniPluginPath , string ( os . PathListSeparator ) )
newconf . Network . CNIPluginDirs = plugins
}
_ , netInt , err := network . NetworkBackend ( store , & newconf , false )
if err != nil {
return nil , err
}
return netInt , nil
}