Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable filtering entry id's by prefix #325

Merged
merged 9 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/v1alpha1/controllermanagerconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ type ControllerManagerConfigurationSpec struct {
// If specified, only syncs the specified CR types. Defaults to all.
// +optional
Reconcile *ReconcileConfig `json:"reconcile,omitempty"`

// If specified, prefixes each entry id with `<prefix>.`. Entries without the Prefix will be ignored (except ones marked for cleanup, see EntryIDPrefixCleanup).
// +optiional
EntryIDPrefix string `json:"entryIDPrefix,omitempty"`

// If specified, entries with the specified prefix will be removed. If set to "" it will clean up all unprefixed entries.
// It can not be set to the same value as EntryIDPrefix.
// Generally useful when switching from nonprefixed to prefixed, or between two different prefixes.
// +optiional
EntryIDPrefixCleanup *string `json:"entryIDPrefixCleanup,omitempty"`
}

// ReconcileConfig configuration used to enable/disable syncing various types
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 34 additions & 12 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ func main() {
}
}

func addDotSuffix(val string) string {
if val != "" && !strings.HasSuffix(val, ".") {
val += "."
}
return val
}

func parseConfig() (Config, error) {
var retval Config
var configFileFlag string
Expand Down Expand Up @@ -179,6 +186,17 @@ func parseConfig() (Config, error) {
retval.reconcile = *retval.ctrlConfig.Reconcile
}

retval.ctrlConfig.EntryIDPrefix = addDotSuffix(retval.ctrlConfig.EntryIDPrefix)

printCleanup := "<unset>"
if retval.ctrlConfig.EntryIDPrefixCleanup != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would all be a little easier to read if there was if block that handled all of the code related to EntryIDPrefixCleanup being non-nil. Right now it's spread across four blocks and it's a little hard to track.

printCleanup = *retval.ctrlConfig.EntryIDPrefixCleanup
*retval.ctrlConfig.EntryIDPrefixCleanup = addDotSuffix(*retval.ctrlConfig.EntryIDPrefixCleanup)
if retval.ctrlConfig.EntryIDPrefix != "" && retval.ctrlConfig.EntryIDPrefix == *retval.ctrlConfig.EntryIDPrefixCleanup {
return retval, fmt.Errorf("if entryIDPrefixCleanup is specified, it can not be the same value as entryIDPrefix")
}
}

setupLog.Info("Config loaded",
"cluster name", retval.ctrlConfig.ClusterName,
"cluster domain", retval.ctrlConfig.ClusterDomain,
Expand All @@ -190,7 +208,9 @@ func parseConfig() (Config, error) {
"handle crs without class name", retval.ctrlConfig.WatchClassless,
"reconcile ClusterSPIFFEIDs", retval.reconcile.ClusterSPIFFEIDs,
"reconcile ClusterFederatedTrustDomains", retval.reconcile.ClusterFederatedTrustDomains,
"reconcile ClusterStaticEntries", retval.reconcile.ClusterStaticEntries)
"reconcile ClusterStaticEntries", retval.reconcile.ClusterStaticEntries,
"entryIDPrefix", retval.ctrlConfig.EntryIDPrefix,
"entryIDPrefixCleanup", printCleanup)

