Skip to content

Commit 59f67e9

Browse files
committed
Migrate OCP-59552: enable image signature verification for RHEL registries
1 parent 76ed5a8 commit 59f67e9

3 files changed

Lines changed: 166 additions & 1 deletion

File tree

test/extended/node/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ This directory contains OpenShift end-to-end tests for node-related features.
88

99
- **kubeletconfig_features.go** - Tests applying KubeletConfig to custom machine config pools, requires node reboots
1010
- **kubelet_secret_pulled_images.go** - Tests kubelet credential verification for image pulls (`KubeletEnsureSecretPulledImages` feature gate). Covers multi-tenancy isolation, credential rotation, ImagePullPolicy behavior, credential verification policy (NeverVerify/AlwaysVerify), and registry availability scenarios. Requires `TechPreviewNoUpgrade` or `CustomNoUpgrade` FeatureSet.
11-
- **node_e2e/image_registry_config.go** - Container registry config change (OCP-44820) - Verifies search registry update triggers MCO rollout and lands on nodes [Disruptive]
11+
- **node_e2e/image_registry_config.go** - Container registry config change (OCP-44820) - Verifies search registry update triggers MCO rollout and lands on nodes \[Disruptive\]
12+
- **node_e2e/image_signature.go** - Image signature verification - Verifies image signature verification for Red Hat Container Registries \[Disruptive\]\[Serial\]\[OTP\]
1213

