github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/hostkeyreporter/worker.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package hostkeyreporter
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/worker/v3"
    14  	"github.com/juju/worker/v3/dependency"
    15  	"gopkg.in/tomb.v2"
    16  
    17  	"github.com/juju/juju/wrench"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.worker.hostkeyreporter")
    21  
    22  // Facade exposes controller functionality to a Worker.
    23  type Facade interface {
    24  	ReportKeys(machineId string, publicKeys []string) error
    25  }
    26  
    27  // Config defines the parameters of the hostkeyreporter worker.
    28  type Config struct {
    29  	Facade    Facade
    30  	MachineId string
    31  	RootDir   string
    32  }
    33  
    34  // Validate returns an error if Config cannot drive a hostkeyreporter.
    35  func (config Config) Validate() error {
    36  	if config.Facade == nil {
    37  		return errors.NotValidf("nil Facade")
    38  	}
    39  	if config.MachineId == "" {
    40  		return errors.NotValidf("empty MachineId")
    41  	}
    42  	return nil
    43  }
    44  
    45  // New returns a Worker backed by config, or an error.
    46  func New(config Config) (worker.Worker, error) {
    47  	if err := config.Validate(); err != nil {
    48  		return nil, errors.Trace(err)
    49  	}
    50  	w := &hostkeyreporter{config: config}
    51  	w.tomb.Go(w.run)
    52  	return w, nil
    53  }
    54  
    55  // Worker waits for a model migration to be active, then locks down the
    56  // configured fortress and implements the migration.
    57  type hostkeyreporter struct {
    58  	tomb   tomb.Tomb
    59  	config Config
    60  }
    61  
    62  // Kill implements worker.Worker.
    63  func (w *hostkeyreporter) Kill() {
    64  	w.tomb.Kill(nil)
    65  }
    66  
    67  // Wait implements worker.Worker.
    68  func (w *hostkeyreporter) Wait() error {
    69  	return w.tomb.Wait()
    70  }
    71  
    72  func (w *hostkeyreporter) run() error {
    73  	if wrench.IsActive("hostkeyreporter", "delay") {
    74  		time.Sleep(time.Minute)
    75  	}
    76  	keys, err := w.readSSHKeys()
    77  	if err != nil {
    78  		return errors.Trace(err)
    79  	}
    80  	if len(keys) < 1 {
    81  		return errors.New("no SSH host keys found")
    82  	}
    83  	err = w.config.Facade.ReportKeys(w.config.MachineId, keys)
    84  	if err != nil {
    85  		return errors.Trace(err)
    86  	}
    87  	logger.Debugf("%d SSH host keys reported for machine %s", len(keys), w.config.MachineId)
    88  	return dependency.ErrUninstall
    89  }
    90  
    91  func (w *hostkeyreporter) readSSHKeys() ([]string, error) {
    92  	sshDir := w.sshDir()
    93  	_, err := os.Stat(sshDir)
    94  	if os.IsNotExist(err) {
    95  		logger.Errorf("%s doesn't exist - giving up", sshDir)
    96  		return nil, dependency.ErrUninstall
    97  	}
    98  	if err != nil {
    99  		return nil, errors.Trace(err)
   100  	}
   101  
   102  	filenames, err := filepath.Glob(sshDir + "/ssh_host_*_key.pub")
   103  	if err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  	keys := make([]string, 0, len(filenames))
   107  	for _, filename := range filenames {
   108  		key, err := os.ReadFile(filename)
   109  		if err != nil {
   110  			logger.Debugf("unable to read SSH host key (skipping): %v", err)
   111  			continue
   112  		}
   113  		keys = append(keys, string(key))
   114  	}
   115  	return keys, nil
   116  }
   117  
   118  func (w *hostkeyreporter) sshDir() string {
   119  	return filepath.Join(w.config.RootDir, "etc", "ssh")
   120  }