mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
[client] Verify rootless state reuse with a stable UID
Persisted profiles remain scoped to the creating UID. Verify same-UID container recreation without broadening application permissions, and document the Kubernetes volume permission behavior observed on OpenShift. Remove unused synthetic-user home metadata.
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
# CGO_ENABLED=0 go build -o netbird ./client
|
||||
# podman build -t localhost/netbird:latest -f client/Dockerfile-rootless --ignorefile .dockerignore-client .
|
||||
# podman run --rm -it --user 1001230000:0 --cap-drop=ALL --security-opt=no-new-privileges localhost/netbird:latest
|
||||
#
|
||||
# Reuse /var/lib/netbird volumes with the same runtime UID.
|
||||
# Config/profile files start at 0600; Kubernetes fsGroup may widen their modes.
|
||||
# Where supported, pod securityContext.fsGroupChangePolicy: OnRootMismatch
|
||||
# preserves modes when the volume root's ownership and permissions match.
|
||||
|
||||
FROM alpine:3.24
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ func InvokingUser() (*user.User, error) {
|
||||
Username: uidString,
|
||||
Uid: uidString,
|
||||
Gid: strconv.Itoa(getegid()),
|
||||
HomeDir: os.Getenv("HOME"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,39 +14,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInvokingUserReturnsResolvedCurrentUser(t *testing.T) {
|
||||
t.Setenv(envSudoUser, "")
|
||||
|
||||
want := &user.User{
|
||||
Username: "misha",
|
||||
Uid: "1234",
|
||||
Gid: "1234",
|
||||
HomeDir: filepath.Join("/home", "misha"),
|
||||
}
|
||||
origCurrentUser := currentUser
|
||||
currentUser = func() (*user.User, error) { return want, nil }
|
||||
t.Cleanup(func() { currentUser = origCurrentUser })
|
||||
|
||||
got, err := InvokingUser()
|
||||
require.NoError(t, err)
|
||||
assert.Same(t, want, got, "resolved process user should be returned unchanged")
|
||||
}
|
||||
|
||||
func TestInvokingUserUsesNumericIdentityForUnmappedNonRoot(t *testing.T) {
|
||||
t.Setenv(envSudoUser, "")
|
||||
t.Setenv("HOME", "/var/lib/netbird")
|
||||
fakeUnmappedUser(t, 1001230000, 0, errors.New("user: unknown userid 1001230000"))
|
||||
|
||||
got, err := InvokingUser()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &user.User{
|
||||
Username: "1001230000",
|
||||
Uid: "1001230000",
|
||||
Gid: "0",
|
||||
HomeDir: "/var/lib/netbird",
|
||||
}, got, "unmapped non-root identity should use kernel credentials")
|
||||
}
|
||||
|
||||
func TestInvokingUserFailsClosedWithoutPositiveUID(t *testing.T) {
|
||||
for _, uid := range []int{0, -1} {
|
||||
t.Run(fmt.Sprintf("UID%d", uid), func(t *testing.T) {
|
||||
@@ -63,7 +30,6 @@ func TestInvokingUserFailsClosedWithoutPositiveUID(t *testing.T) {
|
||||
|
||||
func TestProfileFilePathUsesNumericIdentityForUnmappedNonRoot(t *testing.T) {
|
||||
t.Setenv(envSudoUser, "")
|
||||
t.Setenv("HOME", "/var/lib/netbird")
|
||||
fakeUnmappedUser(t, 1001230000, 0, errors.New("user: unknown userid 1001230000"))
|
||||
|
||||
profilesRoot := t.TempDir()
|
||||
|
||||
@@ -28,10 +28,15 @@ PLATFORM="${PLATFORM:-linux/${TARGETARCH}}"
|
||||
WAIT_TIMEOUT="${WAIT_TIMEOUT:-30}"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
CONTAINER="netbird-rootless-uid-${RANDOM}-$$"
|
||||
VOLUME=""
|
||||
|
||||
cleanup() {
|
||||
local status=$?
|
||||
"${RUNTIME}" rm -f "${CONTAINER}" >/dev/null 2>&1 || true
|
||||
if [[ -n "${VOLUME}" ]] && ! "${RUNTIME}" volume rm "${VOLUME}" >/dev/null; then
|
||||
echo "failed to remove test volume ${VOLUME}" >&2
|
||||
status=1
|
||||
fi
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit "${status}"
|
||||
}
|
||||
@@ -66,9 +71,10 @@ build_image() {
|
||||
|
||||
start_container() {
|
||||
echo "==> Starting ${CONTAINER} as unmapped UID 1001230000"
|
||||
"${RUNTIME}" run --rm -d \
|
||||
"${RUNTIME}" run -d \
|
||||
--name "${CONTAINER}" \
|
||||
--user 1001230000:0 \
|
||||
--volume "${VOLUME}:/var/lib/netbird" \
|
||||
--cap-drop=ALL \
|
||||
--security-opt=no-new-privileges \
|
||||
--entrypoint /usr/local/bin/netbird \
|
||||
@@ -112,14 +118,55 @@ assert_arbitrary_uid_contract() {
|
||||
touch /var/lib/netbird/.uid-smoke
|
||||
rm /var/lib/netbird/.uid-smoke
|
||||
test -S /var/lib/netbird/netbird.sock
|
||||
test "$(stat -c %a /var/lib/netbird/config.json)" = 600
|
||||
test "$(stat -c %a /var/lib/netbird/active_profile.json)" = 600
|
||||
'
|
||||
"${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile list >/dev/null
|
||||
}
|
||||
|
||||
assert_same_uid_restart() {
|
||||
echo "==> Verifying persistent profiles with the same runtime UID"
|
||||
local profile_name="rootless-restart" profiles_before profiles_after
|
||||
|
||||
"${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile add "${profile_name}" >/dev/null
|
||||
profiles_before="$("${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile list --show-id)"
|
||||
if [[ "${profiles_before}" != *"${profile_name}"* ]]; then
|
||||
echo "created profile is missing before restart" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
"${RUNTIME}" stop "${CONTAINER}" >/dev/null
|
||||
"${RUNTIME}" rm "${CONTAINER}" >/dev/null
|
||||
start_container
|
||||
wait_until_live
|
||||
assert_arbitrary_uid_contract
|
||||
|
||||
profiles_after="$("${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile list --show-id)"
|
||||
if [[ "${profiles_after}" != "${profiles_before}" ]]; then
|
||||
echo "profiles changed after recreating the container with the same volume and UID" >&2
|
||||
container_logs
|
||||
return 1
|
||||
fi
|
||||
|
||||
"${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile rename "${profile_name}" "${profile_name}-renamed" >/dev/null
|
||||
profiles_after="$("${RUNTIME}" exec "${CONTAINER}" \
|
||||
/usr/local/bin/netbird profile list --show-id)"
|
||||
if [[ "${profiles_after}" != *"${profile_name}-renamed"* ]]; then
|
||||
echo "persisted profile could not be updated after restart" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_image
|
||||
VOLUME="$("${RUNTIME}" volume create "${CONTAINER}-state")"
|
||||
start_container
|
||||
wait_until_live
|
||||
assert_arbitrary_uid_contract
|
||||
assert_same_uid_restart
|
||||
|
||||
echo "==> Rootless arbitrary UID validation passed"
|
||||
echo "==> Rootless arbitrary UID and same-UID persistence validation passed"
|
||||
|
||||
Reference in New Issue
Block a user