This is an automated email from the ASF dual-hosted git repository.

squakez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 3e7578b08666619a33a9db473321decc5def1761
Author: AllanAlmeida <[email protected]>
AuthorDate: Thu Sep 17 11:51:44 2026 -0300

    Fix #6778: Validate service account permission by resource name
    
    Generated-by: OpenAI Codex
---
 pkg/internal/client.go                 |  3 +-
 pkg/trait/kamelets.go                  | 53 +++++++++++++++-------------
 pkg/trait/kamelets_test.go             | 23 +++++++++++++
 pkg/util/bindings/catalog.go           |  1 +
 pkg/util/bindings/catalog_test.go      | 26 ++++++++++++++
 pkg/util/kubernetes/permission.go      |  5 +--
 pkg/util/kubernetes/permission_test.go | 63 ++++++++++++++++++++++++++++++++++
 7 files changed, 148 insertions(+), 26 deletions(-)

diff --git a/pkg/internal/client.go b/pkg/internal/client.go
index 50f014123..3de56810c 100644
--- a/pkg/internal/client.go
+++ b/pkg/internal/client.go
@@ -269,7 +269,8 @@ type FakeSAR struct {
 // Create fake create implementation (needed in cross namespace Kamelets 
test). Only allow `cross-ns-sa` user in `default` namespace.
 func (f *FakeSAR) Create(ctx context.Context, sar *authv1.SubjectAccessReview, 
opts metav1.CreateOptions) (*authv1.SubjectAccessReview, error) {
        ra := sar.Spec.ResourceAttributes
-       allowed := sar.Spec.User == "system:serviceaccount:default:cross-ns-sa" 
&& ra.Verb == "get" && ra.Resource == "kamelets"
+       allowed := sar.Spec.User == "system:serviceaccount:default:cross-ns-sa" 
&& ra.Verb == "get" &&
+               ra.Resource == "kamelets" && ra.Name != "restricted-kamelet"
 
        sar.Status.Allowed = allowed
        sar.Status.Reason = "mocked"
diff --git a/pkg/trait/kamelets.go b/pkg/trait/kamelets.go
index a2b421f96..6b3c16133 100644
--- a/pkg/trait/kamelets.go
+++ b/pkg/trait/kamelets.go
@@ -202,33 +202,40 @@ func (t *kameletsTrait) collectKamelets(e *Environment) 
(map[string]*v1.Kamelet,
 // calculateNamespaces is in charge to scan the kamelets specification and 
provide a list of
 // namespaces where to look for Kamelets.
 func (t *kameletsTrait) calculateNamespaces(e *Environment, defaultNamespaces 
...string) ([]string, error) {
-       namespaces, err := calculateNamespaces(strings.Split(t.List, ","))
+       kamelets := strings.Split(t.List, ",")
+       namespaces, err := calculateNamespaces(kamelets)
        if err != nil {
                return namespaces, err
        }
-       if len(namespaces) > 0 {
-               if e.Integration.Spec.ServiceAccountName == "" {
-                       return nil, errors.New("you must to use an authorized 
ServiceAccount to access cross-namespace resources kamelets. " +
-                               "Set it in the Integration spec accordingly")
+       if len(namespaces) > 0 && e.Integration.Spec.ServiceAccountName == "" {
+               return nil, errors.New("you must to use an authorized 
ServiceAccount to access cross-namespace resources kamelets. " +
+                       "Set it in the Integration spec accordingly")
+       }
+       // verify an SA exists and it is authorized for each Kamelet
+       for _, kml := range kamelets {
+               ns, err := getKameletNamespace(kml)
+               if err != nil {
+                       return nil, fmt.Errorf("could not parse kamelet 
namespace: %w", err)
                }
-               // verify an SA exists and it is authorized for Kamelets in 
that namespace
-               for _, ns := range namespaces {
-                       ok, err := kubernetes.CheckServiceAccountPermission(
-                               e.Ctx,
-                               e.Client,
-                               fmt.Sprintf("system:serviceaccount:%s:%s", 
e.Integration.Namespace, e.Integration.Spec.ServiceAccountName),
-                               v1.SchemeGroupVersion.Group,
-                               "kamelets",
-                               ns,
-                               "get",
-                       )
-                       if err != nil {
-                               return nil, err
-                       }
-                       if !ok {
-                               return nil, fmt.Errorf("cross-namespace 
Integration reference authorization denied for the ServiceAccount %s and 
resources kamelets",
-                                       e.Integration.Spec.ServiceAccountName)
-                       }
+               if ns == "" {
+                       continue
+               }
+               ok, err := kubernetes.CheckServiceAccountPermission(
+                       e.Ctx,
+                       e.Client,
+                       fmt.Sprintf("system:serviceaccount:%s:%s", 
e.Integration.Namespace, e.Integration.Spec.ServiceAccountName),
+                       v1.SchemeGroupVersion.Group,
+                       "kamelets",
+                       ns,
+                       getKameletKey(kml),
+                       "get",
+               )
+               if err != nil {
+                       return nil, err
+               }
+               if !ok {
+                       return nil, fmt.Errorf("cross-namespace Integration 
reference authorization denied for the ServiceAccount %s and resources 
kamelets",
+                               e.Integration.Spec.ServiceAccountName)
                }
        }
 
diff --git a/pkg/trait/kamelets_test.go b/pkg/trait/kamelets_test.go
index 3afcaf77a..91652f4dd 100644
--- a/pkg/trait/kamelets_test.go
+++ b/pkg/trait/kamelets_test.go
@@ -850,6 +850,29 @@ func TestKameletMultiNamespace(t *testing.T) {
                "Kamelets [extra,timer] found in cluster")
 }
 
+func TestKameletMultiNamespaceDeniedResource(t *testing.T) {
+       flow := `
+- from:
+    uri: kamelet:timer
+    steps:
+    - to: kamelet:extra?kameletNamespace=ns1
+    - to: kamelet:restricted-kamelet?kameletNamespace=ns1
+`
+       trait, environment := createKameletsTestEnvironment(flow)
+       environment.Integration.Namespace = "default"
+       environment.Integration.Spec.ServiceAccountName = "cross-ns-sa"
+
+       enabled, condition, err := trait.Configure(environment)
+       require.NoError(t, err)
+       assert.True(t, enabled)
+       assert.Nil(t, condition)
+
+       err = trait.Apply(environment)
+       require.Error(t, err)
+       assert.Equal(t, "cross-namespace Integration reference authorization 
denied for the ServiceAccount cross-ns-sa "+
+               "and resources kamelets", err.Error())
+}
+
 func TestKameletMultiNamespaceMissing(t *testing.T) {
        flow := `
 - from:
diff --git a/pkg/util/bindings/catalog.go b/pkg/util/bindings/catalog.go
index 6e9f5bd33..36e6eb88b 100644
--- a/pkg/util/bindings/catalog.go
+++ b/pkg/util/bindings/catalog.go
@@ -105,6 +105,7 @@ func verifyResourceRBAC(ctx BindingContext, e v1.Endpoint) 
error {
                e.Ref.GroupVersionKind().Group,
                resources,
                e.Ref.Namespace,
+               e.Ref.Name,
                "get",
        )
 
diff --git a/pkg/util/bindings/catalog_test.go 
b/pkg/util/bindings/catalog_test.go
index 5a73b68db..44e0f2422 100644
--- a/pkg/util/bindings/catalog_test.go
+++ b/pkg/util/bindings/catalog_test.go
@@ -192,6 +192,32 @@ func TestValidateEndpointKameletCrossNS(t *testing.T) {
        require.NoError(t, err)
 }
 
+func TestValidateEndpointKameletCrossNSDeniedResource(t *testing.T) {
+       client, err := internal.NewFakeClient()
+       require.NoError(t, err)
+
+       endpoint := v1.Endpoint{
+               Ref: &corev1.ObjectReference{
+                       Kind:       v1.KameletKind,
+                       APIVersion: v1.SchemeGroupVersion.String(),
+                       Name:       "restricted-kamelet",
+                       Namespace:  "kamelet-ns",
+               },
+       }
+
+       bindingContext := BindingContext{
+               Namespace:          "default",
+               Client:             client,
+               Ctx:                context.Background(),
+               ServiceAccountName: "cross-ns-sa",
+       }
+
+       err = validateEndpoint(bindingContext, endpoint)
+       require.Error(t, err)
+       require.Equal(t, "cross-namespace Pipe reference authorization denied 
for the ServiceAccount cross-ns-sa"+
+               " and resources kamelets", err.Error())
+}
+
 func TestValidateEndpointKameletCrossNSNoSA(t *testing.T) {
        client, err := internal.NewFakeClient()
        require.NoError(t, err)
diff --git a/pkg/util/kubernetes/permission.go 
b/pkg/util/kubernetes/permission.go
index 867d93019..7e2870aec 100644
--- a/pkg/util/kubernetes/permission.go
+++ b/pkg/util/kubernetes/permission.go
@@ -56,14 +56,15 @@ func CheckSelfPermission(ctx context.Context, client 
kubernetes.Interface, group
 
 // CheckServiceAccountPermission verify if a given Service Account can access 
a given resource.
 // Service Account must be provided as "system:serviceaccount:namespace:name" 
format.
-func CheckServiceAccountPermission(ctx context.Context, client 
kubernetes.Interface, sa, group, resources, namespace, verb string) (bool, 
error) {
+func CheckServiceAccountPermission(ctx context.Context, client 
kubernetes.Interface, sa, group, resource, namespace, name, verb string) (bool, 
error) {
        sarReview := &authorizationv1.SubjectAccessReview{
                Spec: authorizationv1.SubjectAccessReviewSpec{
                        User: sa,
                        ResourceAttributes: &authorizationv1.ResourceAttributes{
                                Group:     group,
                                Namespace: namespace,
-                               Resource:  resources,
+                               Resource:  resource,
+                               Name:      name,
                                Verb:      verb,
                        },
                },
diff --git a/pkg/util/kubernetes/permission_test.go 
b/pkg/util/kubernetes/permission_test.go
new file mode 100644
index 000000000..10ed4f878
--- /dev/null
+++ b/pkg/util/kubernetes/permission_test.go
@@ -0,0 +1,63 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kubernetes
+
+import (
+       "context"
+       "testing"
+
+       authorizationv1 "k8s.io/api/authorization/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/client-go/kubernetes/fake"
+       k8stesting "k8s.io/client-go/testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func TestCheckServiceAccountPermissionByResourceName(t *testing.T) {
+       client := fake.NewSimpleClientset()
+       client.PrependReactor("create", "subjectaccessreviews", func(action 
k8stesting.Action) (bool, runtime.Object, error) {
+               createAction, ok := action.(k8stesting.CreateAction)
+               require.True(t, ok)
+               review, ok := 
createAction.GetObject().(*authorizationv1.SubjectAccessReview)
+               require.True(t, ok)
+               require.Equal(t, "system:serviceaccount:source:integration", 
review.Spec.User)
+               require.Equal(t, "camel.apache.org", 
review.Spec.ResourceAttributes.Group)
+               require.Equal(t, "kamelets", 
review.Spec.ResourceAttributes.Resource)
+               require.Equal(t, "target", 
review.Spec.ResourceAttributes.Namespace)
+               require.Equal(t, "get", review.Spec.ResourceAttributes.Verb)
+
+               review.Status.Allowed = review.Spec.ResourceAttributes.Name == 
"allowed-kamelet"
+
+               return true, review, nil
+       })
+
+       allowed, err := CheckServiceAccountPermission(
+               context.Background(), client, 
"system:serviceaccount:source:integration",
+               "camel.apache.org", "kamelets", "target", "allowed-kamelet", 
"get",
+       )
+       require.NoError(t, err)
+       require.True(t, allowed)
+
+       allowed, err = CheckServiceAccountPermission(
+               context.Background(), client, 
"system:serviceaccount:source:integration",
+               "camel.apache.org", "kamelets", "target", "denied-kamelet", 
"get",
+       )
+       require.NoError(t, err)
+       require.False(t, allowed)
+}

Reply via email to