mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-12 09:37:05 +02:00
We should not just rely on the sysctl, also confirm that `UserNamespacesSupport=true` feature gate is set for apiserver, so that the tests gets skipped if only sysctl is set. Signed-off-by: Noel Georgi <git@frezbo.dev>
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
//go:build integration_k8s
|
|
|
|
package k8s
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/siderolabs/talos/internal/integration/base"
|
|
"github.com/siderolabs/talos/pkg/machinery/client"
|
|
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
|
)
|
|
|
|
// UserNamespaceSuite verifies that a pod with user namespace works.
|
|
type UserNamespaceSuite struct {
|
|
base.K8sSuite
|
|
}
|
|
|
|
//go:embed testdata/usernamespace.yaml
|
|
var userNamespacePodSpec []byte
|
|
|
|
// SuiteName returns the name of the suite.
|
|
func (suite *UserNamespaceSuite) SuiteName() string {
|
|
return "k8s.UserNamespaceSuite"
|
|
}
|
|
|
|
// TestUserNamespace verifies that a pod with user namespace works.
|
|
//
|
|
//nolint:gocyclo,cyclop
|
|
func (suite *UserNamespaceSuite) TestUserNamespace() {
|
|
if suite.Cluster == nil {
|
|
suite.T().Skip("without full cluster state reaching out to the node IP is not reliable")
|
|
}
|
|
|
|
if suite.Cluster.Provisioner() != base.ProvisionerQEMU {
|
|
suite.T().Skip("skipping usernamespace test since provisioner is not qemu")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
|
suite.T().Cleanup(cancel)
|
|
|
|
node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker)
|
|
|
|
nodeCtx := client.WithNodes(ctx, node)
|
|
|
|
reader, err := suite.Client.Read(nodeCtx, "/proc/sys/user/max_user_namespaces")
|
|
suite.Require().NoError(err)
|
|
|
|
var maxUserNamespaces bytes.Buffer
|
|
|
|
_, err = maxUserNamespaces.ReadFrom(reader)
|
|
suite.Require().NoError(err)
|
|
|
|
if strings.TrimSpace(maxUserNamespaces.String()) == "0" {
|
|
suite.T().Skip("skipping test since user namespace is disabled")
|
|
}
|
|
|
|
controlPlaneNode := suite.RandomDiscoveredNodeInternalIP(machine.TypeControlPlane)
|
|
|
|
controlPlaneNodeCtx := client.WithNode(ctx, controlPlaneNode)
|
|
|
|
controlPlaneNodeConfig, err := suite.ReadConfigFromNode(controlPlaneNodeCtx)
|
|
suite.Require().NoError(err)
|
|
|
|
if controlPlaneNodeConfig.Cluster().APIServer().ExtraArgs() == nil {
|
|
suite.T().Skip("skipping test since no api server extra args found")
|
|
} else {
|
|
if featureGates, ok := controlPlaneNodeConfig.Cluster().APIServer().ExtraArgs()["feature-gates"]; ok {
|
|
if !strings.Contains(featureGates, "UserNamespacesSupport=true") {
|
|
suite.T().Skip("skipping test since user namespace feature gate is not enabled for kube-apiserver")
|
|
}
|
|
}
|
|
}
|
|
|
|
workerNodeConfig, err := suite.ReadConfigFromNode(client.WithNode(ctx, node))
|
|
suite.Require().NoError(err)
|
|
|
|
if workerNodeConfig.Machine().Kubelet().ExtraConfig() == nil {
|
|
suite.T().Skip("skipping test since no kubelet extra config found")
|
|
} else {
|
|
if featureGates, ok := workerNodeConfig.Machine().Kubelet().ExtraConfig()["featureGates"]; ok {
|
|
if fg, ok := featureGates.(map[string]string); ok {
|
|
if val, ok := fg["UserNamespacesSupport"]; !ok || val != "true" {
|
|
suite.T().Skip("skipping test since user namespace feature gate is not enabled for kubelet")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
usernamespacePodManifest := suite.ParseManifests(userNamespacePodSpec)
|
|
|
|
suite.T().Cleanup(func() {
|
|
cleanUpCtx, cleanupCancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cleanupCancel()
|
|
|
|
suite.DeleteManifests(cleanUpCtx, usernamespacePodManifest)
|
|
})
|
|
|
|
suite.ApplyManifests(ctx, usernamespacePodManifest)
|
|
|
|
suite.Require().NoError(suite.WaitForPodToBeRunning(ctx, time.Minute, "default", "userns"))
|
|
|
|
processResp, err := suite.Client.Processes(nodeCtx)
|
|
suite.Require().NoError(err)
|
|
|
|
var sleepProcessPID int
|
|
|
|
for _, processInfo := range processResp.Messages {
|
|
for _, process := range processInfo.Processes {
|
|
if strings.Contains(process.Args, "sleep 1000") {
|
|
sleepProcessPID = int(process.Pid)
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
suite.Require().NotZero(sleepProcessPID, "sleep process not found for user namespace test")
|
|
|
|
reader, err = suite.Client.Read(nodeCtx, fmt.Sprintf("/proc/%d/status", sleepProcessPID))
|
|
suite.Require().NoError(err)
|
|
|
|
var processStatus bytes.Buffer
|
|
|
|
_, err = processStatus.ReadFrom(reader)
|
|
suite.Require().NoError(err)
|
|
|
|
scanner := bufio.NewScanner(&processStatus)
|
|
|
|
var processUsingUserNamespace bool
|
|
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
if strings.HasPrefix(line, "Uid:") {
|
|
fields := strings.Fields(line)
|
|
|
|
if fields[0] != "0" && fields[1] != "0" && fields[2] != "0" && fields[3] != "0" {
|
|
processUsingUserNamespace = true
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
suite.Require().True(processUsingUserNamespace, "sleep process should not have root UID in host namespace\n", processStatus.String())
|
|
}
|
|
|
|
func init() {
|
|
allSuites = append(allSuites, new(UserNamespaceSuite))
|
|
}
|