forked from kata-containers/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |