package grub import ( "context" "os" "github.com/hashicorp/go-multierror" corev1 "k8s.io/api/core/v1" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/k8s/runtime/controller/reconcile" "edge-infra.dev/pkg/sds/clustersecrets" "edge-infra.dev/pkg/sds/clustersecrets/audit" "edge-infra.dev/pkg/sds/clustersecrets/grub" grubsecret "edge-infra.dev/pkg/sds/clustersecrets/grub" grubfs "edge-infra.dev/pkg/sds/clustersecrets/grub/filesystem" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config" "edge-infra.dev/pkg/sds/ien/node" ) var mount = true // for unit-tests we need to override this to false type Plugin struct{} func (plugin Plugin) Reconcile(ctx context.Context, ienode *v1ien.IENode, cfg config.Config) (reconcile.Result, error) { secret, err := grub.FromClient(ctx, cfg.GetClient()) if err != nil { return reconcile.ResultRequeue, err } if err := apply(secret, ienode, cfg); err != nil { return reconcile.ResultRequeue, err } return reconcile.ResultSuccess, nil } func apply(grubSecret *corev1.Secret, ienode *v1ien.IENode, cfg config.Config) error { secret, err := grub.FromSecret(grubSecret) if err != nil { return err } grubEntry := secret.String() hasChanged, err := write(grubEntry, secret.Version()) if err != nil { return err } if !hasChanged { return nil } terminalID := ienode.ObjectMeta.Labels[node.TerminalIDLabel] // status reporting if ienode.Status.ClusterSecrets == nil { ienode.Status.ClusterSecrets = &v1ien.ClusterSecretInventory{} } ienode.Status.ClusterSecrets.AddClusterSecret(grub.HashedSecretName, secret.Version(), terminalID) eventRecorder := cfg.GetEventRecorder() clustersecrets.RecordClusterSecretEvent(eventRecorder, grubSecret, secret, terminalID) return nil } // write will take a grub entry and attempt to update the grub config file. // This is a safe write so the file is temporarily written to tmp directory and the // entire file is copied to /boot/efi/EFI/grub/grub.cfg or /boot/grub/grub.cfg func write(grubEntry, version string) (hasChanged bool, err error) { rootPath := grubfs.MountPath() filePath := grubfs.GrubFilePath() currentContents, err := os.ReadFile(filePath) // figure out if err != nil { return false, err } hasChanged, contents, err := grubsecret.UpsertEntry(currentContents, grubEntry) if err != nil { return false, err } if !hasChanged { return false, nil } // audit log indicating cluster secret is taking effect auditLog := audit.New("nodeagent") auditLog.Log(model.ClusterSecretTypeGrub, audit.SecretApplied, "action_by", "nodeagent", "version", version) if mount { // remount as read-write cleanup, err := grubfs.Remount(rootPath) if err != nil { return false, err } // remount to read only on defer defer func() { cleanupErr := cleanup() if cleanupErr != nil && err != nil { err = multierror.Append(err, cleanupErr) return } err = cleanupErr }() } if err := os.WriteFile(filePath, contents, grubfs.FileMode); err != nil { return false, err } return true, nil }