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 0dd10d156 Feature/6859 network policy clean (#6866)
0dd10d156 is described below

commit 0dd10d156c6282ac3a55cdfddc55603b53a4d09a
Author: Sanjeev Kumar <[email protected]>
AuthorDate: Tue Oct 13 18:56:37 2026 +0530

    Feature/6859 network policy clean (#6866)
    
    * docs(installation): explain multi-namespace RBAC setup
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * feat(service): add optional ingress NetworkPolicy
    
    * test(e2e): retain NetworkPolicy diagnostics
    
    * ci: use stable artifact uploader
    
    * fix(ci): correct NetworkPolicy E2E test invocation
    
    * chore: remove unrelated installation documentation change
    
    * ci: fix network policy e2e workflow
    
    * ci: run network policy workflow on every PR
    
    * Fix #6859: address Service NetworkPolicy review feedback
    
    * Fix #6859: preserve Makefile link in test target
    
    * Fix #6859: keep Makefile change limited to test target
    
    * Fix #6859: restore exact Makefile behavior
    
    * Fix #6859: preserve Makefile variable escaping
    
    * Fix #6859: address remaining NetworkPolicy review feedback
    
    * ci: align NetworkPolicy workflow triggers
    
    * test: follow shared NetworkPolicy test target pattern
    
    * fix: use canonical license header for NetworkPolicy consumer
    
    * test: add NetworkPolicy server fixture
    
    * fix: preserve shell failure counter expansion
    
    * fix: preserve shell failure counter expansion
    
    * fix: make NetworkPolicy test failure propagation explicit
    
    * style: separate workflow triggers from permissions
    
    * ci: remove unnecessary GitHub token from network policy workflow
---
 .github/actions/infra-setting/action.yml           |   7 ++
 .github/workflows/network-policy.yml               |  84 ++++++++++++++++
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc  |  18 ++++
 docs/modules/traits/pages/service.adoc             |  27 +++++
 e2e/networkpolicy/files/NetworkPolicyConsumer.java |  37 +++++++
 e2e/networkpolicy/files/PlatformHttpServer.java    |  26 +++++
 e2e/networkpolicy/network_policy_test.go           | 110 +++++++++++++++++++++
 helm/camel-k/templates/rbacs-descoped.yaml         |  11 +++
 helm/camel-k/templates/rbacs-namespaced.yaml       |  11 +++
 pkg/apis/camel/v1/trait/service.go                 |   7 ++
 pkg/apis/camel/v1/trait/zz_generated.deepcopy.go   |  19 ++++
 .../camel.apache.org_integrationplatforms.yaml     |  15 +++
 .../camel.apache.org_integrationprofiles.yaml      |  15 +++
 .../crd/bases/camel.apache.org_integrations.yaml   |  15 +++
 .../rbac/descoped/operator-cluster-role.yaml       |  11 +++
 .../config/rbac/namespaced/operator-role.yaml      |  11 +++
 pkg/trait/service.go                               |  58 +++++++++++
 pkg/trait/service_test.go                          |  95 ++++++++++++++++++
 script/Makefile                                    |   8 ++
 19 files changed, 585 insertions(+)

diff --git a/.github/actions/infra-setting/action.yml 
b/.github/actions/infra-setting/action.yml
index 79be5461b..14117c0cc 100644
--- a/.github/actions/infra-setting/action.yml
+++ b/.github/actions/infra-setting/action.yml
@@ -18,6 +18,12 @@
 name: infra-setting
 description: 'Setting CI infrastructure'
 
+inputs:
+  cni:
+    description: 'CNI plug-in to use for Minikube'
+    required: false
+    default: ''
+
 runs:
   using: "composite"
 
@@ -39,6 +45,7 @@ runs:
       cpus: max
       memory: max
       container-runtime: docker
+      cni: ${{ inputs.cni }}
 
   - id: minikube-settings
     name: Set docker-env
diff --git a/.github/workflows/network-policy.yml 
b/.github/workflows/network-policy.yml
new file mode 100644
index 000000000..f0b095e18
--- /dev/null
+++ b/.github/workflows/network-policy.yml
@@ -0,0 +1,84 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+name: network-policy
+
+on:
+  pull_request:
+    branches:
+      - main
+      - "release-*"
+    paths-ignore:
+      - 'docs/**'
+      - 'java/**'
+      - 'proposals/**'
+      - '**.adoc'
+      - '**.md'
+      - 'KEYS'
+      - 'LICENSE'
+      - 'NOTICE'
+  push:
+    branches:
+      - main
+      - "release-*"
+    paths-ignore:
+      - 'docs/**'
+      - 'java/**'
+      - 'proposals/**'
+      - '**.adoc'
+      - '**.md'
+      - 'KEYS'
+      - 'LICENSE'
+      - 'NOTICE'
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+env:
+  CAMEL_K_TEST_TIMEOUT_SHORT: 2m
+  CAMEL_K_TEST_TIMEOUT_MEDIUM: 5m
+  CAMEL_K_TEST_TIMEOUT_LONG: 10m
+
+jobs:
+  network-policy:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v7
+        with:
+          persist-credentials: false
+          submodules: recursive
+
+      - name: Infra setting
+        uses: ./.github/actions/infra-setting
+        with:
+          cni: calico
+
+      - name: Install operator
+        shell: bash
+        run: |
+          kubectl create ns camel-k
+          kubectl create configmap camel-k-operator-configmap-configuration \
+            
--from-literal=MAVEN_REPOSITORIES_ALLOWED="https://maven.repository.redhat.com/ga@id=redhat";
 \
+            -n camel-k
+          kubectl apply -k install/overlays/all-namespaces/ --server-side 
--force-conflicts
+          kubectl wait --for=condition=available deployment/camel-k-operator 
-n camel-k --timeout=60s
+
+      - name: Run NetworkPolicy E2E test
+        shell: bash
+        run: make test-network-policy
diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc 
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index a9ff2589e..a73dd0f2b 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -9725,6 +9725,24 @@ map[string]string
 
 The labels added to the Service object.
 
+|`networkPolicyEnabled` +
+bool
+|
+
+Enables the NetworkPolicy managed by the Service trait. Defaults to false. 
When enabled, at least one of the namespace or pod selectors must be configured.
+
+|`networkPolicyNamespaceSelector` +
+map[string]string
+|
+
+Labels used to restrict the source namespaces allowed by the NetworkPolicy.
+
+|`networkPolicyPodSelector` +
+map[string]string
+|
+
+Labels used to restrict the source pods allowed by the NetworkPolicy.
+
 |`ports` +
 []string
 |
diff --git a/docs/modules/traits/pages/service.adoc 
b/docs/modules/traits/pages/service.adoc
old mode 100755
new mode 100644
index 0bdc83e48..bfda7c2bf
--- a/docs/modules/traits/pages/service.adoc
+++ b/docs/modules/traits/pages/service.adoc
@@ -54,6 +54,18 @@ Deprecated: Use service type instead.
 | map[string]string
 | The labels added to the Service object.
 
+| service.networkPolicyEnabled
+| bool
+| Enables the NetworkPolicy managed by the Service trait. Defaults to false. 
When enabled, at least one of the namespace or pod selectors must be configured.
+
+| service.networkPolicyNamespaceSelector
+| map[string]string
+| Labels used to restrict the source namespaces allowed by the NetworkPolicy.
+
+| service.networkPolicyPodSelector
+| map[string]string
+| Labels used to restrict the source pods allowed by the NetworkPolicy.
+
 | service.ports
 | []string
 | List of container ports available in the container to expose
@@ -80,3 +92,18 @@ $ kamel run PlatformHttpServer.java -p 
quarkus.http.port=8085 -t container.ports
 +
 [source,console]
 $ kamel run MyUDPServer.java -t container.ports=udp;95;UDP -t 
service.ports=udp;95;9095;UDP
+
+* Restrict a Service with an ingress NetworkPolicy. The example allows 
requests only from pods labeled app: my-client in namespaces labeled 
kubernetes.io/metadata.name: backend:
++
+[source,yaml]
+----
+spec:
+  traits:
+    service:
+      enabled: true
+      network-policy-enabled: true
+      network-policy-namespace-selector:
+        kubernetes.io/metadata.name: backend
+      network-policy-pod-selector:
+        app: my-client
+----
diff --git a/e2e/networkpolicy/files/NetworkPolicyConsumer.java 
b/e2e/networkpolicy/files/NetworkPolicyConsumer.java
new file mode 100644
index 000000000..d375bd24a
--- /dev/null
+++ b/e2e/networkpolicy/files/NetworkPolicyConsumer.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+
+public class NetworkPolicyConsumer extends RouteBuilder {
+    @Override
+    public void configure() {
+        onException(Exception.class)
+            .maximumRedeliveries(0)
+            .handled(true)
+            .log("ACCESS_DENIED");
+
+        from("timer:network-policy?delay=1000&period=1000&repeatCount=30")
+            
.to("http://{{serviceName}}/hello?throwExceptionOnFailure=false&connectTimeout=3000&responseTimeout=3000";)
+            .choice()
+                .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(200))
+                    .log("ACCESS_GRANTED")
+                .otherwise()
+                    .log("ACCESS_DENIED");
+    }
+}
diff --git a/e2e/networkpolicy/files/PlatformHttpServer.java 
b/e2e/networkpolicy/files/PlatformHttpServer.java
new file mode 100644
index 000000000..31d8d19f3
--- /dev/null
+++ b/e2e/networkpolicy/files/PlatformHttpServer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class PlatformHttpServer extends RouteBuilder {
+    @Override
+    public void configure() throws Exception {
+        from("platform-http:/hello?httpMethodRestrict=GET")
+            .setBody(simple("Hello ${header.name}"));
+    }
+}
diff --git a/e2e/networkpolicy/network_policy_test.go 
b/e2e/networkpolicy/network_policy_test.go
new file mode 100644
index 000000000..e72a35908
--- /dev/null
+++ b/e2e/networkpolicy/network_policy_test.go
@@ -0,0 +1,110 @@
+//go:build integration
+// +build integration
+
+/*
+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 networkpolicy
+
+import (
+       "context"
+       "testing"
+
+       . "github.com/onsi/gomega"
+
+       corev1 "k8s.io/api/core/v1"
+       networkingv1 "k8s.io/api/networking/v1"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       ctrl "sigs.k8s.io/controller-runtime/pkg/client"
+
+       . "github.com/apache/camel-k/v2/e2e/support"
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+)
+
+func TestServiceNetworkPolicy(t *testing.T) {
+       WithNewTestNamespace(t, func(ctx context.Context, g *WithT, ns string) {
+               namespace, err := TestClient(t).CoreV1().Namespaces().Get(ctx, 
ns, metav1.GetOptions{})
+               g.Expect(err).ToNot(HaveOccurred())
+
+               labels := namespace.Labels
+               if labels == nil {
+                       labels = map[string]string{}
+               }
+               labels["network-policy-test"] = "enabled"
+               namespace.Labels = labels
+               _, err = TestClient(t).CoreV1().Namespaces().Update(ctx, 
namespace, metav1.UpdateOptions{})
+               g.Expect(err).ToNot(HaveOccurred())
+
+               serverName := RandomizedSuffixName("network-policy-server")
+               g.Expect(KamelRun(t, ctx, ns, "files/PlatformHttpServer.java",
+                       "-t", "service.enabled=true",
+                       "-t", "service.network-policy-enabled=true",
+                       "-t", 
"service.network-policy-namespace-selector.'network-policy-test'=enabled",
+                       "-t", "service.network-policy-pod-selector.app=allowed",
+                       "--name", serverName,
+               ).Execute()).To(Succeed())
+
+               g.Eventually(IntegrationConditionStatus(t, ctx, ns, serverName, 
v1.IntegrationConditionReady), TestTimeoutMedium).
+                       Should(Equal(corev1.ConditionTrue))
+               g.Eventually(NetworkPolicyByName(t, ctx, ns, serverName), 
TestTimeoutShort).ShouldNot(BeNil())
+
+               allowedName := RandomizedSuffixName("network-policy-allowed")
+               deniedName := RandomizedSuffixName("network-policy-denied")
+
+               g.Expect(KamelRun(t, ctx, ns, 
"files/NetworkPolicyConsumer.java",
+                       "-p", "serviceName="+serverName,
+                       "--label", "app=allowed",
+                       "-t", "owner.target-labels=*",
+                       "--name", allowedName,
+               ).Execute()).To(Succeed())
+
+               g.Expect(KamelRun(t, ctx, ns, 
"files/NetworkPolicyConsumer.java",
+                       "-p", "serviceName="+serverName,
+                       "--label", "app=denied",
+                       "-t", "owner.target-labels=*",
+                       "--name", deniedName,
+               ).Execute()).To(Succeed())
+
+               g.Eventually(IntegrationConditionStatus(t, ctx, ns, 
allowedName, v1.IntegrationConditionReady), 
TestTimeoutMedium).Should(Equal(corev1.ConditionTrue))
+               g.Eventually(IntegrationConditionStatus(t, ctx, ns, deniedName, 
v1.IntegrationConditionReady), 
TestTimeoutMedium).Should(Equal(corev1.ConditionTrue))
+
+               allowedPod := IntegrationPod(t, ctx, ns, allowedName)()
+               deniedPod := IntegrationPod(t, ctx, ns, deniedName)()
+               g.Expect(allowedPod.GetLabels()["app"]).To(Equal("allowed"))
+               g.Expect(deniedPod.GetLabels()["app"]).To(Equal("denied"))
+
+               g.Eventually(IntegrationLogs(t, ctx, ns, allowedName), 
TestTimeoutMedium).
+                       Should(ContainSubstring("ACCESS_GRANTED"))
+               g.Eventually(IntegrationLogs(t, ctx, ns, deniedName), 
TestTimeoutMedium).
+                       Should(ContainSubstring("ACCESS_DENIED"))
+       })
+}
+
+func NetworkPolicyByName(t *testing.T, ctx context.Context, namespace, name 
string) func() *networkingv1.NetworkPolicy {
+       return func() *networkingv1.NetworkPolicy {
+               policy := &networkingv1.NetworkPolicy{}
+               err := TestClient(t).Get(ctx, ctrl.ObjectKey{Name: name, 
Namespace: namespace}, policy)
+               if err != nil {
+                       if !k8serrors.IsNotFound(err) {
+                               t.Logf("failed to read NetworkPolicy %s/%s: 
%v", namespace, name, err)
+                       }
+                       return nil
+               }
+               return policy
+       }
+}
diff --git a/helm/camel-k/templates/rbacs-descoped.yaml 
b/helm/camel-k/templates/rbacs-descoped.yaml
index 255a5d0d4..2a57524c2 100644
--- a/helm/camel-k/templates/rbacs-descoped.yaml
+++ b/helm/camel-k/templates/rbacs-descoped.yaml
@@ -176,6 +176,17 @@ rules:
   - get
   - list
   - patch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - networkpolicies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - watch
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
diff --git a/helm/camel-k/templates/rbacs-namespaced.yaml 
b/helm/camel-k/templates/rbacs-namespaced.yaml
index 5465347ad..2b3bbbb9c 100644
--- a/helm/camel-k/templates/rbacs-namespaced.yaml
+++ b/helm/camel-k/templates/rbacs-namespaced.yaml
@@ -160,6 +160,17 @@ rules:
   - get
   - list
   - patch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - networkpolicies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - watch
 - apiGroups:
   - gateway.networking.k8s.io
   resources:
diff --git a/pkg/apis/camel/v1/trait/service.go 
b/pkg/apis/camel/v1/trait/service.go
index ca3b58509..f9d4f655a 100644
--- a/pkg/apis/camel/v1/trait/service.go
+++ b/pkg/apis/camel/v1/trait/service.go
@@ -43,6 +43,13 @@ type ServiceTrait struct {
        Annotations map[string]string `json:"annotations,omitempty" 
property:"annotations"`
        // The labels added to the Service object.
        Labels map[string]string `json:"labels,omitempty" property:"labels"`
+       // Enables the NetworkPolicy managed by the Service trait. Defaults to 
false.
+       // When enabled, at least one of the namespace or pod selectors must be 
configured.
+       NetworkPolicyEnabled *bool `json:"networkPolicyEnabled,omitempty" 
property:"network-policy-enabled"`
+       // Labels used to restrict the source namespaces allowed by the 
NetworkPolicy.
+       NetworkPolicyNamespaceSelector map[string]string 
`json:"networkPolicyNamespaceSelector,omitempty" 
property:"network-policy-namespace-selector"`
+       // Labels used to restrict the source pods allowed by the NetworkPolicy.
+       NetworkPolicyPodSelector map[string]string 
`json:"networkPolicyPodSelector,omitempty" 
property:"network-policy-pod-selector"`
        // List of container ports available in the container to expose
        // (syntax: 
<port-name>;<port-number>;<container-port-number>[;<port-protocol]).
        // When omitted, `port-protocol` (admitted values `TCP`, `UDP` or 
`SCTP`) is `TCP`.
diff --git a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go 
b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
index 5466a9cb0..20bf1e321 100644
--- a/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/trait/zz_generated.deepcopy.go
@@ -1475,6 +1475,25 @@ func (in *ServiceTrait) DeepCopyInto(out *ServiceTrait) {
                        (*out)[key] = val
                }
        }
+       if in.NetworkPolicyEnabled != nil {
+               in, out := &in.NetworkPolicyEnabled, &out.NetworkPolicyEnabled
+               *out = new(bool)
+               **out = **in
+       }
+       if in.NetworkPolicyNamespaceSelector != nil {
+               in, out := &in.NetworkPolicyNamespaceSelector, 
&out.NetworkPolicyNamespaceSelector
+               *out = make(map[string]string, len(*in))
+               for key, val := range *in {
+                       (*out)[key] = val
+               }
+       }
+       if in.NetworkPolicyPodSelector != nil {
+               in, out := &in.NetworkPolicyPodSelector, 
&out.NetworkPolicyPodSelector
+               *out = make(map[string]string, len(*in))
+               for key, val := range *in {
+                       (*out)[key] = val
+               }
+       }
        if in.Ports != nil {
                in, out := &in.Ports, &out.Ports
                *out = make([]string, len(*in))
diff --git 
a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
index 2b1454c90..ce3b54181 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml
@@ -2496,6 +2496,21 @@ spec:
 
                           Deprecated: Use service type instead.
                         type: boolean
+                      networkPolicyEnabled:
+                        description: |-
+                          Enables the NetworkPolicy managed by the Service 
trait. Defaults to false.
+                          When enabled, at least one of the namespace or pod 
selectors must be configured.
+                        type: boolean
+                      networkPolicyNamespaceSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source 
namespaces allowed by the NetworkPolicy.
+                        type: object
+                      networkPolicyPodSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source pods 
allowed by the NetworkPolicy.
+                        type: object
                       ports:
                         description: |-
                           List of container ports available in the container 
to expose
diff --git 
a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
index 6a77205b6..de3440185 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml
@@ -2421,6 +2421,21 @@ spec:
 
                           Deprecated: Use service type instead.
                         type: boolean
+                      networkPolicyEnabled:
+                        description: |-
+                          Enables the NetworkPolicy managed by the Service 
trait. Defaults to false.
+                          When enabled, at least one of the namespace or pod 
selectors must be configured.
+                        type: boolean
+                      networkPolicyNamespaceSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source 
namespaces allowed by the NetworkPolicy.
+                        type: object
+                      networkPolicyPodSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source pods 
allowed by the NetworkPolicy.
+                        type: object
                       ports:
                         description: |-
                           List of container ports available in the container 
to expose
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
index 3d495a82d..35b4d8f18 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
@@ -9474,6 +9474,21 @@ spec:
 
                           Deprecated: Use service type instead.
                         type: boolean
+                      networkPolicyEnabled:
+                        description: |-
+                          Enables the NetworkPolicy managed by the Service 
trait. Defaults to false.
+                          When enabled, at least one of the namespace or pod 
selectors must be configured.
+                        type: boolean
+                      networkPolicyNamespaceSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source 
namespaces allowed by the NetworkPolicy.
+                        type: object
+                      networkPolicyPodSelector:
+                        additionalProperties:
+                          type: string
+                        description: Labels used to restrict the source pods 
allowed by the NetworkPolicy.
+                        type: object
                       ports:
                         description: |-
                           List of container ports available in the container 
to expose
diff --git a/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml 
b/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
index f5db9280e..a21bef406 100644
--- a/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
+++ b/pkg/resources/config/rbac/descoped/operator-cluster-role.yaml
@@ -182,6 +182,17 @@ rules:
   - get
   - list
   - patch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - networkpolicies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - watch
 # Required by gateway trait
 - apiGroups:
   - gateway.networking.k8s.io
diff --git a/pkg/resources/config/rbac/namespaced/operator-role.yaml 
b/pkg/resources/config/rbac/namespaced/operator-role.yaml
index 9071708f7..0999ba5b0 100644
--- a/pkg/resources/config/rbac/namespaced/operator-role.yaml
+++ b/pkg/resources/config/rbac/namespaced/operator-role.yaml
@@ -163,6 +163,17 @@ rules:
   - get
   - list
   - patch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - networkpolicies
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - watch
 # Required by gateway trait
 - apiGroups:
   - gateway.networking.k8s.io
diff --git a/pkg/trait/service.go b/pkg/trait/service.go
index 779c3fa6e..110740a47 100644
--- a/pkg/trait/service.go
+++ b/pkg/trait/service.go
@@ -18,12 +18,15 @@ limitations under the License.
 package trait
 
 import (
+       "errors"
        "fmt"
        "maps"
        "strconv"
        "strings"
 
        corev1 "k8s.io/api/core/v1"
+       networkingv1 "k8s.io/api/networking/v1"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/util/intstr"
        "k8s.io/utils/ptr"
 
@@ -139,11 +142,66 @@ func (t *serviceTrait) Apply(e *Environment) error {
                }
                svc.Spec.Type = serviceType
        }
+       var networkPolicy *networkingv1.NetworkPolicy
+       if ptr.Deref(t.NetworkPolicyEnabled, false) {
+               var err error
+               networkPolicy, err = t.getNetworkPolicyFor(e.Integration.Name, 
e.Integration.Namespace)
+               if err != nil {
+                       return err
+               }
+       }
+
        e.Resources.Add(svc)
+       if networkPolicy != nil {
+               e.Resources.Add(networkPolicy)
+       }
 
        return nil
 }
 
+func (t *serviceTrait) getNetworkPolicyFor(itName, itNamespace string) 
(*networkingv1.NetworkPolicy, error) {
+       if len(t.NetworkPolicyNamespaceSelector) == 0 && 
len(t.NetworkPolicyPodSelector) == 0 {
+               return nil, errors.New("network policy is enabled but no 
namespace or pod selector is configured")
+       }
+
+       from := networkingv1.NetworkPolicyPeer{}
+       if len(t.NetworkPolicyNamespaceSelector) > 0 {
+               from.NamespaceSelector = &metav1.LabelSelector{
+                       MatchLabels: 
maps.Clone(t.NetworkPolicyNamespaceSelector),
+               }
+       }
+       if len(t.NetworkPolicyPodSelector) > 0 {
+               from.PodSelector = &metav1.LabelSelector{
+                       MatchLabels: maps.Clone(t.NetworkPolicyPodSelector),
+               }
+       }
+
+       policy := &networkingv1.NetworkPolicy{
+               Name:      itName,
+               Namespace: itNamespace,
+               Labels: map[string]string{
+                       v1.IntegrationLabel: itName,
+               },
+               Kind:       "NetworkPolicy",
+               APIVersion: networkingv1.SchemeGroupVersion.String(),
+               Spec: networkingv1.NetworkPolicySpec{
+                       PodSelector: metav1.LabelSelector{
+                               MatchLabels: map[string]string{
+                                       v1.IntegrationLabel: itName,
+                               },
+                       },
+                       PolicyTypes: []networkingv1.PolicyType{
+                               networkingv1.PolicyTypeIngress,
+                       },
+                       Ingress: []networkingv1.NetworkPolicyIngressRule{
+                               {From: []networkingv1.NetworkPolicyPeer{from}},
+                       },
+               },
+       }
+
+       return policy, nil
+}
+
 func (t *serviceTrait) getServiceFor(itName, itNamespace string) 
*corev1.Service {
        labels := map[string]string{
                v1.IntegrationLabel: itName,
diff --git a/pkg/trait/service_test.go b/pkg/trait/service_test.go
index 74ad9b5e0..139b42bcd 100644
--- a/pkg/trait/service_test.go
+++ b/pkg/trait/service_test.go
@@ -25,7 +25,9 @@ import (
 
        appsv1 "k8s.io/api/apps/v1"
        corev1 "k8s.io/api/core/v1"
+       networkingv1 "k8s.io/api/networking/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/apimachinery/pkg/util/intstr"
        "k8s.io/utils/ptr"
 
@@ -791,3 +793,96 @@ func TestServicePorts(t *testing.T) {
                Protocol:   corev1.ProtocolUDP,
        })
 }
+
+func TestServiceNetworkPolicyDisabledByDefault(t *testing.T) {
+       trait := &serviceTrait{
+               ServiceTrait: traitv1.ServiceTrait{
+                       Trait: traitv1.Trait{Enabled: ptr.To(true)},
+                       Auto:  ptr.To(false),
+               },
+       }
+
+       environment := Environment{
+               Integration: &v1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{Name: ServiceTestName, 
Namespace: ServiceTestNamespace},
+               },
+               Resources: kubernetes.NewCollection(),
+       }
+
+       require.NoError(t, trait.Apply(&environment))
+
+       assert.Nil(t, findNetworkPolicy(environment.Resources))
+}
+
+func TestServiceNetworkPolicyUsesCombinedSelectors(t *testing.T) {
+       trait := &serviceTrait{
+               ServiceTrait: traitv1.ServiceTrait{
+                       Trait: traitv1.Trait{Enabled: ptr.To(true)},
+                       Auto:  ptr.To(false),
+                       NetworkPolicyEnabled: ptr.To(true),
+                       NetworkPolicyNamespaceSelector: map[string]string{
+                               "kubernetes.io/metadata.name": 
ServiceTestNamespace,
+                               "environment":                "backend",
+                       },
+                       NetworkPolicyPodSelector: map[string]string{
+                               "app":  "client",
+                               "tier": "trusted",
+                       },
+               },
+       }
+
+       environment := Environment{
+               Integration: &v1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{Name: ServiceTestName, 
Namespace: ServiceTestNamespace},
+               },
+               Resources: kubernetes.NewCollection(),
+       }
+
+       require.NoError(t, trait.Apply(&environment))
+
+       networkPolicy := findNetworkPolicy(environment.Resources)
+       require.NotNil(t, networkPolicy)
+       assert.Equal(t, ServiceTestName, networkPolicy.Name)
+       assert.Equal(t, ServiceTestNamespace, networkPolicy.Namespace)
+       assert.Equal(t, map[string]string{v1.IntegrationLabel: 
ServiceTestName}, networkPolicy.Spec.PodSelector.MatchLabels)
+       assert.Equal(t, 
[]networkingv1.PolicyType{networkingv1.PolicyTypeIngress}, 
networkPolicy.Spec.PolicyTypes)
+       require.Len(t, networkPolicy.Spec.Ingress, 1)
+       require.Len(t, networkPolicy.Spec.Ingress[0].From, 1)
+
+       peer := networkPolicy.Spec.Ingress[0].From[0]
+       assert.Equal(t, trait.NetworkPolicyNamespaceSelector, 
peer.NamespaceSelector.MatchLabels)
+       assert.Equal(t, trait.NetworkPolicyPodSelector, 
peer.PodSelector.MatchLabels)
+}
+
+func TestServiceNetworkPolicyRequiresSelector(t *testing.T) {
+       trait := &serviceTrait{
+               ServiceTrait: traitv1.ServiceTrait{
+                       Trait: traitv1.Trait{Enabled: ptr.To(true)},
+                       Auto:  ptr.To(false),
+                       NetworkPolicyEnabled: ptr.To(true),
+               },
+       }
+
+       environment := Environment{
+               Integration: &v1.Integration{
+                       ObjectMeta: metav1.ObjectMeta{Name: ServiceTestName, 
Namespace: ServiceTestNamespace},
+               },
+               Resources: kubernetes.NewCollection(),
+       }
+
+       err := trait.Apply(&environment)
+       require.Error(t, err)
+       assert.Equal(t, "network policy is enabled but no namespace or pod 
selector is configured", err.Error())
+       assert.Nil(t, 
environment.Resources.GetServiceForIntegration(environment.Integration))
+}
+
+func findNetworkPolicy(resources *kubernetes.Collection) 
*networkingv1.NetworkPolicy {
+       var result *networkingv1.NetworkPolicy
+       resources.Visit(func(resource runtime.Object) {
+               if networkPolicy, ok := resource.(*networkingv1.NetworkPolicy); 
ok {
+                       result = networkPolicy
+               }
+       })
+       return result
+}
+
diff --git a/script/Makefile b/script/Makefile
index 4ac1ec8c9..5a9db1ca6 100644
--- a/script/Makefile
+++ b/script/Makefile
@@ -277,6 +277,14 @@ test-common: gotestfmt-install
        go test -p $(TEST_COMMON_PARALLEL_COUNT) -timeout 90m -v 
./e2e/common/... --parallel 1 -tags=integration $(GOTESTFMT) || ((FAILED++)); \
        exit $${FAILED}
 
+#
+# NetworkPolicy test that requires a Calico-backed Minikube cluster
+#
+test-network-policy: gotestfmt-install
+       FAILED=0; \
+       go test -p $(TEST_COMMON_PARALLEL_COUNT) -timeout 20m -v 
./e2e/networkpolicy/... --parallel 1 -tags=integration $(GOTESTFMT) || 
((FAILED++)); \
+       if ((FAILED)); then exit 1; fi
+
 #
 # Smoke tests are a subset of common tests used to quickly verify the operator
 #

Reply via email to