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


The following commit(s) were added to refs/heads/main by this push:
     new 45a2dc6b5 Fix #6678: restrict toleration.taints keys via operator 
allow list (#6695)
45a2dc6b5 is described below

commit 45a2dc6b51828f08f4ca05dcd45567f25e3e627f
Author: Harsh Mehta <[email protected]>
AuthorDate: Tue Jun 30 14:54:19 2026 +0530

    Fix #6678: restrict toleration.taints keys via operator allow list (#6695)
    
    * feat(toleration): add support for allowed taint keys in tolerations
    
    Signed-off-by: Harsh Mehta <[email protected]>
    
    * fix(toleration): streamline taint filtering and improve taint key 
extraction
    
    Signed-off-by: Harsh Mehta <[email protected]>
    
    ---------
    
    Signed-off-by: Harsh Mehta <[email protected]>
---
 docs/modules/ROOT/pages/installation/builds.adoc |  4 ++
 docs/modules/traits/pages/toleration.adoc        |  3 ++
 pkg/platform/env_platform.go                     | 21 ++++++++
 pkg/platform/env_platform_test.go                | 26 ++++++++++
 pkg/trait/toleration.go                          | 34 +++++++++++++
 pkg/trait/toleration_test.go                     | 65 ++++++++++++++++++++++++
 6 files changed, 153 insertions(+)

diff --git a/docs/modules/ROOT/pages/installation/builds.adoc 
b/docs/modules/ROOT/pages/installation/builds.adoc
index b9b93ba5d..38f88f508 100644
--- a/docs/modules/ROOT/pages/installation/builds.adoc
+++ b/docs/modules/ROOT/pages/installation/builds.adoc
@@ -47,6 +47,10 @@ Here a quick resume of the parameters you can configure as 
environment variables
 | Maximum number of builds that can run concurrently.
 | `3` if build strategy is `routine`, `10` if `pod`
 
+| TOLERATION_TAINTS_ALLOWED_KEYS
+| Comma-separated list of taint keys that CR authors are permitted to use in 
`toleration.taints`. When unset or empty all keys are accepted. Taints whose 
key is not in the list are dropped and an info message is logged. Example: 
`node-role.kubernetes.io/master,disktype`.
+|
+
 | AFFINITY_NODE_LABELS_ALLOWED_KEYS
 | Comma-separated list of label keys that CR authors are permitted to use in 
`affinity.nodeAffinityLabels`. When unset or empty all keys are accepted. 
Expressions whose key is not in the list are dropped and an info message is 
logged. Example: `kubernetes.io/hostname,topology.kubernetes.io/zone`.
 |
diff --git a/docs/modules/traits/pages/toleration.adoc 
b/docs/modules/traits/pages/toleration.adoc
index b6f1eb38f..01711fe56 100755
--- a/docs/modules/traits/pages/toleration.adoc
+++ b/docs/modules/traits/pages/toleration.adoc
@@ -65,3 +65,6 @@ $ kamel run -t 
toleration.taints="node.kubernetes.io/network-unavailable:NoExecu
 +
 [source,console]
 $ kamel run -t toleration.taints="disktype=ssd:PreferNoSchedule" ...
+
+
+NOTE: Operators can restrict which taint keys CR authors are permitted to use 
in `toleration.taints` by setting the `TOLERATION_TAINTS_ALLOWED_KEYS` 
environment variable on the operator deployment to a comma-separated list of 
allowed keys (e.g. `node-role.kubernetes.io/master,disktype`). Taints whose key 
is not in the list are dropped and an info message is logged. When the variable 
is unset or empty, all keys are accepted (default behavior). See build 
environment variables documentation [...]
diff --git a/pkg/platform/env_platform.go b/pkg/platform/env_platform.go
index 24527ed31..c9ef96d94 100644
--- a/pkg/platform/env_platform.go
+++ b/pkg/platform/env_platform.go
@@ -173,6 +173,27 @@ func publishStrategy() 
v1.IntegrationPlatformBuildPublishStrategy {
        return DefaultPublishStrategy
 }
 
+// TolerationTaintsAllowList returns the list of taint keys that are allowed 
to be used in
+// toleration.taints. When the list is empty (TOLERATION_TAINTS_ALLOWED_KEYS 
is unset or blank),
+// any key is permitted. When the list is non-empty only taints whose key is 
in the list are
+// accepted; others are dropped and an info message is logged by the trait.
+func TolerationTaintsAllowList() []string {
+       raw := GetEnvOrDefault("TOLERATION_TAINTS_ALLOWED_KEYS", "")
+       if raw == "" {
+               return nil
+       }
+       parts := strings.Split(raw, ",")
+       result := make([]string, 0, len(parts))
+       for _, p := range parts {
+               p = strings.TrimSpace(p)
+               if p != "" {
+                       result = append(result, p)
+               }
+       }
+
+       return result
+}
+
 // AffinityNodeLabelsAllowList returns the list of label keys that are allowed 
