diff --git a/cli/main.go b/cli/main.go index 72c9165e7d..9253802b58 100644 --- a/cli/main.go +++ b/cli/main.go @@ -18,6 +18,7 @@ import ( "syscall" "github.com/kata-containers/runtime/pkg/katautils" + "github.com/kata-containers/runtime/pkg/rootless" "github.com/kata-containers/runtime/pkg/signals" vc "github.com/kata-containers/runtime/virtcontainers" vf "github.com/kata-containers/runtime/virtcontainers/factory" @@ -241,6 +242,9 @@ func setExternalLoggers(ctx context.Context, logger *logrus.Entry) { // Set the katautils package logger katautils.SetLogger(ctx, logger, originalLoggerLevel) + + // Set the rootless package logger + rootless.SetLogger(ctx, logger) } // beforeSubcommands is the function to perform preliminary checks diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go new file mode 100644 index 0000000000..f4c6cb1682 --- /dev/null +++ b/pkg/rootless/rootless.go @@ -0,0 +1,97 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package rootless + +import ( + "bufio" + "context" + "io" + "os" + "strings" + + "github.com/sirupsen/logrus" +) + +var ( + // initRootless states whether the isRootless variable + // has been set yet + initRootless bool + + // isRootless states whether execution is rootless or not + isRootless bool + + // XDG_RUNTIME_DIR defines the base directory relative to + // which user-specific non-essential runtime files are stored. + rootlessDir = os.Getenv("XDG_RUNTIME_DIR") + + // uidMapPath defines the location of the uid_map file to + // determine whether a user is root or not + uidMapPath = "/proc/self/uid_map" + + rootlessLog = logrus.WithFields(logrus.Fields{ + "source": "rootless", + }) +) + +// SetLogger sets up a logger for the rootless pkg +func SetLogger(ctx context.Context, logger *logrus.Entry) { + fields := rootlessLog.Data + rootlessLog = logger.WithFields(fields) +} + +// setRootless reads a uid_map file, compares the UID of the +// user inside the container vs on the host. If the host UID +// is not root, but the container ID is, it can be determined +// the user is running rootlessly. +func setRootless() error { + initRootless = true + file, err := os.Open(uidMapPath) + if err != nil { + return err + } + defer file.Close() + + buf := bufio.NewReader(file) + for { + line, _, err := buf.ReadLine() + if err != nil { + if err == io.EOF { + return nil + } + return err + } + if line == nil { + return nil + } + + // if the container id (id[0]) is 0 (root inside the container) + // has a mapping to the host id (id[1]) that is not root, then + // it can be determined that the host user is running rootless + ids := strings.Fields(string(line)) + if ids[0] == "0" && ids[1] != "0" { + rootlessLog.Infof("Running as rootless") + isRootless = true + return nil + } + } +} + +// IsRootless states whether kata is being ran with root or not +func IsRootless() bool { + if !initRootless { + err := setRootless() + if err != nil { + rootlessLog.Errorf("Unable to determine if running rootless") + } + } + return isRootless +} + +// GetRootlessDir returns the path to the location for rootless +// container and sandbox storage +func GetRootlessDir() string { + return rootlessDir +} diff --git a/pkg/rootless/rootless_test.go b/pkg/rootless/rootless_test.go new file mode 100644 index 0000000000..48df54f5c2 --- /dev/null +++ b/pkg/rootless/rootless_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package rootless + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +var uidMapPathStore = uidMapPath + +func createTestUIDMapFile(input string) error { + f, err := os.Create(uidMapPath) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(input) + if err != nil { + return err + } + + return nil +} + +// TestSetRootlessRootlessUID1000 tests that isRootless is set to +// true when a host UID is not 0 +func TestSetRootlessRootlessUID1000(t *testing.T) { + assert := assert.New(t) + + // by default isRootless should be set to false initially + assert.False(IsRootless()) + + tmpDir, err := ioutil.TempDir("", "") + assert.NoError(err) + + uidMapPath = filepath.Join(tmpDir, "testUIDMapFile") + defer func() { + uidMapPath = uidMapPathStore + os.RemoveAll(tmpDir) + isRootless = false + }() + + mapping := "\t0\t1000\t5555" + err = createTestUIDMapFile(mapping) + assert.NoError(err) + + err = SetRootless() + assert.NoError(err) + + assert.True(IsRootless()) +} + +// TestSetRootlessRootUID0 tests that isRootless is not set when +// the host UID is 0 +func TestSetRootlessRootUID0(t *testing.T) { + assert := assert.New(t) + + // by default isRootless should be set to false initially + assert.False(IsRootless()) + + tmpDir, err := ioutil.TempDir("", "") + assert.NoError(err) + + uidMapPath = filepath.Join(tmpDir, "testUIDMapFile") + defer func() { + uidMapPath = uidMapPathStore + os.RemoveAll(uidMapPath) + isRootless = false + }() + + mapping := "\t0\t0\t5555" + err = createTestUIDMapFile(mapping) + assert.NoError(err) + + err = SetRootless() + assert.NoError(err) + + assert.False(IsRootless()) +}