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:
-
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.
-
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.
-
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
- Naming: Is
ClusterVMSPIFFEID the right name, or would something more generic like ClusterResourceSPIFFEID be preferred (to allow future support for other non-Pod resources)?
- 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?
- 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.
Problem
Currently, spire-controller-manager supports automatic registration of Pod workloads via
ClusterSPIFFEID. However, virtual machines (e.g., KubeVirtVirtualMachineInstances) 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:ClusterSPIFFEID is tightly coupled to Pods. The spec exposes
.PodSpecand.NodeSpecin templates, usespodSelector, and the status tracks pod-specific stats (PodsSelected,PodEntryRenderFailures). Adding VM-specific fields would mix two unrelated resource types in one API.It would introduce a kubevirt.io/api dependency. To watch
VirtualMachineInstanceresources with typed structs, spire-controller-manager would need to importkubevirt.io/api. This is a large, platform-specific dependency that not all users need. A dynamic/unstructured client avoids this entirely.Pods and VMs have different trust models. Pod entries use
k8s:workload selectors and a DaemonSet agent as the parent. VM entries useunix: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:
k8s.io/client-go/dynamicandk8s.io/apimachinery/pkg/apis/meta/v1/unstructuredto watch VM resources. Nokubevirt.io/apiimport.vmResourceTypefield (apiGroup, version, resource) tells the controller what GVR to watch. This works with KubeVirt, and could work with other VM platforms that expose CRDs.workloadSelectorTemplatespattern asClusterSPIFFEID(free-formtype:valuestrings). Templates use Go text/template with the VM resource available as.Object.ClusterSPIFFEIDor any existing behavior.Example:
Controller sketch:
Open Questions
ClusterVMSPIFFEIDthe right name, or would something more generic likeClusterResourceSPIFFEIDbe preferred (to allow future support for other non-Pod resources)?I'm happy to implement this and submit a PR if the design direction is acceptable.