Skip to content

Commit ee8517d

Browse files
author
openshift-service-mesh-bot
committed
Automator: merge upstream changes to openshift-service-mesh/istio@master
* upstream/master: (48 commits) fix proxy version check for built-in formatters (#58469) Support safe migration from iptables to nftables in Ambient (#58354) krt: aggregate Join events for conflicting keys (#58324) Automator: update proxy@master in istio/istio@master (#58458) addons: Bump addons version (#58443) add indication for the requests count (#58454) `istioctl`: Display proxy cert serial numbers with trailing zeros (#58449) nds: fix missing IP addresses in headless nametable entries when pods have multiple IPs (#58398) Adapt TestInferencePoolMultipleTargetPorts test to Openshift (#58448) Add istiod_remote_cluster_sync_status metric (#58384) Automator: update proxy@master in istio/istio@master (#58446) Automator: update istio/client-go@master dependency in istio/istio@master (#58421) Automator: update proxy@master in istio/istio@master (#58439) Automator: update proxy@master in istio/istio@master (#58437) istioctl: support show all namespaces waypoint status (#58394) Propagate trust domain to e/w gateway (#58428) xlistenerset: fix namespace selector (#58360) istioctl waypoint status: support specify whether to wait for waypoint to be ready (#57076) timeout in zipkin is optional (#58406) rename NewInformerFiltered to NewFilteredInformer (#58385) ...
2 parents 73e12de + f4a9fd1 commit ee8517d

File tree

241 files changed

+4416
-1846
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

241 files changed

+4416
-1846
lines changed

cni/pkg/install/cniconfig_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,7 @@ func TestGetCNIConfigFilepath(t *testing.T) {
227227

228228
// Handle chained CNI plugin cases
229229
// Call with goroutine to test fsnotify watcher
230-
parent, cancel := context.WithCancel(context.Background())
231-
defer cancel()
230+
parent := t.Context()
232231
resultChan, errChan := make(chan string, 1), make(chan error, 1)
233232
go func(resultChan chan string, errChan chan error, ctx context.Context, cniConfName, mountedCNINetDir string, chained bool) {
234233
result, err := getCNIConfigFilepath(ctx, cniConfName, mountedCNINetDir, chained)

cni/pkg/install/install_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,7 @@ func TestSleepCheckInstall(t *testing.T) {
220220
tempDir := t.TempDir()
221221

222222
// Initialize parameters
223-
ctx, cancel := context.WithCancel(context.Background())
224-
defer cancel()
223+
ctx := t.Context()
225224

226225
if c.istioOwnedCNIConfig && len(c.istioOwnedCNIConfigFilename) == 0 {
227226
c.istioOwnedCNIConfigFilename = "02-istio-conf.conflist"

cni/pkg/nftables/kubeletuid_linux.go

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import (
2222
"github.com/prometheus/procfs"
2323
)
2424

25-
// getKubeletUIDFromPath finds the kubelet process UID by inspecting the proc filesystem path.
25+
// getKubeletUIDFromPath finds the kubelet or kubelite process UID by inspecting the proc filesystem path.
26+
// In standard Kubernetes distributions, it looks for the "kubelet" process.
27+
// On some platforms like MicroK8s, where multiple k8s components are consolidated, it looks for the "kubelite" process.
2628
func getKubeletUIDFromPath(procPath string) (string, error) {
2729
fs, err := procfs.NewFS(procPath)
2830
if err != nil {
@@ -34,44 +36,53 @@ func getKubeletUIDFromPath(procPath string) (string, error) {
3436
return "", fmt.Errorf("failed to read processes from %s: %v", procPath, err)
3537
}
3638

37-
// Find kubelet process
39+
// List of process names to search for, in order of preference
40+
processNames := []string{"kubelet", "kubelite"}
41+
3842
for _, proc := range procs {
3943
comm, err := proc.Comm()
4044
if err != nil {
4145
// Process might have exited, skip
4246
continue
4347
}
4448

45-
if comm == "kubelet" {
46-
// Lets check the command line to ensure it's really kubelet
47-
cmdline, err := proc.CmdLine()
48-
if err != nil {
49-
continue
50-
}
49+
for _, targetName := range processNames {
50+
if comm == targetName {
51+
// Lets check the command line to ensure it's really the target process
52+
cmdline, err := proc.CmdLine()
53+
if err != nil {
54+
continue
55+
}
5156

52-
kubeletFound := false
53-
for _, arg := range cmdline {
54-
if strings.Contains(strings.ToLower(arg), "kubelet") {
55-
kubeletFound = true
56-
break
57+
// Verify that this process is actually related to kubelet by checking
58+
// if "kubelet" appears in any of the command line arguments.
59+
// This works for both:
60+
// - Standard kubelet: /usr/bin/kubelet [args...]
61+
// - MicroK8s kubelite: /snap/microk8s/.../kubelite --kubelet-args-file=...
62+
processFound := false
63+
for _, arg := range cmdline {
64+
if strings.Contains(strings.ToLower(arg), "kubelet") {
65+
processFound = true
66+
break
67+
}
68+
}
69+
if !processFound {
70+
continue
5771
}
58-
}
59-
if !kubeletFound {
60-
continue
61-
}
6272

63-
// Get process status with UIDs
64-
status, err := proc.NewStatus()
65-
if err != nil {
66-
continue
67-
}
73+
// Get process status with UIDs
74+
status, err := proc.NewStatus()
75+
if err != nil {
76+
continue
77+
}
6878

69-
realUID := status.UIDs[0]
70-
realUIDStr := strconv.FormatUint(realUID, 10)
79+
realUID := status.UIDs[0]
80+
realUIDStr := strconv.FormatUint(realUID, 10)
7181

72-
return realUIDStr, nil
82+
return realUIDStr, nil
83+
}
7384
}
7485
}
7586

76-
return "", fmt.Errorf("kubelet process not found in %s", procPath)
87+
return "", fmt.Errorf("kubelet or kubelite process not found in %s", procPath)
7788
}

cni/pkg/nftables/nftables.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ func (cfg *NftablesConfigurator) CreateHostRulesForHealthChecks() error {
443443
//
444444
// Challenge: In nftables, there is no direct equivalent to "--socket-exists", so we explored multiple alternatives
445445
// - Option-1 (UID-based matching): Since kubelet runs as a specific process with a known UID, we can use
446-
// meta skuid to identify traffic originating from kubelet.
446+
// meta skuid to identify traffic originating from kubelet (or kubelite in MicroK8s).
447447
//
448448
// - Option-2: Match on kubelet’s source IP (node IP). This works in theory but is a bit unsafe as
449449
// other host processes can also send traffic from the node IP, and nodes can have multiple IPs making the
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package nodeagent
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/vishvananda/netlink"
22+
23+
"istio.io/istio/cni/pkg/config"
24+
"istio.io/istio/cni/pkg/ipset"
25+
"istio.io/istio/cni/pkg/util"
26+
)
27+
28+
// detectIptablesArtifacts checks for the presence of Istio iptables artifacts (specifically IPsets)
29+
// on the host network to determine if a previous iptables-based deployment exists.
30+
// Returns:
31+
// - true if iptables artifacts (IPsets) are detected
32+
// - false if no artifacts are detected or if detection fails
33+
// - error if there was a failure during detection
34+
func detectIptablesArtifacts(enableIPv6 bool) (bool, error) {
35+
var detected bool
36+
var detectionErr error
37+
38+
// Run the detection in the host network namespace
39+
err := util.RunAsHost(func() error {
40+
// Check if the IPv4 IPset exists
41+
v4Name := fmt.Sprintf(ipset.V4Name, config.ProbeIPSet)
42+
v4Exists, err := ipsetExists(v4Name)
43+
if err != nil {
44+
log.Debugf("failed to check for v4 IPset %s: %v", v4Name, err)
45+
detectionErr = fmt.Errorf("v4 IPset detection failed: %w", err)
46+
// Lets continue checking for v6 (if enabled)
47+
}
48+
49+
if v4Exists {
50+
log.Infof("detected iptables artifact: IPset %s exists", v4Name)
51+
detected = true
52+
// Found v4 artifact, no need to check for v6
53+
return nil
54+
}
55+
56+
// Check if the IPv6 IPset exists
57+
if enableIPv6 {
58+
v6Name := fmt.Sprintf(ipset.V6Name, config.ProbeIPSet)
59+
v6Exists, err := ipsetExists(v6Name)
60+
if err != nil {
61+
log.Debugf("failed to check for v6 IPset %s: %v", v6Name, err)
62+
if detectionErr != nil {
63+
detectionErr = fmt.Errorf("%w; v6 IPset detection failed: %w", detectionErr, err)
64+
} else {
65+
detectionErr = fmt.Errorf("v6 IPset detection failed: %w", err)
66+
}
67+
}
68+
69+
if v6Exists {
70+
log.Infof("detected iptables artifact: IPset %s exists", v6Name)
71+
detected = true
72+
}
73+
}
74+
75+
return nil
76+
})
77+
if err != nil {
78+
return false, fmt.Errorf("failed to run detection in host namespace: %w", err)
79+
}
80+
81+
return detected, detectionErr
82+
}
83+
84+
// ipsetExists checks if an IPset with the given name exists on the host.
85+
// Returns:
86+
// - true, nil if the IPset exists
87+
// - false, nil if the IPset does not exist (expected for clean/fresh setup)
88+
// - false, error for any errors
89+
func ipsetExists(name string) (bool, error) {
90+
_, err := netlink.IpsetList(name)
91+
if err == nil {
92+
// IPset exists
93+
return true, nil
94+
}
95+
96+
if strings.Contains(err.Error(), "no such file") {
97+
// IPSet does not exist
98+
return false, nil
99+
}
100+
101+
return false, err
102+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package nodeagent
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
"sync"
21+
"testing"
22+
23+
// Create a new network namespace. This will have the 'lo' interface ready but nothing else.
24+
_ "github.com/howardjohn/unshare-go/netns"
25+
// Create a new user namespace. This will map the current UID/GID to 0.
26+
"github.com/howardjohn/unshare-go/userns"
27+
"github.com/vishvananda/netlink"
28+
"golang.org/x/sys/unix"
29+
30+
"istio.io/istio/cni/pkg/config"
31+
"istio.io/istio/cni/pkg/ipset"
32+
"istio.io/istio/pkg/test/util/assert"
33+
)
34+
35+
// TestDetectIptablesArtifacts is an e2e test that validates the iptable artifacts on the network.
36+
// It runs in an isolated network namespace created by unshare-go, which provides a clean environment
37+
// with root-like capabilities for netlink operations. It validates the upgrade path from iptables to
38+
// nftables backend by simulating various scenarios.
39+
func TestDetectIptablesArtifacts(t *testing.T) {
40+
setup(t)
41+
42+
tests := []struct {
43+
name string
44+
enableIPv6 bool
45+
setupFunc func(t *testing.T)
46+
cleanupFunc func(t *testing.T)
47+
expectedDetected bool
48+
description string
49+
}{
50+
{
51+
name: "v4_ipset_exists",
52+
enableIPv6: false,
53+
setupFunc: func(t *testing.T) {
54+
v4Name := fmt.Sprintf(ipset.V4Name, config.ProbeIPSet)
55+
err := netlink.IpsetCreate(v4Name, "hash:ip", netlink.IpsetCreateOptions{Replace: true})
56+
if err != nil && !strings.Contains(err.Error(), "exist") {
57+
t.Fatalf("failed to create v4 ipset: %v", err)
58+
}
59+
t.Logf("Created v4 IPset: %s", v4Name)
60+
},
61+
cleanupFunc: func(t *testing.T) {
62+
v4Name := fmt.Sprintf(ipset.V4Name, config.ProbeIPSet)
63+
_ = netlink.IpsetDestroy(v4Name)
64+
},
65+
expectedDetected: true,
66+
description: "Should detect when v4 IPset exists (simulates an existing iptables deployment)",
67+
},
68+
{
69+
name: "v6_ipset_exists",
70+
enableIPv6: true,
71+
setupFunc: func(t *testing.T) {
72+
v6Name := fmt.Sprintf(ipset.V6Name, config.ProbeIPSet)
73+
err := netlink.IpsetCreate(v6Name, "hash:ip", netlink.IpsetCreateOptions{
74+
Family: unix.AF_INET6,
75+
Replace: true,
76+
})
77+
if err != nil && !strings.Contains(err.Error(), "exist") {
78+
t.Fatalf("failed to create v6 ipset: %v", err)
79+
}
80+
t.Logf("Created v6 IPset: %s", v6Name)
81+
},
82+
cleanupFunc: func(t *testing.T) {
83+
v6Name := fmt.Sprintf(ipset.V6Name, config.ProbeIPSet)
84+
_ = netlink.IpsetDestroy(v6Name)
85+
},
86+
expectedDetected: true,
87+
description: "Should detect when v6 IPset exists",
88+
},
89+
{
90+
name: "both_v4_and_v6_ipsets_exist",
91+
enableIPv6: true,
92+
setupFunc: func(t *testing.T) {
93+
v4Name := fmt.Sprintf(ipset.V4Name, config.ProbeIPSet)
94+
v6Name := fmt.Sprintf(ipset.V6Name, config.ProbeIPSet)
95+
96+
err := netlink.IpsetCreate(v4Name, "hash:ip", netlink.IpsetCreateOptions{Replace: true})
97+
if err != nil && !strings.Contains(err.Error(), "exist") {
98+
t.Fatalf("failed to create v4 ipset: %v", err)
99+
}
100+
101+
err = netlink.IpsetCreate(v6Name, "hash:ip", netlink.IpsetCreateOptions{
102+
Family: unix.AF_INET6,
103+
Replace: true,
104+
})
105+
if err != nil && !strings.Contains(err.Error(), "exist") {
106+
t.Fatalf("failed to create v6 ipset: %v", err)
107+
}
108+
t.Logf("Created both v4 and v6 IPsets")
109+
},
110+
cleanupFunc: func(t *testing.T) {
111+
v4Name := fmt.Sprintf(ipset.V4Name, config.ProbeIPSet)
112+
v6Name := fmt.Sprintf(ipset.V6Name, config.ProbeIPSet)
113+
_ = netlink.IpsetDestroy(v4Name)
114+
_ = netlink.IpsetDestroy(v6Name)
115+
},
116+
expectedDetected: true,
117+
description: "Should detect when both v4 and v6 IPsets exist",
118+
},
119+
{
120+
name: "no_ipsets_exist",
121+
enableIPv6: true,
122+
setupFunc: func(t *testing.T) {},
123+
cleanupFunc: func(t *testing.T) {},
124+
expectedDetected: false,
125+
description: "Should not detect artifacts in clean state (simulates a fresh setup)",
126+
},
127+
}
128+
129+
for _, tt := range tests {
130+
t.Run(tt.name, func(t *testing.T) {
131+
t.Log(tt.description)
132+
133+
tt.setupFunc(t)
134+
defer tt.cleanupFunc(t)
135+
136+
detected, _ := detectIptablesArtifacts(tt.enableIPv6)
137+
assert.Equal(t, detected, tt.expectedDetected)
138+
})
139+
}
140+
}
141+
142+
var initialized = &sync.Once{}
143+
144+
// setup initializes the test environment using unshare-go.
145+
// Importing "github.com/howardjohn/unshare-go/netns" causes the test to run in an isolated network
146+
// namespace and userns provides user namespace mapping.
147+
func setup(t *testing.T) {
148+
initialized.Do(func() {
149+
// Map current GID to root (0) in the user namespace
150+
// This gives us the necessary privileges for netlink operations (CAP_NET_ADMIN)
151+
assert.NoError(t, userns.WriteGroupMap(map[uint32]uint32{userns.OriginalGID(): 0}))
152+
t.Log("Successfully initialized an isolated test network namespace with root capabilities")
153+
})
154+
}

0 commit comments

Comments
 (0)