From c3bcd0870b47598771b9b86e13040423c9c7b0e0 Mon Sep 17 00:00:00 2001 From: yaroslavborbat Date: Tue, 10 Dec 2024 20:24:59 +0300 Subject: [PATCH 1/3] add Signed-off-by: yaroslavborbat --- .../cmd/virtualization-controller/main.go | 9 ++ .../pkg/config/load_sa.go | 128 ++++++++++++++++++ .../pkg/controller/webhook/protect.go | 46 +++++++ .../pkg/controller/webhook/webhook.go | 24 ++++ .../virtualization-controller/_helpers.tpl | 28 ++++ .../validation-webhook.yaml | 25 ++++ 6 files changed, 260 insertions(+) create mode 100644 images/virtualization-artifact/pkg/config/load_sa.go create mode 100644 images/virtualization-artifact/pkg/controller/webhook/protect.go create mode 100644 images/virtualization-artifact/pkg/controller/webhook/webhook.go diff --git a/images/virtualization-artifact/cmd/virtualization-controller/main.go b/images/virtualization-artifact/cmd/virtualization-controller/main.go index 5606b2398..8ecd22058 100644 --- a/images/virtualization-artifact/cmd/virtualization-controller/main.go +++ b/images/virtualization-artifact/cmd/virtualization-controller/main.go @@ -37,6 +37,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "github.com/deckhouse/deckhouse/pkg/log" + appconfig "github.com/deckhouse/virtualization-controller/pkg/config" "github.com/deckhouse/virtualization-controller/pkg/controller/cvi" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" @@ -51,6 +52,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/vmop" "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore" "github.com/deckhouse/virtualization-controller/pkg/controller/vmsnapshot" + "github.com/deckhouse/virtualization-controller/pkg/controller/webhook" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/client/kubeclient" virtv2alpha1 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -139,6 +141,11 @@ func main() { viStorageClassSettings := appconfig.LoadVirtualImageStorageClassSettings() vdStorageClassSettings := appconfig.LoadVirtualDiskStorageClassSettings() + serviceAccounts, err := appconfig.LoadServiceAccounts() + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } // Get a config to talk to the apiserver cfg, err := config.GetConfig() if err != nil { @@ -304,6 +311,8 @@ func main() { os.Exit(1) } + webhook.SetupHTTPHooks(mgr, serviceAccounts) + log.Info("Starting the Manager.") // Start the Manager diff --git a/images/virtualization-artifact/pkg/config/load_sa.go b/images/virtualization-artifact/pkg/config/load_sa.go new file mode 100644 index 000000000..b615ac5ac --- /dev/null +++ b/images/virtualization-artifact/pkg/config/load_sa.go @@ -0,0 +1,128 @@ +package config + +import ( + "fmt" + "os" +) + +const ( + SACDIApiserver = "SA_CDI_APISERVER" + SACDICronjob = "SA_CDI_CRONJOB" + SACDIOperator = "SA_CDI_OPERATOR" + SACDISa = "SA_CDI_SA" + SACDIUploadProxy = "SA_CDI_UPLOAD_PROXY" + SAKubevirtApiserver = "SA_KUBEVIRT_APISERVER" + SAKubevirtController = "SA_KUBEVIRT_CONTROLLER" + SAKubevirtExportProxy = "SA_KUBEVIRT_EXPORT_PROXY" + SAKubevirtVirtHandler = "SA_KUBEVIRT_VIRT_HANDLER" + SAKubevirtOperator = "SA_KUBEVIRT_OPERATOR" + SAVirtualizationController = "SA_VIRTUALIZATION_CONTROLLER" + SAVirtualizationAPI = "SA_VIRTUALIZATION_API" + SAVirtualizationPpeDeleteHook = "SA_VIRTUALIZATION_PPE_DELETE_HOOK" + SAVmRouteForge = "SA_VM_ROUTE_FORGE" +) + +type ServiceAccounts struct { + SACDIApiserver string + SACDICronjob string + SACDIOperator string + SACDISa string + SACDIUploadProxy string + SAKubevirtApiserver string + SAKubevirtController string + SAKubevirtExportProxy string + SAKubevirtVirtHandler string + SAKubevirtOperator string + SAVirtualizationController string + SAVirtualizationAPI string + SAVirtualizationPpeDeleteHook string + SAVmRouteForge string +} + +func (s *ServiceAccounts) ToList() []string { + return []string{ + s.SACDIApiserver, + s.SACDICronjob, + s.SACDIOperator, + s.SACDISa, + s.SACDIUploadProxy, + s.SAKubevirtApiserver, + s.SAKubevirtController, + s.SAKubevirtExportProxy, + s.SAKubevirtVirtHandler, + s.SAKubevirtOperator, + s.SAVirtualizationController, + s.SAVirtualizationAPI, + s.SAVirtualizationPpeDeleteHook, + s.SAVmRouteForge, + } +} + +func (s *ServiceAccounts) Validate() error { + if s.SACDIApiserver == "" { + return fmt.Errorf("%q is required", SACDIApiserver) + } + if s.SACDICronjob == "" { + return fmt.Errorf("%q is required", SACDICronjob) + } + if s.SACDIOperator == "" { + return fmt.Errorf("%q is required", SACDIOperator) + } + if s.SACDISa == "" { + return fmt.Errorf("%q is required", SACDISa) + } + if s.SACDIUploadProxy == "" { + return fmt.Errorf("%q is required", SACDIUploadProxy) + } + if s.SAKubevirtApiserver == "" { + return fmt.Errorf("%q is required", SAKubevirtApiserver) + } + if s.SAKubevirtController == "" { + return fmt.Errorf("%q is required", SAKubevirtController) + } + if s.SAKubevirtExportProxy == "" { + return fmt.Errorf("%q is required", SAKubevirtExportProxy) + } + if s.SAKubevirtVirtHandler == "" { + return fmt.Errorf("%q is required", SAKubevirtVirtHandler) + } + if s.SAKubevirtOperator == "" { + return fmt.Errorf("%q is required", SAKubevirtOperator) + } + if s.SAVirtualizationController == "" { + return fmt.Errorf("%q is required", SAVirtualizationController) + } + if s.SAVirtualizationAPI == "" { + return fmt.Errorf("%q is required", SAVirtualizationAPI) + } + if s.SAVirtualizationPpeDeleteHook == "" { + return fmt.Errorf("%q is required", SAVirtualizationPpeDeleteHook) + } + if s.SAVmRouteForge == "" { + return fmt.Errorf("%q is required", SAVmRouteForge) + } + return nil +} + +func LoadServiceAccounts() (ServiceAccounts, error) { + serviceAccounts := ServiceAccounts{ + SACDIApiserver: os.Getenv(SACDIApiserver), + SACDICronjob: os.Getenv(SACDICronjob), + SACDIOperator: os.Getenv(SACDIOperator), + SACDISa: os.Getenv(SACDISa), + SACDIUploadProxy: os.Getenv(SACDIUploadProxy), + SAKubevirtApiserver: os.Getenv(SAKubevirtApiserver), + SAKubevirtController: os.Getenv(SAKubevirtController), + SAKubevirtExportProxy: os.Getenv(SAKubevirtExportProxy), + SAKubevirtVirtHandler: os.Getenv(SAKubevirtVirtHandler), + SAKubevirtOperator: os.Getenv(SAKubevirtOperator), + SAVirtualizationController: os.Getenv(SAVirtualizationController), + SAVirtualizationAPI: os.Getenv(SAVirtualizationAPI), + SAVirtualizationPpeDeleteHook: os.Getenv(SAVirtualizationPpeDeleteHook), + SAVmRouteForge: os.Getenv(SAVmRouteForge), + } + if err := serviceAccounts.Validate(); err != nil { + return ServiceAccounts{}, err + } + return serviceAccounts, nil +} diff --git a/images/virtualization-artifact/pkg/controller/webhook/protect.go b/images/virtualization-artifact/pkg/controller/webhook/protect.go new file mode 100644 index 000000000..6970fcac8 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/webhook/protect.go @@ -0,0 +1,46 @@ +package webhook + +import ( + "context" + "slices" + + admissionv1 "k8s.io/api/admission/v1" + virtcore "kubevirt.io/api/core" + cdicore "kubevirt.io/containerized-data-importer-api/pkg/apis/core" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +const ProtectResourcesPath = "/protect-resources" + +var defaultProtectGroups = []string{ + virtcore.GroupName, + cdicore.GroupName, +} + +func newProtectHook(allowSA []string, groups []string) *protectHook { + return &protectHook{ + allowSA: allowSA, + groups: groups, + operations: []admissionv1.Operation{ + admissionv1.Create, + admissionv1.Update, + admissionv1.Delete, + }, + } +} + +type protectHook struct { + allowSA []string + groups []string + operations []admissionv1.Operation +} + +func (p protectHook) Handle(_ context.Context, req admission.Request) admission.Response { + if slices.Contains(p.groups, req.Resource.Group) && + !slices.Contains(p.allowSA, req.UserInfo.Username) && + slices.Contains(p.operations, req.Operation) { + return admission.Denied("Operation forbidden for this service account.") + } + + return admission.Allowed("") +} diff --git a/images/virtualization-artifact/pkg/controller/webhook/webhook.go b/images/virtualization-artifact/pkg/controller/webhook/webhook.go new file mode 100644 index 000000000..e272170fe --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/webhook/webhook.go @@ -0,0 +1,24 @@ +package webhook + +import ( + "net/http" + + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + appconfig "github.com/deckhouse/virtualization-controller/pkg/config" +) + +func SetupHTTPHooks(mgr manager.Manager, serviceAccounts appconfig.ServiceAccounts) { + saNames := serviceAccounts.ToList() + + var hooks = map[string]http.Handler{ + ProtectResourcesPath: &webhook.Admission{Handler: newProtectHook(saNames, defaultProtectGroups)}, + } + + ws := mgr.GetWebhookServer() + + for path, hook := range hooks { + ws.Register(path, hook) + } +} diff --git a/templates/virtualization-controller/_helpers.tpl b/templates/virtualization-controller/_helpers.tpl index 7ec2b52e3..2689392e0 100644 --- a/templates/virtualization-controller/_helpers.tpl +++ b/templates/virtualization-controller/_helpers.tpl @@ -74,4 +74,32 @@ - name: PPROF_BIND_ADDRESS value: ":8081" {{- end }} +- name: "SA_CDI_APISERVER" + value: "cdi-apiserver" +- name: "SA_CDI_CRONJOB" + value: "cdi-cronjob" +- name: "SA_CDI_OPERATOR" + value: "cdi-operator" +- name: "SA_CDI_SA" + value: "cdi-sa" +- name: "SA_CDI_UPLOAD_PROXY" + value: "cdi-uploadproxy" +- name: "SA_KUBEVIRT_APISERVER" + value: "kubevirt-internal-virtualization-apiserver" +- name: "SA_KUBEVIRT_CONTROLLER" + value: "kubevirt-internal-virtualization-controller" +- name: "SA_KUBEVIRT_EXPORT_PROXY" + value: "kubevirt-internal-virtualization-exportproxy" +- name: "SA_KUBEVIRT_VIRT_HANDLER" + value: "kubevirt-internal-virtualization-handler" +- name: "SA_KUBEVIRT_OPERATOR" + value: "kubevirt-operator" +- name: "SA_VIRTUALIZATION_CONTROLLER" + value: "virtualization-controller" +- name: "SA_VIRTUALIZATION_API" + value: "virtualization-api" +- name: "SA_VIRTUALIZATION_PPE_DELETE_HOOK" + value: "virtualization-pre-delete-hook" +- name: "SA_VM_ROUTE_FORGE" + value: "vm-route-forge" {{- end }} diff --git a/templates/virtualization-controller/validation-webhook.yaml b/templates/virtualization-controller/validation-webhook.yaml index 3eabeba17..735f0d43b 100644 --- a/templates/virtualization-controller/validation-webhook.yaml +++ b/templates/virtualization-controller/validation-webhook.yaml @@ -207,3 +207,28 @@ webhooks: {{ .Values.virtualization.internal.controller.cert.ca }} admissionReviewVersions: ["v1"] sideEffects: None + - name: "protect-resources.virtualization-controller.validate.d8-virtualization" + rules: + - apiGroups: + - "cdi.internal.virtualization.deckhouse.io" + - "clone.internal.virtualization.deckhouse.io" + - "export.internal.virtualization.deckhouse.io" + - "forklift.cdi.internal.virtualization.deckhouse.io" + - "instancetype.internal.virtualization.deckhouse.io" + - "internal.virtualization.deckhouse.io" + - "pool.internal.virtualization.deckhouse.io" + - "snapshot.internal.virtualization.deckhouse.io" + apiVersions: ["*"] + operations: ["CREATE", "UPDATE", "DELETE"] + resources: ["*"] + scope: "*" + clientConfig: + service: + namespace: d8-{{ .Chart.Name }} + name: virtualization-controller + path: "/protect-resources" + port: 443 + caBundle: | + {{ .Values.virtualization.internal.controller.cert.ca }} + admissionReviewVersions: ["v1"] + sideEffects: None From e3d3f5ba3ab018504248d82ee9cce56580a33748 Mon Sep 17 00:00:00 2001 From: yaroslavborbat Date: Wed, 11 Dec 2024 11:21:35 +0300 Subject: [PATCH 2/3] fix Signed-off-by: yaroslavborbat --- .../pkg/controller/webhook/protect.go | 16 ++++++++++++++++ .../validation-webhook.yaml | 1 + 2 files changed, 17 insertions(+) diff --git a/images/virtualization-artifact/pkg/controller/webhook/protect.go b/images/virtualization-artifact/pkg/controller/webhook/protect.go index 6970fcac8..de356e8a4 100644 --- a/images/virtualization-artifact/pkg/controller/webhook/protect.go +++ b/images/virtualization-artifact/pkg/controller/webhook/protect.go @@ -5,8 +5,16 @@ import ( "slices" admissionv1 "k8s.io/api/admission/v1" + "kubevirt.io/api/clone" virtcore "kubevirt.io/api/core" + "kubevirt.io/api/export" + "kubevirt.io/api/instancetype" + "kubevirt.io/api/migrations" + "kubevirt.io/api/pool" + "kubevirt.io/api/snapshot" cdicore "kubevirt.io/containerized-data-importer-api/pkg/apis/core" + "kubevirt.io/containerized-data-importer-api/pkg/apis/forklift" + "kubevirt.io/containerized-data-importer-api/pkg/apis/upload" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -14,7 +22,15 @@ const ProtectResourcesPath = "/protect-resources" var defaultProtectGroups = []string{ virtcore.GroupName, + clone.GroupName, + export.GroupName, + instancetype.GroupName, + migrations.GroupName, + pool.GroupName, + snapshot.GroupName, cdicore.GroupName, + upload.GroupName, + forklift.GroupName, } func newProtectHook(allowSA []string, groups []string) *protectHook { diff --git a/templates/virtualization-controller/validation-webhook.yaml b/templates/virtualization-controller/validation-webhook.yaml index 735f0d43b..4b4630010 100644 --- a/templates/virtualization-controller/validation-webhook.yaml +++ b/templates/virtualization-controller/validation-webhook.yaml @@ -218,6 +218,7 @@ webhooks: - "internal.virtualization.deckhouse.io" - "pool.internal.virtualization.deckhouse.io" - "snapshot.internal.virtualization.deckhouse.io" + - "migrations.internal.virtualization.deckhouse.io" apiVersions: ["*"] operations: ["CREATE", "UPDATE", "DELETE"] resources: ["*"] From 2e21f5a944877935b1dad90a334c72365ebd3e64 Mon Sep 17 00:00:00 2001 From: yaroslavborbat Date: Wed, 11 Dec 2024 11:23:40 +0300 Subject: [PATCH 3/3] add license Signed-off-by: yaroslavborbat --- .../pkg/config/load_sa.go | 16 ++++++++++++++++ .../pkg/controller/webhook/protect.go | 16 ++++++++++++++++ .../pkg/controller/webhook/webhook.go | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/images/virtualization-artifact/pkg/config/load_sa.go b/images/virtualization-artifact/pkg/config/load_sa.go index b615ac5ac..f6c3f090e 100644 --- a/images/virtualization-artifact/pkg/config/load_sa.go +++ b/images/virtualization-artifact/pkg/config/load_sa.go @@ -1,3 +1,19 @@ +/* +Copyright 2024 Flant JSC + +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 config import ( diff --git a/images/virtualization-artifact/pkg/controller/webhook/protect.go b/images/virtualization-artifact/pkg/controller/webhook/protect.go index de356e8a4..a0fab3025 100644 --- a/images/virtualization-artifact/pkg/controller/webhook/protect.go +++ b/images/virtualization-artifact/pkg/controller/webhook/protect.go @@ -1,3 +1,19 @@ +/* +Copyright 2024 Flant JSC + +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 webhook import ( diff --git a/images/virtualization-artifact/pkg/controller/webhook/webhook.go b/images/virtualization-artifact/pkg/controller/webhook/webhook.go index e272170fe..0303b0197 100644 --- a/images/virtualization-artifact/pkg/controller/webhook/webhook.go +++ b/images/virtualization-artifact/pkg/controller/webhook/webhook.go @@ -1,3 +1,19 @@ +/* +Copyright 2024 Flant JSC + +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 webhook import (