255 lines
6.1 KiB
Go
255 lines
6.1 KiB
Go
// +build !windows
|
|
|
|
package oci
|
|
|
|
import (
|
|
"context"
|
|
"path"
|
|
"sync"
|
|
|
|
"github.com/containerd/containerd/containers"
|
|
"github.com/containerd/containerd/contrib/seccomp"
|
|
"github.com/containerd/containerd/mount"
|
|
"github.com/containerd/containerd/namespaces"
|
|
"github.com/containerd/containerd/oci"
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/mitchellh/hashstructure"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/moby/buildkit/snapshot"
|
|
"github.com/moby/buildkit/solver/pb"
|
|
"github.com/moby/buildkit/util/entitlements/security"
|
|
"github.com/moby/buildkit/util/network"
|
|
"github.com/moby/buildkit/util/system"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Ideally we don't have to import whole containerd just for the default spec
|
|
|
|
// GenerateSpec generates spec using containerd functionality.
|
|
// opts are ignored for s.Process, s.Hostname, and s.Mounts .
|
|
func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, processMode ProcessMode, idmap *idtools.IdentityMapping, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {
|
|
c := &containers.Container{
|
|
ID: id,
|
|
}
|
|
_, ok := namespaces.Namespace(ctx)
|
|
if !ok {
|
|
ctx = namespaces.WithNamespace(ctx, "buildkit")
|
|
}
|
|
if meta.SecurityMode == pb.SecurityMode_INSECURE {
|
|
opts = append(opts, security.WithInsecureSpec())
|
|
} else if system.SeccompSupported() && meta.SecurityMode == pb.SecurityMode_SANDBOX {
|
|
opts = append(opts, seccomp.WithDefaultProfile())
|
|
}
|
|
|
|
switch processMode {
|
|
case NoProcessSandbox:
|
|
// Mount for /proc is replaced in GetMounts()
|
|
opts = append(opts,
|
|
oci.WithHostNamespace(specs.PIDNamespace))
|
|
// TODO(AkihiroSuda): Configure seccomp to disable ptrace (and prctl?) explicitly
|
|
}
|
|
|
|
// Note that containerd.GenerateSpec is namespaced so as to make
|
|
// specs.Linux.CgroupsPath namespaced
|
|
s, err := oci.GenerateSpec(ctx, nil, c, opts...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// set the networking information on the spec
|
|
namespace.Set(s)
|
|
|
|
s.Process.Args = meta.Args
|
|
s.Process.Env = meta.Env
|
|
s.Process.Cwd = meta.Cwd
|
|
s.Process.Rlimits = nil // reset open files limit
|
|
s.Process.NoNewPrivileges = false // reset nonewprivileges
|
|
s.Hostname = "buildkitsandbox"
|
|
|
|
s.Mounts, err = GetMounts(ctx,
|
|
withProcessMode(processMode),
|
|
withROBind(resolvConf, "/etc/resolv.conf"),
|
|
withROBind(hostsFile, "/etc/hosts"),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
s.Mounts = append(s.Mounts, specs.Mount{
|
|
Destination: "/sys/fs/cgroup",
|
|
Type: "cgroup",
|
|
Source: "cgroup",
|
|
Options: []string{"ro", "nosuid", "noexec", "nodev"},
|
|
})
|
|
|
|
if processMode == NoProcessSandbox {
|
|
var maskedPaths []string
|
|
for _, s := range s.Linux.MaskedPaths {
|
|
if !hasPrefix(s, "/proc") {
|
|
maskedPaths = append(maskedPaths, s)
|
|
}
|
|
}
|
|
s.Linux.MaskedPaths = maskedPaths
|
|
var readonlyPaths []string
|
|
for _, s := range s.Linux.ReadonlyPaths {
|
|
if !hasPrefix(s, "/proc") {
|
|
readonlyPaths = append(readonlyPaths, s)
|
|
}
|
|
}
|
|
s.Linux.ReadonlyPaths = readonlyPaths
|
|
}
|
|
|
|
if meta.SecurityMode == pb.SecurityMode_INSECURE {
|
|
if err = oci.WithWriteableCgroupfs(ctx, nil, c, s); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if err = oci.WithWriteableSysfs(ctx, nil, c, s); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
if idmap != nil {
|
|
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
|
|
Type: specs.UserNamespace,
|
|
})
|
|
s.Linux.UIDMappings = specMapping(idmap.UIDs())
|
|
s.Linux.GIDMappings = specMapping(idmap.GIDs())
|
|
}
|
|
|
|
sm := &submounts{}
|
|
|
|
var releasers []func() error
|
|
releaseAll := func() {
|
|
sm.cleanup()
|
|
for _, f := range releasers {
|
|
f()
|
|
}
|
|
}
|
|
|
|
for _, m := range mounts {
|
|
if m.Src == nil {
|
|
return nil, nil, errors.Errorf("mount %s has no source", m.Dest)
|
|
}
|
|
mountable, err := m.Src.Mount(ctx, m.Readonly)
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, errors.Wrapf(err, "failed to mount %s", m.Dest)
|
|
}
|
|
mounts, release, err := mountable.Mount()
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, errors.WithStack(err)
|
|
}
|
|
releasers = append(releasers, release)
|
|
for _, mount := range mounts {
|
|
mount, err = sm.subMount(mount, m.Selector)
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, err
|
|
}
|
|
s.Mounts = append(s.Mounts, specs.Mount{
|
|
Destination: m.Dest,
|
|
Type: mount.Type,
|
|
Source: mount.Source,
|
|
Options: mount.Options,
|
|
})
|
|
}
|
|
}
|
|
|
|
return s, releaseAll, nil
|
|
}
|
|
|
|
type mountRef struct {
|
|
mount mount.Mount
|
|
unmount func() error
|
|
}
|
|
|
|
type submounts struct {
|
|
m map[uint64]mountRef
|
|
}
|
|
|
|
func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error) {
|
|
if path.Join("/", subPath) == "/" {
|
|
return m, nil
|
|
}
|
|
if s.m == nil {
|
|
s.m = map[uint64]mountRef{}
|
|
}
|
|
h, err := hashstructure.Hash(m, nil)
|
|
if err != nil {
|
|
return mount.Mount{}, nil
|
|
}
|
|
if mr, ok := s.m[h]; ok {
|
|
sm, err := sub(mr.mount, subPath)
|
|
if err != nil {
|
|
return mount.Mount{}, nil
|
|
}
|
|
return sm, nil
|
|
}
|
|
|
|
lm := snapshot.LocalMounterWithMounts([]mount.Mount{m})
|
|
|
|
mp, err := lm.Mount()
|
|
if err != nil {
|
|
return mount.Mount{}, err
|
|
}
|
|
|
|
opts := []string{"rbind"}
|
|
for _, opt := range m.Options {
|
|
if opt == "ro" {
|
|
opts = append(opts, opt)
|
|
}
|
|
}
|
|
|
|
s.m[h] = mountRef{
|
|
mount: mount.Mount{
|
|
Source: mp,
|
|
Type: "bind",
|
|
Options: opts,
|
|
},
|
|
unmount: lm.Unmount,
|
|
}
|
|
|
|
sm, err := sub(s.m[h].mount, subPath)
|
|
if err != nil {
|
|
return mount.Mount{}, err
|
|
}
|
|
return sm, nil
|
|
}
|
|
|
|
func (s *submounts) cleanup() {
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(s.m))
|
|
for _, m := range s.m {
|
|
func(m mountRef) {
|
|
go func() {
|
|
m.unmount()
|
|
wg.Done()
|
|
}()
|
|
}(m)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
func sub(m mount.Mount, subPath string) (mount.Mount, error) {
|
|
src, err := fs.RootPath(m.Source, subPath)
|
|
if err != nil {
|
|
return mount.Mount{}, err
|
|
}
|
|
m.Source = src
|
|
return m, nil
|
|
}
|
|
|
|
func specMapping(s []idtools.IDMap) []specs.LinuxIDMapping {
|
|
var ids []specs.LinuxIDMapping
|
|
for _, item := range s {
|
|
ids = append(ids, specs.LinuxIDMapping{
|
|
HostID: uint32(item.HostID),
|
|
ContainerID: uint32(item.ContainerID),
|
|
Size: uint32(item.Size),
|
|
})
|
|
}
|
|
return ids
|
|
}
|