to be used in
 // affinity.nodeAffinityLabels. When the list is empty 
(AFFINITY_NODE_LABELS_ALLOWED_KEYS is
 // unset or blank), any key is permitted. When the list is non-empty only 
expressions whose
diff --git a/pkg/platform/env_platform_test.go 
b/pkg/platform/env_platform_test.go
index 797b7517c..468dd0ecf 100644
--- a/pkg/platform/env_platform_test.go
+++ b/pkg/platform/env_platform_test.go
@@ -168,6 +168,32 @@ func TestBuilderNodeSelectorAllowList_MultipleKeys(t 
*testing.T) {
        assert.Equal(t, []string{"kubernetes.io/hostname", 
"node-role.kubernetes.io/worker", "topology.kubernetes.io/zone"}, allowList)
 }
 
+func TestTolerationTaintsAllowList_NotSet(t *testing.T) {
+       allowList := TolerationTaintsAllowList()
+       assert.Nil(t, allowList)
+}
+
+func TestTolerationTaintsAllowList_Empty(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", "")
+
+       allowList := TolerationTaintsAllowList()
+       assert.Empty(t, allowList)
+}
+
+func TestTolerationTaintsAllowList_SingleKey(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", 
"node-role.kubernetes.io/master")
+
+       allowList := TolerationTaintsAllowList()
+       assert.Equal(t, []string{"node-role.kubernetes.io/master"}, allowList)
+}
+
+func TestTolerationTaintsAllowList_MultipleKeys(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", 
"node-role.kubernetes.io/master, disktype ")
+
+       allowList := TolerationTaintsAllowList()
+       assert.Equal(t, []string{"node-role.kubernetes.io/master", "disktype"}, 
allowList)
+}
+
 func TestAffinityNodeLabelsAllowList_NotSet(t *testing.T) {
        allowList := AffinityNodeLabelsAllowList()
        assert.Nil(t, allowList)
diff --git a/pkg/trait/toleration.go b/pkg/trait/toleration.go
index 78e2c6a7f..0b0ab1c44 100644
--- a/pkg/trait/toleration.go
+++ b/pkg/trait/toleration.go
@@ -20,11 +20,14 @@ package trait
 import (
        "errors"
        "fmt"
+       "slices"
+       "strings"
 
        corev1 "k8s.io/api/core/v1"
        "k8s.io/utils/ptr"
 
        traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait"
+       "github.com/apache/camel-k/v2/pkg/platform"
        "github.com/apache/camel-k/v2/pkg/util/kubernetes"
 )
 
@@ -53,6 +56,8 @@ func (t *tolerationTrait) Configure(e *Environment) (bool, 
*TraitCondition, erro
                return false, nil, errors.New("no taint was provided")
        }
 
+       t.filterTaints()
+
        return e.IntegrationInRunningPhases(), nil, nil
 }
 
@@ -73,3 +78,32 @@ func (t *tolerationTrait) Apply(e *Environment) error {
 
        return nil
 }
+
+// filterTaints removes taint entries whose key is not in the 
operator-configured allow list.
+// When TOLERATION_TAINTS_ALLOWED_KEYS is unset or empty all taints are kept.
+func (t *tolerationTrait) filterTaints() {
+       allowList := platform.TolerationTaintsAllowList()
+       if len(allowList) == 0 || len(t.Taints) == 0 {
+               return
+       }
+       kept := make([]string, 0, len(t.Taints))
+       for _, taint := range t.Taints {
+               key := taintKey(taint)
+               if slices.Contains(allowList, key) {
+                       kept = append(kept, taint)
+               } else {
+                       t.L.Info("toleration.taints key is not in the allowed 
list and will be ignored",
+                               "key", key, "allowedKeys", allowList)
+               }
+       }
+       t.Taints = kept
+}
+
+// taintKey extracts the key from a taint string of the form 
Key[=Value]:Effect[:Seconds].
+func taintKey(taint string) string {
+       if parts := strings.SplitN(taint, "=", 2); len(parts) > 1 {
+               return parts[0]
+       }
+
+       return strings.SplitN(taint, ":", 2)[0]
+}
diff --git a/pkg/trait/toleration_test.go b/pkg/trait/toleration_test.go
index ea4538902..7f311ae28 100644
--- a/pkg/trait/toleration_test.go
+++ b/pkg/trait/toleration_test.go
@@ -131,6 +131,71 @@ func TestTolerationValidTaints(t *testing.T) {
        require.NoError(t, err)
 }
 
+func TestFilterTaints_NoAllowList(t *testing.T) {
+       trait := createNominalTolerationTrait()
+       trait.Taints = []string{"disktype:NoSchedule", 
"node-role.kubernetes.io/master:NoExecute"}
+
+       trait.filterTaints()
+       assert.Len(t, trait.Taints, 2)
+}
+
+func TestFilterTaints_AllowListFilters(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", "disktype")
+
+       trait := createNominalTolerationTrait()
+       trait.Taints = []string{"disktype:NoSchedule", 
"node-role.kubernetes.io/master:NoExecute"}
+
+       trait.filterTaints()
+       assert.Equal(t, []string{"disktype:NoSchedule"}, trait.Taints)
+}
+
+func TestFilterTaints_AllAllowed(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", 
"disktype,node-role.kubernetes.io/master")
+
+       trait := createNominalTolerationTrait()
+       trait.Taints = []string{"disktype:NoSchedule", 
"node-role.kubernetes.io/master:NoExecute"}
+
+       trait.filterTaints()
+       assert.Len(t, trait.Taints, 2)
+}
+
+func TestFilterTaints_AllDropped(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", "disktype")
+
+       trait := createNominalTolerationTrait()
+       trait.Taints = []string{"node-role.kubernetes.io/master:NoExecute"}
+
+       trait.filterTaints()
+       assert.Empty(t, trait.Taints)
+}
+
+func TestFilterTaints_KeyWithValue(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", "my-toleration")
+
+       trait := createNominalTolerationTrait()
+       trait.Taints = []string{"my-toleration=my-value:NoExecute", 
"other=val:NoSchedule"}
+
+       trait.filterTaints()
+       assert.Equal(t, []string{"my-toleration=my-value:NoExecute"}, 
trait.Taints)
+}
+
+func TestApplyTolerationWithAllowList(t *testing.T) {
+       t.Setenv("TOLERATION_TAINTS_ALLOWED_KEYS", "disktype")
+
+       tolerationTrait := createNominalTolerationTrait()
+       tolerationTrait.Taints = []string{"disktype:NoSchedule", 
"node-role.kubernetes.io/master:NoExecute"}
+
+       environment, deployment := createNominalDeploymentTraitTest()
+       _, _, err := tolerationTrait.Configure(environment)
+       require.NoError(t, err)
+       err = tolerationTrait.Apply(environment)
+
+       require.NoError(t, err)
+       tolerations := deployment.Spec.Template.Spec.Tolerations
+       assert.Len(t, tolerations, 1)
+       assert.Equal(t, "disktype", tolerations[0].Key)
+}
+
 func createNominalTolerationTrait() *tolerationTrait {
        tolerationTrait, _ := newTolerationTrait().(*tolerationTrait)
        tolerationTrait.Enabled = ptr.To(true)

Reply via email to