rootless: add rootless logic

Add the ability to check whether kata is running rootlessly or
not. Add the setup of the rootless directory located in the dir
/run/user/<UID> directory.

Fixes: #1874

Signed-off-by: Gabi Beyer <gabrielle.n.beyer@intel.com>
Co-developed-by: Marco Vedovati <mvedovati@suse.com>
Signed-off-by: Marco Vedovati <mvedovati@suse.com>
This commit is contained in:
Gabi Beyer 2019-07-11 19:32:44 +00:00 committed by Marco Vedovati
parent 801a9a8fd0
commit 2d8b278c09
3 changed files with 294 additions and 0 deletions

View File

@ -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

123
pkg/rootless/rootless.go Normal file
View File

@ -0,0 +1,123 @@
// Copyright (c) 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package rootless
import (
"bufio"
"context"
"io"
"os"
"strconv"
"strings"
"sync"
"github.com/pkg/errors"
"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
// lock for the initRootless and isRootless variables
rLock sync.Mutex
// 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
}
var parseError = errors.Errorf("Failed to parse uid map file %s", uidMapPath)
// 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))
// do some sanity checks
if len(ids) != 3 {
return parseError
}
userNSUid, err := strconv.ParseUint(ids[0], 10, 0)
if err != nil {
return parseError
}
hostUID, err := strconv.ParseUint(ids[1], 10, 0)
if err != nil {
return parseError
}
rangeUID, err := strconv.ParseUint(ids[1], 10, 0)
if err != nil || rangeUID == 0 {
return parseError
}
if userNSUid == 0 && hostUID != 0 {
rootlessLog.Info("Running as rootless")
isRootless = true
return nil
}
}
}
// IsRootless states whether kata is being ran with root or not
func IsRootless() bool {
rLock.Lock()
if !initRootless {
err := setRootless()
if err != nil {
rootlessLog.WithError(err).Error("Unable to determine if running rootless")
}
}
rLock.Unlock()
return isRootless
}
// GetRootlessDir returns the path to the location for rootless
// container and sandbox storage
func GetRootlessDir() string {
return rootlessDir
}

View File

@ -0,0 +1,167 @@
// Copyright (c) 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package rootless
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
type uidMapping struct {
userNSUID int
hostUID int
rangeUID int
}
type testScenario struct {
isRootless bool
uidMap []uidMapping
}
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
}
func uidMapString(userNSUID, hostUID, rangeUID int) string {
return fmt.Sprintf("\t%d\t%d\t%d", userNSUID, hostUID, rangeUID)
}
func testWithUIDMapContent(content string, expectedRootless bool, t *testing.T) {
assert := assert.New(t)
// Create a test-specific message that is added to each assert
// call. It will be displayed if any assert test fails.
msg := fmt.Sprintf("isRootless[%t]: %s", expectedRootless, content)
tmpDir, err := ioutil.TempDir("", "")
assert.NoError(err)
uidMapPath = filepath.Join(tmpDir, "testUIDMapFile")
defer func() {
uidMapPath = uidMapPathStore
os.RemoveAll(uidMapPath)
os.RemoveAll(tmpDir)
isRootless = false
initRootless = false
}()
err = createTestUIDMapFile(content)
assert.NoError(err, msg)
// make call to IsRootless, this should also call
// SetRootless
assert.Equal(expectedRootless, IsRootless(), msg)
}
func TestIsRootless(t *testing.T) {
assert := assert.New(t)
// by default isRootless should be set to false initially
assert.False(isRootless)
allScenarios := []testScenario{
//"User NS UID is not root UID"
{
isRootless: false,
uidMap: []uidMapping{
{1, 0, 1},
{1, 0, 1000},
{1, 1000, 1},
{1, 1000, 1000},
{1000, 1000, 1},
{1000, 1000, 1000},
{1000, 1000, 5555},
},
},
//"Host NS UID is root UID"
{
isRootless: false,
uidMap: []uidMapping{
{0, 0, 1},
{0, 0, 1000},
{1, 0, 1},
{1, 0, 1000},
{1000, 0, 0},
{1000, 0, 1},
{1000, 0, 1000},
},
},
//"UID range is zero"
{
isRootless: false,
uidMap: []uidMapping{
{0, 0, 0},
{1, 0, 0},
{1, 1000, 0},
{1000, 1000, 0},
},
},
//"Negative UIDs"
{
isRootless: false,
uidMap: []uidMapping{
{-1, 0, 0},
{-1, 0, 1},
{-1, 0, 1000},
{0, -1, 0},
{0, -1, 1},
{0, -1, 1000},
{1000, 1000, -1},
{1000, 1000, -1},
{1000, 1000, -1000},
},
},
//"User NS UID is root UID, host UID is not root UID"
{
isRootless: true,
uidMap: []uidMapping{
{0, 1, 1},
{0, 1000, 1},
{0, 1000, 5555},
},
},
}
// Run the tests
for _, scenario := range allScenarios {
for _, uidMap := range scenario.uidMap {
mapping := uidMapString(uidMap.userNSUID, uidMap.hostUID, uidMap.rangeUID)
testWithUIDMapContent(mapping, scenario.isRootless, t)
}
}
testWithUIDMapContent("", false, t)
testWithUIDMapContent("This is not a mapping", false, t)
}