You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2026/09/23 05:56:53 UTC
(camel-k) 01/02: Fix #6778: Validate service account permission by resource name
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 <al...@gmail.com>
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)
+}