mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-17 07:39:22 +00:00
Merge pull request #118705 from pacoxu/hostnet-sysctl-rejects
forbid sysctls for pod sharing the respective namespaces with the host
This commit is contained in:
commit
57c7b666c4
@ -365,6 +365,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
AllowInvalidLabelValueInSelector: false,
|
||||
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
||||
AllowMutableNodeSelectorAndNodeAffinity: utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness),
|
||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||
}
|
||||
|
||||
if oldPodSpec != nil {
|
||||
@ -377,6 +378,17 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
|
||||
// if old spec has invalid labelSelector in topologySpreadConstraint, we must allow it
|
||||
opts.AllowInvalidTopologySpreadConstraintLabelSelector = hasInvalidTopologySpreadConstraintLabelSelector(oldPodSpec)
|
||||
|
||||
// if old spec has invalid sysctl with hostNet or hostIPC, we must allow it when update
|
||||
if oldPodSpec.SecurityContext != nil && len(oldPodSpec.SecurityContext.Sysctls) != 0 {
|
||||
for _, s := range oldPodSpec.SecurityContext.Sysctls {
|
||||
err := apivalidation.ValidateHostSysctl(s.Name, oldPodSpec.SecurityContext, nil)
|
||||
if err != nil {
|
||||
opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
|
||||
// This is an update, so validate only if the existing object was valid.
|
||||
|
@ -44,6 +44,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
utilsysctl "k8s.io/component-helpers/node/util/sysctl"
|
||||
schedulinghelper "k8s.io/component-helpers/scheduling/corev1"
|
||||
kubeletapis "k8s.io/kubelet/pkg/apis"
|
||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||
@ -3812,6 +3813,8 @@ type PodValidationOptions struct {
|
||||
AllowInvalidTopologySpreadConstraintLabelSelector bool
|
||||
// Allow node selector additions for gated pods.
|
||||
AllowMutableNodeSelectorAndNodeAffinity bool
|
||||
// Allow namespaced sysctls in hostNet and hostIPC pods
|
||||
AllowNamespacedSysctlsForHostNetAndHostIPC bool
|
||||
// The top-level resource being validated is a Pod, not just a PodSpec
|
||||
// embedded in some other resource.
|
||||
ResourceIsPod bool
|
||||
@ -4562,10 +4565,10 @@ func IsValidSysctlName(name string) bool {
|
||||
return sysctlContainSlashRegexp.MatchString(name)
|
||||
}
|
||||
|
||||
func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList {
|
||||
func validateSysctls(securityContext *core.PodSecurityContext, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
names := make(map[string]struct{})
|
||||
for i, s := range sysctls {
|
||||
for i, s := range securityContext.Sysctls {
|
||||
if len(s.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), ""))
|
||||
} else if !IsValidSysctlName(s.Name) {
|
||||
@ -4573,11 +4576,29 @@ func validateSysctls(sysctls []core.Sysctl, fldPath *field.Path) field.ErrorList
|
||||
} else if _, ok := names[s.Name]; ok {
|
||||
allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name))
|
||||
}
|
||||
if !opts.AllowNamespacedSysctlsForHostNetAndHostIPC {
|
||||
err := ValidateHostSysctl(s.Name, securityContext, fldPath.Index(i).Child("name"))
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
}
|
||||
names[s.Name] = struct{}{}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateHostSysctl will return error if namespaced sysctls is applied to pod sharing the respective namespaces with the host.
|
||||
func ValidateHostSysctl(sysctl string, securityContext *core.PodSecurityContext, fldPath *field.Path) *field.Error {
|
||||
ns, _, _ := utilsysctl.GetNamespace(sysctl)
|
||||
switch {
|
||||
case securityContext.HostNetwork && ns == utilsysctl.NetNamespace:
|
||||
return field.Invalid(fldPath, sysctl, "may not be specified when 'hostNetwork' is true")
|
||||
case securityContext.HostIPC && ns == utilsysctl.IPCNamespace:
|
||||
return field.Invalid(fldPath, sysctl, "may not be specified when 'hostIPC' is true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePodSpecSecurityContext verifies the SecurityContext of a PodSpec,
|
||||
// whether that is defined in a Pod or in an embedded PodSpec (e.g. a
|
||||
// Deployment's pod template).
|
||||
@ -4610,7 +4631,7 @@ func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, sp
|
||||
}
|
||||
|
||||
if len(securityContext.Sysctls) != 0 {
|
||||
allErrs = append(allErrs, validateSysctls(securityContext.Sysctls, fldPath.Child("sysctls"))...)
|
||||
allErrs = append(allErrs, validateSysctls(securityContext, fldPath.Child("sysctls"), opts)...)
|
||||
}
|
||||
|
||||
if securityContext.FSGroupChangePolicy != nil {
|
||||
|
@ -21491,16 +21491,32 @@ func TestValidateSysctls(t *testing.T) {
|
||||
"_invalid",
|
||||
}
|
||||
|
||||
invalidWithHostNet := []string{
|
||||
"net.ipv4.conf.enp3s0/200.forwarding",
|
||||
"net/ipv4/conf/enp3s0.200/forwarding",
|
||||
}
|
||||
|
||||
invalidWithHostIPC := []string{
|
||||
"kernel.shmmax",
|
||||
"kernel.msgmax",
|
||||
}
|
||||
|
||||
duplicates := []string{
|
||||
"kernel.shmmax",
|
||||
"kernel.shmmax",
|
||||
}
|
||||
opts := PodValidationOptions{
|
||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||
}
|
||||
|
||||
sysctls := make([]core.Sysctl, len(valid))
|
||||
validSecurityContext := &core.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
}
|
||||
for i, sysctl := range valid {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
errs := validateSysctls(sysctls, field.NewPath("foo"))
|
||||
errs := validateSysctls(validSecurityContext, field.NewPath("foo"), opts)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
@ -21509,7 +21525,10 @@ func TestValidateSysctls(t *testing.T) {
|
||||
for i, sysctl := range invalid {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
errs = validateSysctls(sysctls, field.NewPath("foo"))
|
||||
inValidSecurityContext := &core.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
}
|
||||
errs = validateSysctls(inValidSecurityContext, field.NewPath("foo"), opts)
|
||||
if len(errs) != 2 {
|
||||
t.Errorf("expected 2 validation errors. Got: %v", errs)
|
||||
} else {
|
||||
@ -21525,12 +21544,54 @@ func TestValidateSysctls(t *testing.T) {
|
||||
for i, sysctl := range duplicates {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
errs = validateSysctls(sysctls, field.NewPath("foo"))
|
||||
securityContextWithDup := &core.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
}
|
||||
errs = validateSysctls(securityContextWithDup, field.NewPath("foo"), opts)
|
||||
if len(errs) != 1 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
} else if errs[0].Type != field.ErrorTypeDuplicate {
|
||||
t.Errorf("expected error type %v, got %v", field.ErrorTypeDuplicate, errs[0].Type)
|
||||
}
|
||||
|
||||
sysctls = make([]core.Sysctl, len(invalidWithHostNet))
|
||||
for i, sysctl := range invalidWithHostNet {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
invalidSecurityContextWithHostNet := &core.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
HostIPC: false,
|
||||
HostNetwork: true,
|
||||
}
|
||||
errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
|
||||
if len(errs) != 2 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
|
||||
errs = validateSysctls(invalidSecurityContextWithHostNet, field.NewPath("foo"), opts)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
|
||||
sysctls = make([]core.Sysctl, len(invalidWithHostIPC))
|
||||
for i, sysctl := range invalidWithHostIPC {
|
||||
sysctls[i].Name = sysctl
|
||||
}
|
||||
invalidSecurityContextWithHostIPC := &core.PodSecurityContext{
|
||||
Sysctls: sysctls,
|
||||
HostIPC: true,
|
||||
HostNetwork: false,
|
||||
}
|
||||
opts.AllowNamespacedSysctlsForHostNetAndHostIPC = false
|
||||
errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
|
||||
if len(errs) != 2 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
opts.AllowNamespacedSysctlsForHostNetAndHostIPC = true
|
||||
errs = validateSysctls(invalidSecurityContextWithHostIPC, field.NewPath("foo"), opts)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("unexpected validation errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func newNodeNameEndpoint(nodeName string) *core.Endpoints {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
utilsysctl "k8s.io/component-helpers/node/util/sysctl"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
policyvalidation "k8s.io/kubernetes/pkg/apis/policy/validation"
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
@ -33,8 +34,8 @@ const (
|
||||
// checks validity via a sysctl and prefix map, rejecting those which are not known
|
||||
// to be namespaced.
|
||||
type patternAllowlist struct {
|
||||
sysctls map[string]Namespace
|
||||
prefixes map[string]Namespace
|
||||
sysctls map[string]utilsysctl.Namespace
|
||||
prefixes map[string]utilsysctl.Namespace
|
||||
}
|
||||
|
||||
var _ lifecycle.PodAdmitHandler = &patternAllowlist{}
|
||||
@ -42,8 +43,8 @@ var _ lifecycle.PodAdmitHandler = &patternAllowlist{}
|
||||
// NewAllowlist creates a new Allowlist from a list of sysctls and sysctl pattern (ending in *).
|
||||
func NewAllowlist(patterns []string) (*patternAllowlist, error) {
|
||||
w := &patternAllowlist{
|
||||
sysctls: map[string]Namespace{},
|
||||
prefixes: map[string]Namespace{},
|
||||
sysctls: map[string]utilsysctl.Namespace{},
|
||||
prefixes: map[string]utilsysctl.Namespace{},
|
||||
}
|
||||
|
||||
for _, s := range patterns {
|
||||
@ -54,20 +55,14 @@ func NewAllowlist(patterns []string) (*patternAllowlist, error) {
|
||||
policyvalidation.SysctlContainSlashPatternFmt,
|
||||
)
|
||||
}
|
||||
s = convertSysctlVariableToDotsSeparator(s)
|
||||
if strings.HasSuffix(s, "*") {
|
||||
prefix := s[:len(s)-1]
|
||||
ns := NamespacedBy(prefix)
|
||||
if ns == unknownNamespace {
|
||||
return nil, fmt.Errorf("the sysctls %q are not known to be namespaced", s)
|
||||
}
|
||||
w.prefixes[prefix] = ns
|
||||
ns, sysctlOrPrefix, prefixed := utilsysctl.GetNamespace(s)
|
||||
if ns == utilsysctl.UnknownNamespace {
|
||||
return nil, fmt.Errorf("the sysctls %q are not known to be namespaced", sysctlOrPrefix)
|
||||
}
|
||||
if prefixed {
|
||||
w.prefixes[sysctlOrPrefix] = ns
|
||||
} else {
|
||||
ns := NamespacedBy(s)
|
||||
if ns == unknownNamespace {
|
||||
return nil, fmt.Errorf("the sysctl %q are not known to be namespaced", s)
|
||||
}
|
||||
w.sysctls[s] = ns
|
||||
w.sysctls[sysctlOrPrefix] = ns
|
||||
}
|
||||
}
|
||||
return w, nil
|
||||
@ -81,23 +76,23 @@ func NewAllowlist(patterns []string) (*patternAllowlist, error) {
|
||||
// respective namespaces with the host. This check is only possible for sysctls on
|
||||
// the static default allowlist, not those on the custom allowlist provided by the admin.
|
||||
func (w *patternAllowlist) validateSysctl(sysctl string, hostNet, hostIPC bool) error {
|
||||
sysctl = convertSysctlVariableToDotsSeparator(sysctl)
|
||||
sysctl = utilsysctl.NormalizeName(sysctl)
|
||||
nsErrorFmt := "%q not allowed with host %s enabled"
|
||||
if ns, found := w.sysctls[sysctl]; found {
|
||||
if ns == ipcNamespace && hostIPC {
|
||||
if ns == utilsysctl.IPCNamespace && hostIPC {
|
||||
return fmt.Errorf(nsErrorFmt, sysctl, ns)
|
||||
}
|
||||
if ns == netNamespace && hostNet {
|
||||
if ns == utilsysctl.NetNamespace && hostNet {
|
||||
return fmt.Errorf(nsErrorFmt, sysctl, ns)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for p, ns := range w.prefixes {
|
||||
if strings.HasPrefix(sysctl, p) {
|
||||
if ns == ipcNamespace && hostIPC {
|
||||
if ns == utilsysctl.IPCNamespace && hostIPC {
|
||||
return fmt.Errorf(nsErrorFmt, sysctl, ns)
|
||||
}
|
||||
if ns == netNamespace && hostNet {
|
||||
if ns == utilsysctl.NetNamespace && hostNet {
|
||||
return fmt.Errorf(nsErrorFmt, sysctl, ns)
|
||||
}
|
||||
return nil
|
||||
|
@ -1,3 +1,6 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
@ -17,9 +20,10 @@ limitations under the License.
|
||||
package sysctl
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||
)
|
||||
|
||||
func TestNewAllowlist(t *testing.T) {
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sysctl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Namespace represents a kernel namespace name.
|
||||
type Namespace string
|
||||
|
||||
const (
|
||||
// the Linux IPC namespace
|
||||
ipcNamespace = Namespace("ipc")
|
||||
|
||||
// the network namespace
|
||||
netNamespace = Namespace("net")
|
||||
|
||||
// the zero value if no namespace is known
|
||||
unknownNamespace = Namespace("")
|
||||
)
|
||||
|
||||
var namespaces = map[string]Namespace{
|
||||
"kernel.sem": ipcNamespace,
|
||||
}
|
||||
|
||||
var prefixNamespaces = map[string]Namespace{
|
||||
"kernel.shm": ipcNamespace,
|
||||
"kernel.msg": ipcNamespace,
|
||||
"fs.mqueue.": ipcNamespace,
|
||||
"net.": netNamespace,
|
||||
}
|
||||
|
||||
// NamespacedBy returns the namespace of the Linux kernel for a sysctl, or
|
||||
// unknownNamespace if the sysctl is not known to be namespaced.
|
||||
func NamespacedBy(val string) Namespace {
|
||||
if ns, found := namespaces[val]; found {
|
||||
return ns
|
||||
}
|
||||
for p, ns := range prefixNamespaces {
|
||||
if strings.HasPrefix(val, p) {
|
||||
return ns
|
||||
}
|
||||
}
|
||||
return unknownNamespace
|
||||
}
|
@ -17,39 +17,10 @@ limitations under the License.
|
||||
package sysctl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
utilsysctl "k8s.io/component-helpers/node/util/sysctl"
|
||||
)
|
||||
|
||||
// convertSysctlVariableToDotsSeparator can return sysctl variables in dots separator format.
|
||||
// The '/' separator is also accepted in place of a '.'.
|
||||
// Convert the sysctl variables to dots separator format for validation.
|
||||
// More info:
|
||||
//
|
||||
// https://man7.org/linux/man-pages/man8/sysctl.8.html
|
||||
// https://man7.org/linux/man-pages/man5/sysctl.d.5.html
|
||||
func convertSysctlVariableToDotsSeparator(val string) string {
|
||||
if val == "" {
|
||||
return val
|
||||
}
|
||||
firstSepIndex := strings.IndexAny(val, "./")
|
||||
if firstSepIndex == -1 || val[firstSepIndex] == '.' {
|
||||
return val
|
||||
}
|
||||
|
||||
f := func(r rune) rune {
|
||||
switch r {
|
||||
case '.':
|
||||
return '/'
|
||||
case '/':
|
||||
return '.'
|
||||
}
|
||||
return r
|
||||
}
|
||||
return strings.Map(f, val)
|
||||
}
|
||||
|
||||
// ConvertPodSysctlsVariableToDotsSeparator converts sysctls variable in the Pod.Spec.SecurityContext.Sysctls slice into a dot as a separator
|
||||
// according to the linux sysctl conversion rules.
|
||||
// see https://man7.org/linux/man-pages/man5/sysctl.d.5.html for more details.
|
||||
@ -58,7 +29,7 @@ func ConvertPodSysctlsVariableToDotsSeparator(securityContext *v1.PodSecurityCon
|
||||
return
|
||||
}
|
||||
for i, sysctl := range securityContext.Sysctls {
|
||||
securityContext.Sysctls[i].Name = convertSysctlVariableToDotsSeparator(sysctl.Name)
|
||||
securityContext.Sysctls[i].Name = utilsysctl.NormalizeName(sysctl.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -24,29 +24,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestConvertSysctlVariableToDotsSeparator tests whether the sysctl variable
|
||||
// can be correctly converted to a dot as a separator.
|
||||
func TestConvertSysctlVariableToDotsSeparator(t *testing.T) {
|
||||
type testCase struct {
|
||||
in string
|
||||
out string
|
||||
}
|
||||
valid := []testCase{
|
||||
{in: "kernel.shm_rmid_forced", out: "kernel.shm_rmid_forced"},
|
||||
{in: "kernel/shm_rmid_forced", out: "kernel.shm_rmid_forced"},
|
||||
{in: "net.ipv4.conf.eno2/100.rp_filter", out: "net.ipv4.conf.eno2/100.rp_filter"},
|
||||
{in: "net/ipv4/conf/eno2.100/rp_filter", out: "net.ipv4.conf.eno2/100.rp_filter"},
|
||||
{in: "net/ipv4/ip_local_port_range", out: "net.ipv4.ip_local_port_range"},
|
||||
{in: "kernel/msgmax", out: "kernel.msgmax"},
|
||||
{in: "kernel/sem", out: "kernel.sem"},
|
||||
}
|
||||
|
||||
for _, test := range valid {
|
||||
convertSysctlVal := convertSysctlVariableToDotsSeparator(test.in)
|
||||
assert.Equalf(t, test.out, convertSysctlVal, "The sysctl variable was not converted correctly. got: %s, want: %s", convertSysctlVal, test.out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertPodSysctlsVariableToDotsSeparator tests whether the sysctls variable
|
||||
// can be correctly converted to a dot as a separator.
|
||||
func TestConvertPodSysctlsVariableToDotsSeparator(t *testing.T) {
|
||||
|
@ -6,6 +6,7 @@ go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/client-go v0.0.0
|
||||
@ -33,6 +34,7 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.10.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sysctl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Namespace represents a kernel namespace name.
|
||||
type Namespace string
|
||||
|
||||
const (
|
||||
// refer to https://man7.org/linux/man-pages/man7/ipc_namespaces.7.html
|
||||
// the Linux IPC namespace
|
||||
IPCNamespace = Namespace("IPC")
|
||||
|
||||
// refer to https://man7.org/linux/man-pages/man7/network_namespaces.7.html
|
||||
// the network namespace
|
||||
NetNamespace = Namespace("Net")
|
||||
|
||||
// the zero value if no namespace is known
|
||||
UnknownNamespace = Namespace("")
|
||||
)
|
||||
|
||||
var nameToNamespace = map[string]Namespace{
|
||||
// kernel semaphore parameters: SEMMSL, SEMMNS, SEMOPM, and SEMMNI.
|
||||
"kernel.sem": IPCNamespace,
|
||||
// kernel shared memory limits include shmall, shmmax, shmmni, and shm_rmid_forced.
|
||||
"kernel.shmall": IPCNamespace,
|
||||
"kernel.shmmax": IPCNamespace,
|
||||
"kernel.shmmni": IPCNamespace,
|
||||
"kernel.shm_rmid_forced": IPCNamespace,
|
||||
// make backward compatibility to know the namespace of kernel.shm*
|
||||
"kernel.shm": IPCNamespace,
|
||||
// kernel messages include msgmni, msgmax and msgmnb.
|
||||
"kernel.msgmax": IPCNamespace,
|
||||
"kernel.msgmnb": IPCNamespace,
|
||||
"kernel.msgmni": IPCNamespace,
|
||||
// make backward compatibility to know the namespace of kernel.msg*
|
||||
"kernel.msg": IPCNamespace,
|
||||
}
|
||||
|
||||
var prefixToNamespace = map[string]Namespace{
|
||||
"net": NetNamespace,
|
||||
// mqueue filesystem provides the necessary kernel features to enable the creation
|
||||
// of a user space library that implements the POSIX message queues API.
|
||||
"fs.mqueue": IPCNamespace,
|
||||
}
|
||||
|
||||
// namespaceOf returns the namespace of the Linux kernel for a sysctl, or
|
||||
// unknownNamespace if the sysctl is not known to be namespaced.
|
||||
// The second return is prefixed bool.
|
||||
// It returns true if the key is prefixed with a key in the prefix map
|
||||
func namespaceOf(val string) Namespace {
|
||||
if ns, found := nameToNamespace[val]; found {
|
||||
return ns
|
||||
}
|
||||
for p, ns := range prefixToNamespace {
|
||||
if strings.HasPrefix(val, p+".") {
|
||||
return ns
|
||||
}
|
||||
}
|
||||
return UnknownNamespace
|
||||
}
|
||||
|
||||
// GetNamespace extracts information from a sysctl string. It returns:
|
||||
// 1. The sysctl namespace, which can be one of the following: IPC, Net, or unknown.
|
||||
// 2. sysctlOrPrefix: the prefix of the sysctl parameter until the first '*'.
|
||||
// If there is no '*', it will be the original string.
|
||||
// 3. 'prefixed' is set to true if the sysctl parameter contains '*' or it is in the prefixToNamespace key list, in most cases, it is a suffix *.
|
||||
//
|
||||
// For example, if the input sysctl is 'net.ipv6.neigh.*', GetNamespace will return:
|
||||
// - The Net namespace
|
||||
// - The sysctlOrPrefix as 'net.ipv6.neigh'
|
||||
// - 'prefixed' set to true
|
||||
//
|
||||
// For the input sysctl 'net.ipv6.conf.all.disable_ipv6', GetNamespace will return:
|
||||
// - The Net namespace
|
||||
// - The sysctlOrPrefix as 'net.ipv6.conf.all.disable_ipv6'
|
||||
// - 'prefixed' set to false.
|
||||
func GetNamespace(sysctl string) (ns Namespace, sysctlOrPrefix string, prefixed bool) {
|
||||
sysctlOrPrefix = NormalizeName(sysctl)
|
||||
firstIndex := strings.IndexAny(sysctlOrPrefix, "*")
|
||||
if firstIndex != -1 {
|
||||
sysctlOrPrefix = sysctlOrPrefix[:firstIndex]
|
||||
prefixed = true
|
||||
}
|
||||
ns = namespaceOf(sysctlOrPrefix)
|
||||
return
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -20,16 +20,16 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNamespacedBy(t *testing.T) {
|
||||
func TestNamespacedOf(t *testing.T) {
|
||||
tests := map[string]Namespace{
|
||||
"kernel.shm_rmid_forced": ipcNamespace,
|
||||
"net.a.b.c": netNamespace,
|
||||
"fs.mqueue.a.b.c": ipcNamespace,
|
||||
"foo": unknownNamespace,
|
||||
"kernel.shm_rmid_forced": IPCNamespace,
|
||||
"net.a.b.c": NetNamespace,
|
||||
"fs.mqueue.a.b.c": IPCNamespace,
|
||||
"foo": UnknownNamespace,
|
||||
}
|
||||
|
||||
for sysctl, ns := range tests {
|
||||
if got := NamespacedBy(sysctl); got != ns {
|
||||
if got := namespaceOf(sysctl); got != ns {
|
||||
t.Errorf("wrong namespace for %q: got=%s want=%s", sysctl, got, ns)
|
||||
}
|
||||
}
|
@ -98,3 +98,34 @@ func (*procSysctl) GetSysctl(sysctl string) (int, error) {
|
||||
func (*procSysctl) SetSysctl(sysctl string, newVal int) error {
|
||||
return os.WriteFile(path.Join(sysctlBase, sysctl), []byte(strconv.Itoa(newVal)), 0640)
|
||||
}
|
||||
|
||||
// NormalizeName can return sysctl variables in dots separator format.
|
||||
// The '/' separator is also accepted in place of a '.'.
|
||||
// Convert the sysctl variables to dots separator format for validation.
|
||||
// More info:
|
||||
//
|
||||
// https://man7.org/linux/man-pages/man8/sysctl.8.html
|
||||
// https://man7.org/linux/man-pages/man5/sysctl.d.5.html
|
||||
func NormalizeName(val string) string {
|
||||
if val == "" {
|
||||
return val
|
||||
}
|
||||
firstSepIndex := strings.IndexAny(val, "./")
|
||||
// if the first found is `.` like `net.ipv4.conf.eno2/100.rp_filter`
|
||||
if firstSepIndex == -1 || val[firstSepIndex] == '.' {
|
||||
return val
|
||||
}
|
||||
|
||||
// for `net/ipv4/conf/eno2.100/rp_filter`, swap the use of `.` and `/`
|
||||
// to `net.ipv4.conf.eno2/100.rp_filter`
|
||||
f := func(r rune) rune {
|
||||
switch r {
|
||||
case '.':
|
||||
return '/'
|
||||
case '/':
|
||||
return '.'
|
||||
}
|
||||
return r
|
||||
}
|
||||
return strings.Map(f, val)
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sysctl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestConvertSysctlVariableToDotsSeparator tests whether the sysctl variable
|
||||
// can be correctly converted to a dot as a separator.
|
||||
func TestConvertSysctlVariableToDotsSeparator(t *testing.T) {
|
||||
type testCase struct {
|
||||
in string
|
||||
out string
|
||||
}
|
||||
valid := []testCase{
|
||||
{in: "kernel.shm_rmid_forced", out: "kernel.shm_rmid_forced"},
|
||||
{in: "kernel/shm_rmid_forced", out: "kernel.shm_rmid_forced"},
|
||||
{in: "net.ipv4.conf.eno2/100.rp_filter", out: "net.ipv4.conf.eno2/100.rp_filter"},
|
||||
{in: "net/ipv4/conf/eno2.100/rp_filter", out: "net.ipv4.conf.eno2/100.rp_filter"},
|
||||
{in: "net/ipv4/ip_local_port_range", out: "net.ipv4.ip_local_port_range"},
|
||||
{in: "kernel/msgmax", out: "kernel.msgmax"},
|
||||
{in: "kernel/sem", out: "kernel.sem"},
|
||||
}
|
||||
|
||||
for _, test := range valid {
|
||||
convertSysctlVal := NormalizeName(test.in)
|
||||
assert.Equalf(t, test.out, convertSysctlVal, "The sysctl variable was not converted correctly. got: %s, want: %s", convertSysctlVal, test.out)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user