VYPR
Moderate severityNVD Advisory· Published May 27, 2021· Updated Aug 4, 2024

CVE-2020-1701

CVE-2020-1701

Description

A flaw was found in the KubeVirt main virt-handler versions before 0.26.0 regarding the access permissions of virt-handler. An attacker with access to create VMs could attach any secret within their namespace, allowing them to read the contents of that secret.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

KubeVirt virt-handler before 0.26.0 had overly broad secret access, allowing attackers with VM creation privileges to read any secret in the namespace.

Vulnerability

A flaw in KubeVirt's virt-handler component, versions before 0.26.0, granted the handler excessive Kubernetes API permissions to GET secrets across all namespaces. This was required because virt-handler directly fetched cloud-init user data from secrets via the API to inject into virtual machines. The vulnerability is rooted in the design where virt-handler needed to resolve secret references for CloudInitNoCloud volumes, which it did by calling the Kubernetes API rather than relying on volume mounts [1][2].

Exploitation

An attacker with the ability to create virtual machines (VMs) within a namespace can exploit this flaw. By attaching any secret in the same namespace to a VM (e.g., via a CloudInitNoCloud volume with a UserDataSecretRef), the attacker triggers virt-handler to read that secret's contents. No additional authentication or network position is required beyond the ability to create VMs [4].

Impact

Successful exploitation allows the attacker to read the full contents of any secret in the namespace, leading to unauthorized disclosure of sensitive data such as passwords, API tokens, SSH keys, or other credentials. This compromises the confidentiality of secrets managed within the Kubernetes namespace [2].

Mitigation

