diff --git a/bindata/network/ovn-kubernetes/dpu-rbac.yaml b/bindata/network/ovn-kubernetes/dpu-rbac.yaml new file mode 100644 index 0000000000..13a4cbea6a --- /dev/null +++ b/bindata/network/ovn-kubernetes/dpu-rbac.yaml @@ -0,0 +1,63 @@ +--- +# Grant lease permissions to the DPU service account so the DPU can renew +# the health check lease in the openshift-ovn-kubernetes namespace. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: +{{ if .NETWORK_NODE_IDENTITY_ENABLE }} + name: openshift-ovn-kubernetes-node-dpu-service-identity-limited +{{ else }} + name: openshift-ovn-kubernetes-node-dpu-service-limited +{{ end }} + namespace: openshift-ovn-kubernetes +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: openshift-ovn-kubernetes-node-limited +subjects: +{{ if .NETWORK_NODE_IDENTITY_ENABLE }} +- kind: Group + name: system:ovn-nodes + apiGroup: rbac.authorization.k8s.io +{{ else }} +- kind: ServiceAccount + name: ovn-kubernetes-node-dpu-service + namespace: openshift-ovn-kubernetes +{{ end }} + +{{ if .NETWORK_NODE_IDENTITY_ENABLE }} +--- +# Allow the DPU service account to impersonate node users and groups +# so it can act on behalf of the DPU host node. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: openshift-ovn-kubernetes-node-dpu-host-impersonator +rules: +- apiGroups: [""] + resources: + - users + verbs: + - impersonate +- apiGroups: [""] + resources: + - groups + verbs: + - impersonate + resourceNames: + - system:nodes + - system:authenticated +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: openshift-ovn-kubernetes-node-dpu-host-impersonator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: openshift-ovn-kubernetes-node-dpu-host-impersonator +subjects: +- kind: ServiceAccount + name: ovn-kubernetes-node-dpu-service + namespace: openshift-ovn-kubernetes +{{ end }} diff --git a/pkg/network/ovn_kubernetes.go b/pkg/network/ovn_kubernetes.go index 0ba767b963..82b3fec10f 100644 --- a/pkg/network/ovn_kubernetes.go +++ b/pkg/network/ovn_kubernetes.go @@ -473,6 +473,14 @@ func renderOVNKubernetes(conf *operv1.NetworkSpec, bootstrapResult *bootstrap.Bo objs = append(objs, manifests...) } + if len(bootstrapResult.OVN.OVNKubernetesConfig.DpuHostModeNodes) > 0 || len(bootstrapResult.OVN.OVNKubernetesConfig.DpuModeNodes) > 0 { + manifests, err = render.RenderTemplate(filepath.Join(manifestDir, "network/ovn-kubernetes/dpu-rbac.yaml"), &data) + if err != nil { + return nil, progressing, errors.Wrap(err, "failed to render DPU RBAC manifests") + } + objs = append(objs, manifests...) + } + if len(bootstrapResult.OVN.OVNKubernetesConfig.DpuModeNodes) > 0 { // "OVN_NODE_MODE" not set when render.RenderDir() called above, // so render just the error-cni.yaml with "OVN_NODE_MODE" set. diff --git a/pkg/network/ovn_kubernetes_dpu_host_test.go b/pkg/network/ovn_kubernetes_dpu_host_test.go index 3b8aef7bf8..93f754d6b0 100644 --- a/pkg/network/ovn_kubernetes_dpu_host_test.go +++ b/pkg/network/ovn_kubernetes_dpu_host_test.go @@ -1,6 +1,7 @@ package network import ( + "strings" "testing" "github.com/ghodss/yaml" @@ -355,3 +356,92 @@ func TestOVNKubernetesNodeSelectorOperator(t *testing.T) { } } } + +func TestOVNKubernetesDPURBAC(t *testing.T) { + rbacTemplatePath := "../../bindata/network/ovn-kubernetes/dpu-rbac.yaml" + + testCases := []struct { + name string + identityEnable bool + expectRoleBinding string + expectSubjectKind string + expectSubjectName string + expectImpersonation bool + }{ + { + name: "without identity", + identityEnable: false, + expectRoleBinding: "openshift-ovn-kubernetes-node-dpu-service-limited", + expectSubjectKind: "ServiceAccount", + expectSubjectName: "ovn-kubernetes-node-dpu-service", + expectImpersonation: false, + }, + { + name: "with identity enabled", + identityEnable: true, + expectRoleBinding: "openshift-ovn-kubernetes-node-dpu-service-identity-limited", + expectSubjectKind: "Group", + expectSubjectName: "system:ovn-nodes", + expectImpersonation: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + data := render.MakeRenderData() + data.Data["NETWORK_NODE_IDENTITY_ENABLE"] = tc.identityEnable + + objs, err := render.RenderTemplate(rbacTemplatePath, &data) + g.Expect(err).NotTo(HaveOccurred()) + + var foundRoleBinding bool + var foundImpersonationCR, foundImpersonationCRB bool + + for _, obj := range objs { + kind := obj.GetKind() + name := obj.GetName() + + if kind == "RoleBinding" && strings.HasPrefix(name, "openshift-ovn-kubernetes-node-dpu-service") { + foundRoleBinding = true + g.Expect(name).To(Equal(tc.expectRoleBinding)) + + subjects, found, err := uns.NestedSlice(obj.Object, "subjects") + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(found).To(BeTrue()) + g.Expect(subjects).To(HaveLen(1)) + + subj := subjects[0].(map[string]interface{}) + subjKind, _, _ := uns.NestedString(subj, "kind") + subjName, _, _ := uns.NestedString(subj, "name") + g.Expect(subjKind).To(Equal(tc.expectSubjectKind)) + g.Expect(subjName).To(Equal(tc.expectSubjectName)) + + roleRefName, _, _ := uns.NestedString(obj.Object, "roleRef", "name") + g.Expect(roleRefName).To(Equal("openshift-ovn-kubernetes-node-limited")) + } + + if kind == "ClusterRole" && name == "openshift-ovn-kubernetes-node-dpu-host-impersonator" { + foundImpersonationCR = true + rules, found, err := uns.NestedSlice(obj.Object, "rules") + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(found).To(BeTrue()) + g.Expect(len(rules)).To(BeNumerically(">=", 2)) + + rule0 := rules[0].(map[string]interface{}) + verbs0, _, _ := uns.NestedStringSlice(rule0, "verbs") + g.Expect(verbs0).To(ContainElement("impersonate")) + } + + if kind == "ClusterRoleBinding" && name == "openshift-ovn-kubernetes-node-dpu-host-impersonator" { + foundImpersonationCRB = true + } + } + + g.Expect(foundRoleBinding).To(BeTrue(), "DPU service RoleBinding should be present") + g.Expect(foundImpersonationCR).To(Equal(tc.expectImpersonation)) + g.Expect(foundImpersonationCRB).To(Equal(tc.expectImpersonation)) + }) + } +}