This is an automated email from the ASF dual-hosted git repository. pcongiusti pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit b6e4d4e2d56aeddcf4390d47747fae09160deb55 Author: Pranjul Kalsi <[email protected]> AuthorDate: Sat Dec 13 13:02:21 2025 +0530 chore(trait): deprecate master trait in favour of manual RBAC configuration --- docs/modules/ROOT/partials/apis/camel-k-crds.adoc | 9 ++ docs/modules/traits/pages/master.adoc | 123 ++++++++++++++++++++++ e2e/common/traits/master_test.go | 88 ++++++++++++++-- pkg/apis/camel/v1/trait/master.go | 10 ++ pkg/trait/master.go | 17 ++- pkg/trait/master_test.go | 88 ++++++++++++++-- 6 files changed, 318 insertions(+), 17 deletions(-) diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 511522034..996c48276 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -8326,6 +8326,15 @@ It's activated automatically when using the master endpoint in a route, e.g. `fr NOTE: this trait adds special permissions to the integration service account in order to read/write configmaps and read pods. It's recommended to use a different service account than "default" when running the integration. +WARNING: The Master trait is **deprecated** and will be removed in future release versions. +This trait requires the operator to manage RBAC explicitly, which should be avoided for security +and simplicity reasons. Users should manually create the required Role and RoleBinding, then configure +Quarkus properties directly: + + -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock + -p quarkus.camel.cluster.kubernetes.resource-type=Lease + -p quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name> + [cols="2,2a",options="header"] |=== diff --git a/docs/modules/traits/pages/master.adoc b/docs/modules/traits/pages/master.adoc index e120f47b2..ee82ddf27 100755 --- a/docs/modules/traits/pages/master.adoc +++ b/docs/modules/traits/pages/master.adoc @@ -1,6 +1,8 @@ = Master Trait // Start of autogenerated code - DO NOT EDIT! (badges) +[.badges] +[.badge-key]##Deprecated since##[.badge-unsupported]##2.9.0## // End of autogenerated code - DO NOT EDIT! (badges) // Start of autogenerated code - DO NOT EDIT! (description) The Master trait allows to configure the integration to automatically leverage Kubernetes resources for doing @@ -11,6 +13,15 @@ It's activated automatically when using the master endpoint in a route, e.g. `fr NOTE: this trait adds special permissions to the integration service account in order to read/write configmaps and read pods. It's recommended to use a different service account than "default" when running the integration. +WARNING: The Master trait is **deprecated** and will be removed in future release versions. +This trait requires the operator to manage RBAC explicitly, which should be avoided for security +and simplicity reasons. Users should manually create the required Role and RoleBinding, then configure +Quarkus properties directly: + + -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock + -p quarkus.camel.cluster.kubernetes.resource-type=Lease + -p quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name> + This trait is available in the following profiles: **Kubernetes, Knative, OpenShift**. @@ -63,3 +74,115 @@ Name of the configmap/lease resource that will be used to store the lock. Defaul |=== // End of autogenerated code - DO NOT EDIT! (configuration) + +== Migration Guide + +The Master trait is deprecated and will be removed in a future release. This trait requires the operator to manage RBAC explicitly, which should be avoided for security and simplicity reasons. + +=== Why Migrate? + +* **Reduced Operator Permissions**: The operator no longer needs permissions to create Role and RoleBinding resources +* **Explicit RBAC Control**: Users have full control over the RBAC configuration +* **Simplified Security Model**: Better separation of concerns between integration and infrastructure + +=== Migration Steps + +==== Step 1: Create RBAC Resources Manually + +Apply the following RBAC resources to your namespace: + +[source,yaml] +---- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: <integration-name>-master +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: <integration-name>-master +subjects: + - kind: ServiceAccount + name: <service-account-name> +roleRef: + kind: Role + name: <integration-name>-master + apiGroup: rbac.authorization.k8s.io +---- + +==== Step 2: Configure Quarkus Properties + +Instead of using the trait properties, configure the equivalent Quarkus properties directly: + +[cols="2m,2m,3a"] +|=== +|Master Trait Property | Quarkus Property | Description + +| master.resource-name +| quarkus.camel.cluster.kubernetes.resource-name +| Name of the lock resource + +| master.resource-type +| quarkus.camel.cluster.kubernetes.resource-type +| Type of Kubernetes resource (Lease or ConfigMap) + +| master.label-key / master.label-value +| quarkus.camel.cluster.kubernetes.labels."<key>" +| Labels for pod identification + +|=== + +==== Example Migration + +**Before (using deprecated trait):** +[source,console] +---- +$ kamel run MyRoute.java \ + -t master.resource-name=my-lock \ + -t master.resource-type=Lease \ + -t master.label-key=leader-group \ + -t master.label-value=my-group +---- + +**After (using Quarkus properties):** +[source,console] +---- +# First apply the RBAC resources +$ kubectl apply -f master-rbac.yaml + +# Then run with Quarkus properties +$ kamel run MyRoute.java \ + --service-account my-integration-sa \ + -p quarkus.camel.cluster.kubernetes.resource-name=my-lock \ + -p quarkus.camel.cluster.kubernetes.resource-type=Lease \ + -p quarkus.camel.cluster.kubernetes.labels."leader-group"=my-group +---- + +=== Notes + +* The `master:` component in Camel routes continues to work; only the automatic RBAC creation is removed +* Ensure the service account used by the integration has the necessary permissions +* For existing integrations, create the RBAC resources before removing the trait configuration diff --git a/e2e/common/traits/master_test.go b/e2e/common/traits/master_test.go index b545eba1f..731525b80 100644 --- a/e2e/common/traits/master_test.go +++ b/e2e/common/traits/master_test.go @@ -31,6 +31,8 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" . "github.com/apache/camel-k/v2/e2e/support" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -39,25 +41,46 @@ import ( func TestMasterTrait(t *testing.T) { t.Parallel() WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) { - t.Run("master works", func(t *testing.T) { - g.Expect(KamelRun(t, ctx, ns, "files/Master.java").Execute()).To(Succeed()) - g.Eventually(IntegrationPodPhase(t, ctx, ns, "master"), TestTimeoutLong).Should(Equal(corev1.PodRunning)) - g.Eventually(IntegrationLogs(t, ctx, ns, "master"), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) + t.Run("master works with properties", func(t *testing.T) { + name := "master" + // Create RBAC resources for the master component + CreateMasterRBAC(t, ctx, g, ns, name, "default") + + // Run using Quarkus properties instead of deprecated trait + g.Expect(KamelRun(t, ctx, ns, "files/Master.java", + "-p", fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s-lock", name), + "-p", "quarkus.camel.cluster.kubernetes.resource-type=Lease", + "-p", fmt.Sprintf("quarkus.camel.cluster.kubernetes.labels.\"camel.apache.org/integration\"=%s", name), + ).Execute()).To(Succeed()) + g.Eventually(IntegrationPodPhase(t, ctx, ns, name), TestTimeoutLong).Should(Equal(corev1.PodRunning)) + g.Eventually(IntegrationLogs(t, ctx, ns, name), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) g.Expect(Kamel(t, ctx, "delete", "--all", "-n", ns).Execute()).To(Succeed()) }) - t.Run("only one integration with master runs", func(t *testing.T) { + t.Run("only one integration with master runs using properties", func(t *testing.T) { nameFirst := RandomizedSuffixName("first") + nameSecond := RandomizedSuffixName("second") + lockName := nameFirst + "-lock" + + CreateMasterRBAC(t, ctx, g, ns, nameFirst, "default") + CreateMasterRBAC(t, ctx, g, ns, nameSecond, "default") + g.Expect(KamelRun(t, ctx, ns, "files/Master.java", "--name", nameFirst, - "--label", "leader-group=same", "-t", "master.label-key=leader-group", "-t", "master.label-value=same", "-t", "owner.target-labels=leader-group", + "--label", "leader-group=same", + "-t", "owner.target-labels=leader-group", + "-p", fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s", lockName), + "-p", "quarkus.camel.cluster.kubernetes.resource-type=Lease", + "-p", "quarkus.camel.cluster.kubernetes.labels.\"leader-group\"=same", ).Execute()).To(Succeed()) g.Eventually(IntegrationConditionStatus(t, ctx, ns, nameFirst, v1.IntegrationConditionReady), TestTimeoutShort).Should(Equal(corev1.ConditionTrue)) g.Eventually(IntegrationLogs(t, ctx, ns, nameFirst), TestTimeoutShort).Should(ContainSubstring("Magicstring!")) - // Start a second integration with the same lock (it should not start the route before 15 seconds) - nameSecond := RandomizedSuffixName("second") + g.Expect(KamelRun(t, ctx, ns, "files/Master.java", "--name", nameSecond, - "--label", "leader-group=same", "-t", "master.label-key=leader-group", "-t", "master.label-value=same", "-t", "owner.target-labels=leader-group", - "-t", fmt.Sprintf("master.resource-name=%s-lock", nameFirst), + "--label", "leader-group=same", + "-t", "owner.target-labels=leader-group", + "-p", fmt.Sprintf("quarkus.camel.cluster.kubernetes.resource-name=%s", lockName), + "-p", "quarkus.camel.cluster.kubernetes.resource-type=Lease", + "-p", "quarkus.camel.cluster.kubernetes.labels.\"leader-group\"=same", ).Execute()).To(Succeed()) g.Eventually(IntegrationLogs(t, ctx, ns, nameSecond), TestTimeoutShort).Should(ContainSubstring("started in")) g.Eventually(IntegrationLogs(t, ctx, ns, nameSecond), 15*time.Second).ShouldNot(ContainSubstring("Magicstring!")) @@ -65,3 +88,48 @@ func TestMasterTrait(t *testing.T) { }) }) } + +// CreateMasterRBAC creates the Role and RoleBinding +func CreateMasterRBAC(t *testing.T, ctx context.Context, g *WithT, ns string, name string, serviceAccount string) { + t.Helper() + + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-master", + Namespace: ns, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"coordination.k8s.io"}, + Resources: []string{"leases"}, + Verbs: []string{"create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + } + g.Expect(TestClient(t).Create(ctx, role)).To(Succeed()) + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-master", + Namespace: ns, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: serviceAccount, + Namespace: ns, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: name + "-master", + }, + } + g.Expect(TestClient(t).Create(ctx, roleBinding)).To(Succeed()) +} diff --git a/pkg/apis/camel/v1/trait/master.go b/pkg/apis/camel/v1/trait/master.go index 9a08bb378..a7bf4bb02 100644 --- a/pkg/apis/camel/v1/trait/master.go +++ b/pkg/apis/camel/v1/trait/master.go @@ -25,7 +25,17 @@ package trait // NOTE: this trait adds special permissions to the integration service account in order to read/write configmaps and read pods. // It's recommended to use a different service account than "default" when running the integration. // +// WARNING: The Master trait is **deprecated** and will be removed in future release versions. +// This trait requires the operator to manage RBAC explicitly, which should be avoided for security +// and simplicity reasons. Users should manually create the required Role and RoleBinding, then configure +// Quarkus properties directly: +// +// -p quarkus.camel.cluster.kubernetes.resource-name=<integration>-lock +// -p quarkus.camel.cluster.kubernetes.resource-type=Lease +// -p quarkus.camel.cluster.kubernetes.labels."camel.apache.org/integration"=<integration-name> +// // +camel-k:trait=master. +// +camel-k:deprecated=2.9.0. type MasterTrait struct { Trait `json:",inline" property:",squash"` diff --git a/pkg/trait/master.go b/pkg/trait/master.go index 7f7f5d7bd..9ac75e780 100644 --- a/pkg/trait/master.go +++ b/pkg/trait/master.go @@ -21,6 +21,7 @@ import ( "fmt" "strings" + corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime/pkg/client" @@ -100,7 +101,21 @@ func (t *masterTrait) Configure(e *Environment) (bool, *TraitCondition, error) { } } - return enabled, nil, nil + // Add deprecation warning condition when the trait is enabled + var condition *TraitCondition + if enabled { + condition = NewIntegrationCondition( + "Master", + v1.IntegrationConditionTraitInfo, + corev1.ConditionTrue, + TraitConfigurationReason, + "The master trait is deprecated and will be removed in a future release. "+ + "Please manually create the required Role and RoleBinding, then configure Quarkus properties directly. "+ + "See documentation for migration guide.", + ) + } + + return enabled, condition, nil } func (t *masterTrait) Apply(e *Environment) error { diff --git a/pkg/trait/master_test.go b/pkg/trait/master_test.go index 9a9637272..f819f295f 100644 --- a/pkg/trait/master_test.go +++ b/pkg/trait/master_test.go @@ -89,9 +89,12 @@ func TestMasterOn(t *testing.T) { mt := NewMasterTrait() mt.InjectClient(client) // Initialization phase - configured, conditions, err := mt.Configure(&environment) + configured, condition, err := mt.Configure(&environment) require.NoError(t, err) - assert.Empty(t, conditions) + // Verify deprecation condition is present + assert.NotNil(t, condition) + assert.Equal(t, TraitConfigurationReason, condition.reason) + assert.Contains(t, condition.message, "deprecated") assert.True(t, configured) err = mt.Apply(&environment) require.NoError(t, err) @@ -180,9 +183,10 @@ func TestMasterOff(t *testing.T) { mt := NewMasterTrait() mt.InjectClient(client) // Initialization phase - configured, conditions, err := mt.Configure(&environment) + configured, condition, err := mt.Configure(&environment) require.NoError(t, err) - assert.Empty(t, conditions) + + assert.Nil(t, condition) assert.False(t, configured) } @@ -243,9 +247,12 @@ func TestMasterAuto(t *testing.T) { mt.InjectClient(client) trait, _ := mt.(*masterTrait) // Initialization phase - configured, conditions, err := trait.Configure(&environment) + configured, condition, err := trait.Configure(&environment) require.NoError(t, err) - assert.Empty(t, conditions) + // Verify deprecation condition is present + assert.NotNil(t, condition) + assert.Equal(t, TraitConfigurationReason, condition.reason) + assert.Contains(t, condition.message, "deprecated") assert.True(t, configured) err = trait.Apply(&environment) require.NoError(t, err) @@ -261,3 +268,72 @@ func TestMasterAuto(t *testing.T) { } assert.Equal(t, expectedTrait.Trait, trait.Trait) } + +func TestMasterTraitDeprecationWarning(t *testing.T) { + catalog, err := camel.DefaultCatalog() + require.NoError(t, err) + + client, err := internal.NewFakeClient() + require.NoError(t, err) + traitCatalog := NewCatalog(nil) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Client: client, + Integration: &v1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Status: v1.IntegrationStatus{ + Phase: v1.IntegrationPhaseInitialization, + }, + Spec: v1.IntegrationSpec{ + Profile: v1.TraitProfileKnative, + Sources: []v1.SourceSpec{ + { + DataSpec: v1.DataSpec{ + Name: "Master.java", + Content: `from("master:lock:timer:tick").to("log:test")`, + }, + Language: v1.LanguageJavaSource, + }, + }, + Traits: v1.Traits{}, + }, + }, + Platform: &v1.IntegrationPlatform{ + Spec: v1.IntegrationPlatformSpec{ + Cluster: v1.IntegrationPlatformClusterOpenShift, + Build: v1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1.IntegrationPlatformBuildPublishStrategyJib, + Registry: v1.RegistrySpec{Address: "registry"}, + RuntimeVersion: catalog.Runtime.Version, + }, + Profile: v1.TraitProfileKnative, + }, + Status: v1.IntegrationPlatformStatus{ + Phase: v1.IntegrationPlatformPhaseReady, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + environment.Platform.ResyncStatusFullConfig() + + mt := NewMasterTrait() + mt.InjectClient(client) + configured, condition, err := mt.Configure(&environment) + require.NoError(t, err) + assert.True(t, configured) + + require.NotNil(t, condition) + assert.Equal(t, TraitConfigurationReason, condition.reason) + assert.Contains(t, condition.message, "deprecated") + assert.Contains(t, condition.message, "removed") + assert.Contains(t, condition.message, "Role") + assert.Contains(t, condition.message, "RoleBinding") + assert.Contains(t, condition.message, "Quarkus properties") +}
