Skip to content

Commit

Permalink
rootless: add rootless logic
Browse files Browse the repository at this point in the history
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: kata-containers#1874

Signed-off-by: Gabi Beyer <[email protected]>
Co-developed-by: Marco Vedovati <[email protected]>
Signed-off-by: Marco Vedovati <[email protected]>
  • Loading branch information
Gabi Beyer authored and marcov committed Sep 26, 2019
1 parent 801a9a8 commit ad9e220
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions pkg/rootless/rootless.go
Original file line number Diff line number Diff line change
@@ -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
}
167 changes: 167 additions & 0 deletions pkg/rootless/rootless_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

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
}

type uidMapping struct {
userNSUid int
hostUid int
uidRange int
}

type testScenario struct {
name string
isRootless bool
uidMap []uidMapping
}

func TestIsRootless(t *testing.T) {
assert := assert.New(t)

// by default isRootless should be set to false initially
assert.False(isRootless)

allScenarios := []testScenario{
testScenario{
name: "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},
},
},

testScenario{
name: "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},
},
},

testScenario{
name: "UID range is zero",
isRootless: false,
uidMap: []uidMapping{
{0, 0, 0},
{1, 0, 0},
{1, 1000, 0},
{1000, 1000, 0},
},
},

testScenario{
name: "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},
},
},

testScenario{
name: "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 {
testUIDMap(uidMap, scenario.isRootless, t)
}
}

testWithMapFileContent("", false, t)

testWithMapFileContent("This is not a mapping", false, t)
}

func testUIDMap(uidMap uidMapping, expectedRootless bool, t *testing.T) {
mapping := fmt.Sprintf("\t%d\t%d\t%d", uidMap.userNSUid, uidMap.hostUid, uidMap.uidRange)
testWithMapFileContent(mapping, expectedRootless, t)
}

func testWithMapFileContent(content string, expectedRootless bool, t *testing.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(t, 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(t, err, msg)

// make call to IsRootless, this should also call
// SetRootless
assert.Equal(t, expectedRootless, IsRootless(), msg)
}

0 comments on commit ad9e220

Please sign in to comment.