pkg/upgradereadiness/checks_137_test.go
Rama main · réplica de lectura del repositorio original.
package upgradereadiness
import (
"strings"
"testing"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func scan137(t *testing.T, input *Input) *ScanResults {
t.Helper()
result, err := Scan(input, "1.36", "1.37")
if err != nil {
t.Fatal(err)
}
return result
}
func TestRemovedFeatureGates137(t *testing.T) {
for _, tc := range []struct {
name string
configure func(*Input)
wantStatus CheckStatus
wantPath string
wantImpact string
wantRef string
}{
{
name: "effective kubelet gate",
configure: func(input *Input) {
input.NodeRuntimeEvidence[0].FeatureGates["SidecarContainers"] = true
},
wantStatus: CheckBlocked,
wantPath: "kubeletconfig.featureGates.SidecarContainers",
wantRef: "/pull/137755",
},
{
name: "static pod API opt-out",
configure: func(input *Input) {
input.NodeRuntimeEvidence[0].FeatureGates["PreventStaticPodAPIReferences"] = false
},
wantStatus: CheckBlocked,
wantImpact: "dependent static Pod cannot start",
wantRef: "/pull/140226",
},
{
name: "control plane mirror pod split command and args",
configure: func(input *Input) {
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-apiserver", Command: []string{"kube-apiserver", "--feature-gates"}, Args: []string{"APIServerTracing=false"}}}},
}}
},
wantStatus: CheckBlocked,
wantPath: "spec.containers[0].command[--feature-gates].APIServerTracing",
wantRef: "/pull/138907",
},
{
name: "gate present only in the lifecycle diff",
configure: func(input *Input) {
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-scheduler-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-scheduler", Args: []string{"--feature-gates=SchedulerQueueingHints=false"}}}},
}}
},
wantStatus: CheckBlocked,
wantPath: "spec.containers[0].args[--feature-gates].SchedulerQueueingHints",
wantRef: "versioned_feature_list.yaml",
},
{
name: "configz unavailable",
configure: func(input *Input) {
input.NodeRuntimeEvidence[0].ConfigAvailable = false
},
wantStatus: CheckUnknown,
},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
tc.configure(input)
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != tc.wantStatus {
t.Fatalf("check = %+v, want status %s", check, tc.wantStatus)
}
if tc.wantPath != "" && (len(check.Findings) != 1 || check.Findings[0].Evidence.Path != tc.wantPath) {
t.Fatalf("findings = %+v, want path %q", check.Findings, tc.wantPath)
}
if tc.wantImpact != "" && (len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, tc.wantImpact)) {
t.Fatalf("findings = %+v, want impact containing %q", check.Findings, tc.wantImpact)
}
if tc.wantRef != "" {
requireFindingReference(t, check.Findings, tc.wantRef)
}
})
}
}
func TestFeatureGateLifecycleCatalog137(t *testing.T) {
removed := []string{
"AnonymousAuthConfigurableEndpoints", "APIServerTracing", "AnyVolumeDataSource", "AuthorizeNodeWithSelectors", "AuthorizeWithSelectors",
"BtreeWatchCache", "ConsistentListFromCache", "GangScheduling", "JobBackoffLimitPerIndex", "JobPodReplacementPolicy",
"JobSuccessPolicy", "LogarithmicScaleDown", "OrderedNamespaceDeletion", "PodLifecycleSleepAction", "PodLifecycleSleepActionAllowZero",
"PreventStaticPodAPIReferences", "RelaxedDNSSearchValidation", "ResilientWatchCacheInitialization", "RetryGenerateName", "SchedulerQueueingHints",
"SidecarContainers", "StreamingCollectionEncodingToJSON", "StreamingCollectionEncodingToProtobuf", "StructuredAuthenticationConfiguration", "WorkloadAwarePreemption",
}
if len(removedFeatureGates137) != len(removed) {
t.Fatalf("removed feature-gate count = %d, want %d from the 1.36→1.37 lifecycle diff", len(removedFeatureGates137), len(removed))
}
for _, name := range removed {
if !removedFeatureGates137[name] {
t.Errorf("removed feature-gate catalog is missing %s", name)
}
}
locked := map[string]bool{
"DeclarativeValidationTakeover": false, "DisableCPUQuotaWithExclusiveCPUs": true, "DRAExtendedResource": true,
"DRAPrioritizedList": true, "DRAResourceClaimDeviceStatus": true, "HostnameOverride": true,
"HPAConfigurableTolerance": true, "InPlacePodVerticalScalingInitContainers": true, "NodeDeclaredFeatures": true,
"PLEGOnDemandRelist": true, "PodReadyToStartContainersCondition": true, "RelaxedServiceNameValidation": true,
"WatchCacheInitializationPostStartHook": true,
}
if len(lockedFeatureGates137) != len(locked) {
t.Fatalf("newly locked feature-gate count = %d, want %d from the 1.37 lifecycle", len(lockedFeatureGates137), len(locked))
}
for name, wantDefault := range locked {
if gotDefault, ok := lockedFeatureGates137[name]; !ok || gotDefault != wantDefault {
t.Errorf("locked feature gate %s = (%t, %t), want default %t", name, gotDefault, ok, wantDefault)
}
}
}
func TestLockedAPIServerFeatureGate137(t *testing.T) {
for _, tc := range []struct {
name string
value string
wantStatus CheckStatus
}{
{name: "locked default is accepted", value: "false", wantStatus: CheckPassed},
{name: "non-default is blocked", value: "true", wantStatus: CheckBlocked},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-apiserver", Args: []string{"--feature-gates=DeclarativeValidationTakeover=" + tc.value}}}},
}}
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != tc.wantStatus {
t.Fatalf("check = %+v, want status %s", check, tc.wantStatus)
}
if check.Inspected != 2 {
t.Fatalf("inspected = %d, want one node and one control-plane container", check.Inspected)
}
if tc.wantStatus == CheckPassed && len(check.Findings) != 0 {
t.Fatalf("locked default produced findings: %+v", check.Findings)
}
if tc.wantStatus == CheckBlocked && (len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, "locks this kube-apiserver feature gate to false")) {
t.Fatalf("non-default finding = %+v", check.Findings)
}
if tc.wantStatus == CheckBlocked {
requireFindingReference(t, check.Findings, "/pull/139212")
}
})
}
}
func TestLockedFeatureGateAcrossComponents137(t *testing.T) {
for _, component := range []string{"kube-controller-manager", "kube-scheduler"} {
t.Run(component, func(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: component + "-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: component, Args: []string{"--feature-gates=DeclarativeValidationTakeover=true"}}}},
}}
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckBlocked || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, component) {
t.Fatalf("%s locked gate = %+v, want component-specific blocker", component, check)
}
})
}
input := completeInput()
input.NodeRuntimeEvidence[0].FeatureGates["DeclarativeValidationTakeover"] = true
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckBlocked || len(check.Findings) != 1 || check.Findings[0].Resource.Kind != "Node" {
t.Fatalf("kubelet locked gate = %+v, want node blocker", check)
}
input = completeInput()
input.NodeRuntimeEvidence[0].FeatureGates["DisableCPUQuotaWithExclusiveCPUs"] = false
check = checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckBlocked || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, "locks this kubelet feature gate to true") {
t.Fatalf("newly locked kubelet gate = %+v, want blocker", check)
}
requireFindingReference(t, check.Findings, "versioned_feature_list.yaml")
}
func TestRemovedFeatureGatesReportsManagedControlPlaneGap137(t *testing.T) {
input := completeInput()
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "self-managed control-plane") {
t.Fatalf("unclassified control plane = %+v, want unknown without mirror Pod evidence", check)
}
input = completeInput()
input.Nodes[0].Labels = map[string]string{"eks.amazonaws.com/nodegroup": "workers"}
check = checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckPassed || check.Caveat != "" || !strings.Contains(check.EvidenceNote, "provider manages the control plane") {
t.Fatalf("managed control plane = %+v, want passed node evidence with a non-limiting provider note", check)
}
input = completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-apiserver"}}},
}}
check = checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckPassed || !strings.Contains(check.Caveat, "kube-controller-manager, kube-scheduler") {
t.Fatalf("partial control-plane evidence = %+v, want missing-component caveat", check)
}
input = completeInput()
input.Nodes = nil
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-apiserver", Args: []string{"--feature-gates=APIServerTracing=false"}}}},
}}
check = checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckBlocked || !strings.Contains(check.Caveat, "Node evidence was unavailable") {
t.Fatalf("control-plane blocker without node evidence = %+v, want blocker with kubelet coverage caveat", check)
}
}
func TestRemovedFeatureGatesPreservesMissingNodeSummary137(t *testing.T) {
input := completeInput()
input.Nodes = nil
input.Pods = nil
check := checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckUnknown || !strings.Contains(check.Summary, "Node evidence was unavailable") || strings.Contains(check.Summary, "readable kubelet configuration") {
t.Fatalf("missing node and control-plane evidence = %+v, want the node evidence boundary in the summary", check)
}
input = completeInput()
input.Nodes = []*corev1.Node{}
input.Pods = []*corev1.Pod{}
check = checkByID(t, scan137(t, input), "removed-feature-gates")
if check.Status != CheckUnknown || check.Inspected != 0 || !strings.Contains(check.Summary, "No kubelet or control-plane") {
t.Fatalf("empty node and control-plane evidence = %+v, want unknown without a clean claim", check)
}
}
func TestRemovedControlPlaneConfigurationScope137(t *testing.T) {
input := completeInput()
input.Namespaces = []string{"apps"}
featureGates := checkByID(t, scan137(t, input), "removed-feature-gates")
if featureGates.Status != CheckUnknown || !strings.Contains(featureGates.Caveat, "kube-system") {
t.Fatalf("scoped feature-gate evidence = %+v, want unknown with kube-system caveat", featureGates)
}
componentFlags := checkByID(t, scan137(t, input), "removed-component-flags")
if componentFlags.Status != CheckUnknown || !strings.Contains(componentFlags.Summary, "kube-system") {
t.Fatalf("scoped component-flag evidence = %+v, want unknown", componentFlags)
}
input = completeInput()
input.Pods = nil
featureGates = checkByID(t, scan137(t, input), "removed-feature-gates")
if featureGates.Status != CheckUnknown || !strings.Contains(featureGates.Caveat, "Pods were unavailable") {
t.Fatalf("unavailable Pod evidence = %+v, want unknown", featureGates)
}
}
func TestManagedControlPlaneScopeDoesNotBecomeUnknown137(t *testing.T) {
for _, tc := range []struct {
name string
configure func(*Input)
}{
{name: "kube-system outside Pod informer", configure: func(input *Input) {
input.CacheScopedKinds = map[string][]string{"pods": {"team-a"}}
}},
{name: "Pods unavailable", configure: func(input *Input) {
input.Pods = nil
}},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
input.Nodes[0].Labels = map[string]string{"eks.amazonaws.com/nodegroup": "workers"}
tc.configure(input)
featureGates := checkByID(t, scan137(t, input), "removed-feature-gates")
if featureGates.Status != CheckPassed || featureGates.Caveat != "" || !strings.Contains(featureGates.EvidenceNote, "provider manages the control plane") {
t.Fatalf("managed feature-gate scope = %+v, want passed kubelet evidence with provider boundary", featureGates)
}
componentFlags := checkByID(t, scan137(t, input), "removed-component-flags")
if componentFlags.Status != CheckNotApplicable || !strings.Contains(componentFlags.Summary, "provider manages the control plane") {
t.Fatalf("managed component-option scope = %+v, want not applicable", componentFlags)
}
input.NodeRuntimeEvidence[0].FeatureGates["SidecarContainers"] = true
featureGates = checkByID(t, scan137(t, input), "removed-feature-gates")
if featureGates.Status != CheckBlocked || len(featureGates.Findings) != 1 || featureGates.Caveat != "" || !strings.Contains(featureGates.EvidenceNote, "provider manages the control plane") || !strings.Contains(featureGates.EvidenceNote, "does not reveal") || !strings.Contains(featureGates.Findings[0].Remediation, "provider-supported") {
t.Fatalf("managed control plane with kubelet blocker = %+v, want blocker with provider boundary", featureGates)
}
})
}
}
func TestKubeletEventRecordQPS137(t *testing.T) {
input := completeInput()
input.NodeRuntimeEvidence[0].EventRecordQPS = 0
check := checkByID(t, scan137(t, input), "kubelet-event-qps-change")
if check.Status != CheckWarning || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, "client-go") || !strings.Contains(check.Findings[0].Impact, "5 events per second") || !strings.Contains(check.Findings[0].Remediation, "upgrade note recommends 50") || !strings.Contains(check.Findings[0].Remediation, "matching the old explicit-zero runtime limit") {
t.Fatalf("zero eventRecordQPS = %+v, want behavior-change warning", check)
}
requireFindingReference(t, check.Findings, "/pull/117119")
requireFindingReference(t, check.Findings, "/blob/v1.36.0/")
input = completeInput()
input.NodeRuntimeEvidence[0].EventRecordQPSAvailable = false
check = checkByID(t, scan137(t, input), "kubelet-event-qps-change")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "debugging handlers disabled") {
t.Fatalf("missing eventRecordQPS = %+v, want unknown", check)
}
}
func TestKubeletEventQPSObservedProxyDenialDoesNotBlameDebuggingHandlers(t *testing.T) {
input := completeInput()
input.NodeProxyForbidden = true
input.NodeRuntimeEvidence = []NodeRuntimeEvidence{{NodeName: "node-a", FeatureGates: map[string]bool{}}}
check := checkByID(t, scan137(t, input), "kubelet-event-qps-change")
if !strings.Contains(check.Caveat, "nodes/proxy request was forbidden") || strings.Contains(check.Caveat, "debugging handlers disabled") {
t.Fatalf("observed proxy denial caveat = %q, want RBAC attribution without a configz explanation", check.Caveat)
}
}
func TestRemovedComponentFlag137(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-controller-manager-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-controller-manager", Command: []string{"kube-controller-manager", "--concurrent-service-syncs=10"}}}},
}}
check := checkByID(t, scan137(t, input), "removed-component-flags")
if check.Status != CheckBlocked || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Evidence.Path, ".command[") {
t.Fatalf("removed component flag = %+v, want blocker", check)
}
requireFindingReference(t, check.Findings, "/pull/138002")
input = completeInput()
check = checkByID(t, scan137(t, input), "removed-component-flags")
if check.Status != CheckUnknown || !strings.Contains(check.Summary, "self-managed") {
t.Fatalf("unclassified control plane = %+v, want unknown", check)
}
input.Nodes[0].Labels = map[string]string{"cloud.google.com/gke-nodepool": "default"}
check = checkByID(t, scan137(t, input), "removed-component-flags")
if check.Status != CheckNotApplicable || !strings.Contains(check.Summary, "provider manages") {
t.Fatalf("managed control plane = %+v, want not applicable", check)
}
}
func TestRemovedPodGroupAdmissionPlugin137(t *testing.T) {
for _, tc := range []struct {
name string
container corev1.Container
wantPath string
}{
{name: "enabled in args", container: corev1.Container{Name: "kube-apiserver", Args: []string{"--enable-admission-plugins=NodeRestriction,PodGroupWorkloadExists"}}, wantPath: ".args[--enable-admission-plugins]"},
{name: "disabled with split command value", container: corev1.Container{Name: "kube-apiserver", Command: []string{"kube-apiserver", "--disable-admission-plugins"}, Args: []string{"PodGroupWorkloadExists"}}, wantPath: ".command[--disable-admission-plugins]"},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "control-plane-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{
tc.container,
{Name: "kube-controller-manager"},
}},
}}
check := checkByID(t, scan137(t, input), "removed-component-flags")
if check.Status != CheckBlocked || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Evidence.Path, tc.wantPath) || !strings.Contains(check.Findings[0].Impact, "fails during startup") {
t.Fatalf("removed admission plugin = %+v, want source-anchored blocker", check)
}
requireFindingReference(t, check.Findings, "/pull/139008")
})
}
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "control-plane-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{
{Name: "kube-apiserver", Args: []string{"--enable-admission-plugins=NodeRestriction"}},
{Name: "kube-controller-manager"},
}},
}}
check := checkByID(t, scan137(t, input), "removed-component-flags")
if check.Status != CheckPassed || check.Caveat != "" {
t.Fatalf("clean complete component options = %+v, want passed", check)
}
}
func TestRemovedKubeletCAdvisorOptions137(t *testing.T) {
wantFlags := []string{
"--application-metrics-count-limit", "--boot-id-file", "--container-hints", "--containerd", "--containerd-namespace",
"--enable-load-reader", "--event-storage-age-limit", "--event-storage-event-limit", "--global-housekeeping-interval",
"--log-cadvisor-usage", "--machine-id-file", "--storage-driver-user", "--storage-driver-password", "--storage-driver-host",
"--storage-driver-db", "--storage-driver-table", "--storage-driver-secure", "--storage-driver-buffer-duration",
}
if strings.Join(removedKubeletCAdvisorFlags137, "\n") != strings.Join(wantFlags, "\n") {
t.Fatalf("removed cAdvisor flags = %v, want exact upstream list %v", removedKubeletCAdvisorFlags137, wantFlags)
}
check := checkByID(t, scan137(t, completeInput()), "removed-kubelet-cadvisor-options")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, "fails to start") || !strings.Contains(check.Findings[0].Remediation, "--housekeeping-interval remains supported") || !strings.Contains(check.Findings[0].Remediation, "container_application_*") {
t.Fatalf("cAdvisor manual review = %+v, want bounded review with startup and metric impact", check)
}
requireFindingReference(t, check.Findings, "/pull/139870")
}
func TestRemovedSchedulingAPIs137(t *testing.T) {
input := completeInput()
input.SchedulingV1Alpha2Installed = true
input.SchedulingV1Alpha2Objects = []*unstructured.Unstructured{{Object: map[string]any{
"apiVersion": "scheduling.k8s.io/v1alpha2",
"kind": "PodGroup",
"metadata": map[string]any{"namespace": "batch", "name": "workers"},
}}}
check := checkByID(t, scan137(t, input), "removed-scheduling-apis")
if check.Status != CheckBlocked || len(check.Findings) != 1 || check.Findings[0].Resource.Kind != "PodGroup" || !strings.Contains(check.Findings[0].Remediation, "After the control plane reaches Kubernetes 1.37") {
t.Fatalf("stored alpha scheduling object = %+v, want blocker", check)
}
requireFindingReference(t, check.Findings, "/pull/140184")
input = completeInput()
input.SchedulingV1Alpha2DiscoveryAvailable = false
check = checkByID(t, scan137(t, input), "removed-scheduling-apis")
if check.Status != CheckUnknown {
t.Fatalf("missing scheduling discovery = %+v, want unknown", check)
}
input = completeInput()
check = checkByID(t, scan137(t, input), "removed-scheduling-apis")
if check.Status != CheckNotApplicable {
t.Fatalf("absent scheduling API = %+v, want not applicable", check)
}
input = completeInput()
input.SchedulingV1Alpha2Installed = true
input.Namespaces = []string{"apps"}
check = checkByID(t, scan137(t, input), "removed-scheduling-apis")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "Only namespace apps was inspected") {
t.Fatalf("namespace-scoped scheduling evidence = %+v, want unknown with scope caveat", check)
}
}
func TestKubeadmV1Beta3Config137(t *testing.T) {
input := completeInput()
input.ConfigMaps = []*corev1.ConfigMap{{
ObjectMeta: metav1.ObjectMeta{Name: "kubeadm-config", Namespace: "kube-system"},
Data: map[string]string{"ClusterConfiguration": "apiVersion: kubeadm.k8s.io/v1beta3\nkind: ClusterConfiguration\n"},
}}
check := checkByID(t, scan137(t, input), "kubeadm-config-v1beta3")
if check.Status != CheckBlocked || check.Inspected != 1 || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Remediation, "config migrate") || !strings.Contains(check.Findings[0].Remediation, "upload-config kubeadm") {
t.Fatalf("kubeadm v1beta3 = %+v, want blocker", check)
}
requireFindingReference(t, check.Findings, "/pull/136016")
input.ConfigMaps[0].Data["ClusterConfiguration"] = "apiVersion: kubeadm.k8s.io/v1beta4\nkind: ClusterConfiguration\n"
check = checkByID(t, scan137(t, input), "kubeadm-config-v1beta3")
if check.Status != CheckPassed || check.Caveat != "" || !strings.Contains(check.EvidenceNote, "--config") || !strings.Contains(check.EvidenceNote, "not visible") {
t.Fatalf("kubeadm v1beta4 = %+v, want pass with a non-limiting host-file evidence note", check)
}
input.ConfigMaps[0].Data["ClusterConfiguration"] = "apiVersion: kubeadm.k8s.io/v1beta4\nkind: ClusterConfiguration\nfeatureGates:\n NodeLocalCRISocket: true\n"
check = checkByID(t, scan137(t, input), "kubeadm-config-v1beta3")
if check.Status != CheckBlocked || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Title, "NodeLocalCRISocket") {
t.Fatalf("removed kubeadm feature gate = %+v, want blocker", check)
}
requireFindingReference(t, check.Findings, "/pull/138645")
input.ConfigMaps[0].Data["ClusterConfiguration"] = "[not yaml"
check = checkByID(t, scan137(t, input), "kubeadm-config-v1beta3")
if check.Status != CheckUnknown || !strings.Contains(check.EvidenceNote, "--config") || !strings.Contains(check.Caveat, "could not fully parse") {
t.Fatalf("unparseable kubeadm config = %+v, want unknown with both evidence boundaries", check)
}
}
func kubeProxyDaemonSet(args ...string) *appsv1.DaemonSet {
return &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "kube-proxy", Namespace: "kube-system", Labels: map[string]string{"k8s-app": "kube-proxy"}},
Spec: appsv1.DaemonSetSpec{Template: corev1.PodTemplateSpec{Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-proxy", Args: args}}}}},
}
}
func TestKubeProxyModeTransition137(t *testing.T) {
for _, tc := range []struct {
name string
args []string
wantStatus CheckStatus
wantRef string
wantImpact string
wantSummary string
}{
{name: "iptables explicit", args: []string{"--proxy-mode=iptables"}, wantStatus: CheckPassed},
{name: "ipvs deprecated", args: []string{"--proxy-mode=ipvs"}, wantStatus: CheckReview, wantRef: "5495-deprecate-ipvs-mode-in-kube-proxy"},
{name: "linux default changing", wantStatus: CheckReview, wantRef: "5343-nftables-to-default", wantImpact: "Kubernetes 1.40", wantSummary: "1 kube-proxy mode setting requires migration review."},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
input.DaemonSets = []*appsv1.DaemonSet{kubeProxyDaemonSet(tc.args...)}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != tc.wantStatus {
t.Fatalf("check = %+v, want %s", check, tc.wantStatus)
}
if tc.wantRef != "" {
requireFindingReference(t, check.Findings, tc.wantRef)
}
if tc.wantImpact != "" && (len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, tc.wantImpact)) {
t.Fatalf("impact = %+v, want %q", check.Findings, tc.wantImpact)
}
if tc.wantSummary != "" && check.Summary != tc.wantSummary {
t.Fatalf("summary = %q, want %q", check.Summary, tc.wantSummary)
}
})
}
}
func TestKubeProxyCommandEvidencePath137(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet()
daemonSet.Spec.Template.Spec.Containers[0].Command = []string{"kube-proxy", "--proxy-mode=ipvs"}
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Evidence.Path, ".command[") {
t.Fatalf("command-based proxy mode = %+v, want command evidence path", check)
}
}
func TestKubeProxySoleFallbackContainerUsesExactEvidencePath137(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet()
daemonSet.Spec.Template.Spec.Containers[0].Name = "network-proxy"
daemonSet.Spec.Template.Spec.Containers[0].Args = []string{"--proxy-mode=ipvs"}
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Evidence.Path, "containers[network-proxy]") {
t.Fatalf("fallback container evidence = %+v, want its actual name in the path", check)
}
}
func TestManagedKubeProxyUsesProviderRemediation137(t *testing.T) {
input := completeInput()
input.Nodes[0].Labels = map[string]string{"cloud.google.com/gke-nodepool": "default"}
input.DaemonSets = []*appsv1.DaemonSet{kubeProxyDaemonSet()}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Remediation, "provider-supported") || !strings.Contains(check.Findings[0].Remediation, "do not edit") {
t.Fatalf("managed kube-proxy remediation = %+v, want provider-aware action", check)
}
}
func TestKubeProxyModeTransitionPersistsUntilDefaultChange(t *testing.T) {
input := completeInput()
input.DaemonSets = []*appsv1.DaemonSet{kubeProxyDaemonSet("--proxy-mode=ipvs")}
for _, tc := range []struct {
current string
target string
want bool
}{
{current: "1.36", target: "1.37", want: true},
{current: "1.36", target: "1.38", want: true},
{current: "1.37", target: "1.38", want: true},
{current: "1.38", target: "1.39", want: true},
{current: "1.39", target: "1.40", want: true},
{current: "1.40", target: "1.41", want: false},
} {
result, err := Scan(input, tc.current, tc.target)
if err != nil {
t.Fatal(err)
}
found := false
for _, check := range result.Checks {
if check.ID == "kube-proxy-mode-transition" {
found = true
break
}
}
if found != tc.want {
t.Fatalf("Scan(%s, %s) includes kube-proxy transition = %v, want %v", tc.current, tc.target, found, tc.want)
}
}
}
func TestKubeProxyModeTransitionInspectsMirrorPods137(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "kube-proxy-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-proxy", Args: []string{"--proxy-mode=ipvs"}}}},
}}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || check.Findings[0].Resource == nil || check.Findings[0].Resource.Kind != "Pod" || check.Findings[0].Evidence.Detail != "ipvs" {
t.Fatalf("mirror Pod kube-proxy = %+v, want Pod-backed IPVS review", check)
}
input.Pods[0].Spec.Containers[0].Args = []string{"--config=/var/lib/kube-proxy/config.yaml"}
check = checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "not readable through the Kubernetes API") {
t.Fatalf("mirror Pod file config = %+v, want unknown", check)
}
}
func TestKubeProxyModeTransitionWithoutObservableWorkloadIsUnknown137(t *testing.T) {
check := checkByID(t, scan137(t, completeInput()), "kube-proxy-mode-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Summary, "DaemonSet or mirror Pod") {
t.Fatalf("unobservable kube-proxy = %+v, want unknown", check)
}
}
func TestKubeProxyWindowsPodOSDefaultsToKernelspace137(t *testing.T) {
for _, tc := range []struct {
name string
configure func(*appsv1.DaemonSet)
}{
{name: "pod OS", configure: func(daemonSet *appsv1.DaemonSet) {
daemonSet.Spec.Template.Spec.OS = &corev1.PodOS{Name: corev1.Windows}
}},
{name: "node selector", configure: func(daemonSet *appsv1.DaemonSet) {
daemonSet.Spec.Template.Spec.NodeSelector = map[string]string{corev1.LabelOSStable: "windows"}
}},
} {
t.Run(tc.name, func(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet()
tc.configure(daemonSet)
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckPassed {
t.Fatalf("Windows kube-proxy default = %+v, want passed", check)
}
})
}
}
func TestKubeProxyConfigFileOverridesFlag137(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet("--config=/var/lib/kube-proxy/config.conf", "--proxy-mode=iptables")
daemonSet.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "config", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "kube-proxy"}}}}}
daemonSet.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{Name: "config", MountPath: "/var/lib/kube-proxy"}}
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
input.ConfigMaps = []*corev1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: "kube-proxy", Namespace: "kube-system"}, Data: map[string]string{"config.conf": "apiVersion: kubeproxy.config.k8s.io/v1alpha1\nmode: ipvs\n"}}}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Evidence.Path, "ConfigMap/") {
t.Fatalf("mounted kube-proxy config = %+v, want authoritative IPVS finding", check)
}
input.ConfigMaps = []*corev1.ConfigMap{}
check = checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckUnknown {
t.Fatalf("missing mounted kube-proxy config = %+v, want unknown", check)
}
}
func TestKubeSystemCacheScopesFollowActualEvidence137(t *testing.T) {
input := completeInput()
input.Pods = []*corev1.Pod{
{ObjectMeta: metav1.ObjectMeta{Name: "kube-apiserver-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-apiserver"}}}},
{ObjectMeta: metav1.ObjectMeta{Name: "kube-controller-manager-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-controller-manager"}}}},
{ObjectMeta: metav1.ObjectMeta{Name: "kube-scheduler-node-a", Namespace: "kube-system", Annotations: map[string]string{corev1.MirrorPodAnnotationKey: "mirror"}}, Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "kube-scheduler"}}}},
}
input.ConfigMaps = []*corev1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: "kubeadm-config", Namespace: "kube-system"}, Data: map[string]string{"ClusterConfiguration": "apiVersion: kubeadm.k8s.io/v1beta4\nkind: ClusterConfiguration\n"}}}
input.DaemonSets = []*appsv1.DaemonSet{kubeProxyDaemonSet("--proxy-mode=iptables")}
input.CacheScopedKinds = map[string][]string{
"pods": {"kube-system"},
"configmaps": {"team-a"},
"daemonsets": {"kube-system"},
}
result := scan137(t, input)
for _, id := range []string{"removed-feature-gates", "removed-component-flags"} {
check := checkByID(t, result, id)
if check.Status != CheckPassed || strings.Contains(check.Caveat, "Cached evidence is namespace-limited") {
t.Fatalf("%s = %+v, kube-system Pod coverage should be sufficient", id, check)
}
}
kubeadm := checkByID(t, result, "kubeadm-config-v1beta3")
if kubeadm.Status != CheckUnknown || !strings.Contains(kubeadm.Summary, "kube-system is outside") {
t.Fatalf("kubeadm scope = %+v, want unknown because ConfigMaps exclude kube-system", kubeadm)
}
proxy := checkByID(t, result, "kube-proxy-mode-transition")
if proxy.Status != CheckPassed || proxy.Caveat != "" {
t.Fatalf("flag-based kube-proxy mode = %+v, ConfigMap scope should be irrelevant", proxy)
}
input.CacheScopedKinds["pods"] = []string{"team-a"}
input.CacheScopedKinds["daemonsets"] = []string{"team-a"}
result = scan137(t, input)
for _, id := range []string{"removed-feature-gates", "removed-component-flags", "kube-proxy-mode-transition"} {
check := checkByID(t, result, id)
if check.Status != CheckUnknown || !strings.Contains(check.Summary+" "+check.Caveat, "kube-system is outside") {
t.Fatalf("%s = %+v, want unknown when its informer excludes kube-system", id, check)
}
}
input.CacheScopedKinds["pods"] = []string{"kube-system"}
input.CacheScopedKinds["daemonsets"] = []string{"kube-system"}
configMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "kube-proxy", Namespace: "kube-system"}, Data: map[string]string{"config.conf": "mode: nftables\n"}}
configuredProxy := kubeProxyDaemonSet("--config=/var/lib/kube-proxy/config.conf")
configuredProxy.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "config", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "kube-proxy"}}}}}
configuredProxy.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{Name: "config", MountPath: "/var/lib/kube-proxy"}}
input.DaemonSets = []*appsv1.DaemonSet{configuredProxy}
input.ConfigMaps = append(input.ConfigMaps, configMap)
result = scan137(t, input)
proxy = checkByID(t, result, "kube-proxy-mode-transition")
if proxy.Status != CheckUnknown || !strings.Contains(proxy.Caveat, "outside the readable ConfigMap scope") {
t.Fatalf("file-based kube-proxy mode = %+v, want ConfigMap scope boundary", proxy)
}
input.CacheScopedKinds["configmaps"] = []string{"kube-system"}
result = scan137(t, input)
for _, id := range []string{"kubeadm-config-v1beta3", "kube-proxy-mode-transition"} {
check := checkByID(t, result, id)
if check.Status != CheckPassed || strings.Contains(check.Caveat, "Cached evidence is namespace-limited") {
t.Fatalf("%s = %+v, kube-system ConfigMap coverage should be sufficient", id, check)
}
}
}
func TestKubeProxyConfigMapItemSubPath137(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet("--config=/var/lib/kube-proxy/config.yaml")
daemonSet.Spec.Template.Spec.Volumes = []corev1.Volume{{Name: "config", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "kube-proxy"},
Items: []corev1.KeyToPath{{Key: "config.conf", Path: "projected.yaml"}},
}}}}
daemonSet.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{Name: "config", MountPath: "/var/lib/kube-proxy/config.yaml", SubPath: "projected.yaml"}}
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
input.ConfigMaps = []*corev1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: "kube-proxy", Namespace: "kube-system"}, Data: map[string]string{"config.conf": "mode: nftables\n"}}}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckPassed {
t.Fatalf("ConfigMap item path through subPath = %+v, want passed", check)
}
}
func TestKubeProxyModeDiagnostics137(t *testing.T) {
input := completeInput()
daemonSet := kubeProxyDaemonSet()
daemonSet.Spec.Template.Spec.Containers = []corev1.Container{{Name: "sidecar"}, {Name: "proxy"}}
input.DaemonSets = []*appsv1.DaemonSet{daemonSet}
check := checkByID(t, scan137(t, input), "kube-proxy-mode-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "none named kube-proxy") {
t.Fatalf("ambiguous kube-proxy container = %+v, want actionable caveat", check)
}
if value, field, found := commandFlag(nil, []string{"--proxy-mode", "--config=/etc/kube-proxy.yaml"}, "--proxy-mode"); found || value != "" || field != "" {
t.Fatalf("flag followed by another flag = (%q, %q, %v), want absent value", value, field, found)
}
if gates := parsedFeatureGates(nil, []string{"--feature-gates", "--secure-port=6443"}); len(gates) != 0 {
t.Fatalf("feature-gates followed by another flag = %v, want no parsed gates", gates)
}
}
func TestRemovedControlPlaneMetrics137(t *testing.T) {
input := completeInput()
input.PrometheusRulesInstalled = true
input.PrometheusRules = []*unstructured.Unstructured{{Object: map[string]any{
"apiVersion": "monitoring.coreos.com/v1",
"kind": "PrometheusRule",
"metadata": map[string]any{"namespace": "monitoring", "name": "apiserver"},
"spec": map[string]any{
"groups": []any{map[string]any{
"rules": []any{map[string]any{
"alert": "CacheLists",
"expr": "rate(apiserver_cache_list_total[5m]) > 0",
}},
}},
},
}}}
check := checkByID(t, scan137(t, input), "removed-control-plane-metrics")
if check.Status != CheckPassed || len(check.Findings) != 0 {
t.Fatalf("cache-list metric deprecation = %+v, want no 1.37 compatibility warning", check)
}
groups := input.PrometheusRules[0].Object["spec"].(map[string]any)["groups"].([]any)
rule := groups[0].(map[string]any)["rules"].([]any)[0].(map[string]any)
rule["expr"] = "sum(resourceclaim_controller_resource_claims)"
check = checkByID(t, scan137(t, input), "removed-control-plane-metrics")
if check.Status != CheckWarning || len(check.Findings) != 1 || check.Findings[0].Evidence.Detail != "resourceclaim_controller_resource_claims" || !strings.Contains(check.Findings[0].Remediation, "dynamic_resource_allocation_resource_claims") {
t.Fatalf("renamed DRA metric = %+v, want exact replacement", check)
}
requireFindingReference(t, check.Findings, "/pull/138542")
for _, metric := range []string{"container_cpu_load_average_10s", "container_cpu_load_d_average_10s", "container_tasks_state"} {
rule["expr"] = "sum(" + metric + ")"
check = checkByID(t, scan137(t, input), "removed-control-plane-metrics")
if check.Status != CheckWarning || len(check.Findings) != 1 || check.Findings[0].Evidence.Detail != metric || !strings.Contains(check.Findings[0].Remediation, "no replacement") {
t.Fatalf("removed cAdvisor metric %s = %+v, want warning without invented replacement", metric, check)
}
requireFindingReference(t, check.Findings, "/pull/139870")
}
rule["expr"] = "rate(container_application_http_requests_total[5m]) + container_application_queue_depth"
check = checkByID(t, scan137(t, input), "removed-control-plane-metrics")
if check.Status != CheckWarning || len(check.Findings) != 2 || check.Findings[0].Evidence.Detail != "container_application_http_requests_total" || check.Findings[1].Evidence.Detail != "container_application_queue_depth" {
t.Fatalf("removed custom cAdvisor metric = %+v, want prefix match", check)
}
for _, finding := range check.Findings {
requireFindingReference(t, []Finding{finding}, "/pull/139870")
}
}
func requireFindingReference(t *testing.T, findings []Finding, urlSubstring string) {
t.Helper()
if len(findings) != 1 {
t.Fatalf("findings = %+v, want one finding with reference containing %q", findings, urlSubstring)
}
for _, reference := range findings[0].References {
if strings.Contains(reference.URL, urlSubstring) {
return
}
}
t.Fatalf("finding references = %+v, want URL containing %q", findings[0].References, urlSubstring)
}
func TestEvery137ConfigurationRemovalHasAnUpstreamReference(t *testing.T) {
for name := range removedFeatureGates137 {
if len(removedFeatureGateReferences137[name]) == 0 {
t.Errorf("removed feature gate %s has no upstream reference", name)
}
}
for name := range lockedFeatureGates137 {
if len(lockedFeatureGateReferences137[name]) == 0 {
t.Errorf("locked feature gate %s has no upstream reference", name)
}
}
for name := range removedKubeadmFeatureGates137 {
if len(kubeadmFeatureGateReferences137[name]) == 0 {
t.Errorf("removed kubeadm feature gate %s has no upstream reference", name)
}
}
if len(podGroupAdmissionReferences137) == 0 {
t.Error("removed PodGroupWorkloadExists admission plugin has no upstream reference")
}
}
func selinuxSharedVolumeInput(t *testing.T, firstPolicy, secondPolicy *corev1.PodSELinuxChangePolicy, firstLevel, secondLevel string) *Input {
t.Helper()
input := completeInput()
seLinuxMount := true
input.CSIDrivers = []*storagev1.CSIDriver{{ObjectMeta: metav1.ObjectMeta{Name: "csi.example.com"}, Spec: storagev1.CSIDriverSpec{SELinuxMount: &seLinuxMount}}}
input.PersistentVolumes = []*corev1.PersistentVolume{{
ObjectMeta: metav1.ObjectMeta{Name: "shared"},
Spec: corev1.PersistentVolumeSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany},
ClaimRef: &corev1.ObjectReference{Namespace: "apps", Name: "shared"},
PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{Driver: "csi.example.com", VolumeHandle: "shared"}},
},
}}
input.PersistentVolumeClaims = []*corev1.PersistentVolumeClaim{{
ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "apps"},
Spec: corev1.PersistentVolumeClaimSpec{VolumeName: "shared", AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}},
}}
input.Pods = []*corev1.Pod{
selinuxPod("one", firstPolicy, firstLevel),
selinuxPod("two", secondPolicy, secondLevel),
}
return input
}
func selinuxPod(name string, policy *corev1.PodSELinuxChangePolicy, level string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "apps"},
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{SELinuxChangePolicy: policy, SELinuxOptions: &corev1.SELinuxOptions{Level: level}},
Volumes: []corev1.Volume{{Name: "data", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "shared"}}}},
Containers: []corev1.Container{{Name: "app", Image: "example", VolumeMounts: []corev1.VolumeMount{{Name: "data", MountPath: "/data"}}}},
},
Status: corev1.PodStatus{Phase: corev1.PodRunning},
}
}
func TestSELinuxMountTransition137(t *testing.T) {
mountOption := corev1.SELinuxChangePolicyMountOption
recursive := corev1.SELinuxChangePolicyRecursive
compatibleLabels := selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c1,c2")
compatibleLabels.Pods[1].Spec.SecurityContext.SELinuxOptions.User = "system_u"
compatibleLabels.Pods[1].Spec.SecurityContext.SELinuxOptions.Role = "system_r"
compatibleLabels.Pods[1].Spec.SecurityContext.SELinuxOptions.Type = "container_t"
for _, tc := range []struct {
name string
input *Input
wantStatus CheckStatus
wantTitle string
}{
{name: "different labels conflict", input: selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c3,c4"), wantStatus: CheckReview, wantTitle: "conflicting SELinux labels"},
{name: "different policies conflict", input: selinuxSharedVolumeInput(t, &mountOption, &recursive, "s0:c1,c2", "s0:c1,c2"), wantStatus: CheckReview, wantTitle: "conflicting SELinux change policies"},
{name: "unlabeled pod falls back to recursive policy", input: selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", ""), wantStatus: CheckReview, wantTitle: "falls back to recursive SELinux relabeling"},
{name: "recursive opt out", input: selinuxSharedVolumeInput(t, &recursive, &recursive, "s0:c1,c2", "s0:c3,c4"), wantStatus: CheckPassed},
{name: "matching mount labels", input: selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c1,c2"), wantStatus: CheckPassed},
{name: "defaultable label components", input: compatibleLabels, wantStatus: CheckPassed},
} {
t.Run(tc.name, func(t *testing.T) {
check := checkByID(t, scan137(t, tc.input), "selinux-mount-transition")
if check.Status != tc.wantStatus {
t.Fatalf("check = %+v, want %s", check, tc.wantStatus)
}
if tc.wantTitle != "" && (len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Title, tc.wantTitle)) {
t.Fatalf("findings = %+v, want title containing %q", check.Findings, tc.wantTitle)
}
if tc.wantStatus == CheckReview && (!strings.Contains(check.Caveat, "could not confirm") || !strings.Contains(check.Findings[0].Remediation, "Kubernetes 1.37 only")) {
t.Fatalf("conditional SELinux finding = %+v, want applicability caveat and interim opt-out", check)
}
if tc.wantStatus == CheckReview && !strings.Contains(check.Findings[0].Impact, "same SELinux-enforcing node") {
t.Fatalf("conditional SELinux impact = %q, want the same-node precondition", check.Findings[0].Impact)
}
})
}
}
func TestSELinuxMountTransitionDetectsMultipleLabelsWithinOnePod137(t *testing.T) {
input := selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c1,c2")
input.Pods = input.Pods[:1]
input.Pods[0].Spec.Containers = append(input.Pods[0].Spec.Containers, corev1.Container{
Name: "sidecar", Image: "example", VolumeMounts: []corev1.VolumeMount{{Name: "data", MountPath: "/data"}},
SecurityContext: &corev1.SecurityContext{SELinuxOptions: &corev1.SELinuxOptions{Level: "s0:c3,c4"}},
})
check := checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || check.Findings[0].Title != "Pod uses multiple SELinux labels on one volume" || strings.Contains(check.Findings[0].Title, "conflicting SELinux labels") {
t.Fatalf("single-Pod multi-label volume = %+v, want structural in-Pod review", check)
}
}
func TestSELinuxMountTransitionEvidenceBoundaries137(t *testing.T) {
input := completeInput()
input.NodeRuntimeEvidence[0].SELinuxMismatchErrors = 2
check := checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckWarning || len(check.Findings) != 1 || check.Findings[0].Resource.Kind != "Node" {
t.Fatalf("positive kubelet metric = %+v, want node warning", check)
}
if !strings.Contains(check.Findings[0].Remediation, "selinux_warning_controller_selinux_volume_conflict") || !strings.Contains(check.Findings[0].Remediation, "Kubernetes 1.37 only") {
t.Fatalf("kubelet metric remediation = %q, want controller diagnostics and interim opt-out", check.Findings[0].Remediation)
}
input = completeInput()
input.Events = []*corev1.Event{{Reason: "SELinuxLabelConflict", Message: "volume already mounted with a different context", InvolvedObject: corev1.ObjectReference{Kind: "Pod", Namespace: "apps", Name: "api"}}}
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckWarning || len(check.Findings) != 1 || check.Findings[0].Evidence.Source != "event" {
t.Fatalf("positive conflict event = %+v, want warning", check)
}
if !strings.Contains(check.Findings[0].Impact, "selinux-warning-controller") || !strings.Contains(check.Findings[0].Impact, "potential") {
t.Fatalf("conflict event impact = %q, want accurate producer and semantics", check.Findings[0].Impact)
}
if !strings.Contains(check.Findings[0].Remediation, "selinux_warning_controller_selinux_volume_conflict") {
t.Fatalf("conflict event remediation = %q, want the controller metric that identifies both Pods", check.Findings[0].Remediation)
}
input = completeInput()
input.Events = []*corev1.Event{{Reason: "MultipleSELinuxLabels", Message: "Volume shared is mounted twice with different SELinux labels inside this pod", InvolvedObject: corev1.ObjectReference{Kind: "Pod", Namespace: "apps", Name: "api"}}}
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckWarning || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Impact, "this Pod mounts one volume more than once") || strings.Contains(check.Findings[0].Impact, "same node") {
t.Fatalf("single-Pod SELinux event = %+v, want unconditional in-Pod conflict", check)
}
if !strings.Contains(check.Findings[0].Remediation, "securityContext.seLinuxOptions") || strings.Contains(check.Findings[0].Remediation, "identify both Pods") {
t.Fatalf("single-Pod SELinux remediation = %q, want this Pod's security contexts", check.Findings[0].Remediation)
}
input = selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c1,c2")
input.CSIDrivers = nil
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckUnknown {
t.Fatalf("missing CSI driver evidence = %+v, want unknown", check)
}
input = selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c3,c4")
input.PersistentVolumes[0].Spec.AccessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOncePod}
input.PersistentVolumeClaims[0].Spec.AccessModes = []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOncePod}
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckNotApplicable {
t.Fatalf("already-covered ReadWriteOncePod volume = %+v, want not applicable", check)
}
input = completeInput()
input.Nodes = nil
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if !strings.Contains(check.Caveat, "Node evidence was unavailable") || strings.Contains(check.Caveat, "1 Linux node") {
t.Fatalf("missing node evidence = %+v, want no fabricated node count", check)
}
input = completeInput()
input.Pods = nil
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "Pods were unavailable") || strings.Contains(check.Caveat, "Persistent-volume or CSI-driver evidence") {
t.Fatalf("missing Pod evidence = %+v, want Pod-specific incomplete coverage", check)
}
input = completeInput()
input.Namespaces = []string{"apps"}
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "Pods, PersistentVolumeClaims and Events") {
t.Fatalf("namespace-scoped SELinux evidence = %+v, want unknown with scope caveat", check)
}
input = completeInput()
input.CacheScopedKinds = map[string][]string{"pods": {"team-a"}, "persistentvolumeclaims": {"team-a"}, "events": {"team-a"}}
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckUnknown || !strings.Contains(check.Caveat, "Cached evidence is namespace-limited") {
t.Fatalf("informer-scoped SELinux evidence = %+v, want unknown with cache-scope caveat", check)
}
}
func TestSELinuxMountExplicitOptOut137(t *testing.T) {
input := completeInput()
input.NodeRuntimeEvidence[0].FeatureGates["SELinuxMount"] = false
check := checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckNotApplicable || !strings.Contains(check.Summary, "every readable Linux kubelet") || !strings.Contains(check.Summary, "1.38") || !strings.Contains(check.EvidenceNote, "configz") || check.Caveat != "" {
t.Fatalf("complete 1.37 opt-out = %+v, want not applicable with expiry", check)
}
input = selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c3,c4")
input.NodeRuntimeEvidence[0].FeatureGates["SELinuxMount"] = false
input.Nodes = append(input.Nodes, readyNode("node-b", "v1.35.7"))
input.NodeRuntimeEvidence = append(input.NodeRuntimeEvidence, NodeRuntimeEvidence{NodeName: "node-b", ConfigAvailable: true, FeatureGates: map[string]bool{}})
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckReview {
t.Fatalf("partial node opt-out = %+v, want structural review", check)
}
input = selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c3,c4")
input.NodeRuntimeEvidence[0].FeatureGates["SELinuxMount"] = false
result, err := Scan(input, "1.36", "1.38")
if err != nil {
t.Fatal(err)
}
check = checkByID(t, result, "selinux-mount-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || strings.Contains(check.Findings[0].Remediation, "For Kubernetes 1.37 only") || !strings.Contains(check.Findings[0].Remediation, "limited to a Kubernetes 1.37 target") {
t.Fatalf("1.37-only opt-out with a 1.38 target = %+v, want structural review", check)
}
}
func TestSELinuxMountTransitionTranslatesMigratedInTreeVolume137(t *testing.T) {
input := selinuxSharedVolumeInput(t, nil, nil, "s0:c1,c2", "s0:c3,c4")
input.CSIDrivers[0].Name = "ebs.csi.aws.com"
input.PersistentVolumes[0].Spec.PersistentVolumeSource = corev1.PersistentVolumeSource{
AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{VolumeID: "aws://us-east-1a/vol-123"},
}
check := checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckReview || len(check.Findings) != 1 || !strings.Contains(check.Findings[0].Title, "conflicting SELinux labels") {
t.Fatalf("migrated in-tree EBS volume = %+v, want CSI-translated conflict review", check)
}
if input.PersistentVolumes[0].Spec.AWSElasticBlockStore == nil || input.PersistentVolumes[0].Spec.CSI != nil {
t.Fatalf("scan mutated source PV = %+v", input.PersistentVolumes[0].Spec.PersistentVolumeSource)
}
}
func TestSELinuxMountTransitionIncludesGenericEphemeralPVC137(t *testing.T) {
input := completeInput()
seLinuxMount := true
input.CSIDrivers = []*storagev1.CSIDriver{{ObjectMeta: metav1.ObjectMeta{Name: "csi.example.com"}, Spec: storagev1.CSIDriverSpec{SELinuxMount: &seLinuxMount}}}
input.PersistentVolumes = []*corev1.PersistentVolume{{
ObjectMeta: metav1.ObjectMeta{Name: "ephemeral"},
Spec: corev1.PersistentVolumeSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
ClaimRef: &corev1.ObjectReference{Namespace: "apps", Name: "one-data"},
PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{Driver: "csi.example.com", VolumeHandle: "ephemeral"}},
},
}}
controller := true
input.PersistentVolumeClaims = []*corev1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "one-data", Namespace: "apps", OwnerReferences: []metav1.OwnerReference{{APIVersion: "v1", Kind: "Pod", Name: "one", UID: "pod-one", Controller: &controller}}}, Spec: corev1.PersistentVolumeClaimSpec{VolumeName: "ephemeral", AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}}}}
input.Pods = []*corev1.Pod{{
ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "apps", UID: "pod-one"},
Spec: corev1.PodSpec{
SecurityContext: &corev1.PodSecurityContext{SELinuxOptions: &corev1.SELinuxOptions{Level: "s0:c1,c2"}},
Volumes: []corev1.Volume{{Name: "data", VolumeSource: corev1.VolumeSource{Ephemeral: &corev1.EphemeralVolumeSource{VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{}}}}},
Containers: []corev1.Container{{Name: "app", Image: "example", VolumeMounts: []corev1.VolumeMount{{Name: "data", MountPath: "/data"}}}},
},
Status: corev1.PodStatus{Phase: corev1.PodRunning},
}}
check := checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckPassed || check.Inspected != 1 {
t.Fatalf("generic ephemeral PVC = %+v, want one passed inspection", check)
}
input.PersistentVolumeClaims[0].OwnerReferences = nil
check = checkByID(t, scan137(t, input), "selinux-mount-transition")
if check.Status != CheckUnknown || check.Inspected != 0 {
t.Fatalf("generic ephemeral PVC without Pod ownership = %+v, want incomplete evidence", check)
}
}