From dda66c7e01a5dd6718256858f40a2375e1eeb5a4 Mon Sep 17 00:00:00 2001 From: gauron99 Date: Tue, 13 Oct 2026 10:52:43 +0200 Subject: [PATCH] fix: grant the pipeline deploy step keda.sh ScaledObjects and TriggerAuthentications Since knative/func#4069 func's keda deployer reads ScaledObjects on every deploy, to catch a switch between the http and kafka scalers, and creates ScaledObjects and TriggerAuthentications for kafka triggers. The deploy step of the on-cluster build runs that deployer as the namespace's default service account, with the Role this operator creates. That Role had no keda.sh rules, so, as knative/func#4077 describes for func's own roles, every keda deploy is refused. The E2E keda jobs have failed on every PR since 2026-10-05. The deploy step runs ghcr.io/knative/func-utils:v2, a tag func republishes from its main branch, so the change reached the operator without a func bump. Grant the verbs func grants itself in knative/func#4077. The operator's ClusterRole needs the same rule, or it may not create the Role. Existing Roles get the rule on the next rebuild in their namespace, because ensureDeployFunctionRole rewrites rules that differ. --- config/rbac/role.yaml | 11 ++++ internal/controller/function_controller.go | 1 + internal/controller/function_rbac.go | 7 +++ internal/controller/function_rbac_test.go | 59 ++++++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e8cae5ff..dc6e68d7 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -92,6 +92,17 @@ rules: - patch - update - watch +- apiGroups: + - keda.sh + resources: + - scaledobjects + - triggerauthentications + verbs: + - create + - delete + - get + - list + - update - apiGroups: - objectbucket.io resources: diff --git a/internal/controller/function_controller.go b/internal/controller/function_controller.go index ea00b79e..5de0b1bf 100644 --- a/internal/controller/function_controller.go +++ b/internal/controller/function_controller.go @@ -78,6 +78,7 @@ type FunctionReconciler struct { // +kubebuilder:rbac:groups=tekton.dev,resources=taskruns,verbs=get;list;watch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings;roles,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=http.keda.sh,resources=httpscaledobjects,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=keda.sh,resources=scaledobjects;triggerauthentications,verbs=get;list;create;update;delete // Reconcile a Function with status update func (r *FunctionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { diff --git a/internal/controller/function_rbac.go b/internal/controller/function_rbac.go index 75abc3cd..856f4214 100644 --- a/internal/controller/function_rbac.go +++ b/internal/controller/function_rbac.go @@ -78,6 +78,13 @@ func (r *FunctionReconciler) ensureDeployFunctionRole(ctx context.Context, names APIGroups: []string{"http.keda.sh"}, Resources: []string{"httpscaledobjects"}, Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"}, + }, { + // func's keda deployer reads ScaledObjects on every deploy and creates + // ScaledObjects and TriggerAuthentications for kafka triggers + // (knative/func#4069, granted by func itself in knative/func#4077) + APIGroups: []string{"keda.sh"}, + Resources: []string{"scaledobjects", "triggerauthentications"}, + Verbs: []string{"create", "delete", "get", "list", "update"}, }, }, } diff --git a/internal/controller/function_rbac_test.go b/internal/controller/function_rbac_test.go index d482dc29..e83b699b 100644 --- a/internal/controller/function_rbac_test.go +++ b/internal/controller/function_rbac_test.go @@ -129,4 +129,63 @@ var _ = Describe("Function RBAC", func() { Expect(rb.RoleRef.Name).To(Equal(deployFunctionRoleName)) }) }) + + Context("ensureDeployFunctionRole", func() { + var reconciler *FunctionReconciler + var testNamespace string + + // func's keda deployer reads ScaledObjects on every keda deploy + kedaRule := rbacv1.PolicyRule{ + APIGroups: []string{"keda.sh"}, + Resources: []string{"scaledobjects", "triggerauthentications"}, + Verbs: []string{"create", "delete", "get", "list", "update"}, + } + + BeforeEach(func() { + testNamespace = "rbac-role-test-" + rand.String(6) + ns := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}} + Expect(k8sClient.Create(ctx, ns)).To(Succeed()) + + reconciler = &FunctionReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + Recorder: &events.FakeRecorder{}, + } + }) + + It("should grant the keda deployer access to ScaledObjects and TriggerAuthentications", func() { + Expect(reconciler.ensureDeployFunctionRole(ctx, testNamespace)).To(Succeed()) + + role := &rbacv1.Role{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: deployFunctionRoleName, + Namespace: testNamespace, + }, role)).To(Succeed()) + Expect(role.Rules).To(ContainElement(kedaRule)) + }) + + It("should add the keda.sh rule to an existing Role without it", func() { + existing := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: deployFunctionRoleName, + Namespace: testNamespace, + }, + Rules: []rbacv1.PolicyRule{{ + APIGroups: []string{"http.keda.sh"}, + Resources: []string{"httpscaledobjects"}, + Verbs: []string{"create", "delete", "get", "list", "patch", "update", "watch"}, + }}, + } + Expect(k8sClient.Create(ctx, existing)).To(Succeed()) + + Expect(reconciler.ensureDeployFunctionRole(ctx, testNamespace)).To(Succeed()) + + role := &rbacv1.Role{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: deployFunctionRoleName, + Namespace: testNamespace, + }, role)).To(Succeed()) + Expect(role.Rules).To(ContainElement(kedaRule)) + }) + }) })