Repository: kubermatic/kubermatic
Pull request #14584: Migrate Cilium to static ApplicationDefinition YAML
Author: login: ahmedwaleedmalik
Labels: name: size/XXL
description: Denotes a PR that changes 1000+ lines, ignoring generated files.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: release-note-none
description: Denotes a PR that doesn't merit a release note.
name: dco-signoff: yes
description: Denotes that all commits in the pull request have the valid DCO signoff message.
name: sig/cluster-management
description: Denotes a PR or issue as being assigned to SIG Cluster Management.
name: sig/app-management
description: Denotes a PR or issue as being assigned to SIG App Management.
name: sig/networking
description: Denotes a PR or issue as being assigned to SIG Networking.
name: docs/none
description: Denotes a PR that doesn't need documentation (changes).
Created: 2025-05-21T10:24:52Z | Merged: 2025-05-21T14:46:02Z

**What this PR does / why we need it**:
This is a follow-up PR for https://github.com/kubermatic/kubermatic/pull/14509, building upon the changes from that PR, we are now also moving Cilium to use the system applications reconciliation instead of having dedicated code to deploy it.

**Which issue(s) this PR fixes**:
<!--optional, in `fixes #<issue number>` format, will close the issue(s) when PR gets merged-->
Fixes #14428 (the issue is not related to the core changes in this PR but it was literally one character change so I did this in the same PR)

**What type of PR is this?**
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
/kind chore
-->
/kind chore

**Special notes for your reviewer**:
I've set the release note as NONE since this is only an internal change regarding how we deploy Cilium. Which is not a user facing change IMO.

**Does this PR introduce a user-facing change? Then add your Release Note here**:
<!--
Write your release note. Release notes are being used to generate the changelog:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
NONE
```

**Documentation**:
<!--
Please do one of the following options:
- Add a link to the existing documentation
- Add a link to the kubermatic/docs pull request
- If no documentation change is applicable then add:
  - TBD (documentation will be added later)
  - NONE (no documentation needed for this PR)
-->
```documentation
NONE
```


Linked issue #14428: Typo in cluster-autoscaler application's default values
The default values for the cluster-autoscaler application contain a typo:

```
- apiVersion: rbac.authorization.k8s.io/v1
  kind: ClusterRoleBinding
  metadata:
    name: cluster-autoscaler-clusterapi-cluser-autoscaler
