-
Notifications
You must be signed in to change notification settings - Fork 50
Description
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
.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:
- No platform-specific Go dependencies. The controller uses
k8s.io/client-go/dynamicandk8s.io/apimachinery/pkg/apis/meta/v1/unstructuredto watch VM resources. Nokubevirt.io/apiimport. - Configurable resource type. A
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. - Follows existing patterns. Workload selectors use the same
workloadSelectorTemplatespattern asClusterSPIFFEID(free-formtype:valuestrings). Templates use Go text/template with the VM resource available as.Object. - Purely additive. No changes to
ClusterSPIFFEIDor 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: 3600sController 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
ClusterVMSPIFFEIDthe right name, or would something more generic likeClusterResourceSPIFFEIDbe 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.