diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/capabilities/capabilities.go index dea33159b..1099d32b1 100644 --- a/libcontainer/capabilities/capabilities.go +++ b/libcontainer/capabilities/capabilities.go @@ -3,10 +3,11 @@ package capabilities import ( - "fmt" + "sort" "strings" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" ) @@ -34,27 +35,21 @@ func init() { } // New creates a new Caps from the given Capabilities config. Unknown Capabilities -// or Capabilities that are unavailable in the current environment produce an error. +// or Capabilities that are unavailable in the current environment are ignored, +// printing a warning instead. func New(capConfig *configs.Capabilities) (*Caps, error) { var ( err error - c = Caps{caps: make(map[capability.CapType][]capability.Cap, len(capTypes))} + c Caps ) - if c.caps[capability.BOUNDING], err = capSlice(capConfig.Bounding); err != nil { - return nil, err - } - if c.caps[capability.EFFECTIVE], err = capSlice(capConfig.Effective); err != nil { - return nil, err - } - if c.caps[capability.INHERITABLE], err = capSlice(capConfig.Inheritable); err != nil { - return nil, err - } - if c.caps[capability.PERMITTED], err = capSlice(capConfig.Permitted); err != nil { - return nil, err - } - if c.caps[capability.AMBIENT], err = capSlice(capConfig.Ambient); err != nil { - return nil, err + unknownCaps := make(map[string]struct{}) + c.caps = map[capability.CapType][]capability.Cap{ + capability.BOUNDING: capSlice(capConfig.Bounding, unknownCaps), + capability.EFFECTIVE: capSlice(capConfig.Effective, unknownCaps), + capability.INHERITABLE: capSlice(capConfig.Inheritable, unknownCaps), + capability.PERMITTED: capSlice(capConfig.Permitted, unknownCaps), + capability.AMBIENT: capSlice(capConfig.Ambient, unknownCaps), } if c.pid, err = capability.NewPid2(0); err != nil { return nil, err @@ -62,22 +57,35 @@ func New(capConfig *configs.Capabilities) (*Caps, error) { if err = c.pid.Load(); err != nil { return nil, err } + if len(unknownCaps) > 0 { + logrus.Warn("ignoring unknown or unavailable capabilities: ", mapKeys(unknownCaps)) + } return &c, nil } // capSlice converts the slice of capability names in caps, to their numeric // equivalent, and returns them as a slice. Unknown or unavailable capabilities -// produce an error. -func capSlice(caps []string) ([]capability.Cap, error) { - out := make([]capability.Cap, len(caps)) - for i, c := range caps { - v, ok := capabilityMap[c] - if !ok { - return nil, fmt.Errorf("unknown capability %q", c) +// are not returned, but appended to unknownCaps. +func capSlice(caps []string, unknownCaps map[string]struct{}) []capability.Cap { + var out []capability.Cap + for _, c := range caps { + if v, ok := capabilityMap[c]; !ok { + unknownCaps[c] = struct{}{} + } else { + out = append(out, v) } - out[i] = v } - return out, nil + return out +} + +// mapKeys returns the keys of input in sorted order +func mapKeys(input map[string]struct{}) []string { + var keys []string + for c := range input { + keys = append(keys, c) + } + sort.Strings(keys) + return keys } // Caps holds the capabilities for a container. diff --git a/libcontainer/capabilities/capabilities_linux_test.go b/libcontainer/capabilities/capabilities_linux_test.go index 36b8d186a..13a50e833 100644 --- a/libcontainer/capabilities/capabilities_linux_test.go +++ b/libcontainer/capabilities/capabilities_linux_test.go @@ -1,14 +1,18 @@ package capabilities import ( + "io/ioutil" + "os" "testing" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" "github.com/syndtr/gocapability/capability" ) func TestNew(t *testing.T) { - cs := []string{"CAP_CHOWN"} + cs := []string{"CAP_CHOWN", "CAP_UNKNOWN", "CAP_UNKNOWN2"} conf := configs.Capabilities{ Bounding: cs, Effective: cs, @@ -17,10 +21,36 @@ func TestNew(t *testing.T) { Ambient: cs, } + hook := test.NewGlobal() + defer hook.Reset() + + logrus.SetOutput(ioutil.Discard) caps, err := New(&conf) + logrus.SetOutput(os.Stderr) + if err != nil { t.Error(err) } + e := hook.AllEntries() + if len(e) != 1 { + t.Errorf("expected 1 warning, got %d", len(e)) + } + + expectedLogs := logrus.Entry{ + Level: logrus.WarnLevel, + Message: "ignoring unknown or unavailable capabilities: [CAP_UNKNOWN CAP_UNKNOWN2]", + } + + l := hook.LastEntry() + if l == nil { + t.Fatal("expected a warning, but got none") + } + if l.Level != expectedLogs.Level { + t.Errorf("expected %q, got %q", expectedLogs.Level, l.Level) + } + if l.Message != expectedLogs.Message { + t.Errorf("expected %q, got %q", expectedLogs.Message, l.Message) + } if len(caps.caps) != len(capTypes) { t.Errorf("expected %d capability types, got %d: %v", len(capTypes), len(caps.caps), caps.caps) @@ -36,4 +66,6 @@ func TestNew(t *testing.T) { continue } } + + hook.Reset() } diff --git a/vendor/github.com/sirupsen/logrus/hooks/test/test.go b/vendor/github.com/sirupsen/logrus/hooks/test/test.go new file mode 100644 index 000000000..b16d06654 --- /dev/null +++ b/vendor/github.com/sirupsen/logrus/hooks/test/test.go @@ -0,0 +1,91 @@ +// The Test package is used for testing logrus. +// It provides a simple hooks which register logged messages. +package test + +import ( + "io/ioutil" + "sync" + + "github.com/sirupsen/logrus" +) + +// Hook is a hook designed for dealing with logs in test scenarios. +type Hook struct { + // Entries is an array of all entries that have been received by this hook. + // For safe access, use the AllEntries() method, rather than reading this + // value directly. + Entries []logrus.Entry + mu sync.RWMutex +} + +// NewGlobal installs a test hook for the global logger. +func NewGlobal() *Hook { + + hook := new(Hook) + logrus.AddHook(hook) + + return hook + +} + +// NewLocal installs a test hook for a given local logger. +func NewLocal(logger *logrus.Logger) *Hook { + + hook := new(Hook) + logger.Hooks.Add(hook) + + return hook + +} + +// NewNullLogger creates a discarding logger and installs the test hook. +func NewNullLogger() (*logrus.Logger, *Hook) { + + logger := logrus.New() + logger.Out = ioutil.Discard + + return logger, NewLocal(logger) + +} + +func (t *Hook) Fire(e *logrus.Entry) error { + t.mu.Lock() + defer t.mu.Unlock() + t.Entries = append(t.Entries, *e) + return nil +} + +func (t *Hook) Levels() []logrus.Level { + return logrus.AllLevels +} + +// LastEntry returns the last entry that was logged or nil. +func (t *Hook) LastEntry() *logrus.Entry { + t.mu.RLock() + defer t.mu.RUnlock() + i := len(t.Entries) - 1 + if i < 0 { + return nil + } + return &t.Entries[i] +} + +// AllEntries returns all entries that were logged. +func (t *Hook) AllEntries() []*logrus.Entry { + t.mu.RLock() + defer t.mu.RUnlock() + // Make a copy so the returned value won't race with future log requests + entries := make([]*logrus.Entry, len(t.Entries)) + for i := 0; i < len(t.Entries); i++ { + // Make a copy, for safety + entries[i] = &t.Entries[i] + } + return entries +} + +// Reset removes all Entries from this test hook. +func (t *Hook) Reset() { + t.mu.Lock() + defer t.mu.Unlock() + t.Entries = make([]logrus.Entry, 0) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index f68037b34..4d06108f7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -57,6 +57,7 @@ github.com/shurcooL/sanitized_anchor_name # github.com/sirupsen/logrus v1.7.0 ## explicit github.com/sirupsen/logrus +github.com/sirupsen/logrus/hooks/test # github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 ## explicit github.com/syndtr/gocapability/capability