```

Should be `cluster` (twice) instead of `cluser` (towards the end).

I figure this name isn't used anywhere, so at least the typo shouldn't break anything.

Just in case it's not clear where I'm seeing this:

![Image](https://github.com/user-attachments/assets/ffb6468d-9074-4249-a712-2a4a9c8c5433)
Linked issue comments: user: login: csengerszabo
created_at: 2025-05-07T10:12:31Z
body: /label sig/app-management
author_association: CONTRIBUTOR
user: login: wurbanski
created_at: 2025-05-07T10:17:01Z
body: Note: after fixing the typo there might be a leftover ClusterRoleBinding object with the old name, release notes for the fix should contain an action required to manually delete the leftover object.
author_association: CONTRIBUTOR
user: login: ahmedwaleedmalik
created_at: 2025-05-21T11:02:03Z
body: /assign
author_association: MEMBER

Diff:
diff --git a/.prow/applications-catalog.yaml b/.prow/applications-catalog.yaml
index f19b276c60c..d70e7808e07 100644
--- a/.prow/applications-catalog.yaml
+++ b/.prow/applications-catalog.yaml
@@ -105,7 +105,9 @@ presubmits:
               memory: 6Gi
 
   - name: pre-kubermatic-appcatalog-cluster-autoscaler
-    run_if_changed: "(pkg/ee/default-application-catalog/cluster-autoscaler.yaml)"
+    # We are testing changes for the cluster-autoscaler application definition and the system applications
+    # reconciler through the same job.
+    run_if_changed: "(pkg/applicationdefinitions)"
     decorate: true
     clone_uri: "ssh://git@github.com/kubermatic/kubermatic.git"
     labels:
diff --git a/cmd/kubermatic-installer/cmd_mirror_images.go b/cmd/kubermatic-installer/cmd_mirror_images.go
index 97371bb0559..ba85d4b2ca1 100644
--- a/cmd/kubermatic-installer/cmd_mirror_images.go
+++ b/cmd/kubermatic-installer/cmd_mirror_images.go
@@ -332,8 +332,6 @@ func MirrorImagesFunc(logger *logrus.Logger, versions kubermaticversion.Versions
 			}
 
 			copyKubermaticConfig := kubermaticConfig.DeepCopy()
-			// force using default repos
-			copyKubermaticConfig.Spec.UserCluster.SystemApplications.HelmRepository = defaulting.DefaultSystemApplicationsHelmRepository
 
 			logger.Info("🚀 Getting images from system Applications Helm charts…")
 
@@ -342,8 +340,13 @@ func MirrorImagesFunc(logger *logrus.Logger, versions kubermaticversion.Versions
 					return err
 				}
 
+				chartRepository := sysChart.Template.Source.Helm.URL
+				if copyKubermaticConfig.Spec.UserCluster.SystemApplications.HelmRepository != "" {
+					chartRepository = copyKubermaticConfig.Spec.UserCluster.SystemApplications.HelmRepository
+				}
+
 				chartImage := fmt.Sprintf("%s/%s:%s",
-					defaulting.DefaultSystemApplicationsHelmRepository,
+					chartRepository,
 					sysChart.Template.Source.Helm.ChartName,
 					sysChart.Version,
 				)
diff --git a/codegen/godoc/main.go b/codegen/godoc/main.go
index 115d340bca9..8deb8006731 100644
--- a/codegen/godoc/main.go
+++ b/codegen/godoc/main.go
@@ -31,7 +31,7 @@ import (
 	"golang.org/x/tools/go/packages"
 
 	"k8c.io/kubermatic/v2/pkg/addon"
-	"k8c.io/kubermatic/v2/pkg/ee/applications"
+	"k8c.io/kubermatic/v2/pkg/applications/providers/template"
 	"k8c.io/kubermatic/v2/pkg/resources/prometheus"
 )
 
@@ -44,7 +44,7 @@ func main() {
 	}
 
 	// document application template data
-	if err := generate("zz_generated.applicationdata.go.txt", applications.TemplateData{}); err != nil {
+	if err := generate("zz_generated.applicationdata.go.txt", template.TemplateData{}); err != nil {
 		log.Fatalf("Failed to create documentation: %v", err)
 	}
 
diff --git a/pkg/applicationdefinitions/OWNERS b/pkg/applicationdefinitions/OWNERS
new file mode 100644
index 00000000000..1f93088e78e
--- /dev/null
+++ b/pkg/applicationdefinitions/OWNERS
@@ -0,0 +1,13 @@
+# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
+
+approvers:
+  - sig-app-management
+
+reviewers:
+  - sig-app-management
+
+labels:
+  - sig/app-management
+
+options:
+  no_parent_owners: true
diff --git a/pkg/applicationdefinitions/application_catalog.go b/pkg/applicationdefinitions/application_catalog.go
index 0b2e866e8a5..c60cc2a1232 100644
--- a/pkg/applicationdefinitions/application_catalog.go
+++ b/pkg/applicationdefinitions/application_catalog.go
@@ -24,7 +24,6 @@ import (
 
 	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
 	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
-	"k8c.io/kubermatic/v2/pkg/defaulting"
 	"k8c.io/kubermatic/v2/pkg/kubernetes"
 	kkpreconciling "k8c.io/kubermatic/v2/pkg/resources/reconciling"
 	"k8c.io/kubermatic/v2/pkg/resources/registry"
@@ -150,8 +149,6 @@ func updateApplicationDefinition(appDef *appskubermaticv1.ApplicationDefinition,
 	for i := range appDef.Spec.Versions {
 		if sysAppConfig.HelmRepository != "" {
 			appDef.Spec.Versions[i].Template.Source.Helm.URL = registry.ToOCIURL(sysAppConfig.HelmRepository)
-		} else {
-			appDef.Spec.Versions[i].Template.Source.Helm.URL = defaulting.SystemApplicationsHelmRepositoryFromConfig(&sysAppConfig)
 		}
 
 		if credentials != nil {
diff --git a/pkg/applicationdefinitions/embed.go b/pkg/applicationdefinitions/embed.go
index b6fb20123d3..5716914c919 100644
--- a/pkg/applicationdefinitions/embed.go
+++ b/pkg/applicationdefinitions/embed.go
@@ -21,20 +21,23 @@ import (
 	"io/fs"
 )
 
+const (
+	systemApplicationsDirectory = "system-applications"
+)
+
 //go:embed system-applications
 var f embed.FS
 
 func GetSysAppDefFiles() ([]fs.File, error) {
-	dirname := "system-applications"
 	files := []fs.File{}
-	entries, err := f.ReadDir(dirname)
+	entries, err := f.ReadDir(systemApplicationsDirectory)
 	if err != nil {
 		return nil, err
 	}
 
 	for _, entry := range entries {
 		if !entry.IsDir() {
-			file, err := f.Open(dirname + "/" + entry.Name())
+			file, err := f.Open(systemApplicationsDirectory + "/" + entry.Name())
 			if err != nil {
 				return nil, err
 			}
diff --git a/pkg/applicationdefinitions/system-applications/cilium.yaml b/pkg/applicationdefinitions/system-applications/cilium.yaml
new file mode 100644
index 00000000000..5bbdd45d224
--- /dev/null
+++ b/pkg/applicationdefinitions/system-applications/cilium.yaml
@@ -0,0 +1,163 @@
+# Copyright 2025 The Kubermatic Kubernetes Platform contributors.
+#
+# Licensed 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.
+
+apiVersion: apps.kubermatic.k8c.io/v1
+kind: ApplicationDefinition
+metadata:
+  labels:
+    apps.kubermatic.k8c.io/managed-by: kkp
+    apps.kubermatic.k8c.io/type: cni
+  name: cilium
+spec:
+  defaultValuesBlock: |
+    envoy:
+      enabled: false
+    hubble:
+      relay:
+        enabled: true
+      tls:
+        auto:
+          method: cronJob
+      ui:
+        enabled: true
+    operator:
+      replicas: 1
+  description: Cilium CNI - eBPF-based Networking, Security, and Observability
+  method: helm
+  selector: {}
+  versions:
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.0
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.0
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.3
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.3
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.4
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.4
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.6
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.6
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.7
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.7
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.8
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.8
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.13.14
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.13.14
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.14.1
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.14.1
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.14.2
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.14.2
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.14.3
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.14.3
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.14.9
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.14.9
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.14.16
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.14.16
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.15.3
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.15.3
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.15.10
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.15.10
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.15.16
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.15.16
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.16.6
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.16.6
+    - template:
+        source:
+          helm:
+            chartName: cilium
+            chartVersion: 1.16.9
+            url: oci://quay.io/kubermatic/helm-charts
+      version: 1.16.9
+  documentationURL: https://docs.cilium.io/en/stable/
+  sourceURL: https://github.com/cilium/cilium
+  logo: |+
+    PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0iTGF5ZXJfMSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC45Mi4zICgyNDA1NTQ2LCAyMDE4LTAzLTExKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iY2lsaXVtLWFyMjEuc3ZnIgogICB4PSIwcHgiCiAgIHk9IjBweCIKICAgdmlld0JveD0iMS4yMiAwLjk4IDEyMCA1OS45OTk5OTkiCiAgIHhtbDpzcGFjZT0icHJlc2VydmUiCiAgIHdpZHRoPSIxMjAiCiAgIGhlaWdodD0iNjAiPjxtZXRhZGF0YQogICBpZD0ibWV0YWRhdGE0MiI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgIHJkZjphYm91dD0iIj48ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD48ZGM6dHlwZQogICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjxkYzp0aXRsZT48L2RjOnRpdGxlPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICBpZD0iZGVmczQwIiAvPjxzb2RpcG9kaTpuYW1lZHZpZXcKICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgb2JqZWN0dG9sZXJhbmNlPSIxMCIKICAgZ3JpZHRvbGVyYW5jZT0iMTAiCiAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iMTAwMSIKICAgaWQ9Im5hbWVkdmlldzM4IgogICBzaG93Z3JpZD0iZmFsc2UiCiAgIGlua3NjYXBlOnpvb209IjgiCiAgIGlua3NjYXBlOmN4PSI2OC41NjY0NDgiCiAgIGlua3NjYXBlOmN5PSIxOS4zNDE1MTEiCiAgIGlua3NjYXBlOndpbmRvdy14PSItOSIKICAgaW5rc2NhcGU6d2luZG93LXk9Ii05IgogICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIiAvPgo8c3R5bGUKICAgdHlwZT0idGV4dC9jc3MiCiAgIGlkPSJzdHlsZTIiPgoJLnN0MHtmaWxsOiMzNzM3Mzc7fQoJLnN0MXtmaWxsOiNFODI0Mjk7fQoJLnN0MntmaWxsOiM2Mjg5QzY7fQoJLnN0M3tmaWxsOiNGOUM1MTU7fQoJLnN0NHtmaWxsOiM4MDYxQTk7fQoJLnN0NXtmaWxsOiNGMTc0MjM7fQoJLnN0NntmaWxsOiM5N0M5M0Q7fQoJLnN0N3tmaWxsOiNDQURENzI7fQoJLnN0OHtmaWxsOiMxRTIyMjI7fQo8L3N0eWxlPgoKPGcKICAgaWQ9Imc4ODIiCiAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjc1NzE3MTU3KSI+PGcKICAgICB0cmFuc2Zvcm09Im1hdHJpeCgwLjE0MjQwMjM3LDAsMCwwLjE0MjQwMjM3LDQuNDg5NDgwMSwxMi4xNDAxNjYpIgogICAgIGlkPSJnMzUwMyI+Cgk8ZwogICBpZD0iZzM1MDUiPgoJCTxnCiAgIGlkPSJnMzUwNyI+CgkJCTxwYXRoCiAgIHN0eWxlPSJmaWxsOiMzNzM3MzciCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGQ9Ik0gMTg5LjgsOTIuOSBIIDE0NCBsIC0yMywzOS44IDIyLjksMzkuNCBoIDQ1LjkgbCAyMi45LC0zOS40IHogbSAtNi4xLDY4LjQgSCAxNTAgbCAtMTYuOCwtMjguNyAxNi44LC0yOSBoIDMzLjcgbCAxNi44LDI5IHoiCiAgIGNsYXNzPSJzdDAiCiAgIGlkPSJwYXRoMzUwOSIgLz4KCQkJPHBvbHlnb24KICAgc3R5bGU9ImZpbGw6I2U4MjQyOSIKICAgcG9pbnRzPSIxMzMuMiwxMzIuNyAxNTAsMTYxLjMgMTgzLjcsMTYxLjMgMjAwLjYsMTMyLjcgMTgzLjcsMTAzLjMgMTUwLDEwMy4zICIKICAgY2xhc3M9InN0MSIKICAgaWQ9InBvbHlnb24zNTExIiAvPgoJCTwvZz4KCQk8ZwogICBpZD0iZzM1MTMiPgoJCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMzczNzM3IgogICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICBkPSJNIDE4OS44LDE3Ny41IEggMTQ0IGwgLTIzLDM5LjcgMjIuOSwzOS40IGggNDUuOSBsIDIyLjksLTM5LjQgeiBtIC02LjEsNjguNyBIIDE1MCBsIC0xNi44LC0yOC43IDE2LjgsLTI5IGggMzMuNyBsIDE2LjgsMjkgeiIKICAgY2xhc3M9InN0MCIKICAgaWQ9InBhdGgzNTE1IiAvPgoJCQk8cG9seWdvbgogICBzdHlsZT0iZmlsbDojNjI4OWM2IgogICBwb2ludHM9IjEzMy4yLDIxNy4yIDE1MCwyNDYuMiAxODMuNywyNDYuMiAyMDAuNiwyMTcuMiAxODMuNywxODguMiAxNTAsMTg4LjIgIgogICBjbGFzcz0ic3QyIgogICBpZD0icG9seWdvbjM1MTciIC8+CgkJPC9nPgoJCTxnCiAgIGlkPSJnMzUxOSI+CgkJCTxwYXRoCiAgIHN0eWxlPSJmaWxsOiMzNzM3MzciCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGQ9Ik0gMTg5LjgsOCBIIDE0NCBsIC0yMywzOS44IDIzLDM5LjQgaCA0NS45IGwgMjIuOSwtMzkuNCB6IG0gLTYuMSw2OC40IEggMTUwIEwgMTMzLjIsNDcuNyAxNTAsMTguNCBoIDMzLjcgbCAxNi44LDI5IHoiCiAgIGNsYXNzPSJzdDAiCiAgIGlkPSJwYXRoMzUyMSIgLz4KCQkJPHBvbHlnb24KICAgc3R5bGU9ImZpbGw6I2Y5YzUxNSIKICAgcG9pbnRzPSIxMzMuMiw0Ny44IDE1MCw3Ni40IDE4My43LDc2LjQgMjAwLjYsNDcuOCAxODMuNywxOC40IDE1MCwxOC40ICIKICAgY2xhc3M9InN0MyIKICAgaWQ9InBvbHlnb24zNTIzIiAvPgoJCTwvZz4KCTwvZz4KCTxnCiAgIGlkPSJnMzUyNSI+CgkJPGcKICAgaWQ9ImczNTI3Ij4KCQkJPHBhdGgKICAgc3R5bGU9ImZpbGw6IzM3MzczNyIKICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgZD0ibSAyNjQuMywxMzUuNSBoIC00NS41IGwgLTIyLjksMzkuOCAyMi45LDM5LjQgaCA0NS45IGwgMjIuOSwtMzkuNCB6IG0gLTYuMSw2OC41IGggLTMzLjcgbCAtMTYuOCwtMjguNyAxNi44LC0yOSBoIDMzLjcgbCAxNi44LDI5IHoiCiAgIGNsYXNzPSJzdDAiCiAgIGlkPSJwYXRoMzUyOSIgLz4KCQkJPHBvbHlnb24KICAgc3R5bGU9ImZpbGw6IzgwNjFhOSIKICAgcG9pbnRzPSIyMDguMSwxNzUuMyAyMjQuOSwyMDQgMjU4LjIsMjA0IDI3NS40LDE3NS4zIDI1OC4yLDE0NS45IDIyNC45LDE0NS45ICIKICAgY2xhc3M9InN0NCIKICAgaWQ9InBvbHlnb24zNTMxIiAvPgoJCTwvZz4KCQk8ZwogICBpZD0iZzM1MzMiPgoJCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMzczNzM3IgogICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICBkPSJtIDI2NC4zLDUwLjYgaCAtNDUuNSBsIC0yMi45LDM5LjQgMjIuOSwzOS40IGggNDUuOSBMIDI4Ny42LDkwIFogbSAtNi4xLDY4LjUgaCAtMzMuNyBsIC0xNi44LC0yOC43IDE2LjgsLTI5IGggMzMuNyBsIDE2LjgsMjkgeiIKICAgY2xhc3M9InN0MCIKICAgaWQ9InBhdGgzNTM1IiAvPgoJCQk8cG9seWdvbgogICBzdHlsZT0iZmlsbDojZjE3NDIzIgogICBwb2ludHM9IjIwOC4xLDkwLjQgMjI0LjksMTE5LjEgMjU4LjIsMTE5LjEgMjc1LjQsOTAuNCAyNTguMiw2MSAyMjQuOSw2MSAiCiAgIGNsYXNzPSJzdDUiCiAgIGlkPSJwb2x5Z29uMzUzNyIgLz4KCQk8L2c+Cgk8L2c+Cgk8ZwogICBpZD0iZzM1MzkiPgoJCTxnCiAgIGlkPSJnMzU0MSI+CgkJCTxwYXRoCiAgIHN0eWxlPSJmaWxsOiMzNzM3MzciCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGQ9Ik0gMTE0LjksMTM1LjUgSCA2OS40IGwgLTIyLjksMzkuOCAyMi45LDM5LjQgaCA0NS45IGwgMjIuOSwtMzkuNCB6IE0gMTA4LjgsMjA0IEggNzUuNSBsIC0xNi44LC0yOC43IDE2LjgsLTI5IGggMzMuNyBsIDE2LjgsMjkgeiIKICAgY2xhc3M9InN0MCIKICAgaWQ9InBhdGgzNTQzIiAvPgoJCQk8cG9seWdvbgogICBzdHlsZT0iZmlsbDojOTdjOTNkIgogICBwb2ludHM9IjU4LjcsMTc1LjMgNzUuNSwyMDQgMTA4LjgsMjA0IDEyNiwxNzUuMyAxMDguOCwxNDUuOSA3NS41LDE0NS45ICIKICAgY2xhc3M9InN0NiIKICAgaWQ9InBvbHlnb24zNTQ1IiAvPgoJCTwvZz4KCQk8ZwogICBpZD0iZzM1NDciPgoJCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMzczNzM3IgogICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICBkPSJNIDExNC45LDUwLjYgSCA2OS40IEwgNDYuNSw5MCA2OS40LDEyOS40IGggNDUuOSBMIDEzOC4yLDkwIFogbSAtNi4xLDY4LjUgSCA3NS41IEwgNTguNyw5MC40IDc1LjUsNjEuNCBoIDMzLjcgbCAxNi44LDI5IHoiCiAgIGNsYXNzPSJzdDAiCiAgIGlkPSJwYXRoMzU0OSIgLz4KCQkJPHBvbHlnb24KICAgc3R5bGU9ImZpbGw6I2NhZGQ3MiIKICAgcG9pbnRzPSI1OC43LDkwLjQgNzUuNSwxMTkuMSAxMDguOCwxMTkuMSAxMjYsOTAuNCAxMDguOCw2MSA3NS41LDYxICIKICAgY2xhc3M9InN0NyIKICAgaWQ9InBvbHlnb24zNTUxIiAvPgoJCTwvZz4KCTwvZz4KPC9nPjxnCiAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC4xNTI2OTMxMiwwLDAsMC4xNTI2OTMxMiw1NC41MTQzODEsLTI1LjM0MDg1NykiCiAgICAgaWQ9ImczNTUzIj4KCTxnCiAgIGlkPSJnMzU1NSI+CgkJPHBhdGgKICAgc3R5bGU9ImZpbGw6IzFlMjIyMiIKICAgZD0ibSAzMS44LDM0Ny42IGMgMy44LDAgNy4zLDAuNSAxMC42LDEuMiAzLjMsMC43IDYuMSwxLjkgOC44LDMuMyB2IDEwLjQgYyAtMy4zLC0xLjIgLTYuNCwtMi4xIC05LjIsLTIuOCAtMy4xLC0wLjcgLTUuOSwtMS4yIC05LC0xLjIgLTMuMSwwIC02LjEsMC41IC05LjIsMS40IC0zLjEsMC45IC01LjcsMi42IC04LjMsNSAtMi40LDIuNCAtNC4zLDUuNCAtNS45LDkuMiAtMS40LDMuOCAtMi4xLDguNSAtMi4xLDE0LjIgMCw0IDAuNSw3LjggMS43LDExLjQgMS4yLDMuNSAyLjgsNi40IDUsOSAyLjIsMi42IDUsNC41IDgsNS45IDMuMywxLjQgNy4xLDIuMSAxMS40LDIuMSAzLjEsMCA2LjEsLTAuMiA5LjUsLTAuOSAzLjEsLTAuNyA2LjEsLTEuNyA5LjIsLTIuOCB2IDEwLjQgYyAtMC45LDAuNSAtMi4xLDEuMiAtMy41LDEuNyAtMS40LDAuNSAtMy4xLDAuOSAtNSwxLjQgLTEuNywwLjUgLTMuNSwwLjcgLTUuNywwLjkgLTEuOSwwLjIgLTQsMC41IC01LjksMC41IC01LjIsMCAtOS45LC0wLjcgLTE0LjcsLTIuNCAtNC41LC0xLjcgLTguNSwtNCAtMTIuMSwtNy4xIC0zLjMsLTMuMyAtNi4xLC03LjMgLTgsLTEyLjMgLTEuOSwtNSAtMy4xLC0xMC42IC0zLjEsLTE3LjUgMCwtNSAwLjUsLTkuNSAxLjcsLTEzLjUgMC45LC00IDIuNCwtNy42IDQuMywtMTAuNiAxLjksLTMgNCwtNS43IDYuNCwtNy44IDIuNCwtMi4xIDUsLTMuOCA3LjgsLTUuMiAyLjgsLTEuNCA1LjcsLTIuNCA4LjUsLTMuMSAzLjEsLTAuMyA1LjksLTAuOCA4LjgsLTAuOCB6IgogICBjbGFzcz0ic3Q4IgogICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICBpZD0icGF0aDM1NTciIC8+CgkJPHBhdGgKICAgc3R5bGU9ImZpbGw6IzFlMjIyMiIKICAgZD0iTSA3MS41LDMzMiBWIDMxOCBIIDg0IHYgMTQgeiBtIDAuMiw5My45IHYgLTc2LjIgaCAxMi41IHYgNzYuMiB6IgogICBjbGFzcz0ic3Q4IgogICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICBpZD0icGF0aDM1NTkiIC8+CgkJPHBhdGgKICAgc3R5bGU9ImZpbGw6IzFlMjIyMiIKICAgZD0iTSAxMDkuOCw0MjUuOSBWIDMwOS44IGggMTIuNSB2IDExNi4xIHoiCiAgIGNsYXNzPSJzdDgiCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGlkPSJwYXRoMzU2MSIgLz4KCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMWUyMjIyIgogICBkPSJtIDE0Ny43LDMzMiB2IC0xNCBoIDEyLjUgdiAxNCB6IG0gMC4yLDkzLjkgdiAtNzYuMiBoIDEyLjUgdiA3Ni4yIHoiCiAgIGNsYXNzPSJzdDgiCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGlkPSJwYXRoMzU2MyIgLz4KCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMWUyMjIyIgogICBkPSJtIDIxNS42LDQyNy44IGMgLTQsMCAtNy42LC0wLjUgLTEwLjYsLTEuMiAtMy4xLC0wLjkgLTUuOSwtMi4xIC04LjMsLTMuNSAtMi40LC0xLjcgLTQuNSwtMy4zIC02LjEsLTUuNyAtMS43LC0yLjEgLTMuMSwtNC41IC00LC03LjEgLTAuNywtMS45IC0xLjIsLTQgLTEuNywtNi40IC0wLjIsLTIuNCAtMC41LC00LjcgLTAuNSwtNy42IHYgLTQ2LjYgaCAxMi41IHYgNDYuNiBjIDAsNC4zIDAuNSw3LjggMS43LDEwLjYgMS4yLDMuMyAzLjMsNS43IDYuMSw3LjYgMi44LDEuOSA2LjYsMi44IDEwLjksMi44IDQuNywwIDguNSwtMC45IDExLjQsLTMuMSAyLjgsLTIuMSA1LC00LjcgNi4xLC04LjMgMC45LC0yLjYgMS40LC01LjkgMS40LC05LjcgViAzNDkuNiBIIDI0NyB2IDQ2LjggYyAwLDIuNiAtMC4yLDUgLTAuNSw3LjEgLTAuMiwyLjEgLTAuNyw0LjMgLTEuNCw2LjEgLTAuOSwyLjYgLTIuNCw1IC00LDcuMyAtMS43LDIuMSAtMy44LDQgLTYuMSw1LjcgLTIuNCwxLjcgLTUuMiwyLjggLTguNSwzLjggLTMuNCwwLjkgLTYuOSwxLjQgLTEwLjksMS40IHoiCiAgIGNsYXNzPSJzdDgiCiAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgIGlkPSJwYXRoMzU2NSIgLz4KCQk8cGF0aAogICBzdHlsZT0iZmlsbDojMWUyMjIyIgogICBkPSJtIDMzMi40LDQyNS45IGggLTEyLjUgdiAtNDkgYyAwLC0zLjMgLTAuNSwtNi4xIC0xLjIsLTguNSAtMC43LC0yLjQgLTEuNywtNC4zIC0zLjEsLTUuNyAtMS40LC0xLjQgLTMuMSwtMi42IC01LC0zLjMgLTEuOSwtMC43IC00LC0wLjkgLTYuNCwtMC45IC00LjUsMCAtOC41LDEuMiAtMTIuMSwzLjUgLTMuNSwyLjQgLTYuOSw1LjcgLTkuNyw5LjkgdiA1NC4yIEggMjcwIHYgLTc2LjQgaCA5LjcgbCAyLjQsMTAuNiBoIDAuMiBjIDEuNCwtMS43IDIuOCwtMy4zIDQuNSwtNSAxLjcsLTEuNCAzLjUsLTIuOCA1LjQsLTQgMS45LC0xLjIgNC4zLC0yLjEgNi42LC0yLjYgMi40LC0wLjcgNSwtMC45IDcuOCwtMC45IDUuNywwIDEwLjQsMS40IDE0LjIsNCAzLjgsMi44IDYuNiw2LjQgOC44LDExLjEgaCAwLjIgYyAzLjMsLTQuNyA3LjMsLTguNSAxMS42LC0xMS4xIDQuMywtMi42IDkuNSwtNCAxNS4xLC00IDIuNiwwIDUuNCwwLjUgOC41LDEuMiAzLjEsMC43IDUuNywyLjEgOC4zLDQgMi42LDEuOSA0LjUsNC43IDYuMSw4LjMgMS43LDMuNSAyLjYsOCAyLjYsMTMuNyB2IDUxLjEgSCAzNzAgViAzNzcgYyAwLC0zLjMgLTAuNSwtNi4xIC0xLjIsLTguNSAtMC43LC0yLjQgLTEuNywtNC4zIC0zLjEsLTUuNyAtMS40LC0xLjQgLTMuMSwtMi42IC01LC0zLjMgLTEuOSwtMC43IC00LC0wLjkgLTYuNCwtMC45IC00LjUsMCAtOC41LDEuMiAtMTIuMSwzLjUgLTMuNSwyLjQgLTYuOSw1LjcgLTkuOSw5LjkgeiIKICAgY2xhc3M9InN0OCIKICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgaWQ9InBhdGgzNTY3IiAvPgoJPC9nPgo8L2c+PC9nPgo8L3N2Zz4=
+  logoFormat: svg+xml
diff --git a/pkg/applicationdefinitions/system-applications/cluster-autoscaler-app.yaml b/pkg/applicationdefinitions/system-applications/cluster-autoscaler.yaml
similarity index 99%
rename from pkg/applicationdefinitions/system-applications/cluster-autoscaler-app.yaml
rename to pkg/applicationdefinitions/system-applications/cluster-autoscaler.yaml
index 8240bade130..bbeb313f7d0 100644
--- a/pkg/applicationdefinitions/system-applications/cluster-autoscaler-app.yaml
+++ b/pkg/applicationdefinitions/system-applications/cluster-autoscaler.yaml
@@ -73,7 +73,7 @@ spec:
     - apiVersion: rbac.authorization.k8s.io/v1
       kind: ClusterRoleBinding
       metadata:
-        name: cluster-autoscaler-clusterapi-cluser-autoscaler
+        name: cluster-autoscaler-clusterapi-cluster-autoscaler
       roleRef:
         apiGroup: rbac.authorization.k8s.io
         kind: ClusterRole
diff --git a/pkg/applications/providers/template/helm.go b/pkg/applications/providers/template/helm.go
index aff3f978a36..6b281addad7 100644
--- a/pkg/applications/providers/template/helm.go
+++ b/pkg/applications/providers/template/helm.go
@@ -291,3 +291,12 @@ func (h HelmTemplate) Rollback(applicationInstallation *appskubermaticv1.Applica
 
 	return helmClient.Rollback(getReleaseName(applicationInstallation))
 }
+
+func (h *HelmTemplate) templatePreDefinedValues(applicationValues map[string]any) (map[string]any, error) {
+	templateData, err := GetTemplateData(h.Ctx, h.SeedClient, h.ClusterName)
+
+	if err != nil {
+		return nil, err
+	}
+	return RenderValueTemplate(applicationValues, templateData)
+}
diff --git a/pkg/ee/applications/util.go b/pkg/applications/providers/template/util.go
similarity index 81%
rename from pkg/ee/applications/util.go
rename to pkg/applications/providers/template/util.go
index 026d9811538..1d0e8bb37a5 100644
--- a/pkg/ee/applications/util.go
+++ b/pkg/applications/providers/template/util.go
@@ -1,26 +1,20 @@
 /*
-                  Kubermatic Enterprise Read-Only License
-                         Version 1.0 ("KERO-1.0”)
-                     Copyright © 2025 Kubermatic GmbH
-
-   1.	You may only view, read and display for studying purposes the source
-      code of the software licensed under this license, and, to the extent
-      explicitly provided under this license, the binary code.
-   2.	Any use of the software which exceeds the foregoing right, including,
-      without limitation, its execution, compilation, copying, modification
-      and distribution, is expressly prohibited.
-   3.	THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
-      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-      MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-      IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-      CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-      TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-      SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-   END OF TERMS AND CONDITIONS
+Copyright 2025 The Kubermatic Kubernetes Platform contributors.
+
+Licensed 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 applications
+package template
 
 import (
 	"bytes"
@@ -114,7 +108,7 @@ func GetTemplateData(ctx context.Context, seedClient ctrlruntimeclient.Client, c
 		clusterVersion = cluster.Spec.Version.Semver()
 	}
 	if clusterVersion != nil {
-		clusterAutoscalerVersion, err := getAutoscalerImageTag(fmt.Sprintf("%d.%d", clusterVersion.Major(), clusterVersion.Minor()))
+		clusterAutoscalerVersion, err := GetAutoscalerImageTag(fmt.Sprintf("%d.%d", clusterVersion.Major(), clusterVersion.Minor()))
 		if err != nil {
 			return nil, fmt.Errorf("failed to parse autoscaler version for cluster %q", clusterName)
 		}
@@ -160,7 +154,7 @@ func RenderValueTemplate(applicationValues map[string]interface{}, templateData
 	return parsedMap, nil
 }
 
-func getAutoscalerImageTag(majorMinorVersion string) (string, error) {
+func GetAutoscalerImageTag(majorMinorVersion string) (string, error) {
 	switch majorMinorVersion {
 	case "1.29":
 		return "v1.29.5", nil
diff --git a/pkg/ee/applications/util_test.go b/pkg/applications/providers/template/util_test.go
similarity index 86%
rename from pkg/ee/applications/util_test.go
rename to pkg/applications/providers/template/util_test.go
index 1f1b7d0cfbb..3ba7a0df6ce 100644
--- a/pkg/ee/applications/util_test.go
+++ b/pkg/applications/providers/template/util_test.go
@@ -1,26 +1,20 @@
 /*
-                  Kubermatic Enterprise Read-Only License
-                         Version 1.0 ("KERO-1.0”)
-                     Copyright © 2025 Kubermatic GmbH
+Copyright 2025 The Kubermatic Kubernetes Platform contributors.
 
-   1.	You may only view, read and display for studying purposes the source
-      code of the software licensed under this license, and, to the extent
-      explicitly provided under this license, the binary code.
-   2.	Any use of the software which exceeds the foregoing right, including,
-      without limitation, its execution, compilation, copying, modification
-      and distribution, is expressly prohibited.
-   3.	THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
-      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-      MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-      IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-      CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-      TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-      SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+Licensed 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
 
-   END OF TERMS AND CONDITIONS
+    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 applications
+package template
 
 import (
 	"context"
diff --git a/pkg/applications/providers/template/wrappers_ce.go b/pkg/applications/providers/template/wrappers_ce.go
deleted file mode 100644
index 174fdddab27..00000000000
--- a/pkg/applications/providers/template/wrappers_ce.go
+++ /dev/null
@@ -1,23 +0,0 @@
-//go:build !ee
-
-/*
-Copyright 2025 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 template
-
-func (h *HelmTemplate) templatePreDefinedValues(applicationValues map[string]any) (map[string]any, error) {
-	return applicationValues, nil
-}
diff --git a/pkg/applications/providers/template/wrappers_ee.go b/pkg/applications/providers/template/wrappers_ee.go
deleted file mode 100644
index 8e0140c1d98..00000000000
--- a/pkg/applications/providers/template/wrappers_ee.go
+++ /dev/null
@@ -1,32 +0,0 @@
-//go:build ee
-
-/*
-Copyright 2025 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 template
-
-import (
-	eeutil "k8c.io/kubermatic/v2/pkg/ee/applications"
-)
-
-func (h *HelmTemplate) templatePreDefinedValues(applicationValues map[string]any) (map[string]any, error) {
-	templateData, err := eeutil.GetTemplateData(h.Ctx, h.SeedClient, h.ClusterName)
-
-	if err != nil {
-		return nil, err
-	}
-	return eeutil.RenderValueTemplate(applicationValues, templateData)
-}
diff --git a/pkg/cni/cilium/cilium.go b/pkg/cni/cilium/cilium.go
deleted file mode 100644
index 7e584098065..00000000000
--- a/pkg/cni/cilium/cilium.go
+++ /dev/null
@@ -1,619 +0,0 @@
-/*
-Copyright 2022 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 cilium
-
-import (
-	"fmt"
-	"maps"
-	"strings"
-
-	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
-	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
-	"k8c.io/kubermatic/v2/pkg/defaulting"
-	"k8c.io/kubermatic/v2/pkg/resources"
-	"k8c.io/kubermatic/v2/pkg/resources/reconciling"
-	"k8c.io/kubermatic/v2/pkg/resources/registry"
-
-	"k8s.io/apimachinery/pkg/util/validation/field"
-	"sigs.k8s.io/yaml"
-)
-
-const (
-	ciliumHelmChartName = "cilium"
-	ciliumImageRegistry = "quay.io/cilium/"
-)
-
-// ApplicationDefinitionReconciler creates Cilium ApplicationDefinition managed by KKP to be used
-// for installing Cilium CNI into KKP user clusters.
-func ApplicationDefinitionReconciler(config *kubermaticv1.KubermaticConfiguration) reconciling.NamedApplicationDefinitionReconcilerFactory {
-	return func() (string, reconciling.ApplicationDefinitionReconciler) {
-		return kubermaticv1.CNIPluginTypeCilium.String(), func(app *appskubermaticv1.ApplicationDefinition) (*appskubermaticv1.ApplicationDefinition, error) {
-			app.Labels = map[string]string{
-				appskubermaticv1.ApplicationManagedByLabel: appskubermaticv1.ApplicationManagedByKKPValue,
-				appskubermaticv1.ApplicationTypeLabel:      appskubermaticv1.ApplicationTypeCNIValue,
-			}
-
-			app.Spec.Description = "Cilium CNI - eBPF-based Networking, Security, and Observability"
-			app.Spec.Method = appskubermaticv1.HelmTemplateMethod
-
-			var credentials *appskubermaticv1.HelmCredentials
-			if config.Spec.UserCluster.SystemApplications.HelmRegistryConfigFile != nil {
-				credentials = &appskubermaticv1.HelmCredentials{
-					RegistryConfigFile: config.Spec.UserCluster.SystemApplications.HelmRegistryConfigFile,
-				}
-			}
-
-			defaultHelmRepository := defaulting.SystemApplicationsHelmRepositoryFromConfig(&config.Spec.UserCluster.SystemApplications)
-
-			app.Spec.Versions = []appskubermaticv1.ApplicationVersion{
-				// NOTE: When introducing a new version, make sure it is:
-				//  - introduced in pkg/cni/version.go with the version string exactly matching the Spec.Versions.Version here
-				//  - Helm chart is mirrored in Kubermatic OCI registry, use the script cilium-mirror-chart.sh
-				{
-					Version: "1.13.0",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.0",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.3",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.3",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.4",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.4",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.6",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.6",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.7",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.7",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.8",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.8",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.13.14",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.13.14",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.14.1",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.14.1",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.14.2",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.14.2",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.14.3",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.14.3",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.14.9",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.14.9",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.14.16",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.14.16",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.15.3",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.15.3",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.15.10",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.15.10",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.15.16",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.15.16",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.16.6",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.16.6",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-				{
-					Version: "1.16.9",
-					Template: appskubermaticv1.ApplicationTemplate{
-						Source: appskubermaticv1.ApplicationSource{
-							Helm: &appskubermaticv1.HelmSource{
-								ChartName:    ciliumHelmChartName,
-								ChartVersion: "1.16.9",
-								URL:          defaultHelmRepository,
-								Credentials:  credentials,
-							},
-						},
-					},
-				},
-			}
-
-			// to make it compatible with older cilium controller versions, convert any DefaultValues into DefaultValuesBlock
-			if err := convertDefaultValuesToDefaultValuesBlock(app); err != nil {
-				return app, fmt.Errorf("failed to convert DefaultValues into DefaultValuesBlock: %w", err)
-			}
-
-			// we want to allow overriding the default values, so reconcile them only if nil
-			if app.Spec.DefaultValuesBlock == "" || app.Spec.DefaultValuesBlock == "{}" {
-				if err := setDefaultValues(app); err != nil {
-					return app, err
-				}
-				return app, nil
-			}
-
-			defaultValues, err := app.Spec.GetParsedDefaultValues()
-			if err != nil {
-				return app, fmt.Errorf("failed to unmarshal CNI values: %w", err)
-			}
-
-			if defaultValues != nil {
-				sanitizeValues(defaultValues)
-				rawValues, err := yaml.Marshal(defaultValues)
-				if err != nil {
-					return app, fmt.Errorf("failed to marshal CNI values: %w", err)
-				}
-
-				app.Spec.DefaultValuesBlock = string(rawValues)
-			}
-
-			return app, nil
-		}
-	}
-}
-
-// setDefaultValues sets the default values for the application.
-func setDefaultValues(app *appskubermaticv1.ApplicationDefinition) error {
-	defaultValues := map[string]any{
-		"operator": map[string]any{
-			"replicas": 1,
-		},
-		"envoy": map[string]any{
-			"enabled": false,
-		},
-		"hubble": map[string]any{
-			"relay": map[string]any{
-				"enabled": true,
-			},
-			"ui": map[string]any{
-				"enabled": true,
-			},
-			"tls": map[string]any{
-				"auto": map[string]any{
-					"method": "cronJob",
-				},
-			},
-		},
-	}
-	rawValues, err := yaml.Marshal(defaultValues)
-	if err != nil {
-		return fmt.Errorf("failed to marshal default CNI values: %w", err)
-	}
-	app.Spec.DefaultValuesBlock = string(rawValues)
-	return nil
-}
-
-func sanitizeValues(values map[string]any) {
-	// If not specified, set envoy.enabled to false
-	// https://github.com/cilium/cilium/commit/471f19a16593e1e9342c31bf3e26e5383737cb0a
-	if envoy, ok := values["envoy"].(map[string]any); ok {
-		if _, ok := envoy["enabled"]; !ok {
-			envoy["enabled"] = false
-		}
-	} else {
-		values["envoy"] = map[string]any{
-			"enabled": false,
-		}
-	}
-}
-
-// GetAppInstallOverrideValues returns Helm values to be enforced on the cluster's ApplicationInstallation
-// of the Cilium CNI managed by KKP.
-func GetAppInstallOverrideValues(cluster *kubermaticv1.Cluster, overwriteRegistry string) map[string]any {
-	podSecurityContext := map[string]any{
-		"seccompProfile": map[string]any{
-			"type": "RuntimeDefault",
-		},
-	}
-	defaultValues := map[string]any{
-		"podSecurityContext": podSecurityContext,
-	}
-	values := map[string]any{
-		"podSecurityContext": podSecurityContext,
-	}
-	valuesEnvoy := map[string]any{
-		"podSecurityContext": podSecurityContext,
-	}
-
-	valuesOperator := map[string]any{
-		"securityContext": map[string]any{
-			"seccompProfile": map[string]any{
-				"type": "RuntimeDefault",
-			},
-		},
-		"podSecurityContext": podSecurityContext,
-	}
-	valuesCni := map[string]any{
-		// we run Cilium as non-exclusive CNI to allow for Multus use-cases
-		"exclusive": false,
-	}
-
-	valuesCertGen := maps.Clone(defaultValues)
-	valuesRelay := maps.Clone(defaultValues)
-	valuesFrontend := map[string]any{}
-	valuesBackend := map[string]any{}
-
-	if cluster.Spec.ClusterNetwork.ProxyMode == resources.EBPFProxyMode {
-		values["kubeProxyReplacement"] = "true"
-		values["k8sServiceHost"] = cluster.Status.Address.ExternalName
-		values["k8sServicePort"] = cluster.Status.Address.Port
-
-		nodePortRange := cluster.Spec.ComponentsOverride.Apiserver.NodePortRange
-		if nodePortRange != "" && nodePortRange != resources.DefaultNodePortRange {
-			values["nodePort"] = map[string]any{
-				"range": strings.ReplaceAll(nodePortRange, "-", ","),
-			}
-		}
-	} else {
-		values["kubeProxyReplacement"] = "false"
-		values["sessionAffinity"] = true
-		valuesCni["chainingMode"] = "portmap"
-	}
-
-	ipamOperator := map[string]any{
-		"clusterPoolIPv4PodCIDRList": cluster.Spec.ClusterNetwork.Pods.GetIPv4CIDRs(),
-	}
-
-	if cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv4 != nil {
-		ipamOperator["clusterPoolIPv4MaskSize"] = *cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv4
-	}
-
-	if cluster.IsDualStack() {
-		values["ipv6"] = map[string]any{"enabled": true}
-		ipamOperator["clusterPoolIPv6PodCIDRList"] = cluster.Spec.ClusterNetwork.Pods.GetIPv6CIDRs()
-		if cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv6 != nil {
-			ipamOperator["clusterPoolIPv6MaskSize"] = *cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv6
-		}
-	}
-
-	values["ipam"] = map[string]any{"operator": ipamOperator}
-
-	// Override images if registry override is configured
-	if overwriteRegistry != "" {
-		values["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"cilium", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesOperator["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"operator", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesRelay["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-relay", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesBackend["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-ui-backend", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesFrontend["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-ui", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesCertGen["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"certgen", overwriteRegistry)),
-			"useDigest":  false,
-		}
-		valuesEnvoy["image"] = map[string]any{
-			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"cilium-envoy", overwriteRegistry)),
-			"useDigest":  false,
-		}
-	}
-
-	uiSecContext := maps.Clone(podSecurityContext)
-	uiSecContext["enabled"] = true
-
-	values["cni"] = valuesCni
-	values["envoy"] = valuesEnvoy
-	values["operator"] = valuesOperator
-	values["certgen"] = valuesCertGen
-	values["hubble"] = map[string]any{
-		"relay": valuesRelay,
-		"ui": map[string]any{
-			"securityContext": uiSecContext,
-			"frontend":        valuesFrontend,
-			"backend":         valuesBackend,
-		},
-	}
-
-	return values
-}
-
-// ValidateValuesUpdate validates the update operation on provided Cilium Helm values.
-func ValidateValuesUpdate(newValues, oldValues map[string]any, fieldPath *field.Path) field.ErrorList {
-	allErrs := field.ErrorList{}
-	// Validate immutability of specific top-level value subtrees, managed solely by KKP
-	exclusions := []exclusion{
-		{
-			fullPath:  "cni.chainingMode",
-			pathParts: []string{"cni", "chainingMode"},
-		},
-		{
-			fullPath:  "ipam.operator.clusterPoolIPv4MaskSize",
-			pathParts: []string{"ipam", "operator", "clusterPoolIPv4MaskSize"},
-		},
-		{
-			fullPath:  "ipam.operator.clusterPoolIPv6MaskSize",
-			pathParts: []string{"ipam", "operator", "clusterPoolIPv6MaskSize"},
-		},
-	}
-	allErrs = append(allErrs, validateImmutableValues(newValues, oldValues, fieldPath, []string{
-		"cni",
-		"ipam",
-		"ipv6",
-	}, exclusions)...)
-
-	// Validate that mandatory top-level values are present
-	allErrs = append(allErrs, validateMandatoryValues(newValues, fieldPath, []string{
-		"kubeProxyReplacement", // can be changed if switching KKP proxy mode, but must be present
-	})...)
-
-	// Validate config for strict kubeProxyReplacement (ebpf "proxy mode")
-	if newValues["kubeProxyReplacement"] == "strict" {
-		// Validate mandatory values for kube-proxy-replacement
-		allErrs = append(allErrs, validateMandatoryValues(newValues, fieldPath, []string{
-			"k8sServiceHost",
-			"k8sServicePort",
-		})...)
-	}
-
-	// Validate immutability of operator.securityContext
-	operPath := fieldPath.Child("operator")
-	newOperator, ok := newValues["operator"].(map[string]any)
-	if !ok {
-		allErrs = append(allErrs, field.Invalid(operPath, newValues["operator"], "value missing or incorrect"))
-	}
-	oldOperator, ok := oldValues["operator"].(map[string]any)
-	if !ok {
-		allErrs = append(allErrs, field.Invalid(operPath, oldValues["operator"], "value missing or incorrect"))
-	}
-	allErrs = append(allErrs, validateImmutableValues(newOperator, oldOperator, operPath, []string{
-		"securityContext",
-	}, []exclusion{})...)
-
-	return allErrs
-}
-
-type exclusion struct {
-	fullPath  string
-	pathParts []string
-}
-
-func validateImmutableValues(newValues, oldValues map[string]any, fieldPath *field.Path, immutableValues []string, exclusions []exclusion) field.ErrorList {
-	allErrs := field.ErrorList{}
-	allowedValues := map[string]bool{}
-
-	for _, v := range immutableValues {
-		// Check if the value in `oldValues` is non-empty and missing in `newValues`
-		if _, existsInOld := oldValues[v]; existsInOld {
-			if _, existsInNew := newValues[v]; !existsInNew || newValues[v] == nil || fmt.Sprint(newValues[v]) == "" {
-				allErrs = append(allErrs, field.Invalid(fieldPath.Child(v), newValues[v], "value is immutable"))
-				continue
-			}
-		}
-		for _, exclusion := range exclusions {
-			if strings.HasPrefix(exclusion.fullPath, v) {
-				if excludedKeyExists(newValues, exclusion.pathParts...) {
-					allowedValues[v] = true
-				}
-				if excludedKeyExists(oldValues, exclusion.pathParts...) {
-					allowedValues[v] = true
-				}
-			}
-		}
-		if fmt.Sprint(oldValues[v]) != fmt.Sprint(newValues[v]) && !allowedValues[v] {
-			allErrs = append(allErrs, field.Invalid(fieldPath.Child(v), newValues[v], "value is immutable"))
-		}
-	}
-	return allErrs
-}
-
-func validateMandatoryValues(newValues map[string]any, fieldPath *field.Path, mandatoryValues []string) field.ErrorList {
-	allErrs := field.ErrorList{}
-	for _, v := range mandatoryValues {
-		if _, ok := newValues[v]; !ok {
-			allErrs = append(allErrs, field.NotFound(fieldPath.Child(v), newValues[v]))
-		}
-	}
-	return allErrs
-}
-
-func excludedKeyExists(values map[string]any, keys ...string) bool {
-	if len(keys) == 0 || values == nil {
-		return false
-	}
-	root := keys[0]
-	value, ok := values[root]
-	if !ok {
-		return false
-	}
-
-	if len(keys) == 1 {
-		return true
-	}
-
-	if innerMap, ok := value.(map[string]any); ok {
-		return excludedKeyExists(innerMap, keys[1:]...)
-	}
-
-	return false
-}
-
-func convertDefaultValuesToDefaultValuesBlock(app *appskubermaticv1.ApplicationDefinition) error {
-	if app.Spec.DefaultValues != nil {
-		oldDefVals, err := yaml.JSONToYAML(app.Spec.DefaultValues.Raw)
-		if err != nil {
-			return err
-		}
-		app.Spec.DefaultValuesBlock = string(oldDefVals)
-		app.Spec.DefaultValues = nil
-	}
-	return nil
-}
diff --git a/pkg/cni/cilium/cilium_test.go b/pkg/cni/cilium/cilium_test.go
deleted file mode 100644
index a42e68e4e30..00000000000
--- a/pkg/cni/cilium/cilium_test.go
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
-Copyright 2022 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 cilium
-
-import (
-	"encoding/json"
-	"fmt"
-	"reflect"
-	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-
-	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
-	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
-	"k8c.io/kubermatic/v2/pkg/cni"
-	"k8c.io/kubermatic/v2/pkg/resources"
-
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/util/validation/field"
-	"k8s.io/utils/ptr"
-)
-
-var testCluster = &kubermaticv1.Cluster{
-	Spec: kubermaticv1.ClusterSpec{
-		CNIPlugin: &kubermaticv1.CNIPluginSettings{
-			Type:    kubermaticv1.CNIPluginTypeCilium,
-			Version: cni.GetDefaultCNIPluginVersion(kubermaticv1.CNIPluginTypeCilium),
-		},
-		ClusterNetwork: kubermaticv1.ClusterNetworkingConfig{
-			Pods: kubermaticv1.NetworkRanges{
-				CIDRBlocks: []string{"192.168.0.0/24", "192.168.178.0/24"},
-			},
-			NodeCIDRMaskSizeIPv4: ptr.To[int32](16),
-			ProxyMode:            resources.EBPFProxyMode,
-		},
-		ComponentsOverride: kubermaticv1.ComponentSettings{
-			Apiserver: kubermaticv1.APIServerSettings{
-				NodePortRange: "30000-31777",
-			},
-		},
-	},
-	Status: kubermaticv1.ClusterStatus{
-		Address: kubermaticv1.ClusterAddress{
-			ExternalName: "cluster.kubermatic.test",
-			Port:         6443,
-		},
-	},
-}
-
-func TestGetCiliumAppInstallOverrideValues(t *testing.T) {
-	testCases := []struct {
-		name              string
-		cluster           *kubermaticv1.Cluster
-		overwriteRegistry string
-		expectedValues    string
-	}{
-		{
-			name:              "default values",
-			cluster:           testCluster,
-			overwriteRegistry: "",
-			expectedValues:    `{"certgen":{"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"cni":{"exclusive":false},"envoy":{"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"hubble":{"relay":{"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"ui":{"backend":{},"frontend":{},"securityContext":{"enabled":true,"seccompProfile":{"type":"RuntimeDefault"}}}},"ipam":{"operator":{"clusterPoolIPv4MaskSize":16,"clusterPoolIPv4PodCIDRList":["192.168.0.0/24","192.168.178.0/24"]}},"k8sServiceHost":"cluster.kubermatic.test","k8sServicePort":6443,"kubeProxyReplacement":"true","nodePort":{"range":"30000,31777"},"operator":{"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}},"securityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}}`,
-		},
-		{
-			name:              "default values with overwrite registry",
-			cluster:           testCluster,
-			overwriteRegistry: "myregistry.io",
-			expectedValues:    `{"certgen":{"image":{"repository":"myregistry.io/cilium/certgen","useDigest":false},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"cni":{"exclusive":false},"envoy":{"image":{"repository":"myregistry.io/cilium/cilium-envoy","useDigest":false},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"hubble":{"relay":{"image":{"repository":"myregistry.io/cilium/hubble-relay","useDigest":false},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"ui":{"backend":{"image":{"repository":"myregistry.io/cilium/hubble-ui-backend","useDigest":false}},"frontend":{"image":{"repository":"myregistry.io/cilium/hubble-ui","useDigest":false}},"securityContext":{"enabled":true,"seccompProfile":{"type":"RuntimeDefault"}}}},"image":{"repository":"myregistry.io/cilium/cilium","useDigest":false},"ipam":{"operator":{"clusterPoolIPv4MaskSize":16,"clusterPoolIPv4PodCIDRList":["192.168.0.0/24","192.168.178.0/24"]}},"k8sServiceHost":"cluster.kubermatic.test","k8sServicePort":6443,"kubeProxyReplacement":"true","nodePort":{"range":"30000,31777"},"operator":{"image":{"repository":"myregistry.io/cilium/operator","useDigest":false},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}},"securityContext":{"seccompProfile":{"type":"RuntimeDefault"}}},"podSecurityContext":{"seccompProfile":{"type":"RuntimeDefault"}}}`,
-		},
-	}
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			values := GetAppInstallOverrideValues(testCluster, testCase.overwriteRegistry)
-			rawValues, _ := json.Marshal(values)
-			if string(rawValues) != testCase.expectedValues {
-				t.Fatalf("values '%s' do not match expected values '%s'", rawValues, testCase.expectedValues)
-			}
-		})
-	}
-}
-
-func TestValidateCiliumValuesUpdate(t *testing.T) {
-	testCases := []struct {
-		name               string
-		expectedError      string
-		testValuesModifier func(map[string]any)
-	}{
-		{
-			name: "No value change",
-			testValuesModifier: func(values map[string]any) {
-				// NOOP
-			},
-			expectedError: "[]",
-		},
-		{
-			name: "Allowed values change",
-			testValuesModifier: func(values map[string]any) {
-				values["allowed"] = "true"
-			},
-			expectedError: "[]",
-		},
-		{
-			name: "Modified immutable value",
-			testValuesModifier: func(values map[string]any) {
-				values["ipv6"] = map[string]any{"enabled": "true"}
-			},
-			expectedError: "[spec.values.ipv6: Invalid value: map[string]interface {}{\"enabled\":\"true\"}: value is immutable]",
-		},
-		{
-			name: "Removed immutable value",
-			testValuesModifier: func(values map[string]any) {
-				delete(values, "ipam")
-			},
-			expectedError: "[spec.values.ipam: Invalid value: \"null\": value is immutable]",
-		},
-		{
-			name: "Change mandatory value",
-			testValuesModifier: func(values map[string]any) {
-				values["kubeProxyReplacement"] = "false"
-			},
-			expectedError: "[]",
-		},
-		{
-			name: "Remove mandatory value",
-			testValuesModifier: func(values map[string]any) {
-				delete(values, "kubeProxyReplacement")
-			},
-			expectedError: "[spec.values.kubeProxyReplacement: Not found: \"null\"]",
-		},
-		{
-			name: "Remove nested immutable value",
-			testValuesModifier: func(values map[string]any) {
-				o := values["operator"].(map[string]any)
-				delete(o, "securityContext")
-			},
-			expectedError: "[spec.values.operator.securityContext: Invalid value: \"null\": value is immutable]",
-		},
-		{
-			name: "cni excluded field introduced under immutable value",
-			testValuesModifier: func(values map[string]any) {
-				o := values["cni"].(map[string]any)
-				o["chainingMode"] = "test"
-			},
-			expectedError: "[]",
-		},
-		{
-			name: "ipam excluded field introduced under immutable value",
-			testValuesModifier: func(values map[string]any) {
-				ipam := values["ipam"].(map[string]any)
-				op := ipam["operator"].(map[string]any)
-				op["clusterPoolIPv4MaskSize"] = 32
-			},
-			expectedError: "[]",
-		},
-		{
-			name: "Modified multiple immutable nested value in ipam and one is excluded",
-			testValuesModifier: func(values map[string]any) {
-				cni := values["cni"].(map[string]any)
-				cni["chainingMode"] = "test"
-				values["ipv6"] = map[string]any{"enabled": "true"}
-			},
-			expectedError: "[spec.values.ipv6: Invalid value: map[string]interface {}{\"enabled\":\"true\"}: value is immutable]",
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			// get values reconciled by KKP
-			oldValues := GetAppInstallOverrideValues(testCluster, "")
-
-			// copy oldValues to newValues to modify
-			newValues := make(map[string]any)
-			rawValues, _ := json.Marshal(oldValues)
-			err := json.Unmarshal(rawValues, &newValues)
-			if err != nil {
-				t.Fatalf("values unmarshalling failed: %s", err)
-			}
-
-			// modify newValues
-			testCase.testValuesModifier(newValues)
-
-			// validate the update and check for expected errors
-			errList := ValidateValuesUpdate(newValues, oldValues, field.NewPath("spec").Child("values"))
-			if fmt.Sprint(errList) != testCase.expectedError {
-				if testCase.expectedError == "[]" {
-					testCase.expectedError = "nil"
-				}
-				t.Fatalf("expected error to be %s but got %v", testCase.expectedError, errList)
-			}
-		})
-	}
-}
-
-// TestValidateImmutableValues ensures that map comparison works as expected.
-func TestValidateImmutableValues(t *testing.T) {
-	oldValues := GetAppInstallOverrideValues(testCluster, "")
-	// copy oldValues to newValues to modify
-	equalValues := make(map[string]any)
-	rawValues, _ := json.Marshal(oldValues)
-	err := json.Unmarshal(rawValues, &equalValues)
-	if err != nil {
-		t.Fatalf("values unmarshalling failed: %s", err)
-	}
-
-	alteredCluster := testCluster.DeepCopy()
-	alteredCluster.Spec.ClusterNetwork.Pods.CIDRBlocks = []string{"192.123.123.0/24"}
-	alteredValues := GetAppInstallOverrideValues(alteredCluster, "")
-
-	tests := []struct {
-		name            string
-		want            field.ErrorList
-		immutableValues []string
-		acceptedFields  []exclusion
-		fieldPath       *field.Path
-		oldValues       map[string]any
-		newValues       map[string]any
-	}{
-		{
-			name:            "equal spec",
-			immutableValues: []string{"values"},
-			acceptedFields:  []exclusion{},
-			want:            field.ErrorList{},
-			fieldPath:       field.NewPath("spec"),
-			oldValues:       oldValues,
-			newValues:       equalValues,
-		},
-		{
-			name:            "equal values",
-			immutableValues: []string{"cni", "ipam", "ipv6"},
-			acceptedFields:  []exclusion{},
-			want:            field.ErrorList{},
-			fieldPath:       field.NewPath("spec").Child("values"),
-			oldValues:       oldValues,
-			newValues:       equalValues,
-		},
-		{
-			name:            "ipam modified",
-			immutableValues: []string{"cni", "ipam", "ipv6"},
-			acceptedFields:  []exclusion{},
-			want:            field.ErrorList{field.Invalid(field.NewPath("spec").Child("values").Child("ipam"), alteredValues["ipam"], "value is immutable")},
-			fieldPath:       field.NewPath("spec").Child("values"),
-			oldValues:       oldValues,
-			newValues:       alteredValues,
-		},
-		{
-			name:            "ipam modified, but the clusterPoolIPv4MaskSize is accepted",
-			immutableValues: []string{"cni", "ipam", "ipv6"},
-			acceptedFields:  []exclusion{{fullPath: "ipam.operator.clusterPoolIPv4MaskSize", pathParts: strings.Split("ipam.operator.clusterPoolIPv4MaskSize", ".")}},
-			want:            field.ErrorList{},
-			fieldPath:       field.NewPath("spec").Child("values"),
-			oldValues:       oldValues,
-			newValues:       alteredValues,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := validateImmutableValues(tt.newValues, tt.oldValues, tt.fieldPath, tt.immutableValues, tt.acceptedFields); !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("%s: validateImmutableValues() = %v, want %v", tt.name, got, tt.want)
-			}
-		})
-	}
-}
-
-func TestConvertValuesToValuesBlock(t *testing.T) {
-	tests := []struct {
-		name   string
-		appIn  *appskubermaticv1.ApplicationDefinition
-		expApp *appskubermaticv1.ApplicationDefinition
-	}{
-		{
-			name: "already using DefaultValuesBlock, nothing to do",
-			appIn: &appskubermaticv1.ApplicationDefinition{
-				Spec: appskubermaticv1.ApplicationDefinitionSpec{
-					DefaultValuesBlock: "key: value",
-				},
-			},
-			expApp: &appskubermaticv1.ApplicationDefinition{
-				Spec: appskubermaticv1.ApplicationDefinitionSpec{
-					DefaultValuesBlock: "key: value",
-				},
-			},
-		},
-		{
-			name: "using DefaultValues, convert to DefaultValuesBlock",
-			appIn: &appskubermaticv1.ApplicationDefinition{
-				Spec: appskubermaticv1.ApplicationDefinitionSpec{
-					DefaultValues: &runtime.RawExtension{Raw: []byte(`{"key": "value"}`)},
-				},
-			},
-			expApp: &appskubermaticv1.ApplicationDefinition{
-				Spec: appskubermaticv1.ApplicationDefinitionSpec{
-					DefaultValuesBlock: "key: value\n",
-				},
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			err := convertDefaultValuesToDefaultValuesBlock(tt.appIn)
-			if err != nil {
-				t.Error(err)
-			}
-
-			assert.Equal(t, tt.appIn, tt.expApp)
-		})
-	}
-}
diff --git a/pkg/controller/operator/master/reconciler.go b/pkg/controller/operator/master/reconciler.go
index 58410795271..a14b2cbf688 100644
--- a/pkg/controller/operator/master/reconciler.go
+++ b/pkg/controller/operator/master/reconciler.go
@@ -24,7 +24,6 @@ import (
 
 	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
 	"k8c.io/kubermatic/v2/pkg/applicationdefinitions"
-	"k8c.io/kubermatic/v2/pkg/cni/cilium"
 	"k8c.io/kubermatic/v2/pkg/controller/operator/common"
 	"k8c.io/kubermatic/v2/pkg/controller/operator/master/resources/kubermatic"
 	"k8c.io/kubermatic/v2/pkg/defaulting"
@@ -488,9 +487,6 @@ func (r *Reconciler) reconcileApplicationDefinitions(ctx context.Context, config
 	if err != nil {
 		return fmt.Errorf("failed to get system application definition reconciler factories: %w", err)
 	}
-
-	sysAppDefReconcilers = append(sysAppDefReconcilers, cilium.ApplicationDefinitionReconciler(config))
-
 	if err := kkpreconciling.ReconcileApplicationDefinitions(ctx, sysAppDefReconcilers, "", r.Client); err != nil {
 		return fmt.Errorf("failed to reconcile ApplicationDefinitions: %w", err)
 	}
diff --git a/pkg/controller/seed-controller-manager/cni-application-installation-controller/controller.go b/pkg/controller/seed-controller-manager/cni-application-installation-controller/controller.go
index 01fcfe54375..b5918e34d60 100644
--- a/pkg/controller/seed-controller-manager/cni-application-installation-controller/controller.go
+++ b/pkg/controller/seed-controller-manager/cni-application-installation-controller/controller.go
@@ -20,7 +20,9 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"maps"
 	"reflect"
+	"strings"
 	"time"
 
 	"dario.cat/mergo"
@@ -30,9 +32,10 @@ import (
 	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
 	clusterclient "k8c.io/kubermatic/v2/pkg/cluster/client"
 	"k8c.io/kubermatic/v2/pkg/cni"
-	"k8c.io/kubermatic/v2/pkg/cni/cilium"
 	"k8c.io/kubermatic/v2/pkg/controller/util"
+	"k8c.io/kubermatic/v2/pkg/resources"
 	"k8c.io/kubermatic/v2/pkg/resources/reconciling"
+	"k8c.io/kubermatic/v2/pkg/resources/registry"
 	"k8c.io/kubermatic/v2/pkg/util/workerlabel"
 	"k8c.io/kubermatic/v2/pkg/version/kubermatic"
 
@@ -58,6 +61,12 @@ const (
 	cniPluginNamespace = "kube-system"
 )
 
+// Cilium specific constants.
+const (
+	ciliumHelmChartName = "cilium"
+	ciliumImageRegistry = "quay.io/cilium/"
+)
+
 // UserClusterClientProvider provides functionality to get a user cluster client.
 type UserClusterClientProvider interface {
 	GetClient(ctx context.Context, c *kubermaticv1.Cluster, options ...clusterclient.ConfigOption) (ctrlruntimeclient.Client, error)
@@ -379,7 +388,129 @@ func ApplicationInstallationReconciler(cluster *kubermaticv1.Cluster, overwriteR
 
 func getCNIOverrideValues(cluster *kubermaticv1.Cluster, overwriteRegistry string) map[string]any {
 	if cluster.Spec.CNIPlugin.Type == kubermaticv1.CNIPluginTypeCilium {
-		return cilium.GetAppInstallOverrideValues(cluster, overwriteRegistry)
+		return getAppInstallOverrideValues(cluster, overwriteRegistry)
 	}
 	return nil
 }
+
+// getAppInstallOverrideValues returns Helm values to be enforced on the cluster's ApplicationInstallation
+// of the Cilium CNI managed by KKP.
+func getAppInstallOverrideValues(cluster *kubermaticv1.Cluster, overwriteRegistry string) map[string]any {
+	podSecurityContext := map[string]any{
+		"seccompProfile": map[string]any{
+			"type": "RuntimeDefault",
+		},
+	}
+	defaultValues := map[string]any{
+		"podSecurityContext": podSecurityContext,
+	}
+	values := map[string]any{
+		"podSecurityContext": podSecurityContext,
+	}
+	valuesEnvoy := map[string]any{
+		"podSecurityContext": podSecurityContext,
+	}
+
+	valuesOperator := map[string]any{
+		"securityContext": map[string]any{
+			"seccompProfile": map[string]any{
+				"type": "RuntimeDefault",
+			},
+		},
+		"podSecurityContext": podSecurityContext,
+	}
+	valuesCni := map[string]any{
+		// we run Cilium as non-exclusive CNI to allow for Multus use-cases
+		"exclusive": false,
+	}
+
+	valuesCertGen := maps.Clone(defaultValues)
+	valuesRelay := maps.Clone(defaultValues)
+	valuesFrontend := map[string]any{}
+	valuesBackend := map[string]any{}
+
+	if cluster.Spec.ClusterNetwork.ProxyMode == resources.EBPFProxyMode {
+		values["kubeProxyReplacement"] = "true"
+		values["k8sServiceHost"] = cluster.Status.Address.ExternalName
+		values["k8sServicePort"] = cluster.Status.Address.Port
+
+		nodePortRange := cluster.Spec.ComponentsOverride.Apiserver.NodePortRange
+		if nodePortRange != "" && nodePortRange != resources.DefaultNodePortRange {
+			values["nodePort"] = map[string]any{
+				"range": strings.ReplaceAll(nodePortRange, "-", ","),
+			}
+		}
+	} else {
+		values["kubeProxyReplacement"] = "false"
+		values["sessionAffinity"] = true
+		valuesCni["chainingMode"] = "portmap"
+	}
+
+	ipamOperator := map[string]any{
+		"clusterPoolIPv4PodCIDRList": cluster.Spec.ClusterNetwork.Pods.GetIPv4CIDRs(),
+	}
+
+	if cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv4 != nil {
+		ipamOperator["clusterPoolIPv4MaskSize"] = *cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv4
+	}
+
+	if cluster.IsDualStack() {
+		values["ipv6"] = map[string]any{"enabled": true}
+		ipamOperator["clusterPoolIPv6PodCIDRList"] = cluster.Spec.ClusterNetwork.Pods.GetIPv6CIDRs()
+		if cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv6 != nil {
+			ipamOperator["clusterPoolIPv6MaskSize"] = *cluster.Spec.ClusterNetwork.NodeCIDRMaskSizeIPv6
+		}
+	}
+
+	values["ipam"] = map[string]any{"operator": ipamOperator}
+
+	// Override images if registry override is configured
+	if overwriteRegistry != "" {
+		values["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"cilium", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesOperator["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"operator", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesRelay["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-relay", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesBackend["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-ui-backend", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesFrontend["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"hubble-ui", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesCertGen["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"certgen", overwriteRegistry)),
+			"useDigest":  false,
+		}
+		valuesEnvoy["image"] = map[string]any{
+			"repository": registry.Must(registry.RewriteImage(ciliumImageRegistry+"cilium-envoy", overwriteRegistry)),
+			"useDigest":  false,
+		}
+	}
+
+	uiSecContext := maps.Clone(podSecurityContext)
+	uiSecContext["enabled"] = true
+
+	values["cni"] = valuesCni
+	values["envoy"] = valuesEnvoy
+	values["operator"] = valuesOperator
+	values["certgen"] = valuesCertGen
+	values["hubble"] = map[string]any{
+		"relay": valuesRelay,
+		"ui": map[string]any{
+			"securityContext": uiSecContext,
+			"frontend":        valuesFrontend,
+			"backend":         valuesBackend,
+		},
+	}
+
+	return values
+}
diff --git a/pkg/controller/seed-controller-manager/cni-application-installation-controller/validation.go b/pkg/controller/seed-controller-manager/cni-application-installation-controller/validation.go
new file mode 100644
index 00000000000..902cae69993
--- /dev/null
+++ b/pkg/controller/seed-controller-manager/cni-application-installation-controller/validation.go
@@ -0,0 +1,144 @@
+/*
+Copyright 2025 The Kubermatic Kubernetes Platform contributors.
+
+Licensed 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 cniapplicationinstallationcontroller
+
+import (
+	"fmt"
+	"strings"
+
+	"k8s.io/apimachinery/pkg/util/validation/field"
+)
+
+// ValidateValuesUpdate validates the update operation on provided Cilium Helm values.
+func ValidateValuesUpdate(newValues, oldValues map[string]any, fieldPath *field.Path) field.ErrorList {
+	allErrs := field.ErrorList{}
+	// Validate immutability of specific top-level value subtrees, managed solely by KKP
+	exclusions := []exclusion{
+		{
+			fullPath:  "cni.chainingMode",
+			pathParts: []string{"cni", "chainingMode"},
+		},
+		{
+			fullPath:  "ipam.operator.clusterPoolIPv4MaskSize",
+			pathParts: []string{"ipam", "operator", "clusterPoolIPv4MaskSize"},
+		},
+		{
+			fullPath:  "ipam.operator.clusterPoolIPv6MaskSize",
+			pathParts: []string{"ipam", "operator", "clusterPoolIPv6MaskSize"},
+		},
+	}
+	allErrs = append(allErrs, validateImmutableValues(newValues, oldValues, fieldPath, []string{
+		"cni",
+		"ipam",
+		"ipv6",
+	}, exclusions)...)
+
+	// Validate that mandatory top-level values are present
+	allErrs = append(allErrs, validateMandatoryValues(newValues, fieldPath, []string{
+		"kubeProxyReplacement", // can be changed if switching KKP proxy mode, but must be present
+	})...)
+
+	// Validate config for strict kubeProxyReplacement (ebpf "proxy mode")
+	if newValues["kubeProxyReplacement"] == "strict" {
+		// Validate mandatory values for kube-proxy-replacement
+		allErrs = append(allErrs, validateMandatoryValues(newValues, fieldPath, []string{
+			"k8sServiceHost",
+			"k8sServicePort",
+		})...)
+	}
+
+	// Validate immutability of operator.securityContext
+	operPath := fieldPath.Child("operator")
+	newOperator, ok := newValues["operator"].(map[string]any)
+	if !ok {
+		allErrs = append(allErrs, field.Invalid(operPath, newValues["operator"], "value missing or incorrect"))
+	}
+	oldOperator, ok := oldValues["operator"].(map[string]any)
+	if !ok {
+		allErrs = append(allErrs, field.Invalid(operPath, oldValues["operator"], "value missing or incorrect"))
+	}
+	allErrs = append(allErrs, validateImmutableValues(newOperator, oldOperator, operPath, []string{
+		"securityContext",
+	}, []exclusion{})...)
+
+	return allErrs
+}
+
+type exclusion struct {
+	fullPath  string
+	pathParts []string
+}
+
+func validateImmutableValues(newValues, oldValues map[string]any, fieldPath *field.Path, immutableValues []string, exclusions []exclusion) field.ErrorList {
+	allErrs := field.ErrorList{}
+	allowedValues := map[string]bool{}
+
+	for _, v := range immutableValues {
+		// Check if the value in `oldValues` is non-empty and missing in `newValues`
+		if _, existsInOld := oldValues[v]; existsInOld {
+			if _, existsInNew := newValues[v]; !existsInNew || newValues[v] == nil || fmt.Sprint(newValues[v]) == "" {
+				allErrs = append(allErrs, field.Invalid(fieldPath.Child(v), newValues[v], "value is immutable"))
+				continue
+			}
+		}
+		for _, exclusion := range exclusions {
+			if strings.HasPrefix(exclusion.fullPath, v) {
+				if excludedKeyExists(newValues, exclusion.pathParts...) {
+					allowedValues[v] = true
+				}
+				if excludedKeyExists(oldValues, exclusion.pathParts...) {
+					allowedValues[v] = true
+				}
+			}
+		}
+		if fmt.Sprint(oldValues[v]) != fmt.Sprint(newValues[v]) && !allowedValues[v] {
+			allErrs = append(allErrs, field.Invalid(fieldPath.Child(v), newValues[v], "value is immutable"))
+		}
+	}
+	return allErrs
+}
+
+func validateMandatoryValues(newValues map[string]any, fieldPath *field.Path, mandatoryValues []string) field.ErrorList {
+	allErrs := field.ErrorList{}
+	for _, v := range mandatoryValues {
+		if _, ok := newValues[v]; !ok {
+			allErrs = append(allErrs, field.NotFound(fieldPath.Child(v), newValues[v]))
+		}
+	}
+	return allErrs
+}
+
+func excludedKeyExists(values map[string]any, keys ...string) bool {
+	if len(keys) == 0 || values == nil {
+		return false
+	}
+	root := keys[0]
+	value, ok := values[root]
+	if !ok {
+		return false
+	}
+
+	if len(keys) == 1 {
+		return true
+	}
+
+	if innerMap, ok := value.(map[string]any); ok {
+		return excludedKeyExists(innerMap, keys[1:]...)
+	}
+
+	return false
+}
diff --git a/pkg/controller/user-cluster-controller-manager/application-installation-controller/controller.go b/pkg/controller/user-cluster-controller-manager/application-installation-controller/controller.go
index 5f95d64c439..ee31f963d8a 100644
--- a/pkg/controller/user-cluster-controller-manager/application-installation-controller/controller.go
+++ b/pkg/controller/user-cluster-controller-manager/application-installation-controller/controller.go
@@ -27,6 +27,7 @@ import (
 	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
 	"k8c.io/kubermatic/sdk/v2/apis/equality"
 	"k8c.io/kubermatic/v2/pkg/applications"
+	applicationtemplates "k8c.io/kubermatic/v2/pkg/applications/providers/template"
 	userclustercontrollermanager "k8c.io/kubermatic/v2/pkg/controller/user-cluster-controller-manager"
 	"k8c.io/kubermatic/v2/pkg/controller/util"
 	kuberneteshelper "k8c.io/kubermatic/v2/pkg/kubernetes"
@@ -382,3 +383,7 @@ func enqueueAppInstallationForAppDef(userClient ctrlruntimeclient.Client) func(c
 		return res
 	}
 }
+
+func handleAddonCleanup(ctx context.Context, applicationName string, seedClusterNamespace string, seedClient ctrlruntimeclient.Client, log *zap.SugaredLogger) error {
+	return applicationtemplates.HandleAddonCleanup(ctx, applicationName, seedClusterNamespace, seedClient, log)
+}
diff --git a/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ce.go b/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ce.go
deleted file mode 100644
index 0353a344933..00000000000
--- a/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ce.go
+++ /dev/null
@@ -1,31 +0,0 @@
-//go:build !ee
-
-/*
-Copyright 2025 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 applicationinstallationcontroller
-
-import (
-	"context"
-
-	"go.uber.org/zap"
-
-	ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-func handleAddonCleanup(ctx context.Context, applicationName string, seedClusterNamespace string, seedClient ctrlruntimeclient.Client, log *zap.SugaredLogger) error {
-	return nil
-}
diff --git a/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ee.go b/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ee.go
deleted file mode 100644
index 9b4f7baa461..00000000000
--- a/pkg/controller/user-cluster-controller-manager/application-installation-controller/wrappers_ee.go
+++ /dev/null
@@ -1,33 +0,0 @@
-//go:build ee
-
-/*
-Copyright 2025 The Kubermatic Kubernetes Platform contributors.
-
-Licensed 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 applicationinstallationcontroller
-
-import (
-	"context"
-
-	"go.uber.org/zap"
-
-	eeutil "k8c.io/kubermatic/v2/pkg/ee/applications"
-
-	ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-func handleAddonCleanup(ctx context.Context, applicationName string, seedClusterNamespace string, seedClient ctrlruntimeclient.Client, log *zap.SugaredLogger) error {
-	return eeutil.HandleAddonCleanup(ctx, applicationName, seedClusterNamespace, seedClient, log)
-}
diff --git a/pkg/defaulting/configuration.go b/pkg/defaulting/configuration.go
index c5bde025ec2..55fcf26e210 100755
--- a/pkg/defaulting/configuration.go
+++ b/pkg/defaulting/configuration.go
@@ -30,7 +30,6 @@ import (
 	"k8c.io/kubermatic/sdk/v2/semver"
 	"k8c.io/kubermatic/v2/pkg/features"
 	"k8c.io/kubermatic/v2/pkg/resources"
-	"k8c.io/kubermatic/v2/pkg/resources/registry"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -682,22 +681,6 @@ func defaultExternalClusterVersioning(settings *kubermaticv1.KubermaticVersionin
 	return nil
 }
 
-// SystemApplicationsHelmRepositoryFromConfig returns the default Helm repository for the given
-// system applications configuration.
-// if the configuration contains a HelmRepository, it will be used as the helm repository.
-// otherwise the default system applications helm repository will be used.
-func SystemApplicationsHelmRepositoryFromConfig(conf *kubermaticv1.SystemApplicationsConfiguration) string {
-	if conf == nil {
-		return registry.ToOCIURL(DefaultSystemApplicationsHelmRepository)
-	}
-
-	if conf.HelmRepository != "" {
-		return registry.ToOCIURL(conf.HelmRepository)
-	}
-
-	return registry.ToOCIURL(DefaultSystemApplicationsHelmRepository)
-}
-
 const DefaultBackupStoreContainer = `
 name: store-container
 image: d3fk/s3cmd@sha256:fb4c4dcf3b842c3d0ead58bda26d05d045b77546e11ac2143d90abca02cbe823
diff --git a/pkg/install/images/application_definitions.go b/pkg/install/images/application_definitions.go
index 9e017048978..ba277e3eb6c 100644
--- a/pkg/install/images/application_definitions.go
+++ b/pkg/install/images/application_definitions.go
@@ -28,11 +28,13 @@ import (
 
 	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
 	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
+	"k8c.io/kubermatic/sdk/v2/semver"
+	"k8c.io/kubermatic/v2/pkg/applicationdefinitions"
 	"k8c.io/kubermatic/v2/pkg/applications/providers"
-	"k8c.io/kubermatic/v2/pkg/cni/cilium"
+	"k8c.io/kubermatic/v2/pkg/applications/providers/template"
+	"k8c.io/kubermatic/v2/pkg/defaulting"
 	"k8c.io/kubermatic/v2/pkg/install/helm"
-	"k8c.io/kubermatic/v2/pkg/log"
-	"k8c.io/kubermatic/v2/pkg/resources/reconciling"
+	kubermaticlog "k8c.io/kubermatic/v2/pkg/log"
 
 	"sigs.k8s.io/yaml"
 )
@@ -70,12 +72,24 @@ func SystemAppsHelmCharts(
 	helmTimeout time.Duration,
 	registryPrefix string,
 ) iter.Seq2[*SystemAppsHelmChart, error] {
-	appDefReconcilers := []reconciling.NamedApplicationDefinitionReconcilerFactory{
-		cilium.ApplicationDefinitionReconciler(config),
+	// If system applications are disabled we don't need to do anything.
+	if config.Spec.SystemApplications.Disable {
+		logger.Debug("System applications are disabled, skipping deployment of system application definitions.")
+		return nil
 	}
 
+	log := kubermaticlog.NewDefault().Sugar()
+	sysAppDefReconcilers, err := applicationdefinitions.SystemApplicationDefinitionReconcilerFactories(log, config)
+	if err != nil {
+		return func(yield func(*SystemAppsHelmChart, error) bool) {
+			yield(nil, fmt.Errorf("failed to get system application definition reconciler factories: %w", err))
+		}
+	}
+
+	defaultKubernetesVersion := defaulting.DefaultKubernetesVersioning.Default
+
 	return func(yield func(*SystemAppsHelmChart, error) bool) {
-		for _, createFunc := range appDefReconcilers {
+		for _, createFunc := range sysAppDefReconcilers {
 			appName, creator := createFunc()
 			appDef, err := creator(&appskubermaticv1.ApplicationDefinition{})
 			if err != nil {
@@ -112,6 +126,13 @@ func SystemAppsHelmCharts(
 				return
 			}
 
+			// Render the ApplicationDefinition values to inject template variables
+			values, err = renderApplicationDefinitionValues(values, defaultKubernetesVersion)
+			if err != nil {
+				yield(nil, fmt.Errorf("failed to render ApplicationDefinition values: %w", err))
+				return
+			}
+
 			if appName == kubermaticv1.CNIPluginTypeCilium.String() {
 				// For Cilium, we'll use the default values from the Helm chart but will mutate the values to ensure that additional images are also mirrored.
 				defaultValues, err := appDef.Spec.GetParsedDefaultValues()
@@ -181,7 +202,7 @@ func downloadAppSourceChart(appSource *appskubermaticv1.ApplicationSource, direc
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	sp, err := providers.NewSourceProvider(ctx, log.NewDefault().Sugar(), nil, "", directory, appSource, "")
+	sp, err := providers.NewSourceProvider(ctx, kubermaticlog.NewDefault().Sugar(), nil, "", directory, appSource, "")
 	if err != nil {
 		return "", fmt.Errorf("failed to create app source provider: %w", err)
 	}
@@ -193,3 +214,32 @@ func downloadAppSourceChart(appSource *appskubermaticv1.ApplicationSource, direc
 
 	return chartPath, nil
 }
+
+func renderApplicationDefinitionValues(values []byte, clusterVersion *semver.Semver) ([]byte, error) {
+	// Convert []byte to map[string]interface{}
+	var valuesMap map[string]interface{}
+	if values != nil {
+		if err := yaml.Unmarshal(values, &valuesMap); err != nil {
+			return nil, fmt.Errorf("failed to unmarshal values: %w", err)
+		}
+	}
+
+	clusterAutoscalerVersion, err := template.GetAutoscalerImageTag(fmt.Sprintf("%d.%d", clusterVersion.Semver().Major(), clusterVersion.Semver().Minor()))
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse autoscaler version with error: %w", err)
+	}
+
+	templateData := template.TemplateData{
+		Cluster: template.ClusterData{
+			Version:           fmt.Sprintf("%d.%d.%d", clusterVersion.Semver().Major(), clusterVersion.Semver().Minor(), clusterVersion.Semver().Patch()),
+			MajorMinorVersion: fmt.Sprintf("%d.%d", clusterVersion.Semver().Major(), clusterVersion.Semver().Minor()),
+			AutoscalerVersion: clusterAutoscalerVersion,
+		},
+	}
+	renderedValues, err := template.RenderValueTemplate(valuesMap, &templateData)
+	if err != nil {
+		return nil, fmt.Errorf("failed to render ApplicationDefinition values: %w", err)
+	}
+
+	return yaml.Marshal(renderedValues)
+}
diff --git a/pkg/test/e2e/appdefinitions/application_definitions_test.go b/pkg/test/e2e/appdefinitions/application_definitions_test.go
index db5d78bc879..f73395d527d 100644
--- a/pkg/test/e2e/appdefinitions/application_definitions_test.go
+++ b/pkg/test/e2e/appdefinitions/application_definitions_test.go
@@ -45,7 +45,7 @@ var (
 
 const (
 	projectName                    = "app-definitions-test-project"
-	countOfInstalledAppDefinitions = 4
+	countOfInstalledAppDefinitions = 5
 )
 
 func init() {
@@ -88,6 +88,6 @@ func TestClusters(t *testing.T) {
 	}
 
 	if len(appDefsList.Items) != countOfInstalledAppDefinitions {
-		t.Fatalf("the number of the applications definitions in the seed cluster is not correct: %v", err)
+		t.Fatalf("the number of the applications definitions in the seed cluster is not correct. Expected: %d, Got: %d", countOfInstalledAppDefinitions, len(appDefsList.Items))
 	}
 }
diff --git a/pkg/validation/application_installation.go b/pkg/validation/application_installation.go
index 612ec3eee9e..ae26bf4d1aa 100644
--- a/pkg/validation/application_installation.go
+++ b/pkg/validation/application_installation.go
@@ -22,7 +22,7 @@ import (
 
 	appskubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/apps.kubermatic/v1"
 	kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1"
-	"k8c.io/kubermatic/v2/pkg/cni/cilium"
+	cniapplicationinstallationcontroller "k8c.io/kubermatic/v2/pkg/controller/seed-controller-manager/cni-application-installation-controller"
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
@@ -170,7 +170,7 @@ func ValidateKKPManagedApplicationInstallationUpdate(newAI, oldAI appskubermatic
 
 		if newAI.Name == kubermaticv1.CNIPluginTypeCilium.String() {
 			// Validate Cilium values update
-			allErrs = append(allErrs, cilium.ValidateValuesUpdate(newValues, oldValues, valuesPath)...)
+			allErrs = append(allErrs, cniapplicationinstallationcontroller.ValidateValuesUpdate(newValues, oldValues, valuesPath)...)
 		}
 	}
 


Reviews:
user: login: ahmedwaleedmalik
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T11:08:04Z
user: login: ahmedwaleedmalik
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T11:49:05Z
user: login: kron4eg
body: /approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T14:39:59Z
user: login: buraksekili
body: /approve
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:44:50Z

Review comments:
diff_hunk: @@ -150,8 +149,6 @@ func updateApplicationDefinition(appDef *appskubermaticv1.ApplicationDefinition,
 	for i := range appDef.Spec.Versions {
 		if sysAppConfig.HelmRepository != "" {
 			appDef.Spec.Versions[i].Template.Source.Helm.URL = registry.ToOCIURL(sysAppConfig.HelmRepository)
-		} else {
-			appDef.Spec.Versions[i].Template.Source.Helm.URL = defaulting.SystemApplicationsHelmRepositoryFromConfig(&sysAppConfig)
path: pkg/applicationdefinitions/application_catalog.go
user: login: ahmedwaleedmalik
body: Note for reviewer:

We are now using YAMLs to store these ApplicationDefinitions and use explicit URLs there. So there is no need for this defaulting through code.
created_at: 2025-05-21T11:08:03Z
author_association: MEMBER
path: pkg/applications/providers/template/wrappers_ee.go
user: login: ahmedwaleedmalik
body: Note for reviewer:
This was never meant to be an EE only feature and the implementation is completely wrong. I've refactored the code to make sure that this works for both CE and EE.

For details: https://github.com/kubermatic/kubermatic/issues/13913#issuecomment-2897641742
created_at: 2025-05-21T11:49:05Z
author_association: MEMBER

PR comments:
user: login: ahmedwaleedmalik
created_at: 2025-05-21T11:05:35Z
body: /assign
/cc @kron4eg @buraksekili 
author_association: MEMBER
user: login: ahmedwaleedmalik
created_at: 2025-05-21T11:49:28Z
body: /milestone KKP 2.28
author_association: MEMBER
user: login: ahmedwaleedmalik
created_at: 2025-05-21T13:53:21Z
body: /approve

(for sig-app-management review)
author_association: MEMBER
user: login: ahmedwaleedmalik
created_at: 2025-05-21T14:29:57Z
body: /cc @cnvergence 
author_association: MEMBER
user: login: kubermatic-bot
created_at: 2025-05-21T14:40:04Z
body: LGTM label has been added.  <details>Git tree hash: 55923153c09475cb8d5fe46aba0bd6225bde09a0</details>
author_association: CONTRIBUTOR
user: login: kubermatic-bot
created_at: 2025-05-21T14:44:58Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/kubermatic/pull/14584#issuecomment-2898056357" title="Approved">ahmedwaleedmalik</a>*, *<a href="https://github.com/kubermatic/kubermatic/pull/14584#pullrequestreview-2858025981" title="Approved">buraksekili</a>*, *<a href="https://github.com/kubermatic/kubermatic/pull/14584#pullrequestreview-2858008810" title="Approved">kron4eg</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubermatic%2Fkubermatic).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubermatic/kubermatic/blob/main/OWNERS)~~ [ahmedwaleedmalik,buraksekili,kron4eg]
- ~~[cmd/kubermatic-installer/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/cmd/kubermatic-installer/OWNERS)~~ [ahmedwaleedmalik,kron4eg]
- ~~[pkg/applications/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/applications/OWNERS)~~ [ahmedwaleedmalik]
- ~~[pkg/cni/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/cni/OWNERS)~~ [buraksekili]
- ~~[pkg/controller/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/controller/OWNERS)~~ [ahmedwaleedmalik,kron4eg]
- ~~[pkg/controller/seed-controller-manager/cni-application-installation-controller/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/controller/seed-controller-manager/cni-application-installation-controller/OWNERS)~~ [buraksekili]
- ~~[pkg/controller/user-cluster-controller-manager/application-installation-controller/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/controller/user-cluster-controller-manager/application-installation-controller/OWNERS)~~ [ahmedwaleedmalik]
- ~~[pkg/install/OWNERS](https://github.com/kubermatic/kubermatic/blob/main/pkg/install/OWNERS)~~ [ahmedwaleedmalik,kron4eg]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR