github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/authenticationworker/worker_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package authenticationworker_test
     5  
     6  import (
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/juju/names/v5"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils/v3/ssh"
    13  	sshtesting "github.com/juju/utils/v3/ssh/testing"
    14  	"github.com/juju/worker/v3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/agent"
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/agent/keyupdater"
    20  	jujutesting "github.com/juju/juju/juju/testing"
    21  	"github.com/juju/juju/state"
    22  	coretesting "github.com/juju/juju/testing"
    23  	"github.com/juju/juju/worker/authenticationworker"
    24  )
    25  
    26  type workerSuite struct {
    27  	jujutesting.JujuConnSuite
    28  	machine       *state.Machine
    29  	keyupdaterAPI *keyupdater.State
    30  
    31  	existingEnvKey string
    32  	existingKeys   []string
    33  }
    34  
    35  var _ = gc.Suite(&workerSuite{})
    36  
    37  func (s *workerSuite) SetUpTest(c *gc.C) {
    38  	s.JujuConnSuite.SetUpTest(c)
    39  	// Default ssh user is currently "ubuntu".
    40  	c.Assert(authenticationworker.SSHUser, gc.Equals, "ubuntu")
    41  	// Set the ssh user to empty (the current user) as required by the test infrastructure.
    42  	s.PatchValue(&authenticationworker.SSHUser, "")
    43  
    44  	// Replace the default dummy key in the test environment with a valid one.
    45  	// This will be added to the ssh authorised keys when the agent starts.
    46  	s.setAuthorisedKeys(c, sshtesting.ValidKeyOne.Key+" firstuser@host")
    47  	// Record the existing key with its prefix for testing later.
    48  	s.existingEnvKey = sshtesting.ValidKeyOne.Key + " Juju:firstuser@host"
    49  
    50  	// Set up an existing key (which is not in the environment) in the ssh authorised_keys file.
    51  	s.existingKeys = []string{sshtesting.ValidKeyTwo.Key + " existinguser@host"}
    52  	err := ssh.AddKeys(authenticationworker.SSHUser, s.existingKeys...)
    53  	c.Assert(err, jc.ErrorIsNil)
    54  
    55  	var apiRoot api.Connection
    56  	apiRoot, s.machine = s.OpenAPIAsNewMachine(c)
    57  	c.Assert(apiRoot, gc.NotNil)
    58  	s.keyupdaterAPI = keyupdater.NewState(apiRoot)
    59  	c.Assert(s.keyupdaterAPI, gc.NotNil)
    60  }
    61  
    62  func stop(c *gc.C, w worker.Worker) {
    63  	c.Assert(worker.Stop(w), gc.IsNil)
    64  }
    65  
    66  type mockConfig struct {
    67  	agent.Config
    68  	c   *gc.C
    69  	tag names.Tag
    70  }
    71  
    72  func (mock *mockConfig) Tag() names.Tag {
    73  	return mock.tag
    74  }
    75  
    76  func agentConfig(c *gc.C, tag names.MachineTag) *mockConfig {
    77  	return &mockConfig{c: c, tag: tag}
    78  }
    79  
    80  func (s *workerSuite) setAuthorisedKeys(c *gc.C, keys ...string) {
    81  	keyStr := strings.Join(keys, "\n")
    82  	err := s.Model.UpdateModelConfig(map[string]interface{}{"authorized-keys": keyStr}, nil)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  }
    85  
    86  func (s *workerSuite) waitSSHKeys(c *gc.C, expected []string) {
    87  	timeout := time.After(coretesting.LongWait)
    88  	for {
    89  		select {
    90  		case <-timeout:
    91  			c.Fatalf("timeout while waiting for authoirsed ssh keys to change")
    92  		case <-time.After(coretesting.ShortWait):
    93  			keys, err := ssh.ListKeys(authenticationworker.SSHUser, ssh.FullKeys)
    94  			c.Assert(err, jc.ErrorIsNil)
    95  			keysStr := strings.Join(keys, "\n")
    96  			expectedStr := strings.Join(expected, "\n")
    97  			if expectedStr != keysStr {
    98  				continue
    99  			}
   100  			return
   101  		}
   102  	}
   103  }
   104  
   105  func (s *workerSuite) TestKeyUpdateRetainsExisting(c *gc.C) {
   106  	authWorker, err := authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	defer stop(c, authWorker)
   109  
   110  	newKey := sshtesting.ValidKeyThree.Key + " user@host"
   111  	s.setAuthorisedKeys(c, newKey)
   112  	newKeyWithCommentPrefix := sshtesting.ValidKeyThree.Key + " Juju:user@host"
   113  	s.waitSSHKeys(c, append(s.existingKeys, newKeyWithCommentPrefix))
   114  }
   115  
   116  func (s *workerSuite) TestNewKeysInJujuAreSavedOnStartup(c *gc.C) {
   117  	newKey := sshtesting.ValidKeyThree.Key + " user@host"
   118  	s.setAuthorisedKeys(c, newKey)
   119  
   120  	authWorker, err := authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	defer stop(c, authWorker)
   123  
   124  	newKeyWithCommentPrefix := sshtesting.ValidKeyThree.Key + " Juju:user@host"
   125  	s.waitSSHKeys(c, append(s.existingKeys, newKeyWithCommentPrefix))
   126  }
   127  
   128  func (s *workerSuite) TestDeleteKey(c *gc.C) {
   129  	authWorker, err := authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	defer stop(c, authWorker)
   132  
   133  	// Add another key
   134  	anotherKey := sshtesting.ValidKeyThree.Key + " another@host"
   135  	s.setAuthorisedKeys(c, s.existingEnvKey, anotherKey)
   136  	anotherKeyWithCommentPrefix := sshtesting.ValidKeyThree.Key + " Juju:another@host"
   137  	s.waitSSHKeys(c, append(s.existingKeys, s.existingEnvKey, anotherKeyWithCommentPrefix))
   138  
   139  	// Delete the original key and check anotherKey plus the existing keys remain.
   140  	s.setAuthorisedKeys(c, anotherKey)
   141  	s.waitSSHKeys(c, append(s.existingKeys, anotherKeyWithCommentPrefix))
   142  }
   143  
   144  func (s *workerSuite) TestMultipleChanges(c *gc.C) {
   145  	authWorker, err := authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	defer stop(c, authWorker)
   148  	s.waitSSHKeys(c, append(s.existingKeys, s.existingEnvKey))
   149  
   150  	// Perform a set to add a key and delete a key.
   151  	// added: key 3
   152  	// deleted: key 1 (existing env key)
   153  	s.setAuthorisedKeys(c, sshtesting.ValidKeyThree.Key+" yetanother@host")
   154  	yetAnotherKeyWithComment := sshtesting.ValidKeyThree.Key + " Juju:yetanother@host"
   155  	s.waitSSHKeys(c, append(s.existingKeys, yetAnotherKeyWithComment))
   156  }
   157  
   158  func (s *workerSuite) TestWorkerRestart(c *gc.C) {
   159  	authWorker, err := authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	defer stop(c, authWorker)
   162  	s.waitSSHKeys(c, append(s.existingKeys, s.existingEnvKey))
   163  
   164  	// Stop the worker and delete and add keys from the environment while it is down.
   165  	// added: key 3
   166  	// deleted: key 1 (existing env key)
   167  	stop(c, authWorker)
   168  	s.setAuthorisedKeys(c, sshtesting.ValidKeyThree.Key+" yetanother@host")
   169  
   170  	// Restart the worker and check that the ssh auth keys are as expected.
   171  	authWorker, err = authenticationworker.NewWorker(s.keyupdaterAPI, agentConfig(c, s.machine.Tag().(names.MachineTag)))
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	defer stop(c, authWorker)
   174  
   175  	yetAnotherKeyWithCommentPrefix := sshtesting.ValidKeyThree.Key + " Juju:yetanother@host"
   176  	s.waitSSHKeys(c, append(s.existingKeys, yetAnotherKeyWithCommentPrefix))
   177  }