1314
### Suite: openshift/usernamespace
1415

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"path/filepath"
6+
"strings"
7+
"time"
8+
9+
g "github.com/onsi/ginkgo/v2"
10+
o "github.com/onsi/gomega"
11+
12+
"k8s.io/apimachinery/pkg/util/wait"
13+
e2e "k8s.io/kubernetes/test/e2e/framework"
14+
15+
nodeutils "github.com/openshift/origin/test/extended/node"
16+
exutil "github.com/openshift/origin/test/extended/util"
17+
)
18+
19+
var _ = g.Describe("[Suite:openshift/disruptive-longrunning][sig-node][Disruptive][Serial] Image signature verification", func() {
20+
var (
21+
oc = exutil.NewCLIWithoutNamespace("image-sig")
22+
nodeE2EBaseDir = exutil.FixturePath("testdata", "node", "node_e2e")
23+
imgSignatureYAML = filepath.Join(nodeE2EBaseDir, "machineconfig-image-signature.yaml")
24+
)
25+
26+
// Skip all tests on MicroShift clusters as MachineConfig resources are not available
27+
g.BeforeEach(func() {
28+
isMicroShift, err := exutil.IsMicroShiftCluster(oc.AdminKubeClient())
29+
if err != nil {
30+
e2e.Logf("Error checking if cluster is MicroShift: %v", err)
31+
g.Skip("Cannot determine cluster type")
32+
}
33+
if isMicroShift {
34+
g.Skip("Skipping test on MicroShift cluster - MachineConfig resources are not available")
35+
}
36+
})
37+
38+
//author: bgudi@redhat.com
39+
g.It("[OTP] Enable image signature verification for Red Hat Container Registries [OCP-59552]", func() {
40+
ctx := context.Background()
41+
42+
g.By("Check if mcp worker exists in current cluster")
43+
machineCount, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("mcp", "worker", "-o=jsonpath={.status.machineCount}").Output()
44+
if err != nil || machineCount == "0" {
45+
g.Skip("Skipping test: mcp worker does not exist in this cluster")
46+
}
47+
e2e.Logf("Worker MCP machine count: %s", machineCount)
48+
49+
g.By("Apply a machine config to set image signature policy for worker nodes")
50+
err = oc.AsAdmin().WithoutNamespace().Run("create").Args("-f", imgSignatureYAML).Execute()
51+
o.Expect(err).NotTo(o.HaveOccurred(), "failed to create MachineConfig")
52+
53+
g.DeferCleanup(func(ctx context.Context) {
54+
g.By("Delete the MachineConfig")
55+
oc.AsAdmin().WithoutNamespace().Run("delete").Args("-f", imgSignatureYAML, "--ignore-not-found").Execute()
56+
57+
g.By("Wait for MCP to finish rolling back")
58+
err := waitForMCPUpdate(ctx, oc, "worker", 30*time.Minute)
59+
if err != nil {
60+
e2e.Logf("Warning: MCP did not finish rolling back: %v", err)
61+
}
62+
}, ctx)
63+
64+
g.By("Wait for MCP to finish updating")
65+
err = waitForMCPUpdate(ctx, oc, "worker", 30*time.Minute)
66+
o.Expect(err).NotTo(o.HaveOccurred(), "MCP worker did not finish updating")
67+
68+
g.By("Verify the signature configuration in /etc/containers/policy.json")
69+
err = checkImageSignature(oc)
70+
o.Expect(err).NotTo(o.HaveOccurred(), "image signature configuration verification failed")
71+
})
72+
})
73+
74+
// waitForMCPUpdate waits for the MachineConfigPool to finish updating.
75+
// It checks the Updated condition to become True (which means update is complete).
76+
// Returns nil when the MCP is updated, or an error if it times out.
77+
// This is a helper function and does not contain assertions.
78+
func waitForMCPUpdate(ctx context.Context, oc *exutil.CLI, mcpName string, timeout time.Duration) error {
79+
g.GinkgoHelper()
80+
return wait.PollUntilContextTimeout(ctx, 30*time.Second, timeout, false, func(ctx context.Context) (bool, error) {
81+
// Check the Updated condition instead of Updating
82+
// Updated=True means the MCP has finished updating
83+
updatedStatus, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("mcp", mcpName, "-o=jsonpath={.status.conditions[?(@.type=='Updated')].status}").Output()
84+
if err != nil {
85+
e2e.Logf("Error getting MCP Updated status: %v", err)
86+
return false, nil
87+
}
88+
89+
// Check that machine counts match (all machines have the desired config)
90+
machineCount, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("mcp", mcpName, "-o=jsonpath={.status.machineCount}").Output()
91+
if err != nil {
92+
e2e.Logf("Error getting machine count: %v", err)
93+
return false, nil
94+
}
95+
updatedMachineCount, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("mcp", mcpName, "-o=jsonpath={.status.updatedMachineCount}").Output()
96+
if err != nil {
97+
e2e.Logf("Error getting updated machine count: %v", err)
98+
return false, nil
99+
}
100+
101+
e2e.Logf("MCP %s: Updated=%s, machines=%s, updatedMachines=%s", mcpName, updatedStatus, machineCount, updatedMachineCount)
102+
103+
if strings.Contains(updatedStatus, "True") && machineCount == updatedMachineCount {
104+
e2e.Logf("MCP %s updated successfully", mcpName)
105+
return true, nil
106+
}
107+
e2e.Logf("MCP %s is still updating", mcpName)
108+
return false, nil
109+
})
110+
}
111+
112+
// checkImageSignature verifies that the image signature policy is correctly configured on worker nodes.
113+
// It checks for required entries in /etc/containers/policy.json for Red Hat registries.
114+
// This is a helper function and does not contain assertions.
115+
func checkImageSignature(oc *exutil.CLI) error {
116+
g.GinkgoHelper()
117+
return wait.PollUntilContextTimeout(context.Background(), 10*time.Second, 30*time.Second, true, func(ctx context.Context) (bool, error) {
118+
workerNode := nodeutils.GetFirstReadyWorkerNode(oc)
119+
policyJSON, err := nodeutils.ExecOnNodeWithChroot(oc, workerNode, "cat", "/etc/containers/policy.json")
120+
if err != nil {
121+
e2e.Logf("Error reading policy.json: %v", err)
122+
return false, nil
123+
}
124+
125+
e2e.Logf("Checking policy.json content from node %s", workerNode)
126+
127+
// Check for required entries in the policy.json
128+
requiredEntries := []string{
129+
"registry.access.redhat.com",
130+
"signedBy",
131+
"GPGKeys",
132+
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
133+
"registry.redhat.io",
134+
}
135+
136+
for _, entry := range requiredEntries {
137+
if !strings.Contains(policyJSON, entry) {
138+
e2e.Logf("Missing required entry in policy.json: %s", entry)
139+
return false, nil
140+
}
141+
}
142+
143+
e2e.Logf("Image signature policy verified successfully")
144+
return true, nil
145+
})
146+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Butane; do not edit
2+
apiVersion: machineconfiguration.openshift.io/v1
3+
kind: MachineConfig
4+
metadata:
5+
labels:
6+
machineconfiguration.openshift.io/role: worker
7+
name: 51-worker-rh-registry-trust
8+
spec:
9+
config:
10+
ignition:
11+
version: 3.2.0
12+
storage:
13+
files:
14+
- contents:
15+
source: data:;base64,ewogICJkZWZhdWx0IjogWwogICAgewogICAgICAidHlwZSI6ICJpbnNlY3VyZUFjY2VwdEFueXRoaW5nIgogICAgfQogIF0sCiAgInRyYW5zcG9ydHMiOiB7CiAgICAiZG9ja2VyIjogewogICAgICAicmVnaXN0cnkuYWNjZXNzLnJlZGhhdC5jb20iOiBbCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAic2lnbmVkQnkiLAogICAgICAgICAgImtleVR5cGUiOiAiR1BHS2V5cyIsCiAgICAgICAgICAia2V5UGF0aCI6ICIvZXRjL3BraS9ycG0tZ3BnL1JQTS1HUEctS0VZLXJlZGhhdC1yZWxlYXNlIgogICAgICAgIH0KICAgICAgXSwKICAgICAgInJlZ2lzdHJ5LnJlZGhhdC5pbyI6IFsKICAgICAgICB7CiAgICAgICAgICAidHlwZSI6ICJzaWduZWRCeSIsCiAgICAgICAgICAia2V5VHlwZSI6ICJHUEdLZXlzIiwKICAgICAgICAgICJrZXlQYXRoIjogIi9ldGMvcGtpL3JwbS1ncGcvUlBNLUdQRy1LRVktcmVkaGF0LXJlbGVhc2UiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImRvY2tlci1kYWVtb24iOiB7CiAgICAgICIiOiBbCiAgICAgICAgewogICAgICAgICAgInR5cGUiOiAiaW5zZWN1cmVBY2NlcHRBbnl0aGluZyIKICAgICAgICB9CiAgICAgIF0KICAgIH0KICB9Cn0K
16+
mode: 420
17+
overwrite: true
18+
path: /etc/containers/policy.json

0 commit comments

Comments
 (0)