diff --git a/api/v1alpha1/clusterspiffeid_types.go b/api/v1alpha1/clusterspiffeid_types.go index fb1f60a..2d63290 100644 --- a/api/v1alpha1/clusterspiffeid_types.go +++ b/api/v1alpha1/clusterspiffeid_types.go @@ -79,6 +79,10 @@ type ClusterSPIFFEIDSpec struct { // +kubebuilder:validation:Optional ClassName string `json:"className,omitempty"` + // Apply this ID only if there are no other matching non fallback ClusterSPIFFEIDs. + // +kubebuilder:validation:Optional + Fallback bool `json:"fallback,omitempty"` + // Set the entry hint // +kubebuilder:validation:Optional Hint string `json:"hint,omitempty"` diff --git a/config/crd/bases/spire.spiffe.io_clusterspiffeids.yaml b/config/crd/bases/spire.spiffe.io_clusterspiffeids.yaml index 301a788..b9de4fb 100644 --- a/config/crd/bases/spire.spiffe.io_clusterspiffeids.yaml +++ b/config/crd/bases/spire.spiffe.io_clusterspiffeids.yaml @@ -52,6 +52,11 @@ spec: className: description: Set which Controller Class will act on this object type: string + fallback: + description: |- + Apply this ID only if there are no other matching non fallback + ClusterSPIFFEIDs + type: boolean dnsNameTemplates: description: |- DNSNameTemplate represents templates for extra DNS names that are diff --git a/docs/clusterspiffeid-crd.md b/docs/clusterspiffeid-crd.md index 3e3b501..2d70641 100644 --- a/docs/clusterspiffeid-crd.md +++ b/docs/clusterspiffeid-crd.md @@ -27,6 +27,7 @@ The definition can be found [here](../api/v1alpha1/clusterspiffeid_types.go). | `admin` | OPTIONAL | Indicates whether the target workload is an admin workload (i.e. can access SPIRE administrative APIs) | | `downstream` | OPTIONAL | Indicates that the entry describes a downstream SPIRE server. | | `autoPopulateDNSNames` | OPTIONAL | Indicates whether or not to auto populate service DNS names. | +| `fallback` | OPTIONAL | Apply this ID only if there are no other matching non fallback ClusterSPIFFEIDs. | ## ClusterSPIFFEIDStatus diff --git a/pkg/spireentry/reconciler.go b/pkg/spireentry/reconciler.go index 3520b30..20ca0f9 100644 --- a/pkg/spireentry/reconciler.go +++ b/pkg/spireentry/reconciler.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "regexp" + "slices" "sort" "strings" "text/template" @@ -358,6 +359,17 @@ func (r *entryReconciler) addClusterStaticEntryEntriesState(ctx context.Context, func (r *entryReconciler) addClusterSPIFFEIDEntriesState(ctx context.Context, state entriesState, clusterSPIFFEIDs []*ClusterSPIFFEID) { log := log.FromContext(ctx) + podsWithNonFallbackApplied := make(map[types.UID]struct{}) + // Process all the fallback clusterSPIFFEIDs last. + slices.SortStableFunc(clusterSPIFFEIDs, func(x, y *ClusterSPIFFEID) int { + if x.Spec.Fallback == y.Spec.Fallback { + return 0 + } + if x.Spec.Fallback { + return 1 + } + return -1 + }) for _, clusterSPIFFEID := range clusterSPIFFEIDs { log := log.WithValues(clusterSPIFFEIDLogKey, objectName(clusterSPIFFEID)) @@ -399,6 +411,9 @@ func (r *entryReconciler) addClusterSPIFFEIDEntriesState(ctx context.Context, st clusterSPIFFEID.NextStatus.Stats.PodsSelected += len(pods) for i := range pods { log := log.WithValues(podLogKey, objectName(&pods[i])) + if _, ok := podsWithNonFallbackApplied[pods[i].UID]; ok && clusterSPIFFEID.Spec.Fallback { + continue + } entry, err := r.renderPodEntry(ctx, spec, &pods[i]) switch { @@ -409,6 +424,9 @@ func (r *entryReconciler) addClusterSPIFFEIDEntriesState(ctx context.Context, st // renderPodEntry will return a nil entry if requisite k8s // objects disappeared from underneath. state.AddDeclared(*entry, clusterSPIFFEID) + if !clusterSPIFFEID.Spec.Fallback { + podsWithNonFallbackApplied[pods[i].UID] = struct{}{} + } } } }