mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 13:57:38 +00:00
tests/e2e_node: Add test for userNamespaces.idsPerPod
Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com> Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
committed by
Akihiro Suda
parent
b38d702807
commit
2a488616bf
@@ -20,23 +20,39 @@ limitations under the License.
|
||||
package e2enode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
kubefeatures "k8s.io/kubernetes/pkg/features"
|
||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
||||
"k8s.io/kubernetes/test/e2e/feature"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
||||
e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
|
||||
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||
admissionapi "k8s.io/pod-security-admission/api"
|
||||
)
|
||||
|
||||
var (
|
||||
customIDsPerPod int64 = 65536 * 2
|
||||
// kubelet user used for userns mapping.
|
||||
kubeletUserForUsernsMapping = "kubelet"
|
||||
getsubuidsBinary = "getsubids"
|
||||
)
|
||||
|
||||
var _ = SIGDescribe("UserNamespaces", "[LinuxOnly]", feature.UserNamespacesSupport, framework.WithSerial(), func() {
|
||||
f := framework.NewDefaultFramework("user-namespace-off-test")
|
||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
||||
@@ -87,3 +103,100 @@ var _ = SIGDescribe("UserNamespaces", "[LinuxOnly]", feature.UserNamespacesSuppo
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = SIGDescribe("user namespaces kubeconfig tests", "[LinuxOnly]", feature.UserNamespacesSupport, framework.WithFeatureGate(kubefeatures.UserNamespacesSupport), framework.WithSerial(), func() {
|
||||
f := framework.NewDefaultFramework("userns-kubeconfig")
|
||||
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
|
||||
f.Context("test config using userNamespaces.idsPerPod", func() {
|
||||
ginkgo.BeforeEach(func() {
|
||||
if hasMappings, err := hasKubeletUsernsMappings(); err != nil {
|
||||
framework.Failf("failed to check kubelet user namespace mappings: %v", err)
|
||||
} else if hasMappings {
|
||||
// idsPerPod needs to be in sync with the kubelet's user namespace
|
||||
// mappings. Let's skip the test if there are mappings present.
|
||||
e2eskipper.Skipf("kubelet is configured with custom user namespace mappings, skipping test")
|
||||
}
|
||||
})
|
||||
|
||||
tempSetCurrentKubeletConfig(f, func(ctx context.Context, initialConfig *kubeletconfig.KubeletConfiguration) {
|
||||
if initialConfig.UserNamespaces == nil {
|
||||
initialConfig.UserNamespaces = &kubeletconfig.UserNamespaces{}
|
||||
}
|
||||
initialConfig.UserNamespaces.IDsPerPod = &customIDsPerPod
|
||||
})
|
||||
f.It("honors idsPerPod in userns pods", func(ctx context.Context) {
|
||||
if !supportsUserNS(ctx, f) {
|
||||
e2eskipper.Skipf("runtime does not support user namespaces")
|
||||
}
|
||||
falseVar := false
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "userns-pod" + string(uuid.NewUUID())},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "container",
|
||||
Image: imageutils.GetE2EImage(imageutils.BusyBox),
|
||||
// The third field is the mapping length, that must be equal to idsPerPod.
|
||||
Command: []string{"awk", "NR != 1 { exit 1 } { print $3 }", "/proc/self/uid_map"},
|
||||
},
|
||||
},
|
||||
HostUsers: &falseVar,
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
},
|
||||
}
|
||||
expected := []string{strconv.FormatInt(customIDsPerPod, 10)}
|
||||
e2eoutput.TestContainerOutput(ctx, f, "idsPerPod is configured correctly", pod, 0, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func hasKubeletUsernsMappings() (bool, error) {
|
||||
if _, err := user.Lookup(kubeletUserForUsernsMapping); err != nil {
|
||||
var e user.UnknownUserError
|
||||
if errors.As(err, &e) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
cmdBin, err := exec.LookPath(getsubuidsBinary)
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
outUids, err := getsubids(cmdBin, kubeletUserForUsernsMapping)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if outUids == "" {
|
||||
return false, nil
|
||||
}
|
||||
outGids, err := getsubids(cmdBin, "-g", kubeletUserForUsernsMapping)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if string(outUids) != string(outGids) {
|
||||
return false, fmt.Errorf("user %q has different subuids and subgids: %q vs %q", kubeletUserForUsernsMapping, outUids, outGids)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// getsubids runs the getsubids command to fetch subuid mappings for a user.
|
||||
// If the command fails with "Error fetching ranges", it returns an empty string
|
||||
// to indicate that no subuid mappings were found, which is not considered an error.
|
||||
// Otherwise, it returns the output of the command as a string.
|
||||
// (e.g., "0: user 100000 65536")
|
||||
func getsubids(cmdBin string, cmdArgs ...string) (string, error) {
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command(cmdBin, cmdArgs...)
|
||||
cmd.Stderr = &stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
if strings.TrimSpace(stderr.String()) == "Error fetching ranges" {
|
||||
return "", nil // No subuid mappings found, this is not an error
|
||||
}
|
||||
return "", fmt.Errorf("failed to run %v: %w (stderr=%q)", cmd.Args, err, stderr.String())
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user