From 520702dac5130f3fd07153cb113e05ddb63ac76e Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Mon, 29 Nov 2021 19:34:31 +0900 Subject: [PATCH] Add `runc features` command Fix issue 3274 See `types/features/features.go`. Signed-off-by: Akihiro Suda --- features.go | 74 +++++++++++ libcontainer/capabilities/capabilities.go | 11 ++ libcontainer/configs/config.go | 13 ++ libcontainer/seccomp/config.go | 34 ++++++ libcontainer/seccomp/seccomp_linux.go | 3 + libcontainer/seccomp/seccomp_unsupported.go | 3 + libcontainer/specconv/spec_linux.go | 33 +++++ main.go | 1 + types/features/features.go | 129 ++++++++++++++++++++ 9 files changed, 301 insertions(+) create mode 100644 features.go create mode 100644 types/features/features.go diff --git a/features.go b/features.go new file mode 100644 index 00000000000..4b496549ec0 --- /dev/null +++ b/features.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/opencontainers/runc/libcontainer/capabilities" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/seccomp" + "github.com/opencontainers/runc/libcontainer/specconv" + "github.com/opencontainers/runc/types/features" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +var featuresCommand = cli.Command{ + Name: "features", + Usage: "show the enabled features", + ArgsUsage: "", + Description: `Show the enabled features. + The result is parsable as a JSON. + See https://pkg.go.dev/github.com/opencontainers/runc/types/features for the type definition. +`, + Action: func(context *cli.Context) error { + if err := checkArgs(context, 0, exactArgs); err != nil { + return err + } + + tru := true + + feat := features.Features{ + OCIVersionMin: "1.0.0", + OCIVersionMax: specs.Version, + Annotations: map[string]string{ + features.AnnotationRuncVersion: version, + features.AnnotationRuncCommit: gitCommit, + features.AnnotationRuncCheckpointEnabled: "true", + }, + Hooks: configs.KnownHookNames(), + MountOptions: specconv.KnownMountOptions(), + Linux: &features.Linux{ + Namespaces: specconv.KnownNamespaces(), + Capabilities: capabilities.KnownCapabilities(), + Cgroup: &features.Cgroup{ + V1: &tru, + V2: &tru, + Systemd: &tru, + SystemdUser: &tru, + }, + Apparmor: &features.Apparmor{ + Enabled: &tru, + }, + Selinux: &features.Selinux{ + Enabled: &tru, + }, + }, + } + + if seccomp.Enabled { + feat.Linux.Seccomp = &features.Seccomp{ + Enabled: &tru, + Actions: seccomp.KnownActions(), + Operators: seccomp.KnownOperators(), + Archs: seccomp.KnownArchs(), + } + major, minor, patch := seccomp.Version() + feat.Annotations[features.AnnotationLibseccompVersion] = fmt.Sprintf("%d.%d.%d", major, minor, patch) + } + + enc := json.NewEncoder(context.App.Writer) + enc.SetIndent("", " ") + return enc.Encode(feat) + }, +} diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/capabilities/capabilities.go index 7e938d3f505..d38b8a7cd89 100644 --- a/libcontainer/capabilities/capabilities.go +++ b/libcontainer/capabilities/capabilities.go @@ -35,6 +35,17 @@ func init() { } } +// KnownCapabilities returns the list of the known capabilities. +// Used by `runc features`. +func KnownCapabilities() []string { + list := capability.List() + res := make([]string, len(list)) + for i, c := range list { + res[i] = "CAP_" + strings.ToUpper(c.String()) + } + return res +} + // New creates a new Caps from the given Capabilities config. Unknown Capabilities // or Capabilities that are unavailable in the current environment are ignored, // printing a warning instead. diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 329f05ccb79..c1b4a0041c2 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -251,6 +251,19 @@ const ( Poststop HookName = "poststop" ) +// KnownHookNames returns the known hook names. +// Used by `runc features`. +func KnownHookNames() []string { + return []string{ + string(Prestart), // deprecated + string(CreateRuntime), + string(CreateContainer), + string(StartContainer), + string(Poststart), + string(Poststop), + } +} + type Capabilities struct { // Bounding is the set of capabilities checked by the kernel. Bounding []string diff --git a/libcontainer/seccomp/config.go b/libcontainer/seccomp/config.go index 2c69a51c249..d0c9bb71fb0 100644 --- a/libcontainer/seccomp/config.go +++ b/libcontainer/seccomp/config.go @@ -2,6 +2,7 @@ package seccomp import ( "fmt" + "sort" "github.com/opencontainers/runc/libcontainer/configs" ) @@ -16,6 +17,17 @@ var operators = map[string]configs.Operator{ "SCMP_CMP_MASKED_EQ": configs.MaskEqualTo, } +// KnownOperators returns the list of the known operations. +// Used by `runc features`. +func KnownOperators() []string { + var res []string + for k := range operators { + res = append(res, k) + } + sort.Strings(res) + return res +} + var actions = map[string]configs.Action{ "SCMP_ACT_KILL": configs.Kill, "SCMP_ACT_ERRNO": configs.Errno, @@ -26,6 +38,17 @@ var actions = map[string]configs.Action{ "SCMP_ACT_NOTIFY": configs.Notify, } +// KnownActions returns the list of the known actions. +// Used by `runc features`. +func KnownActions() []string { + var res []string + for k := range actions { + res = append(res, k) + } + sort.Strings(res) + return res +} + var archs = map[string]string{ "SCMP_ARCH_X86": "x86", "SCMP_ARCH_X86_64": "amd64", @@ -45,6 +68,17 @@ var archs = map[string]string{ "SCMP_ARCH_S390X": "s390x", } +// KnownArchs returns the list of the known archs. +// Used by `runc features`. +func KnownArchs() []string { + var res []string + for k := range archs { + res = append(res, k) + } + sort.Strings(res) + return res +} + // ConvertStringToOperator converts a string into a Seccomp comparison operator. // Comparison operators use the names they are assigned by Libseccomp's header. // Attempting to convert a string that is not a valid operator results in an diff --git a/libcontainer/seccomp/seccomp_linux.go b/libcontainer/seccomp/seccomp_linux.go index f552aba486d..f177b7f05f2 100644 --- a/libcontainer/seccomp/seccomp_linux.go +++ b/libcontainer/seccomp/seccomp_linux.go @@ -265,3 +265,6 @@ func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall, defAct libs func Version() (uint, uint, uint) { return libseccomp.GetLibraryVersion() } + +// Enabled is true if seccomp support is compiled in. +const Enabled = true diff --git a/libcontainer/seccomp/seccomp_unsupported.go b/libcontainer/seccomp/seccomp_unsupported.go index 293e9c5cb79..be2b324e057 100644 --- a/libcontainer/seccomp/seccomp_unsupported.go +++ b/libcontainer/seccomp/seccomp_unsupported.go @@ -23,3 +23,6 @@ func InitSeccomp(config *configs.Seccomp) (int, error) { func Version() (uint, uint, uint) { return 0, 0, 0 } + +// Enabled is true if seccomp support is compiled in. +const Enabled = false diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index cf54cf3dd5a..cf8d7fb4922 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "sync" "time" @@ -105,6 +106,38 @@ func initMaps() { }) } +// KnownNamespaces returns the list of the known namespaces. +// Used by `runc features`. +func KnownNamespaces() []string { + initMaps() + var res []string + for k := range namespaceMapping { + res = append(res, string(k)) + } + sort.Strings(res) + return res +} + +// KnownMountOptions returns the list of the known mount options. +// Used by `runc features`. +func KnownMountOptions() []string { + initMaps() + var res []string + for k := range mountFlags { + res = append(res, k) + } + for k := range mountPropagationMapping { + if k != "" { + res = append(res, k) + } + } + for k := range extensionFlags { + res = append(res, k) + } + sort.Strings(res) + return res +} + // AllowedDevices is the set of devices which are automatically included for // all containers. // diff --git a/main.go b/main.go index bb69672f71d..4d666382740 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,7 @@ func main() { startCommand, stateCommand, updateCommand, + featuresCommand, } app.Before = func(context *cli.Context) error { if !context.IsSet("root") && xdgRuntimeDir != "" { diff --git a/types/features/features.go b/types/features/features.go new file mode 100644 index 00000000000..37fcbce215f --- /dev/null +++ b/types/features/features.go @@ -0,0 +1,129 @@ +// Package features provides the JSON structure that is printed by `runc features` (since runc v1.1.0). +package features + +// Features represents the supported features of the runtime. +type Features struct { + // OCIVersionMin is the minimum OCI Runtime Spec version recognized by the runtime, e.g., "1.0.0". + OCIVersionMin string `json:"ociVersionMin,omitempty"` + + // OCIVersionMax is the maximum OCI Runtime Spec version recognized by the runtime, e.g., "1.0.2-dev". + OCIVersionMax string `json:"ociVersionMax,omitempty"` + + // Hooks is the list of the recognized hook names, e.g., "createRuntime". + // Nil value means "unknown", not "no support for any hook". + Hooks []string `json:"hooks,omitempty"` + + // MountOptions is the list of the recognized mount options, e.g., "ro". + // Nil value means "unknown", not "no support for any mount option". + MountOptions []string `json:"mountOptions,omitempty"` + + // Linux is specific to Linux. + Linux *Linux `json:"linux,omitempty"` + + // Annotations contains implementation-specific annotation strings, + // such as the implementation version, and third-party extensions. + Annotations map[string]string `json:"annotations,omitempty"` +} + +// Linux is specific to Linux. +type Linux struct { + // Namespaces is the list of the recognized namespaces, e.g., "mount". + // Nil value means "unknown", not "no support for any namespace". + Namespaces []string `json:"namespaces,omitempty"` + + // Capabilities is the list of the recognized capabilities , e.g., "CAP_SYS_ADMIN". + // Nil value means "unknown", not "no support for any capability". + Capabilities []string `json:"capabilities,omitempty"` + + Cgroup *Cgroup `json:"cgroup,omitempty"` + Seccomp *Seccomp `json:"seccomp,omitempty"` + Apparmor *Apparmor `json:"apparmor,omitempty"` + Selinux *Selinux `json:"selinux,omitempty"` +} + +// Seccomp represents the "seccomp" field. +type Seccomp struct { + // Enabled is true if seccomp support is compiled in. + // Nil value means "unknown", not "false". + Enabled *bool `json:"enabled,omitempty"` + + // Actions is the list of the recognized actions, e.g., "SCMP_ACT_NOTIFY". + // Nil value means "unknown", not "no support for any action". + Actions []string `json:"actions,omitempty"` + + // Operators is the list of the recognized actions, e.g., "SCMP_CMP_NE". + // Nil value means "unknown", not "no support for any operator". + Operators []string `json:"operators,omitempty"` + + // Operators is the list of the recognized archs, e.g., "SCMP_ARCH_X86_64". + // Nil value means "unknown", not "no support for any arch". + Archs []string `json:"archs,omitempty"` +} + +// Apparmor represents the "apparmor" field. +type Apparmor struct { + // Enabled is true if AppArmor support is compiled in. + // Unrelated to whether the host supports AppArmor or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + Enabled *bool `json:"enabled,omitempty"` +} + +// Selinux represents the "selinux" field. +type Selinux struct { + // Enabled is true if SELinux support is compiled in. + // Unrelated to whether the host supports SELinux or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + Enabled *bool `json:"enabled,omitempty"` +} + +// Cgroup represents the "cgroup" field. +type Cgroup struct { + // V1 represents whether Cgroup v1 support is compiled in. + // Unrelated to whether the host uses cgroup v1 or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + V1 *bool `json:"v1,omitempty"` + + // V2 represents whether Cgroup v2 support is compiled in. + // Unrelated to whether the host uses cgroup v2 or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + V2 *bool `json:"v2,omitempty"` + + // Systemd represents whether systemd-cgroup support is compiled in. + // Unrelated to whether the host uses systemd or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + Systemd *bool `json:"systemd,omitempty"` + + // SystemdUser represents whether user-scoped systemd-cgroup support is compiled in. + // Unrelated to whether the host uses systemd or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + SystemdUser *bool `json:"systemdUser,omitempty"` +} + +const ( + // AnnotationRuncVersion represents the version of runc, e.g., "1.2.3", "1.2.3+dev", "1.2.3-rc.4.", "1.2.3-rc.4+dev". + // Third party implementations such as crun and runsc MAY use this annotation to report the most compatible runc version, + // however, parsing this annotation value is discouraged. + AnnotationRuncVersion = "org.opencontainers.runc.version" + + // AnnotationRuncCommit corresponds to the output of `git describe --dirty --long --always` in the runc repo. + // Third party implementations such as crun and runsc SHOULD NOT use this annotation, as their repo is different from the runc repo. + // Parsing this annotation value is discouraged. + AnnotationRuncCommit = "org.opencontainers.runc.commit" + + // AnnotationRuncCheckpointEnabled is set to "true" if CRIU-based checkpointing is supported. + // Unrelated to whether the host supports CRIU or not. + // Always set to "true" in the current version of runc. + // This is defined as an annotation because checkpointing is a runc-specific feature that is not defined in the OCI Runtime Spec. + // Third party implementations such as crun and runsc MAY use this annotation. + AnnotationRuncCheckpointEnabled = "org.opencontainers.runc.checkpoint.enabled" + + // AnnotationLibseccompVersion is the version of libseccomp, e.g., "2.5.1". + // Note that the runtime MAY support seccomp even when this annotation is not present. + AnnotationLibseccompVersion = "io.github.seccomp.libseccomp.version" +)