mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-11 17:11:39 +00:00
Add support for parsing Linux Capabilities for older OCI specs
This was added to fix a regression with support for the default runc shipped with CentOS 7. The version of runc that is installed by default on CentOS 7 is 1.0.0-rc2 which uses OCI spec 1.0.0-rc2-dev. This is a prerelease of the OCI spec, which defines the capabilities section of a process configuration to be a flat list of capabilities (e.g. SYS_ADMIN, SYS_PTRACE, SYS_RAWIO, etc.) https://github.com/opencontainers/runtime-spec/blob/v1.0.0-rc2/config.md#process-configuration By the time the official 1.0.0 version of the OCI spec came out, the capabilities section of a process configuration was expanded to include embedded fields for effective, bounding, inheritable, permitted and ambient (each of which can contain a flat list of capabilities of the form SYS_ADMIN, SYS_PTRACE, SYS_RAWIO, etc.) https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md#linux-process Previously, we only inspected the capabilities section of a process configuration assuming it was in the format of OCI spec 1.0.0. This patch makes sure we can parse the capaibilites in either format. Signed-off-by: Kevin Klues <kklues@nvidia.com>
This commit is contained in:
parent
39a985ce96
commit
c32237f39c
@ -8,6 +8,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var envSwarmGPU *string
|
var envSwarmGPU *string
|
||||||
@ -56,7 +58,7 @@ type Root struct {
|
|||||||
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L57
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L57
|
||||||
type Process struct {
|
type Process struct {
|
||||||
Env []string `json:"env,omitempty"`
|
Env []string `json:"env,omitempty"`
|
||||||
Capabilities *LinuxCapabilities `json:"capabilities,omitempty" platform:"linux"`
|
Capabilities *json.RawMessage `json:"capabilities,omitempty" platform:"linux"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L61
|
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L61
|
||||||
@ -71,6 +73,7 @@ type LinuxCapabilities struct {
|
|||||||
// We use pointers to structs, similarly to the latest version of runtime-spec:
|
// We use pointers to structs, similarly to the latest version of runtime-spec:
|
||||||
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L5-L28
|
// https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L5-L28
|
||||||
type Spec struct {
|
type Spec struct {
|
||||||
|
Version *string `json:"ociVersion"`
|
||||||
Process *Process `json:"process,omitempty"`
|
Process *Process `json:"process,omitempty"`
|
||||||
Root *Root `json:"root,omitempty"`
|
Root *Root `json:"root,omitempty"`
|
||||||
}
|
}
|
||||||
@ -133,6 +136,9 @@ func loadSpec(path string) (spec *Spec) {
|
|||||||
if err = json.NewDecoder(f).Decode(&spec); err != nil {
|
if err = json.NewDecoder(f).Decode(&spec); err != nil {
|
||||||
log.Panicln("could not decode OCI spec:", err)
|
log.Panicln("could not decode OCI spec:", err)
|
||||||
}
|
}
|
||||||
|
if spec.Version == nil {
|
||||||
|
log.Panicln("Version is empty in OCI spec")
|
||||||
|
}
|
||||||
if spec.Process == nil {
|
if spec.Process == nil {
|
||||||
log.Panicln("Process is empty in OCI spec")
|
log.Panicln("Process is empty in OCI spec")
|
||||||
}
|
}
|
||||||
@ -142,31 +148,45 @@ func loadSpec(path string) (spec *Spec) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPrivileged(caps *LinuxCapabilities) bool {
|
func isPrivileged(s *Spec) bool {
|
||||||
if caps == nil {
|
if s.Process.Capabilities == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCapSysAdmin := func(caps []string) bool {
|
var caps []string
|
||||||
|
// If v1.1.0-rc1 <= OCI version < v1.0.0-rc5 parse s.Process.Capabilities as:
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/specs-go/config.go#L30-L54
|
||||||
|
rc1cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc1")
|
||||||
|
rc5cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc5")
|
||||||
|
if (rc1cmp == 1 || rc1cmp == 0) && (rc5cmp == -1) {
|
||||||
|
err := json.Unmarshal(*s.Process.Capabilities, &caps)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
||||||
|
}
|
||||||
|
// Otherwise, parse s.Process.Capabilities as:
|
||||||
|
// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L54
|
||||||
|
} else {
|
||||||
|
var lc LinuxCapabilities
|
||||||
|
err := json.Unmarshal(*s.Process.Capabilities, &lc)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln("could not decode Process.Capabilities in OCI spec:", err)
|
||||||
|
}
|
||||||
|
// We only make sure that the bounding capabibility set has
|
||||||
|
// CAP_SYS_ADMIN. This allows us to make sure that the container was
|
||||||
|
// actually started as '--privileged', but also allow non-root users to
|
||||||
|
// access the priviliged NVIDIA capabilities.
|
||||||
|
caps = lc.Bounding
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range caps {
|
for _, c := range caps {
|
||||||
if c == capSysAdmin {
|
if c == capSysAdmin {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only make sure that the bounding capabibility set has
|
|
||||||
// CAP_SYS_ADMIN. This allows us to make sure that the container was
|
|
||||||
// actually started as '--privileged', but also allow non-root users to
|
|
||||||
// access the priviliged NVIDIA capabilities.
|
|
||||||
if !hasCapSysAdmin(caps.Bounding) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDevices(env map[string]string) *string {
|
func getDevices(env map[string]string) *string {
|
||||||
gpuVars := []string{envNVVisibleDevices}
|
gpuVars := []string{envNVVisibleDevices}
|
||||||
if envSwarmGPU != nil {
|
if envSwarmGPU != nil {
|
||||||
@ -365,7 +385,7 @@ func getContainerConfig(hook HookConfig) (config containerConfig) {
|
|||||||
s := loadSpec(path.Join(b, "config.json"))
|
s := loadSpec(path.Join(b, "config.json"))
|
||||||
|
|
||||||
env := getEnvMap(s.Process.Env, hook.NvidiaContainerCLI)
|
env := getEnvMap(s.Process.Env, hook.NvidiaContainerCLI)
|
||||||
privileged := isPrivileged(s.Process.Capabilities)
|
privileged := isPrivileged(s)
|
||||||
envSwarmGPU = hook.SwarmResource
|
envSwarmGPU = hook.SwarmResource
|
||||||
return containerConfig{
|
return containerConfig{
|
||||||
Pid: h.Pid,
|
Pid: h.Pid,
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseCudaVersionValid(t *testing.T) {
|
func TestParseCudaVersionValid(t *testing.T) {
|
||||||
@ -58,3 +59,85 @@ func TestParseCudaVersionInvalid(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsPrivileged(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
spec string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0",
|
||||||
|
"process": {
|
||||||
|
"capabilities": {
|
||||||
|
"bounding": [ "CAP_SYS_ADMIN" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0",
|
||||||
|
"process": {
|
||||||
|
"capabilities": {
|
||||||
|
"bounding": [ "CAP_SYS_OTHER" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0",
|
||||||
|
"process": {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0-rc2-dev",
|
||||||
|
"process": {
|
||||||
|
"capabilities": [ "CAP_SYS_ADMIN" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0-rc2-dev",
|
||||||
|
"process": {
|
||||||
|
"capabilities": [ "CAP_SYS_OTHER" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"ociVersion": "1.0.0-rc2-dev",
|
||||||
|
"process": {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
var spec Spec
|
||||||
|
_ = json.Unmarshal([]byte(tc.spec), &spec)
|
||||||
|
privileged := isPrivileged(&spec)
|
||||||
|
if privileged != tc.expected {
|
||||||
|
t.Errorf("isPrivileged() returned unexpectred value (privileged: %v, tc.expected: %v)", privileged, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user