Skip to content

Add ClusterVMSPIFFEID CRD for VM workload Registration #651

@rausingh-rh

Description

@rausingh-rh

Problem

Currently, spire-controller-manager supports automatic registration of Pod workloads via ClusterSPIFFEID. However, virtual machines (e.g., KubeVirt VirtualMachineInstances) require manual registration entry creation via the SPIRE Server CLI, which doesn't scale.

Why Not Extend ClusterSPIFFEID?

We initially considered adding VM fields to the existing ClusterSPIFFEID, but concluded a separate CRD is better:

  1. ClusterSPIFFEID is tightly coupled to Pods. The spec exposes .PodSpec and .NodeSpec in templates, uses podSelector, and the status tracks pod-specific stats (PodsSelected, PodEntryRenderFailures). Adding VM-specific fields would mix two unrelated resource types in one API.

  2. It would introduce a kubevirt.io/api dependency. To watch VirtualMachineInstance resources with typed structs, spire-controller-manager would need to import kubevirt.io/api. This is a large, platform-specific dependency that not all users need. A dynamic/unstructured client avoids this entirely.

  3. Pods and VMs have different trust models. Pod entries use k8s: workload selectors and a DaemonSet agent as the parent. VM entries use unix: workload selectors and a per-VM agent as the parent. These differences are better expressed as a separate CRD with its own reconciler.

Proposed Solution

Introduce a new CRD: ClusterVMSPIFFEID.

Design principles:

  • No platform-specific Go dependencies. The controller uses k8s.io/client-go/dynamic and k8s.io/apimachinery/pkg/apis/meta/v1/unstructured to watch VM resources. No kubevirt.io/api import.
  • Configurable resource type. A vmResourceType field (apiGroup, version, resource) tells the controller what GVR to watch. This works with KubeVirt, and could work with other VM platforms that expose CRDs.
  • Follows existing patterns. Workload selectors use the same workloadSelectorTemplates pattern as ClusterSPIFFEID (free-form type:value strings). Templates use Go text/template with the VM resource available as .Object.
  • Purely additive. No changes to ClusterSPIFFEID or any existing behavior.

Example:

apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterVMSPIFFEID
metadata:
  name: database-vms
spec:
  vmResourceType:
    apiGroup: "kubevirt.io"
    version: "v1"
    resource: "virtualmachineinstances"

  vmSelector:
    matchLabels:
      app: database

  namespaceSelector:
    matchLabels:
      environment: production

  spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .Object.metadata.namespace }}/vm/{{ .Object.metadata.name }}

  workloadEntries:
  - name: postgres
    spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .Object.metadata.namespace }}/vm/{{ .Object.metadata.name }}/postgres
    workloadSelectorTemplates:
    - "unix:uid:26"
    ttl: 3600s

  - name: redis
    spiffeIDTemplate: spiffe://{{ .TrustDomain }}/ns/{{ .Object.metadata.namespace }}/vm/{{ .Object.metadata.name }}/redis
    workloadSelectorTemplates:
    - "unix:uid:994"
    ttl: 3600s

Controller sketch:

// Reconciler uses dynamic client - no kubevirt.io import
vmGVR := schema.GroupVersionResource{
    Group:    cr.Spec.VMResourceType.APIGroup,
    Version:  cr.Spec.VMResourceType.Version,
    Resource: cr.Spec.VMResourceType.Resource,
}

vmList, err := dynamicClient.Resource(vmGVR).Namespace(ns).List(ctx, listOpts)

for _, vm := range vmList.Items {
    // vm is *unstructured.Unstructured
    // Render templates with .Object = vm.Object
    // Create SPIRE entries via existing SPIRE API client
}

Open Questions

  1. Naming: Is ClusterVMSPIFFEID the right name, or would something more generic like ClusterResourceSPIFFEID be preferred (to allow future support for other non-Pod resources)?
  2. Parent ID: How should the parent SPIFFE ID for workload entries be determined? Should this be configurable in the CRD, or derived from the VM resource?
  3. Status phase: Should the controller skip VMs that aren't in a "Running" phase? If so, the phase field path and running value should be configurable (different VM platforms may use different status fields).

I'm happy to implement this and submit a PR if the design direction is acceptable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions