mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
239 lines
5.6 KiB
Go
239 lines
5.6 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/criyle/go-sandbox/pkg/forkexec"
|
|
"github.com/criyle/go-sandbox/pkg/memfd"
|
|
"github.com/criyle/go-sandbox/pkg/mount"
|
|
"github.com/criyle/go-sandbox/pkg/unixsocket"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Builder builds instance of container masters
|
|
type Builder struct {
|
|
// Root is container root mount path, empty uses current work path
|
|
Root string
|
|
|
|
// Mounts defines container mount points, empty uses default mounts
|
|
Mounts []mount.SyscallParams
|
|
|
|
// Stderr defines whether to dup container stderr to stderr for debug
|
|
Stderr bool
|
|
|
|
// ExecFile defines executable that called Init, otherwise defer current
|
|
// executable (/proc/self/exe)
|
|
ExecFile *os.File
|
|
|
|
// CredGenerator defines a credential generator used to create new container
|
|
CredGenerator CredGenerator
|
|
}
|
|
|
|
// CredGenerator generates uid / gid credential used by container
|
|
// to isolate process and file system access
|
|
type CredGenerator interface {
|
|
Get() syscall.Credential
|
|
}
|
|
|
|
// Master manages single pre-forked container
|
|
type Master struct {
|
|
pid int // underlying container init pid
|
|
socket *unixsocket.Socket // master - container communication
|
|
mu sync.Mutex // lock to avoid race condition
|
|
}
|
|
|
|
// Build creates new master with underlying container
|
|
func (b *Builder) Build() (*Master, error) {
|
|
var (
|
|
err error
|
|
cred syscall.Credential
|
|
uidMap, gidMap []syscall.SysProcIDMap
|
|
)
|
|
|
|
// container mount points
|
|
mounts := b.Mounts
|
|
if len(mounts) == 0 {
|
|
if mounts, err = mount.NewBuilder().WithMounts(DefaultMounts).Build(true); err != nil {
|
|
return nil, fmt.Errorf("daemon: failed to build rootfs mount %v", err)
|
|
}
|
|
}
|
|
root := b.Root
|
|
if root == "" {
|
|
if root, err = os.Getwd(); err != nil {
|
|
return nil, fmt.Errorf("daemon: failed to get work directory %v", err)
|
|
}
|
|
}
|
|
// prepare stdin / stdout / stderr
|
|
devNull, err := os.OpenFile(os.DevNull, os.O_RDWR, os.ModePerm)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("daemon: failed to open devNull(%v)", err)
|
|
}
|
|
defer devNull.Close()
|
|
|
|
files := make([]uintptr, 0, 4)
|
|
files = append(files, devNull.Fd(), devNull.Fd())
|
|
if b.Stderr {
|
|
files = append(files, os.Stderr.Fd())
|
|
} else {
|
|
files = append(files, devNull.Fd())
|
|
}
|
|
|
|
// prepare self memfd
|
|
execFile, err := b.exec()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("deamon: %v", err)
|
|
}
|
|
defer execFile.Close()
|
|
|
|
// prepare socket
|
|
ins, outs, err := newPassCredSocketPair()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("daemon: failed to create socket: %v", err)
|
|
}
|
|
defer outs.Close()
|
|
|
|
outf, err := outs.File()
|
|
if err != nil {
|
|
ins.Close()
|
|
return nil, fmt.Errorf("daemon: failed to dup file outs(%v)", err)
|
|
}
|
|
defer outf.Close()
|
|
|
|
files = append(files, uintptr(outf.Fd()))
|
|
|
|
// prepare credential
|
|
if b.CredGenerator != nil {
|
|
cred = b.CredGenerator.Get()
|
|
uidMap, gidMap = getIDMapping(&cred)
|
|
}
|
|
|
|
r := &forkexec.Runner{
|
|
Args: []string{os.Args[0], initArg},
|
|
Env: []string{DefaultPath},
|
|
ExecFile: execFile.Fd(),
|
|
Files: files,
|
|
WorkDir: containerWD,
|
|
CloneFlags: forkexec.UnshareFlags,
|
|
Mounts: mounts,
|
|
HostName: containerName,
|
|
DomainName: containerName,
|
|
PivotRoot: root,
|
|
UIDMappings: uidMap,
|
|
GIDMappings: gidMap,
|
|
}
|
|
pid, err := r.Start()
|
|
if err != nil {
|
|
ins.Close()
|
|
return nil, fmt.Errorf("daemon: failed to execve(%v)", err)
|
|
}
|
|
m := &Master{
|
|
pid: pid,
|
|
socket: ins,
|
|
}
|
|
|
|
// set configuration and check if container creation successful
|
|
if err = m.conf(&containerConfig{
|
|
Cred: b.CredGenerator != nil,
|
|
}); err != nil {
|
|
m.Destroy()
|
|
return nil, err
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// Destroy kill the daemon process (with container)
|
|
// if stderr enabled, collect the output as error
|
|
func (m *Master) Destroy() error {
|
|
// close socket (abort any ongoing command)
|
|
m.socket.Close()
|
|
|
|
// wait commands terminates
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// kill process
|
|
var wstatus unix.WaitStatus
|
|
unix.Kill(m.pid, unix.SIGKILL)
|
|
// wait for container process to exit
|
|
_, err := unix.Wait4(m.pid, &wstatus, 0, nil)
|
|
for err == unix.EINTR {
|
|
_, err = unix.Wait4(m.pid, &wstatus, 0, nil)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// exec prepares executable
|
|
func (b *Builder) exec() (*os.File, error) {
|
|
if b.ExecFile != nil {
|
|
return b.ExecFile, nil
|
|
}
|
|
return OpenCurrentExec()
|
|
}
|
|
|
|
// OpenCurrentExec opens current executable and dup it to memfd
|
|
func OpenCurrentExec() (*os.File, error) {
|
|
self, err := os.Open(currentExec)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open %v: %v", currentExec, err)
|
|
}
|
|
defer self.Close()
|
|
|
|
execFile, err := memfd.DupToMemfd("daemon", self)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create memfd: %v", err)
|
|
}
|
|
return execFile, nil
|
|
}
|
|
|
|
func newPassCredSocketPair() (*unixsocket.Socket, *unixsocket.Socket, error) {
|
|
ins, outs, err := unixsocket.NewSocketPair()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if err = ins.SetPassCred(1); err != nil {
|
|
ins.Close()
|
|
outs.Close()
|
|
return nil, nil, err
|
|
}
|
|
if err = outs.SetPassCred(1); err != nil {
|
|
ins.Close()
|
|
outs.Close()
|
|
return nil, nil, err
|
|
}
|
|
return ins, outs, nil
|
|
}
|
|
|
|
func getIDMapping(cred *syscall.Credential) ([]syscall.SysProcIDMap, []syscall.SysProcIDMap) {
|
|
uidMap := []syscall.SysProcIDMap{
|
|
syscall.SysProcIDMap{
|
|
ContainerID: 0,
|
|
HostID: os.Geteuid(),
|
|
Size: 1,
|
|
},
|
|
syscall.SysProcIDMap{
|
|
ContainerID: containerUID,
|
|
HostID: int(cred.Uid),
|
|
Size: 1,
|
|
},
|
|
}
|
|
|
|
gidMap := []syscall.SysProcIDMap{
|
|
syscall.SysProcIDMap{
|
|
ContainerID: 0,
|
|
HostID: os.Getegid(),
|
|
Size: 1,
|
|
},
|
|
syscall.SysProcIDMap{
|
|
ContainerID: containerGID,
|
|
HostID: int(cred.Gid),
|
|
Size: 1,
|
|
},
|
|
}
|
|
|
|
return uidMap, gidMap
|
|
}
|