diff --git a/pkg/cdi/container-edits.go b/pkg/cdi/container-edits.go index 450a84f..fb32dcf 100644 --- a/pkg/cdi/container-edits.go +++ b/pkg/cdi/container-edits.go @@ -114,9 +114,18 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { } if len(e.Mounts) > 0 { + var ( + uids []oci.LinuxIDMapping + gids []oci.LinuxIDMapping + ) + + if specHasUserNamespace(spec) { + uids = cloneIDMappings(spec.Linux.UIDMappings) + gids = cloneIDMappings(spec.Linux.GIDMappings) + } for _, m := range e.Mounts { specgen.RemoveMount(m.ContainerPath) - specgen.AddMount((&Mount{m}).toOCI()) + specgen.AddMount((&Mount{m}).toOCI(withMountIDMappings(uids, gids))) } sortMounts(&specgen) } @@ -389,3 +398,26 @@ func (m orderedMounts) Swap(i, j int) { func (m orderedMounts) parts(i int) int { return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator)) } + +// specHasUserNamespace returns true if the OCI Spec has a Linux UserNamespace. +func specHasUserNamespace(spec *oci.Spec) bool { + if spec == nil || spec.Linux == nil { + return false + } + for _, ns := range spec.Linux.Namespaces { + if ns.Type == oci.UserNamespace { + return true + } + } + return false +} + +// cloneIDMappings clones a slice of OCI LinuxIDMappings. +func cloneIDMappings(mappings []oci.LinuxIDMapping) []oci.LinuxIDMapping { + if mappings == nil { + return nil + } + clone := make([]oci.LinuxIDMapping, len(mappings)) + copy(clone, mappings) + return clone +} diff --git a/pkg/cdi/container-edits_test.go b/pkg/cdi/container-edits_test.go index c1fcfab..1919655 100644 --- a/pkg/cdi/container-edits_test.go +++ b/pkg/cdi/container-edits_test.go @@ -687,6 +687,103 @@ func TestApplyContainerEdits(t *testing.T) { }, }, }, + { + name: "mount added to container with Linux user namespace and uid/gid mappings", + spec: &oci.Spec{ + Linux: &oci.Linux{ + UIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 999, + }, + }, + GIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 2000, + Size: 777, + }, + }, + Namespaces: []oci.LinuxNamespace{ + { + Type: oci.UserNamespace, + Path: "/foo/bar", + }, + }, + }, + Mounts: []oci.Mount{ + { + Source: "/some/host/path1", + Destination: "/dest/path/c", + }, + { + Source: "/some/host/path2", + Destination: "/dest/path/b", + }, + }, + }, + edits: &cdi.ContainerEdits{ + Mounts: []*cdi.Mount{ + { + HostPath: "/some/host/path3", + ContainerPath: "/dest/path/a", + }, + }, + }, + result: &oci.Spec{ + Linux: &oci.Linux{ + UIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 999, + }, + }, + GIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 2000, + Size: 777, + }, + }, + Namespaces: []oci.LinuxNamespace{ + { + Type: oci.UserNamespace, + Path: "/foo/bar", + }, + }, + }, + Mounts: []oci.Mount{ + { + Source: "/some/host/path1", + Destination: "/dest/path/c", + }, + { + Source: "/some/host/path2", + Destination: "/dest/path/b", + }, + { + Source: "/some/host/path3", + Destination: "/dest/path/a", + UIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 1000, + Size: 999, + }, + }, + GIDMappings: []oci.LinuxIDMapping{ + { + ContainerID: 0, + HostID: 2000, + Size: 777, + }, + }, + }, + }, + }, + }, } { t.Run(tc.name, func(t *testing.T) { edits := ContainerEdits{tc.edits} diff --git a/pkg/cdi/oci.go b/pkg/cdi/oci.go index e7d18cd..237e615 100644 --- a/pkg/cdi/oci.go +++ b/pkg/cdi/oci.go @@ -30,14 +30,33 @@ func (h *Hook) toOCI() spec.Hook { } } +// Additional OCI mount option to apply to injected mounts. +type ociMountOption func(*spec.Mount) + +// withMountIDMappings adds UID and GID mappings for the given mount. +func withMountIDMappings(uid, gid []spec.LinuxIDMapping) ociMountOption { + return func(m *spec.Mount) { + if uid != nil { + m.UIDMappings = uid + } + if gid != nil { + m.GIDMappings = gid + } + } +} + // toOCI returns the opencontainers runtime Spec Mount for this Mount. -func (m *Mount) toOCI() spec.Mount { - return spec.Mount{ +func (m *Mount) toOCI(options ...ociMountOption) spec.Mount { + om := spec.Mount{ Source: m.HostPath, Destination: m.ContainerPath, Options: m.Options, Type: m.Type, } + for _, o := range options { + o(&om) + } + return om } // toOCI returns the opencontainers runtime Spec LinuxDevice for this DeviceNode.