mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 06:54:01 +00:00
KEP-127: check for runtime handler userns support
block the creation of a pod that requires a user namespace, unless the runtime handler has support for it. If the pod requested for a user namespace, and the handler does not support it then return an error regardless of the feature gate. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
parent
024146f705
commit
b2a92406ef
@ -59,7 +59,7 @@ type RuntimeHelper interface {
|
||||
GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64
|
||||
|
||||
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
|
||||
GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error)
|
||||
GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error)
|
||||
|
||||
// PrepareDynamicResources prepares resources for a pod.
|
||||
PrepareDynamicResources(pod *v1.Pod) error
|
||||
|
@ -68,7 +68,7 @@ func (f *FakeRuntimeHelper) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int6
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeRuntimeHelper) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
|
||||
func (f *FakeRuntimeHelper) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -1178,7 +1178,8 @@ type Kubelet struct {
|
||||
updatePodCIDRMux sync.Mutex
|
||||
|
||||
// updateRuntimeMux is a lock on updating runtime, because this path is not thread-safe.
|
||||
// This lock is used by Kubelet.updateRuntimeUp and Kubelet.fastNodeStatusUpdate functions and shouldn't be used anywhere else.
|
||||
// This lock is used by Kubelet.updateRuntimeUp, Kubelet.fastNodeStatusUpdate and
|
||||
// Kubelet.HandlerSupportsUserNamespaces functions and shouldn't be used anywhere else.
|
||||
updateRuntimeMux sync.Mutex
|
||||
|
||||
// nodeLeaseController claims and renews the node lease for this Kubelet
|
||||
|
@ -109,6 +109,20 @@ func (kl *Kubelet) ListPodsFromDisk() ([]types.UID, error) {
|
||||
return kl.listPodsFromDisk()
|
||||
}
|
||||
|
||||
// HandlerSupportsUserNamespaces checks whether the specified handler supports
|
||||
// user namespaces.
|
||||
func (kl *Kubelet) HandlerSupportsUserNamespaces(rtHandler string) (bool, error) {
|
||||
rtHandlers := kl.runtimeState.runtimeHandlers()
|
||||
if rtHandlers == nil {
|
||||
return false, fmt.Errorf("runtime handlers are not set")
|
||||
}
|
||||
h, found := rtHandlers[rtHandler]
|
||||
if !found {
|
||||
return false, fmt.Errorf("the handler %q is not known", rtHandler)
|
||||
}
|
||||
return h.SupportsUserNamespaces, nil
|
||||
}
|
||||
|
||||
// getPodDir returns the full path to the per-pod directory for the pod with
|
||||
// the given UID.
|
||||
func (kl *Kubelet) getPodDir(podUID types.UID) string {
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
)
|
||||
|
||||
func TestKubeletDirs(t *testing.T) {
|
||||
@ -99,3 +100,32 @@ func TestKubeletDirs(t *testing.T) {
|
||||
exp = filepath.Join(root, "pods/abc123/volume-subpaths")
|
||||
assert.Equal(t, exp, got)
|
||||
}
|
||||
|
||||
func TestHandlerSupportsUserNamespaces(t *testing.T) {
|
||||
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
|
||||
defer testKubelet.Cleanup()
|
||||
kubelet := testKubelet.kubelet
|
||||
|
||||
kubelet.runtimeState.setRuntimeHandlers(map[string]kubecontainer.RuntimeHandler{
|
||||
"has-support": {
|
||||
Name: "has-support",
|
||||
SupportsUserNamespaces: true,
|
||||
},
|
||||
"has-no-support": {
|
||||
Name: "has-support",
|
||||
SupportsUserNamespaces: false,
|
||||
},
|
||||
})
|
||||
|
||||
got, err := kubelet.HandlerSupportsUserNamespaces("has-support")
|
||||
assert.Equal(t, true, got)
|
||||
assert.NoError(t, err)
|
||||
|
||||
got, err = kubelet.HandlerSupportsUserNamespaces("has-no-support")
|
||||
assert.Equal(t, false, got)
|
||||
assert.NoError(t, err)
|
||||
|
||||
got, err = kubelet.HandlerSupportsUserNamespaces("unknown")
|
||||
assert.Equal(t, false, got)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
@ -425,8 +425,8 @@ func truncatePodHostnameIfNeeded(podName, hostname string) (string, error) {
|
||||
}
|
||||
|
||||
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
|
||||
func (kl *Kubelet) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
|
||||
return kl.usernsManager.GetOrCreateUserNamespaceMappings(pod)
|
||||
func (kl *Kubelet) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) {
|
||||
return kl.usernsManager.GetOrCreateUserNamespaceMappings(pod, runtimeHandler)
|
||||
}
|
||||
|
||||
// GeneratePodHostNameAndDomain creates a hostname and domain name for a pod,
|
||||
|
@ -100,7 +100,11 @@ func PidNamespaceForPod(pod *v1.Pod) runtimeapi.NamespaceMode {
|
||||
// namespacesForPod returns the runtimeapi.NamespaceOption for a given pod.
|
||||
// An empty or nil pod can be used to get the namespace defaults for v1.Pod.
|
||||
func NamespacesForPod(pod *v1.Pod, runtimeHelper kubecontainer.RuntimeHelper) (*runtimeapi.NamespaceOption, error) {
|
||||
userNs, err := runtimeHelper.GetOrCreateUserNamespaceMappings(pod)
|
||||
runtimeHandler := ""
|
||||
if pod != nil && pod.Spec.RuntimeClassName != nil {
|
||||
runtimeHandler = *pod.Spec.RuntimeClassName
|
||||
}
|
||||
userNs, err := runtimeHelper.GetOrCreateUserNamespaceMappings(pod, runtimeHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ const maxPods = 1024
|
||||
const mapReInitializeThreshold = 1000
|
||||
|
||||
type userNsPodsManager interface {
|
||||
HandlerSupportsUserNamespaces(runtimeHandler string) (bool, error)
|
||||
GetPodDir(podUID types.UID) string
|
||||
ListPodsFromDisk() ([]types.UID, error)
|
||||
}
|
||||
@ -379,20 +380,41 @@ func (m *UsernsManager) createUserNs(pod *v1.Pod) (userNs userNamespace, err err
|
||||
}
|
||||
|
||||
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
|
||||
func (m *UsernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) {
|
||||
func (m *UsernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) {
|
||||
featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport)
|
||||
|
||||
if pod == nil || pod.Spec.HostUsers == nil {
|
||||
// if the feature is enabled, specify to use the node mode...
|
||||
if featureEnabled {
|
||||
return &runtimeapi.UserNamespace{
|
||||
Mode: runtimeapi.NamespaceMode_NODE,
|
||||
}, nil
|
||||
}
|
||||
// ...otherwise don't even specify it
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
if pod.Spec.HostUsers == nil || *pod.Spec.HostUsers {
|
||||
// pod.Spec.HostUsers is set to true/false
|
||||
if !featureEnabled {
|
||||
return nil, fmt.Errorf("the feature gate %q is disabled: can't set spec.HostUsers", features.UserNamespacesSupport)
|
||||
}
|
||||
if *pod.Spec.HostUsers {
|
||||
return &runtimeapi.UserNamespace{
|
||||
Mode: runtimeapi.NamespaceMode_NODE,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// From here onwards, hostUsers=false and the feature gate is enabled.
|
||||
|
||||
// if the pod requested a user namespace and the runtime doesn't support user namespaces then return an error.
|
||||
if handlerSupportsUserns, err := m.kl.HandlerSupportsUserNamespaces(runtimeHandler); err != nil {
|
||||
return nil, err
|
||||
} else if !handlerSupportsUserns {
|
||||
return nil, fmt.Errorf("RuntimeClass handler %q does not support user namespaces", runtimeHandler)
|
||||
}
|
||||
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
content, err := m.readMappingsFromFile(pod.UID)
|
||||
if err != nil && err != utilstore.ErrKeyNotFound {
|
||||
return nil, err
|
||||
|
@ -53,7 +53,7 @@ func TestGetOrCreateUserNamespaceMappingsDisabled(t *testing.T) {
|
||||
m, err := MakeUserNsManager(testUserNsPodsManager)
|
||||
require.NoError(t, err)
|
||||
|
||||
userns, err := m.GetOrCreateUserNamespaceMappings(nil)
|
||||
userns, err := m.GetOrCreateUserNamespaceMappings(nil, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, userns)
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ func (m *testUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) {
|
||||
return m.podList, nil
|
||||
}
|
||||
|
||||
func (m *testUserNsPodsManager) HandlerSupportsUserNamespaces(runtimeHandler string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func TestUserNsManagerAllocate(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
|
||||
|
||||
@ -232,7 +236,7 @@ func TestGetOrCreateUserNamespaceMappings(t *testing.T) {
|
||||
m, err := MakeUserNsManager(testUserNsPodsManager)
|
||||
assert.NoError(t, err)
|
||||
|
||||
userns, err := m.GetOrCreateUserNamespaceMappings(tc.pod)
|
||||
userns, err := m.GetOrCreateUserNamespaceMappings(tc.pod, "")
|
||||
if (tc.success && err != nil) || (!tc.success && err == nil) {
|
||||
t.Errorf("expected success: %v but got error: %v", tc.success, err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user