Skip to content

Commit 3b72c7b

Browse files
committed
cdi: inject mount UID/GID mappings if user NS is in use.
When injecting mounts to a container with user namespaces, if the mount is a bind mount inject it with mappings taken from the OCI Spec (since currently the CDI Spec does not provide a way to add ID mappings to a mount). Signed-off-by: Krisztian Litkey <[email protected]>
1 parent 16a1328 commit 3b72c7b

File tree

3 files changed

+166
-3
lines changed

3 files changed

+166
-3
lines changed

pkg/cdi/container-edits.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,23 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
114114
}
115115

116116
if len(e.Mounts) > 0 {
117+
var (
118+
uids []oci.LinuxIDMapping
119+
gids []oci.LinuxIDMapping
120+
)
121+
122+
if specHasUserNamespace(spec) {
123+
uids = cloneIDMappings(spec.Linux.UIDMappings)
124+
gids = cloneIDMappings(spec.Linux.GIDMappings)
125+
}
117126
for _, m := range e.Mounts {
118127
specgen.RemoveMount(m.ContainerPath)
119-
specgen.AddMount((&Mount{m}).toOCI())
128+
mnt := &Mount{m}
129+
if !mnt.isBindMount() {
130+
specgen.AddMount(mnt.toOCI())
131+
} else {
132+
specgen.AddMount(mnt.toOCI(withMountIDMappings(uids, gids)))
133+
}
120134
}
121135
sortMounts(&specgen)
122136
}
@@ -322,6 +336,18 @@ func (m *Mount) Validate() error {
322336
return nil
323337
}
324338

339+
func (m *Mount) isBindMount() bool {
340+
if m.Type == "bind" {
341+
return true
342+
}
343+
for _, o := range m.Options {
344+
if o == "bind" || o == "rbind" {
345+
return true
346+
}
347+
}
348+
return false
349+
}
350+
325351
// IntelRdt is a CDI IntelRdt wrapper.
326352
// This is used for validation and conversion to OCI specifications.
327353
type IntelRdt struct {
@@ -389,3 +415,26 @@ func (m orderedMounts) Swap(i, j int) {
389415
func (m orderedMounts) parts(i int) int {
390416
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
391417
}
418+
419+
// specHasUserNamespace returns true if the OCI Spec has a Linux UserNamespace.
420+
func specHasUserNamespace(spec *oci.Spec) bool {
421+
if spec == nil || spec.Linux == nil {
422+
return false
423+
}
424+
for _, ns := range spec.Linux.Namespaces {
425+
if ns.Type == oci.UserNamespace {
426+
return true
427+
}
428+
}
429+
return false
430+
}
431+
432+
// cloneIDMappings clones a slice of OCI LinuxIDMappings.
433+
func cloneIDMappings(mappings []oci.LinuxIDMapping) []oci.LinuxIDMapping {
434+
if mappings == nil {
435+
return nil
436+
}
437+
clone := make([]oci.LinuxIDMapping, len(mappings))
438+
copy(clone, mappings)
439+
return clone
440+
}

pkg/cdi/container-edits_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,105 @@ func TestApplyContainerEdits(t *testing.T) {
701701
},
702702
},
703703
},
704+
{
705+
name: "mount added to container with Linux user namespace and uid/gid mappings",
706+
spec: &oci.Spec{
707+
Linux: &oci.Linux{
708+
UIDMappings: []oci.LinuxIDMapping{
709+
{
710+
ContainerID: 0,
711+
HostID: 1000,
712+
Size: 999,
713+
},
714+
},
715+
GIDMappings: []oci.LinuxIDMapping{
716+
{
717+
ContainerID: 0,
718+
HostID: 2000,
719+
Size: 777,
720+
},
721+
},
722+
Namespaces: []oci.LinuxNamespace{
723+
{
724+
Type: oci.UserNamespace,
725+
Path: "/foo/bar",
726+
},
727+
},
728+
},
729+
Mounts: []oci.Mount{
730+
{
731+
Source: "/some/host/path1",
732+
Destination: "/dest/path/c",
733+
},
734+
{
735+
Source: "/some/host/path2",
736+
Destination: "/dest/path/b",
737+
},
738+
},
739+
},
740+
edits: &cdi.ContainerEdits{
741+
Mounts: []*cdi.Mount{
742+
{
743+
HostPath: "/some/host/path3",
744+
ContainerPath: "/dest/path/a",
745+
Type: "bind",
746+
},
747+
},
748+
},
749+
result: &oci.Spec{
750+
Linux: &oci.Linux{
751+
UIDMappings: []oci.LinuxIDMapping{
752+
{
753+
ContainerID: 0,
754+
HostID: 1000,
755+
Size: 999,
756+
},
757+
},
758+
GIDMappings: []oci.LinuxIDMapping{
759+
{
760+
ContainerID: 0,
761+
HostID: 2000,
762+
Size: 777,
763+
},
764+
},
765+
Namespaces: []oci.LinuxNamespace{
766+
{
767+
Type: oci.UserNamespace,
768+
Path: "/foo/bar",
769+
},
770+
},
771+
},
772+
Mounts: []oci.Mount{
773+
{
774+
Source: "/some/host/path1",
775+
Destination: "/dest/path/c",
776+
},
777+
{
778+
Source: "/some/host/path2",
779+
Destination: "/dest/path/b",
780+
},
781+
{
782+
Source: "/some/host/path3",
783+
Destination: "/dest/path/a",
784+
Type: "bind",
785+
UIDMappings: []oci.LinuxIDMapping{
786+
{
787+
ContainerID: 0,
788+
HostID: 1000,
789+
Size: 999,
790+
},
791+
},
792+
GIDMappings: []oci.LinuxIDMapping{
793+
{
794+
ContainerID: 0,
795+
HostID: 2000,
796+
Size: 777,
797+
},
798+
},
799+
},
800+
},
801+
},
802+
},
704803
} {
705804
t.Run(tc.name, func(t *testing.T) {
706805
edits := ContainerEdits{tc.edits}

pkg/cdi/oci.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,29 @@ func (h *Hook) toOCI() spec.Hook {
3030
}
3131
}
3232

33+
// Additional OCI mount option to apply to injected mounts.
34+
type ociMountOption func(*spec.Mount)
35+
36+
// withMountIDMappings adds UID and GID mappings for the given mount.
37+
func withMountIDMappings(uid, gid []spec.LinuxIDMapping) ociMountOption {
38+
return func(m *spec.Mount) {
39+
m.UIDMappings = uid
40+
m.GIDMappings = gid
41+
}
42+
}
43+
3344
// toOCI returns the opencontainers runtime Spec Mount for this Mount.
34-
func (m *Mount) toOCI() spec.Mount {
35-
return spec.Mount{
45+
func (m *Mount) toOCI(options ...ociMountOption) spec.Mount {
46+
om := spec.Mount{
3647
Source: m.HostPath,
3748
Destination: m.ContainerPath,
3849
Options: m.Options,
3950
Type: m.Type,
4051
}
52+
for _, o := range options {
53+
o(&om)
54+
}
55+
return om
4156
}
4257

4358
// toOCI returns the opencontainers runtime Spec LinuxDevice for this DeviceNode.

0 commit comments

Comments
 (0)