The vulnerability is fixed in KubeVirt version 0.26.0. The fix changes the approach: virt-controller now mounts secrets as volumes to the virt-launcher pod, and virt-handler reads the secret data from the mounted filesystem instead of via the API. This removes the need for virt-handler to have GET secrets permission across the cluster. Users should upgrade to 0.26.0 or later. No workaround is available other than restricting VM creation permissions or upgrading [1][4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kubevirt.io/kubevirtGo
< 0.26.00.26.0

Affected products

2

Patches

1
9efa8d7388d4

Merge pull request #3001 from danielBelenky/cloud-init-secrets

https://github.com/kubevirt/kubevirtkubevirt-botFeb 3, 2020via ghsa
13 files changed · +312 366
  • manifests/generated/operator-csv.yaml.in+1 1 modified
    @@ -503,7 +503,6 @@ spec:
             - apiGroups:
               - ""
               resources:
    -          - secrets
               - persistentvolumeclaims
               verbs:
               - get
    @@ -542,6 +541,7 @@ spec:
               - secrets
               verbs:
               - create
    +          - get
             - apiGroups:
               - subresources.kubevirt.io
               resources:
    
  • manifests/generated/rbac-kubevirt.authorization.k8s.yaml.in+1 1 modified
    @@ -311,7 +311,6 @@ rules:
     - apiGroups:
       - ""
       resources:
    -  - secrets
       - persistentvolumeclaims
       verbs:
       - get
    @@ -374,6 +373,7 @@ rules:
       - secrets
       verbs:
       - create
    +  - get
     ---
     apiVersion: rbac.authorization.k8s.io/v1
     kind: RoleBinding
    
  • manifests/generated/rbac-operator.authorization.k8s.yaml.in+1 1 modified
    @@ -358,7 +358,6 @@ rules:
     - apiGroups:
       - ""
       resources:
    -  - secrets
       - persistentvolumeclaims
       verbs:
       - get
    @@ -397,6 +396,7 @@ rules:
       - secrets
       verbs:
       - create
    +  - get
     - apiGroups:
       - subresources.kubevirt.io
       resources:
    
  • pkg/cloud-init/BUILD.bazel+0 7 modified
    @@ -9,11 +9,8 @@ go_library(
             "//pkg/ephemeral-disk-utils:go_default_library",
             "//pkg/util/net/dns:go_default_library",
             "//staging/src/kubevirt.io/client-go/api/v1:go_default_library",
    -        "//staging/src/kubevirt.io/client-go/kubecli:go_default_library",
             "//staging/src/kubevirt.io/client-go/log:go_default_library",
             "//staging/src/kubevirt.io/client-go/precond:go_default_library",
    -        "//vendor/k8s.io/api/core/v1:go_default_library",
    -        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
         ],
     )
     
    @@ -27,14 +24,10 @@ go_test(
         deps = [
             "//pkg/ephemeral-disk-utils:go_default_library",
             "//staging/src/kubevirt.io/client-go/api/v1:go_default_library",
    -        "//staging/src/kubevirt.io/client-go/kubecli:go_default_library",
             "//staging/src/kubevirt.io/client-go/log:go_default_library",
             "//staging/src/kubevirt.io/client-go/precond:go_default_library",
    -        "//vendor/github.com/golang/mock/gomock:go_default_library",
             "//vendor/github.com/onsi/ginkgo:go_default_library",
             "//vendor/github.com/onsi/gomega:go_default_library",
             "//vendor/k8s.io/api/core/v1:go_default_library",
    -        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
    -        "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
         ],
     )
    
  • pkg/cloud-init/cloud-init.go+97 92 modified
    @@ -25,13 +25,10 @@ import (
     	"io/ioutil"
     	"os"
     	"os/exec"
    +	"path/filepath"
     	"time"
     
    -	corev1 "k8s.io/api/core/v1"
    -	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    -
     	v1 "kubevirt.io/client-go/api/v1"
    -	"kubevirt.io/client-go/kubecli"
     	"kubevirt.io/client-go/log"
     	"kubevirt.io/client-go/precond"
     	diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils"
    @@ -72,7 +69,7 @@ func IsValidCloudInitData(cloudInitData *CloudInitData) bool {
     
     // ReadCloudInitVolumeDataSource scans the given VMI for CloudInit volumes and
     // reads their content into a CloudInitData struct. Does not resolve secret refs.
    -// To ensure that secrets are read correctly, call InjectCloudInitSecrets beforehand.
    +// To ensure that secrets are read correctly, call ResolveNoCloudSecrets beforehand.
     func ReadCloudInitVolumeDataSource(vmi *v1.VirtualMachineInstance) (cloudInitData *CloudInitData, err error) {
     	precond.MustNotBeNil(vmi)
     	hostname := dns.SanitizeHostname(vmi)
    @@ -92,6 +89,101 @@ func ReadCloudInitVolumeDataSource(vmi *v1.VirtualMachineInstance) (cloudInitDat
     	return nil, nil
     }
     
    +// ResolveNoCloudSecrets is looking for CloudInitNoCloud volumes with UserDataSecretRef
    +// requests. It reads the `userdata` secret the corresponds to the given CloudInitNoCloud
    +// volume and sets the UserData field on that volume.
    +//
    +// Note: when using this function, make sure that your code can access the secret volumes.
    +func ResolveNoCloudSecrets(vmi *v1.VirtualMachineInstance, secretSourceDir string) error {
    +	volume := findCloudInitNoCloudSecretVolume(vmi.Spec.Volumes)
    +	if volume == nil {
    +		return nil
    +	}
    +
    +	baseDir := filepath.Join(secretSourceDir, volume.Name)
    +	userData, userDataError := readFileFromDir(baseDir, "userdata")
    +	networkData, networkDataError := readFileFromDir(baseDir, "networkdata")
    +	if userDataError != nil && networkDataError != nil {
    +		return fmt.Errorf("no cloud-init data-source found at volume: %s", volume.Name)
    +	}
    +
    +	if userData != "" {
    +		volume.CloudInitNoCloud.UserData = userData
    +	}
    +	if networkData != "" {
    +		volume.CloudInitNoCloud.NetworkData = networkData
    +	}
    +
    +	return nil
    +}
    +
    +// ResolveConfigDriveSecrets is looking for CloudInitConfigDriveSource volume source with
    +// UserDataSecretRef and NetworkDataSecretRef and resolves the secret from the corresponding
    +// VolumeMount.
    +//
    +// Note: when using this function, make sure that your code can access the secret volumes.
    +func ResolveConfigDriveSecrets(vmi *v1.VirtualMachineInstance, secretSourceDir string) error {
    +	volume := findCloudInitConfigDriveSecretVolume(vmi.Spec.Volumes)
    +	if volume == nil {
    +		return nil
    +	}
    +
    +	baseDir := filepath.Join(secretSourceDir, volume.Name)
    +	userData, userDataError := readFileFromDir(baseDir, "userdata")
    +	networkData, networkDataError := readFileFromDir(baseDir, "networkdata")
    +	if userDataError != nil && networkDataError != nil {
    +		return fmt.Errorf("no cloud-init data-source found at volume: %s", volume.Name)
    +	}
    +
    +	if userData != "" {
    +		volume.CloudInitConfigDrive.UserData = userData
    +	}
    +	if networkData != "" {
    +		volume.CloudInitConfigDrive.NetworkData = networkData
    +	}
    +
    +	return nil
    +}
    +
    +// findCloudInitConfigDriveSecretVolume loops over a given list of volumes and return a pointer
    +// to the first volume with a CloudInitConfigDrive source and UserDataSecretRef field set.
    +func findCloudInitConfigDriveSecretVolume(volumes []v1.Volume) *v1.Volume {
    +	for _, volume := range volumes {
    +		if volume.CloudInitConfigDrive == nil {
    +			continue
    +		}
    +		if volume.CloudInitConfigDrive.UserDataSecretRef != nil ||
    +			volume.CloudInitConfigDrive.NetworkDataSecretRef != nil {
    +			return &volume
    +		}
    +	}
    +
    +	return nil
    +}
    +
    +func readFileFromDir(basedir, secretFile string) (string, error) {
    +	userDataSecretFile := filepath.Join(basedir, secretFile)
    +	userDataSecret, err := ioutil.ReadFile(userDataSecretFile)
    +	if err != nil {
    +		log.Log.V(2).Reason(err).
    +			Errorf("could not read secret data from source: %s", userDataSecretFile)
    +		return "", err
    +	}
    +	return string(userDataSecret), nil
    +}
    +
    +// findCloudInitNoCloudSecretVolume loops over a given list of volumes and return a pointer
    +// to the first CloudInitNoCloud volume with a UserDataSecretRef field set.
    +func findCloudInitNoCloudSecretVolume(volumes []v1.Volume) *v1.Volume {
    +	for _, volume := range volumes {
    +		if volume.CloudInitNoCloud != nil && volume.CloudInitNoCloud.UserDataSecretRef != nil {
    +			return &volume
    +		}
    +	}
    +
    +	return nil
    +}
    +
     func readRawOrBase64Data(rawData, base64Data string) (string, error) {
     	if rawData != "" {
     		return rawData, nil
    @@ -242,93 +334,6 @@ func removeLocalData(domain string, namespace string) error {
     	return err
     }
     
    -// InjectCloudInitSecrets inspects cloud-init volumes in the given VMI and
    -// resolves any userdata and networkdata secret refs it may find. The resolved
    -// cloud-init secrets are then injected into the VMI.
    -func InjectCloudInitSecrets(vmi *v1.VirtualMachineInstance, clientset kubecli.KubevirtClient) error {
    -	precond.MustNotBeNil(vmi)
    -	namespace := precond.MustNotBeEmpty(vmi.GetObjectMeta().GetNamespace())
    -
    -	var err error
    -	for _, volume := range vmi.Spec.Volumes {
    -		if volume.CloudInitNoCloud != nil {
    -			err = resolveNoCloudSecrets(volume.CloudInitNoCloud, namespace, clientset)
    -			break
    -		}
    -		if volume.CloudInitConfigDrive != nil {
    -			err = resolveConfigDriveSecrets(volume.CloudInitConfigDrive, namespace, clientset)
    -			break
    -		}
    -	}
    -	if err != nil {
    -		return err
    -	}
    -	return nil
    -}
    -
    -func resolveNoCloudSecrets(source *v1.CloudInitNoCloudSource, namespace string, clientset kubecli.KubevirtClient) error {
    -	precond.CheckNotNil(source)
    -
    -	secretRefs := []*corev1.LocalObjectReference{source.UserDataSecretRef, source.NetworkDataSecretRef}
    -	dataKeys := []string{"userdata", "networkdata"}
    -	resolvedData, err := resolveSecrets(secretRefs, dataKeys, namespace, clientset)
    -	if err != nil {
    -		return err
    -	}
    -
    -	if userData, ok := resolvedData["userdata"]; ok {
    -		source.UserData = userData
    -	}
    -	if networkData, ok := resolvedData["networkdata"]; ok {
    -		source.NetworkData = networkData
    -	}
    -	return nil
    -}
    -
    -func resolveConfigDriveSecrets(source *v1.CloudInitConfigDriveSource, namespace string, clientset kubecli.KubevirtClient) error {
    -	precond.CheckNotNil(source)
    -
    -	secretRefs := []*corev1.LocalObjectReference{source.UserDataSecretRef, source.NetworkDataSecretRef}
    -	dataKeys := []string{"userdata", "networkdata"}
    -	resolvedData, err := resolveSecrets(secretRefs, dataKeys, namespace, clientset)
    -	if err != nil {
    -		return err
    -	}
    -
    -	if userData, ok := resolvedData["userdata"]; ok {
    -		source.UserData = userData
    -	}
    -	if networkData, ok := resolvedData["networkdata"]; ok {
    -		source.NetworkData = networkData
    -	}
    -	return nil
    -}
    -
    -func resolveSecrets(secretRefs []*corev1.LocalObjectReference, dataKeys []string, namespace string, clientset kubecli.KubevirtClient) (map[string]string, error) {
    -	precond.CheckNotEmpty(namespace)
    -	precond.CheckNotNil(clientset)
    -	resolvedData := make(map[string]string, len(secretRefs))
    -
    -	for i, secretRef := range secretRefs {
    -		if secretRef == nil {
    -			continue
    -		}
    -
    -		secretID := secretRef.Name
    -		secret, err := clientset.CoreV1().Secrets(namespace).Get(secretID, metav1.GetOptions{})
    -		if err != nil {
    -			return resolvedData, err
    -		}
    -		data, ok := secret.Data[dataKeys[i]]
    -		if !ok {
    -			return resolvedData, fmt.Errorf("%s key not found in k8s secret %s %v", dataKeys[i], secretID, err)
    -		}
    -		resolvedData[dataKeys[i]] = string(data)
    -	}
    -
    -	return resolvedData, nil
    -}
    -
     func GenerateLocalData(vmiName string, namespace string, data *CloudInitData) error {
     	precond.MustNotBeEmpty(vmiName)
     	precond.MustNotBeNil(data)
    
  • pkg/cloud-init/cloud-init_test.go+86 239 modified
    @@ -25,41 +25,53 @@ import (
     	"io/ioutil"
     	"os"
     	"os/exec"
    +	"path/filepath"
     	"time"
     
    -	"github.com/golang/mock/gomock"
     	. "github.com/onsi/ginkgo"
     	. "github.com/onsi/gomega"
     
     	k8sv1 "k8s.io/api/core/v1"
    -	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    -	"k8s.io/client-go/kubernetes/fake"
     
     	v1 "kubevirt.io/client-go/api/v1"
    -	"kubevirt.io/client-go/kubecli"
     	"kubevirt.io/client-go/precond"
     )
     
     var _ = Describe("CloudInit", func() {
     
     	var (
    -		ctrl            *gomock.Controller
    -		virtClient      *kubecli.MockKubevirtClient
     		isoCreationFunc IsoCreationFunc
    +		tmpDir          string
     	)
     
    -	tmpDir, _ := ioutil.TempDir("", "cloudinittest")
    +	createEmptyVMIWithVolumes := func(volumes []v1.Volume) *v1.VirtualMachineInstance {
    +		return &v1.VirtualMachineInstance{
    +			Spec: v1.VirtualMachineInstanceSpec{
    +				Volumes: volumes,
    +			},
    +		}
    +	}
    +
    +	fakeVolumeMountDir := func(dirName string, files map[string]string) string {
    +		volumeDir := filepath.Join(tmpDir, dirName)
    +		err := os.Mkdir(volumeDir, 0700)
    +		Expect(err).To(Not(HaveOccurred()), "could not create volume dir: ", volumeDir)
    +		for fileName, content := range files {
    +			err = ioutil.WriteFile(
    +				filepath.Join(volumeDir, fileName),
    +				[]byte(content),
    +				0644)
    +			Expect(err).To(Not(HaveOccurred()), "could not create file: ", fileName)
    +		}
    +		return volumeDir
    +	}
     
    -	BeforeSuite(func() {
    +	BeforeEach(func() {
    +		tmpDir, _ = ioutil.TempDir("", "cloudinittest")
     		err := SetLocalDirectory(tmpDir)
     		if err != nil {
     			panic(err)
     		}
    -	})
    -
    -	BeforeEach(func() {
    -		ctrl = gomock.NewController(GinkgoT())
    -		virtClient = kubecli.NewMockKubevirtClient(ctrl)
     		isoCreationFunc = func(isoOutFile, volumeID string, inDir string) error {
     			switch volumeID {
     			case "cidata", "config-2":
    @@ -79,7 +91,7 @@ var _ = Describe("CloudInit", func() {
     		SetIsoCreationFunction(isoCreationFunc)
     	})
     
    -	AfterSuite(func() {
    +	AfterEach(func() {
     		os.RemoveAll(tmpDir)
     	})
     
    @@ -279,127 +291,44 @@ var _ = Describe("CloudInit", func() {
     				})
     
     				Context("with secretRefs", func() {
    -					userDataSecretName := "userDataSecretName"
    -					networkDataSecretName := "networkDataSecretName"
    -					namespace := "testing"
    -
    -					createSecret := func(name, dataKey, dataValue string) *k8sv1.Secret {
    -						return &k8sv1.Secret{
    -							ObjectMeta: metav1.ObjectMeta{
    -								Name:      name,
    -								Namespace: namespace,
    -							},
    -							Type: "Opaque",
    -							Data: map[string][]byte{
    -								dataKey: []byte(dataValue),
    +					createCloudInitSecretRefVolume := func(name, secret string) *v1.Volume {
    +						return &v1.Volume{
    +							Name: name,
    +							VolumeSource: v1.VolumeSource{
    +								CloudInitNoCloud: &v1.CloudInitNoCloudSource{
    +									UserDataSecretRef: &k8sv1.LocalObjectReference{
    +										Name: secret,
    +									},
    +								},
     							},
     						}
     					}
    -					createUserDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(userDataSecretName, "userdata", data)
    -					}
    -					createBadUserDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(userDataSecretName, "baduserdara", data)
    -					}
    -					createNetworkDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(networkDataSecretName, "networkdata", data)
    -					}
    -					createBadNetworkDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(networkDataSecretName, "badnetworkdata", data)
    -					}
    -
    -					It("should succeed to verify userDataSecretRef", func() {
    -						userSecret := createUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()).AnyTimes()
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
     
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    -						Expect(cloudInitData.UserData).To(Equal("secretUserData"))
    +					It("should resolve no-cloud data from volume", func() {
    +						testVolume := createCloudInitSecretRefVolume("test-volume", "test-secret")
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{*testVolume})
    +						fakeVolumeMountDir("test-volume", map[string]string{
    +							"userdata":    "secret-userdata",
    +							"networkdata": "secret-networkdata",
    +						})
    +						err := ResolveNoCloudSecrets(vmi, tmpDir)
    +						Expect(err).To(Not(HaveOccurred()), "could not resolve secret volume")
    +						Expect(testVolume.CloudInitNoCloud.UserData).To(Equal("secret-userdata"))
    +						Expect(testVolume.CloudInitNoCloud.NetworkData).To(Equal("secret-networkdata"))
     					})
     
    -					It("should succeed to verify userDataSecretRef and networkDataSecretRef", func() {
    -						userSecret := createUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						networkSecret := createNetworkDataSecret("secretNetworkData")
    -						networkClient := fake.NewSimpleClientset(networkSecret)
    -
    -						gomock.InOrder(
    -							virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()),
    -							virtClient.EXPECT().CoreV1().Return(networkClient.CoreV1()),
    -						)
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							UserDataSecretRef:    &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    -						Expect(cloudInitData.UserData).To(Equal("secretUserData"))
    -						Expect(cloudInitData.NetworkData).To(Equal("secretNetworkData"))
    +					It("should resolve empty no-cloud volume and do nothing", func() {
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{})
    +						err := ResolveNoCloudSecrets(vmi, tmpDir)
    +						Expect(err).To(Not(HaveOccurred()), "failed to resolve empty volumes")
     					})
     
    -					It("should succeed to verify nothing", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -						cloudInitData := &v1.CloudInitNoCloudSource{}
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    -					})
    -
    -					It("should fail to verify UserDataSecretRef without a secret", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
    -
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("secrets \"%s\" not found", userDataSecretName)))
    -					})
    -
    -					It("should fail to verify NetworkDataSecretRef without a secret", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("secrets \"%s\" not found", networkDataSecretName)))
    -					})
    -
    -					It("should fail to verify UserDataSecretRef with a misnamed secret", func() {
    -						userSecret := createBadUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()).AnyTimes()
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
    -
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("userdata key not found in k8s secret %s <nil>", userDataSecretName)))
    -					})
    -
    -					It("should fail to verify NetworkDataSecretRef with a misnamed secret", func() {
    -						networkSecret := createBadNetworkDataSecret("secretNetworkData")
    -						networkClient := fake.NewSimpleClientset(networkSecret)
    -						virtClient.EXPECT().CoreV1().Return(networkClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitNoCloudSource{
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveNoCloudSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("networkdata key not found in k8s secret %s <nil>", networkDataSecretName)))
    +					It("should fail if both userdata and network data does not exist", func() {
    +						testVolume := createCloudInitSecretRefVolume("test-volume", "test-secret")
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{*testVolume})
    +						err := ResolveNoCloudSecrets(vmi, tmpDir)
    +						Expect(err).To(HaveOccurred(), "expected a failure when no sources found")
    +						Expect(err.Error()).To(Equal("no cloud-init data-source found at volume: test-volume"))
     					})
     				})
     			})
    @@ -473,128 +402,46 @@ var _ = Describe("CloudInit", func() {
     				})
     
     				Context("with secretRefs", func() {
    -					userDataSecretName := "userDataSecretName"
    -					networkDataSecretName := "networkDataSecretName"
    -					namespace := "testing"
    -
    -					createSecret := func(name, dataKey, dataValue string) *k8sv1.Secret {
    -						return &k8sv1.Secret{
    -							ObjectMeta: metav1.ObjectMeta{
    -								Name:      name,
    -								Namespace: namespace,
    -							},
    -							Type: "Opaque",
    -							Data: map[string][]byte{
    -								dataKey: []byte(dataValue),
    +					createCloudInitConfigDriveVolume := func(name, secret string) *v1.Volume {
    +						return &v1.Volume{
    +							Name: name,
    +							VolumeSource: v1.VolumeSource{
    +								CloudInitConfigDrive: &v1.CloudInitConfigDriveSource{
    +									UserDataSecretRef: &k8sv1.LocalObjectReference{
    +										Name: secret,
    +									},
    +								},
     							},
     						}
     					}
    -					createUserDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(userDataSecretName, "userdata", data)
    -					}
    -					createBadUserDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(userDataSecretName, "baduserdara", data)
    -					}
    -					createNetworkDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(networkDataSecretName, "networkdata", data)
    -					}
    -					createBadNetworkDataSecret := func(data string) *k8sv1.Secret {
    -						return createSecret(networkDataSecretName, "badnetworkdata", data)
    -					}
    -
    -					It("should succeed to verify userDataSecretRef", func() {
    -						userSecret := createUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()).AnyTimes()
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
    -
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    -						Expect(cloudInitData.UserData).To(Equal("secretUserData"))
    -					})
    -
    -					It("should succeed to verify userDataSecretRef and networkDataSecretRef", func() {
    -						userSecret := createUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						networkSecret := createNetworkDataSecret("secretNetworkData")
    -						networkClient := fake.NewSimpleClientset(networkSecret)
    -
    -						gomock.InOrder(
    -							virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()),
    -							virtClient.EXPECT().CoreV1().Return(networkClient.CoreV1()),
    -						)
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							UserDataSecretRef:    &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    -						Expect(cloudInitData.UserData).To(Equal("secretUserData"))
    -						Expect(cloudInitData.NetworkData).To(Equal("secretNetworkData"))
    +					It("should resolve config-drive data from volume", func() {
    +						testVolume := createCloudInitConfigDriveVolume("test-volume", "test-secret")
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{*testVolume})
    +						fakeVolumeMountDir("test-volume", map[string]string{
    +							"userdata":    "secret-userdata",
    +							"networkdata": "secret-networkdata",
    +						})
    +						err := ResolveConfigDriveSecrets(vmi, tmpDir)
    +						Expect(err).To(Not(HaveOccurred()), "could not resolve secret volume")
    +						Expect(testVolume.CloudInitConfigDrive.UserData).To(Equal("secret-userdata"))
    +						Expect(testVolume.CloudInitConfigDrive.NetworkData).To(Equal("secret-networkdata"))
     					})
     
    -					It("should succeed to verify nothing", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -						cloudInitData := &v1.CloudInitConfigDriveSource{}
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err).To(BeNil())
    +					It("should resolve empty config-drive volume and do nothing", func() {
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{})
    +						err := ResolveConfigDriveSecrets(vmi, tmpDir)
    +						Expect(err).To(Not(HaveOccurred()), "failed to resolve empty volumes")
     					})
     
    -					It("should fail to verify UserDataSecretRef without a secret", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
    +					It("should fail if both userdata and network data does not exist", func() {
    +						testVolume := createCloudInitConfigDriveVolume("test-volume", "test-secret")
    +						vmi := createEmptyVMIWithVolumes([]v1.Volume{*testVolume})
    +						err := ResolveConfigDriveSecrets(vmi, tmpDir)
    +						Expect(err).To(HaveOccurred(), "expected a failure when no sources found")
    +						Expect(err.Error()).To(Equal("no cloud-init data-source found at volume: test-volume"))
     
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("secrets \"%s\" not found", userDataSecretName)))
     					})
     
    -					It("should fail to verify NetworkDataSecretRef without a secret", func() {
    -						fakeClient := fake.NewSimpleClientset()
    -						virtClient.EXPECT().CoreV1().Return(fakeClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("secrets \"%s\" not found", networkDataSecretName)))
    -					})
    -
    -					It("should fail to verify UserDataSecretRef with a misnamed secret", func() {
    -						userSecret := createBadUserDataSecret("secretUserData")
    -						userClient := fake.NewSimpleClientset(userSecret)
    -						virtClient.EXPECT().CoreV1().Return(userClient.CoreV1()).AnyTimes()
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							UserDataSecretRef: &k8sv1.LocalObjectReference{Name: userDataSecretName},
    -						}
    -
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("userdata key not found in k8s secret %s <nil>", userDataSecretName)))
    -					})
    -
    -					It("should fail to verify NetworkDataSecretRef with a misnamed secret", func() {
    -						networkSecret := createBadNetworkDataSecret("secretNetworkData")
    -						networkClient := fake.NewSimpleClientset(networkSecret)
    -						virtClient.EXPECT().CoreV1().Return(networkClient.CoreV1())
    -
    -						cloudInitData := &v1.CloudInitConfigDriveSource{
    -							NetworkDataSecretRef: &k8sv1.LocalObjectReference{Name: networkDataSecretName},
    -						}
    -
    -						err := resolveConfigDriveSecrets(cloudInitData, namespace, virtClient)
    -						Expect(err.Error()).To(Equal(fmt.Sprintf("networkdata key not found in k8s secret %s <nil>", networkDataSecretName)))
    -					})
     				})
     			})
     		})
    
  • pkg/virt-controller/services/template.go+34 0 modified
    @@ -504,6 +504,40 @@ func (t *templateService) RenderLaunchManifest(vmi *v1.VirtualMachineInstance) (
     		if volume.ServiceAccount != nil {
     			serviceAccountName = volume.ServiceAccount.ServiceAccountName
     		}
    +
    +		if volume.CloudInitNoCloud != nil && volume.CloudInitNoCloud.UserDataSecretRef != nil {
    +			// attach a secret referenced by the user
    +			volumes = append(volumes, k8sv1.Volume{
    +				Name: volume.Name,
    +				VolumeSource: k8sv1.VolumeSource{
    +					Secret: &k8sv1.SecretVolumeSource{
    +						SecretName: volume.CloudInitNoCloud.UserDataSecretRef.Name,
    +					},
    +				},
    +			})
    +			volumeMounts = append(volumeMounts, k8sv1.VolumeMount{
    +				Name:      volume.Name,
    +				MountPath: filepath.Join(config.SecretSourceDir, volume.Name),
    +				ReadOnly:  true,
    +			})
    +		}
    +
    +		if volume.CloudInitConfigDrive != nil && volume.CloudInitConfigDrive.UserDataSecretRef != nil {
    +			// attach a secret referenced by the user
    +			volumes = append(volumes, k8sv1.Volume{
    +				Name: volume.Name,
    +				VolumeSource: k8sv1.VolumeSource{
    +					Secret: &k8sv1.SecretVolumeSource{
    +						SecretName: volume.CloudInitConfigDrive.UserDataSecretRef.Name,
    +					},
    +				},
    +			})
    +			volumeMounts = append(volumeMounts, k8sv1.VolumeMount{
    +				Name:      volume.Name,
    +				MountPath: filepath.Join(config.SecretSourceDir, volume.Name),
    +				ReadOnly:  true,
    +			})
    +		}
     	}
     
     	if t.imagePullSecret != "" {
    
  • pkg/virt-controller/services/template_test.go+44 0 modified
    @@ -198,6 +198,50 @@ var _ = Describe("Template", func() {
     				Expect(debugLogsValue).To(Equal("1"))
     			})
     		})
    +		Context("with cloud-init secret", func() {
    +			It("should add volume with secret referenced by cloud-init user secret ref", func() {
    +				vmi := v1.VirtualMachineInstance{
    +					ObjectMeta: metav1.ObjectMeta{
    +						Name:      "testvmi",
    +						Namespace: "default",
    +						UID:       "1234",
    +					},
    +					Spec: v1.VirtualMachineInstanceSpec{
    +						Volumes: []v1.Volume{
    +							{
    +								Name: "cloud-init-user-data-secret-ref",
    +								VolumeSource: v1.VolumeSource{
    +									CloudInitNoCloud: &v1.CloudInitNoCloudSource{
    +										UserDataSecretRef: &kubev1.LocalObjectReference{
    +											Name: "some-secret",
    +										},
    +									},
    +								},
    +							},
    +						},
    +					},
    +				}
    +
    +				pod, err := svc.RenderLaunchManifest(&vmi)
    +				Expect(err).ToNot(HaveOccurred())
    +
    +				cloudInitVolumeFound := false
    +				for _, volume := range pod.Spec.Volumes {
    +					if volume.Name == "cloud-init-user-data-secret-ref" {
    +						cloudInitVolumeFound = true
    +					}
    +				}
    +				Expect(cloudInitVolumeFound).To(BeTrue(), "could not find cloud init secret volume")
    +
    +				cloudInitVolumeMountFound := false
    +				for _, volumeMount := range pod.Spec.Containers[0].VolumeMounts {
    +					if volumeMount.Name == "cloud-init-user-data-secret-ref" {
    +						cloudInitVolumeMountFound = true
    +					}
    +				}
    +				Expect(cloudInitVolumeMountFound).To(BeTrue(), "could not find cloud init secret volume mount")
    +			})
    +		})
     		Context("with multus annotation", func() {
     			It("should add multus networks in the pod annotation", func() {
     				vmi := v1.VirtualMachineInstance{
    
  • pkg/virt-handler/BUILD.bazel+0 1 modified
    @@ -6,7 +6,6 @@ go_library(
         importpath = "kubevirt.io/kubevirt/pkg/virt-handler",
         visibility = ["//visibility:public"],
         deps = [
    -        "//pkg/cloud-init:go_default_library",
             "//pkg/controller:go_default_library",
             "//pkg/handler-launcher-com/cmd/v1:go_default_library",
             "//pkg/host-disk:go_default_library",
    
  • pkg/virt-handler/vm.go+0 6 modified
    @@ -46,7 +46,6 @@ import (
     	v1 "kubevirt.io/client-go/api/v1"
     	"kubevirt.io/client-go/kubecli"
     	"kubevirt.io/client-go/log"
    -	cloudinit "kubevirt.io/kubevirt/pkg/cloud-init"
     	"kubevirt.io/kubevirt/pkg/controller"
     	cmdv1 "kubevirt.io/kubevirt/pkg/handler-launcher-com/cmd/v1"
     	hostdisk "kubevirt.io/kubevirt/pkg/host-disk"
    @@ -1396,11 +1395,6 @@ func (d *VirtualMachineController) processVmUpdate(origVMI *v1.VirtualMachineIns
     		return err
     	}
     
    -	err = cloudinit.InjectCloudInitSecrets(vmi, d.clientset)
    -	if err != nil {
    -		return err
    -	}
    -
     	client, err := d.getLauncherClient(vmi)
     	if err != nil {
     		return fmt.Errorf("unable to create virt-launcher client connection: %v", err)
    
  • pkg/virt-launcher/virtwrap/manager.go+10 0 modified
    @@ -774,6 +774,16 @@ func (l *LibvirtDomainManager) preStartHook(vmi *v1.VirtualMachineInstance, doma
     
     	logger.Info("Executing PreStartHook on VMI pod environment")
     
    +	err := cloudinit.ResolveNoCloudSecrets(vmi, config.SecretSourceDir)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	err = cloudinit.ResolveConfigDriveSecrets(vmi, config.SecretSourceDir)
    +	if err != nil {
    +		return nil, err
    +	}
    +
     	// generate cloud-init data
     	cloudInitData, err := cloudinit.ReadCloudInitVolumeDataSource(vmi)
     	if err != nil {
    
  • pkg/virt-operator/creation/rbac/handler.go+2 1 modified
    @@ -84,7 +84,7 @@ func newHandlerClusterRole() *rbacv1.ClusterRole {
     					"",
     				},
     				Resources: []string{
    -					"secrets", "persistentvolumeclaims",
    +					"persistentvolumeclaims",
     				},
     				Verbs: []string{
     					"get",
    @@ -190,6 +190,7 @@ func newHandlerRole(namespace string) *rbacv1.Role {
     				},
     				Verbs: []string{
     					"create",
    +					"get",
     				},
     			},
     		},
    
  • tests/vmi_lifecycle_test.go+36 17 modified
    @@ -313,8 +313,26 @@ var _ = Describe("[rfe_id:273][crit:high][vendor:cnv-qe@redhat.com][level:compon
     		})
     
     		Context("with user-data", func() {
    +
    +			findLauncherForVMI := func(vmi *v1.VirtualMachineInstance) *k8sv1.Pod {
    +				By("Finding a launcher for VMI: " + vmi.Name)
    +				launchers, err := virtClient.
    +					CoreV1().
    +					Pods(tests.NamespaceTestDefault).
    +					List(metav1.ListOptions{LabelSelector: "kubevirt.io=virt-launcher"})
    +				Expect(err).To(BeNil(), "Should list virt-launchers")
    +				var launcher k8sv1.Pod
    +				for _, launcherPod := range launchers.Items {
    +					if domain, ok := launcherPod.ObjectMeta.Annotations[v1.DomainAnnotation]; ok && domain == vmi.Name {
    +						return &launcherPod
    +					}
    +				}
    +				Expect(launcher).ToNot(BeNil(), "Should find virt-launcher pod for created VMI")
    +				return nil
    +			}
    +
     			Context("without k8s secret", func() {
    -				It("[test_id:1629]should retry starting the VirtualMachineInstance", func() {
    +				It("[test_id:1629]should not be able to start virt-launcher pod", func() {
     					userData := fmt.Sprintf("#!/bin/sh\n\necho 'hi'\n")
     					vmi = tests.NewRandomVMIWithEphemeralDiskAndUserdata(tests.ContainerDiskFor(tests.ContainerDiskCirros), userData)
     
    @@ -327,23 +345,21 @@ var _ = Describe("[rfe_id:273][crit:high][vendor:cnv-qe@redhat.com][level:compon
     						}
     					}
     					By("Starting a VirtualMachineInstance")
    -					obj, err := virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
    +					_, err := virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
     					Expect(err).To(BeNil(), "Should create VMI successfully")
    -
    -					By("Checking that VirtualMachineInstance was restarted twice")
    -					retryCount := 0
     					stopChan := make(chan struct{})
     					defer close(stopChan)
    -					tests.NewObjectEventWatcher(obj).SinceWatchedObjectResourceVersion().Timeout(60*time.Second).Watch(stopChan, func(event *k8sv1.Event) bool {
    -						if event.Type == "Warning" && event.Reason == v1.SyncFailed.String() {
    -							retryCount++
    -							if retryCount >= 2 {
    -								// Done, two retries is enough
    +					launcher := findLauncherForVMI(vmi)
    +					tests.NewObjectEventWatcher(launcher).
    +						SinceWatchedObjectResourceVersion().
    +						Timeout(60*time.Second).
    +						Watch(stopChan, func(event *k8sv1.Event) bool {
    +							if event.Type == "Warning" && event.Reason == "FailedMount" {
     								return true
     							}
    -						}
    -						return false
    -					}, fmt.Sprintf("two events of type Warning, reason = %s", v1.SyncFailed.String()))
    +							return false
    +						},
    +							"event of type Warning, reason = FailedMount")
     				})
     
     				It("[test_id:1630]should log warning and proceed once the secret is there", func() {
    @@ -363,13 +379,16 @@ var _ = Describe("[rfe_id:273][crit:high][vendor:cnv-qe@redhat.com][level:compon
     					By("Starting a VirtualMachineInstance")
     					createdVMI, err := virtClient.VirtualMachineInstance(tests.NamespaceTestDefault).Create(vmi)
     					Expect(err).To(BeNil(), "Should create VMI successfully")
    -
    +					launcher := findLauncherForVMI(vmi)
     					// Wait until we see that starting the VirtualMachineInstance is failing
     					By("Checking that VirtualMachineInstance start failed")
     					stopChan := make(chan struct{})
     					defer close(stopChan)
    -					event := tests.NewObjectEventWatcher(createdVMI).Timeout(60*time.Second).SinceWatchedObjectResourceVersion().WaitFor(stopChan, tests.WarningEvent, v1.SyncFailed)
    -					Expect(event.Message).To(ContainSubstring("nonexistent"), "VMI should not be started")
    +					event := tests.NewObjectEventWatcher(launcher).Timeout(60*time.Second).SinceWatchedObjectResourceVersion().WaitFor(stopChan, tests.WarningEvent, "FailedMount")
    +					Expect(event.Message).To(SatisfyAny(
    +						ContainSubstring(`secret "nonexistent" not found`),
    +						ContainSubstring(`secrets "nonexistent" not found`), // for k8s 1.11.x
    +					), "VMI should not be started")
     
     					// Creat nonexistent secret, so that the VirtualMachineInstance can recover
     					By("Creating a user-data secret")
    @@ -391,7 +410,7 @@ var _ = Describe("[rfe_id:273][crit:high][vendor:cnv-qe@redhat.com][level:compon
     
     					// Wait for the VirtualMachineInstance to be started, allow warning events to occur
     					By("Checking that VirtualMachineInstance start succeeded")
    -					tests.NewObjectEventWatcher(createdVMI).SinceWatchedObjectResourceVersion().Timeout(30*time.Second).WaitFor(stopChan, tests.NormalEvent, v1.Started)
    +					tests.NewObjectEventWatcher(createdVMI).SinceWatchedObjectResourceVersion().Timeout(60*time.Second).WaitFor(stopChan, tests.NormalEvent, v1.Started)
     				})
     			})
     		})
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.