~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
88 lines · 2.9 KB Raw History
package clusterprovider

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterstack"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/tenantstore"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/materialiser"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/procgroup"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/revocation"
)

var ErrNoRevokedEraser = errors.New(
	"clusterprovider: the identity that owns this cluster is revoked and no datastore eraser is wired, " +
		"so its ServiceAccount would be served by k3s from its first answer: refusing to start k3s")

type RevokedServiceAccounts struct {
	State func(ctx context.Context, did string) (v1alpha1.IdentityState, error)

	Account func(did string) tenantstore.ServiceAccount

	Store tenantstore.Eraser

	Log *slog.Logger
}

func (r *RevokedServiceAccounts) beforeK3s(steps []procgroup.Step, o materialiser.Observed, spec clusterstack.Spec) []procgroup.Step {
	did := o.Cluster.Spec.Account
	if r == nil || did == "" {
		return steps
	}
	for i := range steps {
		if steps[i].Name != procgroup.ComponentK3s {
			continue
		}
		inner := steps[i].Prepare
		steps[i].Prepare = func(ctx context.Context, ps procgroup.Spec) (procgroup.Spec, error) {
			if err := r.erase(ctx, spec, did, o.IdentityState); err != nil {
				return ps, err
			}
			if inner == nil {
				return ps, nil
			}
			return inner(ctx, ps)
		}
	}
	return steps
}

func (r *RevokedServiceAccounts) erase(ctx context.Context, spec clusterstack.Spec, did string, observed v1alpha1.IdentityState) error {
	state := observed
	if r.State != nil {
		fresh, err := r.State(ctx, did)
		if err != nil {
			return fmt.Errorf("clusterprovider: reading whether the identity of %s is revoked before k3s starts: %w", spec.Cluster, err)
		}
		if revocation.Revoked(fresh) {
			state = fresh
		}
	}
	if !revocation.Revoked(state) {
		return nil
	}
	if r.Store == nil || r.Account == nil {
		return fmt.Errorf("%w (cluster %s, identity %s)", ErrNoRevokedEraser, spec.Cluster, state)
	}
	sa := r.Account(did)
	start := time.Now()
	erased, err := r.Store.EraseServiceAccount(ctx, spec.DatastoreEndpoint, sa)
	if err != nil {
		return fmt.Errorf("clusterprovider: erasing the ServiceAccount %s/%s of revoked identity %s from the datastore of %s before k3s starts: %w",
			sa.Namespace, sa.Name, did, spec.Cluster, err)
	}
	log := r.Log
	if log == nil {
		log = slog.Default()
	}
	log.Info("cluster provider: erased the ServiceAccount of a revoked identity from the datastore before k3s starts",
		"cluster", spec.Cluster, "did", did, "state", string(state), "serviceAccount", sa.Namespace+"/"+sa.Name,
		"deleted", erased.ServiceAccount, "tokenSecrets", erased.Secrets, "datastore", spec.DatastoreEndpoint,
		"slot", spec.Slot, "took", time.Since(start).String())
	return nil
}