switch {
case retval.ctrlConfig.TrustDomain == "":
Expand Down Expand Up @@ -297,17 +317,19 @@ func run(mainConfig Config) (err error) {
var entryReconciler reconciler.Reconciler
if mainConfig.reconcile.ClusterSPIFFEIDs || mainConfig.reconcile.ClusterStaticEntries {
entryReconciler = spireentry.Reconciler(spireentry.ReconcilerConfig{
TrustDomain: trustDomain,
ClusterName: mainConfig.ctrlConfig.ClusterName,
ClusterDomain: mainConfig.ctrlConfig.ClusterDomain,
K8sClient: mgr.GetClient(),
EntryClient: spireClient,
IgnoreNamespaces: mainConfig.ignoreNamespacesRegex,
GCInterval: mainConfig.ctrlConfig.GCInterval,
ClassName: mainConfig.ctrlConfig.ClassName,
WatchClassless: mainConfig.ctrlConfig.WatchClassless,
ParentIDTemplate: mainConfig.parentIDTemplate,
Reconcile: mainConfig.reconcile,
TrustDomain: trustDomain,
ClusterName: mainConfig.ctrlConfig.ClusterName,
ClusterDomain: mainConfig.ctrlConfig.ClusterDomain,
K8sClient: mgr.GetClient(),
EntryClient: spireClient,
IgnoreNamespaces: mainConfig.ignoreNamespacesRegex,
GCInterval: mainConfig.ctrlConfig.GCInterval,
ClassName: mainConfig.ctrlConfig.ClassName,
WatchClassless: mainConfig.ctrlConfig.WatchClassless,
ParentIDTemplate: mainConfig.parentIDTemplate,
Reconcile: mainConfig.reconcile,
EntryIDPrefix: mainConfig.ctrlConfig.EntryIDPrefix,
EntryIDPrefixCleanup: mainConfig.ctrlConfig.EntryIDPrefixCleanup,
})
}

Expand Down
48 changes: 45 additions & 3 deletions pkg/spireentry/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"regexp"
"sort"
Expand All @@ -28,6 +29,7 @@ import (
"time"

"github.com/go-logr/logr"
"github.com/google/uuid"
"github.com/spiffe/go-spiffe/v2/spiffeid"
"google.golang.org/grpc/codes"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -68,6 +70,8 @@ type ReconcilerConfig struct {
WatchClassless bool
ParentIDTemplate *template.Template
Reconcile spirev1alpha1.ReconcileConfig
EntryIDPrefix string
EntryIDPrefixCleanup *string

// GCInterval how long to sit idle (i.e. untriggered) before doing
// another reconcile.
Expand Down Expand Up @@ -101,7 +105,7 @@ func (r *entryReconciler) reconcile(ctx context.Context) {
unsupportedFields := r.unsupportedFields

// Load current entries from SPIRE server.
currentEntries, err := r.listEntries(ctx)
currentEntries, deleteOnlyEntries, err := r.listEntries(ctx)
if err != nil {
log.Error(err, "Failed to list SPIRE entries")
return
Expand Down Expand Up @@ -156,6 +160,9 @@ func (r *entryReconciler) reconcile(ctx context.Context) {
// drop the current entry from the list so it isn't added to the
// "to delete" list.
if len(s.Current) == 0 {
if preferredEntry.Entry.ID == "" && r.config.EntryIDPrefix != "" {
preferredEntry.Entry.ID = fmt.Sprintf("%s%s", r.config.EntryIDPrefix, uuid.New())
}
toCreate = append(toCreate, preferredEntry)
} else {
preferredEntry.Entry.ID = s.Current[0].ID
Expand All @@ -172,6 +179,7 @@ func (r *entryReconciler) reconcile(ctx context.Context) {
toDelete = append(toDelete, filterJoinTokenEntries(s.Current)...)
}

toDelete = append(toDelete, deleteOnlyEntries...)
if len(toDelete) > 0 {
r.deleteEntries(ctx, toDelete)
}
Expand Down Expand Up @@ -250,9 +258,43 @@ func (r *entryReconciler) recalculateUnsupportFields(ctx context.Context, log lo
r.nextGetUnsupportedFields = time.Now().Add(10 * time.Minute)
}

func (r *entryReconciler) listEntries(ctx context.Context) ([]spireapi.Entry, error) {
func (r *entryReconciler) shouldProcessOrDeleteEntryID(entry spireapi.Entry) (bool, bool) {
if r.config.EntryIDPrefix == "" {
return true, false
}
if strings.HasPrefix(entry.ID, r.config.EntryIDPrefix) {
return true, false
}
if r.config.EntryIDPrefixCleanup != nil {
cleanupPrefix := *r.config.EntryIDPrefixCleanup
if cleanupPrefix == "" {
return false, !strings.Contains(entry.ID, ".")
}
if strings.HasPrefix(entry.ID, cleanupPrefix) {
return false, true
}
}
return false, false
}

func (r *entryReconciler) listEntries(ctx context.Context) ([]spireapi.Entry, []spireapi.Entry, error) {
// TODO: cache?
return r.config.EntryClient.ListEntries(ctx)
var deleteOnlyEntries []spireapi.Entry
var currentEntries []spireapi.Entry
tmpvals, err := r.config.EntryClient.ListEntries(ctx)
if err != nil {
return currentEntries, deleteOnlyEntries, err
}
for _, value := range tmpvals {
proc, del := r.shouldProcessOrDeleteEntryID(value)
if proc {
currentEntries = append(currentEntries, value)
}
if del {
deleteOnlyEntries = append(deleteOnlyEntries, value)
}
}
return currentEntries, deleteOnlyEntries, nil
}

func (r *entryReconciler) getUnsupportedFields(ctx context.Context) (map[spireapi.Field]struct{}, error) {
Expand Down