package breakglass import ( "context" "fmt" "os" 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/breakglass" 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" ) type Plugin struct{} var ( readOnlyShadowFilePath = "/host-rofs/etc/shadow" // path to read-only shadow file shadowFilePath = "/host-etc/shadow" // path to shadow file to update ) func (plugin Plugin) Reconcile(ctx context.Context, ienode *v1ien.IENode, cfg config.Config) (reconcile.Result, error) { secret, err := breakglass.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(breakglassSecret *corev1.Secret, ienode *v1ien.IENode, cfg config.Config) error { secret, err := breakglass.FromSecret(breakglassSecret) if err != nil { return fmt.Errorf("error reading breakglass cluster secret: %w", err) } shadowFileEntry := secret.String() changed, err := write(shadowFileEntry, secret.Version()) if err != nil { return fmt.Errorf("error applying breakglass secret: %w", err) } if !changed { return nil } terminalID := ienode.ObjectMeta.Labels[node.TerminalIDLabel] // status reporting if ienode.Status.ClusterSecrets == nil { ienode.Status.ClusterSecrets = &v1ien.ClusterSecretInventory{} } ienode.Status.ClusterSecrets.AddClusterSecret(breakglass.HashedSecretName, secret.Version(), terminalID) eventRecorder := cfg.GetEventRecorder() clustersecrets.RecordClusterSecretEvent(eventRecorder, breakglassSecret, secret, terminalID) return nil } // write will take a shadow file entry and attempt to update the shadow file. // This is a safe write so the file is temporarily written to tmp directory and the // entire file is copied to /etc/shadow. func write(shadowFileEntry, version string) (bool, error) { baseFile, err := os.ReadFile(readOnlyShadowFilePath) if err != nil { return false, err } currentContents, err := os.ReadFile(shadowFilePath) if err != nil { return false, err } hasChanged, contents := breakglass.UpsertEntry(currentContents, baseFile, shadowFileEntry) if !hasChanged { return false, nil } // audit log indicating cluster secret is taking effect auditLog := audit.New("nodeagent") auditLog.Log(model.ClusterSecretTypeBreakglass, audit.SecretApplied, "action_by", "nodeagent", "version", version) shadowFileInfo, err := os.Stat(shadowFilePath) if err != nil { return false, fmt.Errorf("error stat on file: %v", err) } file, err := os.OpenFile(shadowFilePath, os.O_WRONLY|os.O_TRUNC, shadowFileInfo.Mode()) if err != nil { return false, fmt.Errorf("error opening file: %v", err) } defer file.Close() _, err = file.Write(contents) return err == nil, err }