Merge pull request #1251 from crosbymichael/apparmor

Add User Namespace and Apparmor support to client
This commit is contained in:
Derek McGowan 2017-07-27 11:13:03 -07:00 committed by GitHub
commit d4349eff39
17 changed files with 382 additions and 147 deletions

13
apparmor.go Normal file
View File

@ -0,0 +1,13 @@
// +build linux
package containerd
import specs "github.com/opencontainers/runtime-spec/specs-go"
// WithApparmor sets the provided apparmor profile to the spec
func WithApparmorProfile(profile string) SpecOpts {
return func(s *specs.Spec) error {
s.Process.ApparmorProfile = profile
return nil
}
}

View File

@ -1,107 +1 @@
package archive package archive
import (
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
var (
errTooManyLinks = errors.New("too many links")
)
// rootPath joins a path with a root, evaluating and bounding any
// symlink to the root directory.
// TODO(dmcgowan): Expose and move to fs package or continuity path driver
func rootPath(root, path string) (string, error) {
if path == "" {
return root, nil
}
var linksWalked int // to protect against cycles
for {
i := linksWalked
newpath, err := walkLinks(root, path, &linksWalked)
if err != nil {
return "", err
}
path = newpath
if i == linksWalked {
newpath = filepath.Join("/", newpath)
if path == newpath {
return filepath.Join(root, newpath), nil
}
path = newpath
}
}
}
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
if *linksWalked > 255 {
return "", false, errTooManyLinks
}
path = filepath.Join("/", path)
if path == "/" {
return path, false, nil
}
realPath := filepath.Join(root, path)
fi, err := os.Lstat(realPath)
if err != nil {
// If path does not yet exist, treat as non-symlink
if os.IsNotExist(err) {
return path, false, nil
}
return "", false, err
}
if fi.Mode()&os.ModeSymlink == 0 {
return path, false, nil
}
newpath, err = os.Readlink(realPath)
if err != nil {
return "", false, err
}
if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
newpath = newpath[:len(root)]
if !strings.HasPrefix(newpath, "/") {
newpath = "/" + newpath
}
}
*linksWalked++
return newpath, true, nil
}
func walkLinks(root, path string, linksWalked *int) (string, error) {
switch dir, file := filepath.Split(path); {
case dir == "":
newpath, _, err := walkLink(root, file, linksWalked)
return newpath, err
case file == "":
if os.IsPathSeparator(dir[len(dir)-1]) {
if dir == "/" {
return dir, nil
}
return walkLinks(root, dir[:len(dir)-1], linksWalked)
}
newpath, _, err := walkLink(root, dir, linksWalked)
return newpath, err
default:
newdir, err := walkLinks(root, dir, linksWalked)
if err != nil {
return "", err
}
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
if err != nil {
return "", err
}
if !islink {
return newpath, nil
}
if filepath.IsAbs(newpath) {
return newpath, nil
}
return filepath.Join(newdir, newpath), nil
}
}

View File

@ -128,7 +128,7 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
// Split name and resolve symlinks for root directory. // Split name and resolve symlinks for root directory.
ppath, base := filepath.Split(hdr.Name) ppath, base := filepath.Split(hdr.Name)
ppath, err = rootPath(root, ppath) ppath, err = fs.RootPath(root, ppath)
if err != nil { if err != nil {
return 0, errors.Wrap(err, "failed to get root path") return 0, errors.Wrap(err, "failed to get root path")
} }
@ -170,7 +170,7 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
} }
defer os.RemoveAll(aufsTempdir) defer os.RemoveAll(aufsTempdir)
} }
p, err := rootPath(aufsTempdir, basename) p, err := fs.RootPath(aufsTempdir, basename)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -243,7 +243,7 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
if srcHdr == nil { if srcHdr == nil {
return 0, fmt.Errorf("Invalid aufs hardlink") return 0, fmt.Errorf("Invalid aufs hardlink")
} }
p, err := rootPath(aufsTempdir, linkBasename) p, err := fs.RootPath(aufsTempdir, linkBasename)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -268,7 +268,7 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
} }
for _, hdr := range dirs { for _, hdr := range dirs {
path, err := rootPath(root, hdr.Name) path, err := fs.RootPath(root, hdr.Name)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -478,7 +478,7 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
} }
case tar.TypeLink: case tar.TypeLink:
targetPath, err := rootPath(extractDir, hdr.Linkname) targetPath, err := fs.RootPath(extractDir, hdr.Linkname)
if err != nil { if err != nil {
return err return err
} }

