Skip to content

Commit 412d715

Browse files
committed
Add DRA status feedback to hub-and-spoke ManifestWork
Add three DRA status feedback JSON path entries (dra.availableNumber, dra.desiredNumber, dra.nodesMatchingSelectorNumber) to the ManifestWork feedback rules, mirroring the existing devicePlugin pattern. Validate DRA hostPath volumes against the same allowlist (/dev, /sys, /var, /opt) used for DevicePlugin volumes, ensuring the hub webhook rejects disallowed mounts before they reach spoke clusters.
1 parent 29c905a commit 412d715

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

internal/manifestwork/manifestwork.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ var moduleStatusJSONPaths = []workv1.JsonPath{
3737
Name: "devicePlugin.nodesMatchingSelectorNumber",
3838
Path: ".status.devicePlugin.nodesMatchingSelectorNumber",
3939
},
40+
{
41+
Name: "dra.availableNumber",
42+
Path: ".status.dra.availableNumber",
43+
},
44+
{
45+
Name: "dra.desiredNumber",
46+
Path: ".status.dra.desiredNumber",
47+
},
48+
{
49+
Name: "dra.nodesMatchingSelectorNumber",
50+
Path: ".status.dra.nodesMatchingSelectorNumber",
51+
},
4052
{
4153
Name: "moduleLoader.availableNumber",
4254
Path: ".status.moduleLoader.availableNumber",

internal/manifestwork/manifestwork_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ var (
2626
mockKM *module.MockKernelMapper
2727
)
2828

29+
var _ = Describe("moduleStatusJSONPaths", func() {
30+
It("should contain all expected status feedback paths", func() {
31+
Expect(moduleStatusJSONPaths).To(ConsistOf(
32+
workv1.JsonPath{Name: "devicePlugin.availableNumber", Path: ".status.devicePlugin.availableNumber"},
33+
workv1.JsonPath{Name: "devicePlugin.desiredNumber", Path: ".status.devicePlugin.desiredNumber"},
34+
workv1.JsonPath{Name: "devicePlugin.nodesMatchingSelectorNumber", Path: ".status.devicePlugin.nodesMatchingSelectorNumber"},
35+
workv1.JsonPath{Name: "dra.availableNumber", Path: ".status.dra.availableNumber"},
36+
workv1.JsonPath{Name: "dra.desiredNumber", Path: ".status.dra.desiredNumber"},
37+
workv1.JsonPath{Name: "dra.nodesMatchingSelectorNumber", Path: ".status.dra.nodesMatchingSelectorNumber"},
38+
workv1.JsonPath{Name: "moduleLoader.availableNumber", Path: ".status.moduleLoader.availableNumber"},
39+
workv1.JsonPath{Name: "moduleLoader.desiredNumber", Path: ".status.moduleLoader.desiredNumber"},
40+
workv1.JsonPath{Name: "moduleLoader.nodesMatchingSelectorNumber", Path: ".status.moduleLoader.nodesMatchingSelectorNumber"},
41+
))
42+
})
43+
})
44+
2945
var _ = Describe("GarbageCollect", func() {
3046
BeforeEach(func() {
3147
ctrl = gomock.NewController(GinkgoT())

internal/webhook/module.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ func validateModule(mod *kmmv1beta1.Module, kubeVersion *KubeVersion) (admission
163163
return nil, fmt.Errorf("failed to validate DRA: %v", err)
164164
}
165165

166+
if err := validateDRAVolumes(mod.Spec.DRA); err != nil {
167+
return nil, fmt.Errorf("failed to validate DRA volumes: %v", err)
168+
}
169+
166170
if err := validateDevicePluginVolumes(mod.Spec.DevicePlugin); err != nil {
167171
return nil, fmt.Errorf("failed to validate device plugin volumes: %v", err)
168172
}
@@ -256,6 +260,23 @@ func validateDevicePluginVolumes(dp *kmmv1beta1.DevicePluginSpec) error {
256260
return nil
257261
}
258262

263+
func validateDRAVolumes(dra *kmmv1beta1.DRASpec) error {
264+
if dra == nil {
265+
return nil
266+
}
267+
268+
for i, vol := range dra.Volumes {
269+
if vol.HostPath != nil && !isAllowedHostPath(vol.HostPath.Path) {
270+
return fmt.Errorf(
271+
"spec.dra.volumes[%d]: hostPath %q is not allowed; only /dev, /sys, /var and /opt paths are permitted",
272+
i, vol.HostPath.Path,
273+
)
274+
}
275+
}
276+
277+
return nil
278+
}
279+
259280
func validateImageFormat(img string) error {
260281

261282
if !strings.Contains(img, ":") && !strings.Contains(img, "@") {

internal/webhook/module_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,3 +1009,83 @@ var _ = Describe("validateDevicePluginVolumes", func() {
10091009
Expect(validateDevicePluginVolumes(dp)).NotTo(HaveOccurred())
10101010
})
10111011
})
1012+
1013+
var _ = Describe("validateDRAVolumes", func() {
1014+
It("should accept nil DRA", func() {
1015+
Expect(validateDRAVolumes(nil)).NotTo(HaveOccurred())
1016+
})
1017+
1018+
It("should accept DRA with no volumes", func() {
1019+
dra := &kmmv1beta1.DRASpec{
1020+
Container: kmmv1beta1.CommonContainerSpec{Image: "img:tag"},
1021+
DriverName: "example.com",
1022+
}
1023+
Expect(validateDRAVolumes(dra)).NotTo(HaveOccurred())
1024+
})
1025+
1026+
It("should accept non-hostPath volumes", func() {
1027+
dra := &kmmv1beta1.DRASpec{
1028+
Container: kmmv1beta1.CommonContainerSpec{Image: "img:tag"},
1029+
DriverName: "example.com",
1030+
Volumes: []v1.Volume{
1031+
{
1032+
Name: "config",
1033+
VolumeSource: v1.VolumeSource{
1034+
ConfigMap: &v1.ConfigMapVolumeSource{
1035+
LocalObjectReference: v1.LocalObjectReference{Name: "my-cm"},
1036+
},
1037+
},
1038+
},
1039+
},
1040+
}
1041+
Expect(validateDRAVolumes(dra)).NotTo(HaveOccurred())
1042+
})
1043+
1044+
It("should accept hostPath volumes under /dev", func() {
1045+
dra := &kmmv1beta1.DRASpec{
1046+
Container: kmmv1beta1.CommonContainerSpec{Image: "img:tag"},
1047+
DriverName: "example.com",
1048+
Volumes: []v1.Volume{
1049+
{
1050+
Name: "dev-vfio",
1051+
VolumeSource: v1.VolumeSource{
1052+
HostPath: &v1.HostPathVolumeSource{Path: "/dev/vfio"},
1053+
},
1054+
},
1055+
},
1056+
}
1057+
Expect(validateDRAVolumes(dra)).NotTo(HaveOccurred())
1058+
})
1059+
1060+
It("should reject hostPath volumes outside allowed paths", func() {
1061+
dra := &kmmv1beta1.DRASpec{
1062+
Container: kmmv1beta1.CommonContainerSpec{Image: "img:tag"},
1063+
DriverName: "example.com",
1064+
Volumes: []v1.Volume{
1065+
{
1066+
Name: "root",
1067+
VolumeSource: v1.VolumeSource{
1068+
HostPath: &v1.HostPathVolumeSource{Path: "/"},
1069+
},
1070+
},
1071+
},
1072+
}
1073+
Expect(validateDRAVolumes(dra)).To(MatchError(ContainSubstring("not allowed")))
1074+
})
1075+
1076+
It("should reject hostPath with path traversal like /dev/../etc", func() {
1077+
dra := &kmmv1beta1.DRASpec{
1078+
Container: kmmv1beta1.CommonContainerSpec{Image: "img:tag"},
1079+
DriverName: "example.com",
1080+
Volumes: []v1.Volume{
1081+
{
1082+
Name: "traversal",
1083+
VolumeSource: v1.VolumeSource{
1084+
HostPath: &v1.HostPathVolumeSource{Path: "/dev/../etc/shadow"},
1085+
},
1086+
},
1087+
},
1088+
}
1089+
Expect(validateDRAVolumes(dra)).To(MatchError(ContainSubstring("not allowed")))
1090+
})
1091+
})

0 commit comments

Comments
 (0)