cri: Devices ownership from SecurityContext

CRI container runtimes mount devices (set via kubernetes device plugins)
to containers by taking the host user/group IDs (uid/gid) to the
corresponding container device.

This triggers a problem when trying to run those containers with
non-zero (root uid/gid = 0) uid/gid set via runAsUser/runAsGroup:
the container process has no permission to use the device even when
its gid is permissive to non-root users because the container user
does not belong to that group.

It is possible to workaround the problem by manually adding the device
gid(s) to supplementalGroups. However, this is also problematic because
the device gid(s) may have different values depending on the workers'
distro/version in the cluster.

This patch suggests to take RunAsUser/RunAsGroup set via SecurityContext
as the device UID/GID, respectively. The feature must be enabled by
setting device_ownership_from_security_context runtime config value to
true (valid on Linux only).

Signed-off-by: Mikko Ylinen <mikko.ylinen@intel.com>
This commit is contained in:
Mikko Ylinen
2021-03-09 09:33:03 +02:00
parent af1a0908d0
commit e0f8c04dad
5 changed files with 115 additions and 4 deletions

View File

@@ -1304,6 +1304,72 @@ func TestGenerateUserString(t *testing.T) {
}
}
func TestNonRootUserAndDevices(t *testing.T) {
testPid := uint32(1234)
c := newTestCRIService()
testSandboxID := "sandbox-id"
testContainerName := "container-name"
containerConfig, sandboxConfig, imageConfig, _ := getCreateContainerTestData()
hostDevicesRaw, err := oci.HostDevices()
assert.NoError(t, err)
testDevice := hostDevicesRaw[0]
for desc, test := range map[string]struct {
uid, gid *runtime.Int64Value
deviceOwnershipFromSecurityContext bool
expectedDeviceUID uint32
expectedDeviceGID uint32
}{
"expect non-root container's Devices Uid/Gid to be the same as the device Uid/Gid on the host when deviceOwnershipFromSecurityContext is disabled": {
uid: &runtime.Int64Value{Value: 1},
gid: &runtime.Int64Value{Value: 10},
expectedDeviceUID: *testDevice.UID,
expectedDeviceGID: *testDevice.GID,
},
"expect root container's Devices Uid/Gid to be the same as the device Uid/Gid on the host when deviceOwnershipFromSecurityContext is disabled": {
uid: &runtime.Int64Value{Value: 0},
gid: &runtime.Int64Value{Value: 0},
expectedDeviceUID: *testDevice.UID,
expectedDeviceGID: *testDevice.GID,
},
"expect non-root container's Devices Uid/Gid to be the same as RunAsUser/RunAsGroup when deviceOwnershipFromSecurityContext is enabled": {
uid: &runtime.Int64Value{Value: 1},
gid: &runtime.Int64Value{Value: 10},
deviceOwnershipFromSecurityContext: true,
expectedDeviceUID: 1,
expectedDeviceGID: 10,
},
"expect root container's Devices Uid/Gid to be the same as the device Uid/Gid on the host when deviceOwnershipFromSecurityContext is enabled": {
uid: &runtime.Int64Value{Value: 0},
gid: &runtime.Int64Value{Value: 0},
deviceOwnershipFromSecurityContext: true,
expectedDeviceUID: *testDevice.UID,
expectedDeviceGID: *testDevice.GID,
},
} {
t.Logf("TestCase %q", desc)
c.config.DeviceOwnershipFromSecurityContext = test.deviceOwnershipFromSecurityContext
containerConfig.Linux.SecurityContext.RunAsUser = test.uid
containerConfig.Linux.SecurityContext.RunAsGroup = test.gid
containerConfig.Devices = []*runtime.Device{
{
ContainerPath: testDevice.Path,
HostPath: testDevice.Path,
Permissions: "r",
},
}
spec, err := c.containerSpec(t.Name(), testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, config.Runtime{})
assert.NoError(t, err)
assert.Equal(t, test.expectedDeviceUID, *spec.Linux.Devices[0].UID)
assert.Equal(t, test.expectedDeviceGID, *spec.Linux.Devices[0].GID)
}
}
func TestPrivilegedDevices(t *testing.T) {
testPid := uint32(1234)
c := newTestCRIService()