View File

@ -182,11 +182,11 @@ func TestBreakouts(t *testing.T) {
errFileDiff := errors.New("files differ") errFileDiff := errors.New("files differ")
sameFile := func(f1, f2 string) func(string) error { sameFile := func(f1, f2 string) func(string) error {
return func(root string) error { return func(root string) error {
p1, err := rootPath(root, f1) p1, err := fs.RootPath(root, f1)
if err != nil { if err != nil {
return err return err
} }
p2, err := rootPath(root, f2) p2, err := fs.RootPath(root, f2)
if err != nil { if err != nil {
return err return err
} }
@ -484,7 +484,7 @@ func TestApplyTar(t *testing.T) {
directoriesExist := func(dirs ...string) func(string) error { directoriesExist := func(dirs ...string) func(string) error {
return func(root string) error { return func(root string) error {
for _, d := range dirs { for _, d := range dirs {
p, err := rootPath(root, d) p, err := fs.RootPath(root, d)
if err != nil { if err != nil {
return err return err
} }

View File

@ -889,3 +889,81 @@ func TestContainerExecNoBinaryExists(t *testing.T) {
} }
<-finished <-finished
} }
func TestUserNamespaces(t *testing.T) {
client, err := newClient(t, address)
if err != nil {
t.Fatal(err)
}
defer client.Close()
var (
image Image
ctx, cancel = testContext()
id = t.Name()
)
defer cancel()
if runtime.GOOS != "windows" {
image, err = client.GetImage(ctx, testImage)
if err != nil {
t.Error(err)
return
}
}
spec, err := generateSpec(
withImageConfig(ctx, image),
withExitStatus(7),
withUserNamespace(0, 1000, 10000),
)
if err != nil {
t.Error(err)
return
}
container, err := client.NewContainer(ctx, id,
WithSpec(spec),
withRemappedSnapshot(id, image, 1000, 1000),
)
if err != nil {
t.Error(err)
return
}
defer container.Delete(ctx, WithSnapshotCleanup)
task, err := container.NewTask(ctx, Stdio)
if err != nil {
t.Error(err)
return
}
defer task.Delete(ctx)
statusC := make(chan uint32, 1)
go func() {
status, err := task.Wait(ctx)
if err != nil {
t.Error(err)
}
statusC <- status
}()
if pid := task.Pid(); pid <= 0 {
t.Errorf("invalid task pid %d", pid)
}
if err := task.Start(ctx); err != nil {
t.Error(err)
task.Delete(ctx)
return
}
status := <-statusC
if status != 7 {
t.Errorf("expected status 7 from wait but received %d", status)
}
if status, err = task.Delete(ctx); err != nil {
t.Error(err)
return
}
if status != 7 {
t.Errorf("expected status 7 from delete but received %d", status)
}
}

View File

@ -7,6 +7,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
)
var (
errTooManyLinks = errors.New("too many links")
) )
type currentPath struct { type currentPath struct {
@ -160,3 +166,96 @@ func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, err
return p, nil return p, nil
} }
} }
// RootPath joins a path with a root, evaluating and bounding any
// symlink to the root directory.
func RootPath(root, path string) (string, error) {
if path == "" {
return root, nil
}
var linksWalked int // to protect against cycles
for {
i := linksWalked
newpath, err := walkLinks(root, path, &linksWalked)
if err != nil {
return "", err
}
path = newpath
if i == linksWalked {
newpath = filepath.Join("/", newpath)
if path == newpath {
return filepath.Join(root, newpath), nil
}
path = newpath
}
}
}
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
if *linksWalked > 255 {
return "", false, errTooManyLinks
}
path = filepath.Join("/", path)
if path == "/" {
return path, false, nil
}
realPath := filepath.Join(root, path)
fi, err := os.Lstat(realPath)
if err != nil {
// If path does not yet exist, treat as non-symlink
if os.IsNotExist(err) {
return path, false, nil
}
return "", false, err
}
if fi.Mode()&os.ModeSymlink == 0 {
return path, false, nil
}
newpath, err = os.Readlink(realPath)
if err != nil {
return "", false, err
}
if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
newpath = newpath[:len(root)]
if !strings.HasPrefix(newpath, "/") {
newpath = "/" + newpath
}
}
*linksWalked++
return newpath, true, nil
}
func walkLinks(root, path string, linksWalked *int) (string, error) {
switch dir, file := filepath.Split(path); {
case dir == "":
newpath, _, err := walkLink(root, file, linksWalked)
return newpath, err
case file == "":
if os.IsPathSeparator(dir[len(dir)-1]) {
if dir == "/" {
return dir, nil
}
return walkLinks(root, dir[:len(dir)-1], linksWalked)
}
newpath, _, err := walkLink(root, dir, linksWalked)
return newpath, err
default:
newdir, err := walkLinks(root, dir, linksWalked)
if err != nil {
return "", err
}
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
if err != nil {
return "", err
}
if !islink {
return newpath, nil
}
if filepath.IsAbs(newpath) {
return newpath, nil
}
return filepath.Join(newdir, newpath), nil
}
}

View File

@ -1,6 +1,6 @@
// +build !windows // +build !windows
package archive package fs
import ( import (
"io/ioutil" "io/ioutil"
@ -179,7 +179,7 @@ func testRootPathSymlinkRootScope(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rewrite, err := rootPath("/", tmpdir) rewrite, err := RootPath("/", tmpdir)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -192,7 +192,7 @@ func testRootPathSymlinkEmpty(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
res, err := rootPath(wd, "") res, err := RootPath(wd, "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -221,7 +221,7 @@ func makeRootPathTest(t *testing.T, apply fstest.Applier, checks []rootCheck) fu
root = check.scope(root) root = check.scope(root)
} }
actual, err := rootPath(root, check.unresolved) actual, err := RootPath(root, check.unresolved)
if check.cause != nil { if check.cause != nil {
if err == nil { if err == nil {
t.Errorf("(Check %d) Expected error %q, %q evaluated as %q", i+1, check.cause.Error(), check.unresolved, actual) t.Errorf("(Check %d) Expected error %q, %q evaluated as %q", i+1, check.cause.Error(), check.unresolved, actual)

View File

@ -49,3 +49,7 @@ func withImageConfig(ctx context.Context, i Image) SpecOpts {
func withNewSnapshot(id string, i Image) NewContainerOpts { func withNewSnapshot(id string, i Image) NewContainerOpts {
return WithNewSnapshot(id, i) return WithNewSnapshot(id, i)
} }
var withUserNamespace = WithUserNamespace
var withRemappedSnapshot = WithRemappedSnapshot

View File

@ -63,3 +63,15 @@ func withNewSnapshot(id string, i Image) NewContainerOpts {
return nil return nil
} }
} }
func withUserNamespace(u, g, s uint32) SpecOpts {
return func(s *specs.Spec) error {
return nil
}
}
func withRemappedSnapshot(id string, i Image, u, g uint32) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
return nil
}
}

View File

@ -21,7 +21,7 @@ func loadBundle(path, namespace string) *bundle {
// newBundle creates a new bundle on disk at the provided path for the given id // newBundle creates a new bundle on disk at the provided path for the given id
func newBundle(path, namespace, id string, spec []byte) (b *bundle, err error) { func newBundle(path, namespace, id string, spec []byte) (b *bundle, err error) {
if err := os.MkdirAll(path, 0700); err != nil { if err := os.MkdirAll(path, 0711); err != nil {
return nil, err return nil, err
} }
path = filepath.Join(path, id) path = filepath.Join(path, id)
@ -30,10 +30,10 @@ func newBundle(path, namespace, id string, spec []byte) (b *bundle, err error) {
os.RemoveAll(path) os.RemoveAll(path)
} }
}() }()
if err := os.Mkdir(path, 0700); err != nil { if err := os.Mkdir(path, 0711); err != nil {
return nil, err return nil, err
} }
if err := os.Mkdir(filepath.Join(path, "rootfs"), 0700); err != nil { if err := os.Mkdir(filepath.Join(path, "rootfs"), 0711); err != nil {
return nil, err return nil, err
} }
f, err := os.Create(filepath.Join(path, configFilename)) f, err := os.Create(filepath.Join(path, configFilename))

View File

@ -68,7 +68,7 @@ type Config struct {
} }
func New(ic *plugin.InitContext) (interface{}, error) { func New(ic *plugin.InitContext) (interface{}, error) {
if err := os.MkdirAll(ic.Root, 0700); err != nil { if err := os.MkdirAll(ic.Root, 0711); err != nil {
return nil, err return nil, err
} }
monitor, err := ic.Get(plugin.TaskMonitorPlugin) monitor, err := ic.Get(plugin.TaskMonitorPlugin)

View File

@ -120,7 +120,6 @@ func newInitProcess(context context.Context, path, namespace string, r *shimapi.
} }
defer os.Remove(socket.Path()) defer os.Remove(socket.Path())
} else { } else {
// TODO: get uid/gid
if io, err = runc.NewPipeIO(0, 0); err != nil { if io, err = runc.NewPipeIO(0, 0); err != nil {
return nil, errors.Wrap(err, "failed to create OCI runtime io pipes") return nil, errors.Wrap(err, "failed to create OCI runtime io pipes")
} }

View File

@ -36,7 +36,7 @@ func New(ctx context.Context, config *Config) (*Server, error) {
if config.Root == "" { if config.Root == "" {
return nil, errors.New("root must be specified") return nil, errors.New("root must be specified")
} }
if err := os.MkdirAll(config.Root, 0700); err != nil { if err := os.MkdirAll(config.Root, 0711); err != nil {
return nil, err return nil, err
} }
if err := apply(ctx, config); err != nil { if err := apply(ctx, config); err != nil {
@ -168,7 +168,7 @@ func loadPlugins(config *Config) ([]*plugin.Registration, error) {
Type: plugin.MetadataPlugin, Type: plugin.MetadataPlugin,
ID: "bolt", ID: "bolt",
Init: func(ic *plugin.InitContext) (interface{}, error) { Init: func(ic *plugin.InitContext) (interface{}, error) {
if err := os.MkdirAll(ic.Root, 0700); err != nil { if err := os.MkdirAll(ic.Root, 0711); err != nil {
return nil, err return nil, err
} }
return bolt.Open(filepath.Join(ic.Root, "meta.db"), 0644, nil) return bolt.Open(filepath.Join(ic.Root, "meta.db"), 0644, nil)

View File

@ -255,12 +255,12 @@ func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshot.Kind, ke
} }
}() }()
if err = os.MkdirAll(filepath.Join(td, "fs"), 0711); err != nil { if err = os.MkdirAll(filepath.Join(td, "fs"), 0755); err != nil {
return nil, err return nil, err
} }
if kind == snapshot.KindActive { if kind == snapshot.KindActive {
if err = os.MkdirAll(filepath.Join(td, "work"), 0700); err != nil { if err = os.MkdirAll(filepath.Join(td, "work"), 0711); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -2,8 +2,10 @@ package containerd
import specs "github.com/opencontainers/runtime-spec/specs-go" import specs "github.com/opencontainers/runtime-spec/specs-go"
// SpecOpts sets spec specific information to a newly generated OCI spec
type SpecOpts func(s *specs.Spec) error type SpecOpts func(s *specs.Spec) error
// WithProcessArgs replaces the args on the generated spec
func WithProcessArgs(args ...string) SpecOpts { func WithProcessArgs(args ...string) SpecOpts {
return func(s *specs.Spec) error { return func(s *specs.Spec) error {
s.Process.Args = args s.Process.Args = args

View File

@ -6,12 +6,21 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/fs"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/typeurl" "github.com/containerd/containerd/typeurl"
"github.com/opencontainers/image-spec/identity"
"github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" specs "github.com/opencontainers/runtime-spec/specs-go"
) )
@ -78,7 +87,6 @@ func createDefaultSpec() (*specs.Spec, error) {
Permitted: defaltCaps(), Permitted: defaltCaps(),
Inheritable: defaltCaps(), Inheritable: defaltCaps(),
Effective: defaltCaps(), Effective: defaltCaps(),
Ambient: defaltCaps(),
}, },
Rlimits: []specs.POSIXRlimit{ Rlimits: []specs.POSIXRlimit{
{ {
@ -130,24 +138,6 @@ func createDefaultSpec() (*specs.Spec, error) {
Source: "tmpfs", Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
}, },
{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
},
}, },
Linux: &specs.Linux{ Linux: &specs.Linux{
// TODO (@crosbymichael) make sure we don't have have two containers in the same cgroup // TODO (@crosbymichael) make sure we don't have have two containers in the same cgroup
@ -272,6 +262,7 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
} }
} }
// WithSpec sets the provided spec for a new container
func WithSpec(spec *specs.Spec) NewContainerOpts { func WithSpec(spec *specs.Spec) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error { return func(ctx context.Context, client *Client, c *containers.Container) error {
any, err := typeurl.MarshalAny(spec) any, err := typeurl.MarshalAny(spec)
@ -283,9 +274,153 @@ func WithSpec(spec *specs.Spec) NewContainerOpts {
} }
} }
// WithResources sets the provided resources on the spec for task updates
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts { func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
r.Resources = resources r.Resources = resources
return nil return nil
} }
} }
// WithNoNewPrivileges sets no_new_privileges on the process for the container
func WithNoNewPrivileges(s *specs.Spec) error {
s.Process.NoNewPrivileges = true
return nil
}
func WithHostHosts(s *specs.Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
})
return nil
}
func WithHostResoveconf(s *specs.Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
})
return nil
}
func WithHostLocaltime(s *specs.Spec) error {
s.Mounts = append(s.Mounts, specs.Mount{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
})
return nil
}
// WithUserNamespace sets the uid and gid mappings for the task
// this can be called multiple times to add more mappings to the generated spec
func WithUserNamespace(container, host, size uint32) SpecOpts {
return func(s *specs.Spec) error {
var hasUserns bool
for _, ns := range s.Linux.Namespaces {
if ns.Type == specs.UserNamespace {
hasUserns = true
break
}
}
if !hasUserns {
s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{
Type: specs.UserNamespace,
})
}
mapping := specs.LinuxIDMapping{
ContainerID: container,
HostID: host,
Size: size,
}
s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping)
s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping)
return nil
}
}
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
// filesystem to be used by a container with user namespaces
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore())
if err != nil {
return err
}
var (
snapshotter = client.SnapshotService(c.Snapshotter)
parent = identity.ChainID(diffIDs).String()
usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
)
if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil {
return err
}
c.RootFS = id
c.Image = i.Name()
return nil
}
mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent)
if err != nil {
return err
}
if err := remapRootFS(mounts, uid, gid); err != nil {
snapshotter.Remove(ctx, usernsID)
return err
}
if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil {
return err
}
if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil {
return err
}
c.RootFS = id
c.Image = i.Name()
return nil
}
}
func remapRootFS(mounts []mount.Mount, uid, gid uint32) error {
root, err := ioutil.TempDir("", "ctd-remap")
if err != nil {
return err
}
defer os.RemoveAll(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
defer unix.Unmount(root, 0)
return filepath.Walk(root, incrementFS(root, uid, gid))
}
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if root == path {
return nil
}
var (
stat = info.Sys().(*syscall.Stat_t)
u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc)
symlink = info.Mode()&os.ModeSymlink != 0
)
// make sure we resolve links inside the root for symlinks
if path, err = fs.RootPath(root, strings.TrimPrefix(path, root)); err != nil {
return err
}
if err := os.Chown(path, u, g); err != nil && !symlink {
return err
}
return nil
}
}

View File

@ -20,7 +20,6 @@ func TestGenerateSpec(t *testing.T) {
// check for matching caps // check for matching caps
defaults := defaltCaps() defaults := defaltCaps()
for _, cl := range [][]string{ for _, cl := range [][]string{
s.Process.Capabilities.Ambient,
s.Process.Capabilities.Bounding, s.Process.Capabilities.Bounding,
s.Process.Capabilities.Permitted, s.Process.Capabilities.Permitted,
s.Process.Capabilities.Inheritable, s.Process.Capabilities.Inheritable,