diff --git a/features.go b/features.go new file mode 100644 index 00000000000..15b5461a84d --- /dev/null +++ b/features.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/opencontainers/runc/libcontainer/capabilities" + "github.com/opencontainers/runc/libcontainer/seccomp" + "github.com/opencontainers/runc/libcontainer/specconv" + "github.com/opencontainers/runc/types/features" + "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{ + Annotations: map[string]string{ + features.AnnotationRuncVersion: version, + features.AnnotationRuncCommit: gitCommit, + }, + Cgroup: features.Cgroup{ + V1: &tru, + V2: &tru, + Systemd: &tru, + SystemdUser: &tru, + }, + Apparmor: features.Apparmor{ + Enabled: &tru, + }, + Selinux: features.Selinux{ + Enabled: &tru, + }, + Criu: features.Criu{ + Enabled: &tru, + }, + MountOptions: specconv.KnownMountOptions(), + Capabilities: capabilities.KnownCapabilities(), + } + + if seccomp.Enabled { + feat.Seccomp.Enabled = &tru + feat.Seccomp.Actions = seccomp.KnownActions() + feat.Seccomp.Operators = seccomp.KnownOperators() + feat.Seccomp.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/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..9e7100a3e2b 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,26 @@ func initMaps() { }) } +// 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..4ee1ba02434 --- /dev/null +++ b/types/features/features.go @@ -0,0 +1,111 @@ +// Package features provides the JSON structure that is printed by `runc features` (since runc v1.1.0). +package features + +// Features is printed by `runc features`. +type Features struct { + // 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"` + + // 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"` + + Cgroup Cgroup `json:"cgroup"` + Seccomp Seccomp `json:"seccomp"` + Apparmor Apparmor `json:"apparmor"` + Selinux Selinux `json:"selinux"` + Criu Criu `json:"criu"` + + // Annotations contains implementation-specific annotation strings, + // such as the implementation version, and third-party extensions. + Annotations map[string]string `json:"annotations,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"` + + // 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"` +} + +// 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"` +} + +// Criu represents the "criu" field. +type Criu struct { + // Enabled is true if CRIU support is compiled in. + // Unrelated to whether the host supports CRIU or not. + // Nil value means "unknown", not "false". + // Always true in the current version of runc. + Enabled *bool `json:"enabled"` +} + +// 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"` + + // 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"` + + // 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"` + + // 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"` +} + +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. + AnnotationRuncCommit = "org.opencontainers.runc.commit" + + // 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" +)