github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/keymanager/keymanager_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package keymanager_test
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/names/v5"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/v3/ssh"
    14  	sshtesting "github.com/juju/utils/v3/ssh/testing"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/apiserver/errors"
    19  	"github.com/juju/juju/apiserver/facades/client/keymanager"
    20  	"github.com/juju/juju/apiserver/facades/client/keymanager/mocks"
    21  	keymanagertesting "github.com/juju/juju/apiserver/facades/client/keymanager/testing"
    22  	apiservertesting "github.com/juju/juju/apiserver/testing"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/rpc/params"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  type keyManagerSuite struct {
    29  	testing.CleanupSuite
    30  
    31  	model        *mocks.MockModel
    32  	blockChecker *mocks.MockBlockChecker
    33  	apiUser      names.UserTag
    34  	api          *keymanager.KeyManagerAPI
    35  
    36  	authorizer apiservertesting.FakeAuthorizer
    37  }
    38  
    39  var _ = gc.Suite(&keyManagerSuite{})
    40  
    41  func (s *keyManagerSuite) SetUpTest(c *gc.C) {
    42  	s.PatchValue(&keymanager.RunSSHImportId, keymanagertesting.FakeImport)
    43  	s.apiUser = names.NewUserTag("admin")
    44  }
    45  
    46  func (s *keyManagerSuite) setup(c *gc.C) *gomock.Controller {
    47  	ctrl := gomock.NewController(c)
    48  	s.model = mocks.NewMockModel(ctrl)
    49  	s.model.EXPECT().ModelTag().Return(coretesting.ModelTag).AnyTimes()
    50  	s.blockChecker = mocks.NewMockBlockChecker(ctrl)
    51  	s.authorizer = apiservertesting.FakeAuthorizer{
    52  		Tag: s.apiUser,
    53  	}
    54  
    55  	s.api = keymanager.NewKeyManagerAPI(s.model, s.authorizer, s.blockChecker, coretesting.ControllerTag)
    56  
    57  	return ctrl
    58  }
    59  
    60  func (s *keyManagerSuite) setAuthorizedKeys(c *gc.C, keys ...string) {
    61  	joined := strings.Join(keys, "\n")
    62  	attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{
    63  		"authorized-keys": joined,
    64  	})
    65  	s.model.EXPECT().ModelConfig().Return(config.New(config.UseDefaults, attrs)).AnyTimes()
    66  }
    67  
    68  func (s *keyManagerSuite) TestListKeys(c *gc.C) {
    69  	defer s.setup(c).Finish()
    70  
    71  	key1 := sshtesting.ValidKeyOne.Key + " user@host"
    72  	key2 := sshtesting.ValidKeyTwo.Key
    73  	s.setAuthorizedKeys(c, key1, key2, "bad key")
    74  
    75  	args := params.ListSSHKeys{
    76  		Entities: params.Entities{Entities: []params.Entity{
    77  			{Tag: names.NewUserTag("admin").String()},
    78  			{Tag: "invalid"},
    79  		}},
    80  		Mode: ssh.FullKeys,
    81  	}
    82  	results, err := s.api.ListKeys(args)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	c.Assert(results, gc.DeepEquals, params.StringsResults{
    85  		Results: []params.StringsResult{
    86  			{Result: []string{key1, key2, "Invalid key: bad key"}},
    87  			{Result: []string{key1, key2, "Invalid key: bad key"}},
    88  		},
    89  	})
    90  }
    91  
    92  func (s *keyManagerSuite) TestListKeysHidesJujuInternal(c *gc.C) {
    93  	defer s.setup(c).Finish()
    94  
    95  	key1 := sshtesting.ValidKeyOne.Key + " juju-client-key"
    96  	key2 := sshtesting.ValidKeyTwo.Key + " " + config.JujuSystemKey
    97  	s.setAuthorizedKeys(c, key1, key2)
    98  
    99  	args := params.ListSSHKeys{
   100  		Entities: params.Entities{Entities: []params.Entity{
   101  			{Tag: names.NewUserTag("admin").String()},
   102  		}},
   103  		Mode: ssh.FullKeys,
   104  	}
   105  	results, err := s.api.ListKeys(args)
   106  	c.Assert(err, jc.ErrorIsNil)
   107  	c.Assert(results, gc.DeepEquals, params.StringsResults{
   108  		Results: []params.StringsResult{
   109  			{Result: nil},
   110  		},
   111  	})
   112  }
   113  
   114  func (s *keyManagerSuite) TestListJujuSystemKey(c *gc.C) {
   115  	defer s.setup(c).Finish()
   116  
   117  	key1 := sshtesting.ValidKeyOne.Key
   118  	s.setAuthorizedKeys(c, key1)
   119  
   120  	args := params.ListSSHKeys{
   121  		Entities: params.Entities{Entities: []params.Entity{
   122  			{Tag: config.JujuSystemKey},
   123  		}},
   124  		Mode: ssh.FullKeys,
   125  	}
   126  	results, err := s.api.ListKeys(args)
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	c.Assert(results.Results, gc.HasLen, 1)
   129  	c.Assert(results.Results[0].Error, gc.ErrorMatches, "permission denied")
   130  }
   131  
   132  func (s *keyManagerSuite) assertAddKeys(c *gc.C) {
   133  	key1 := sshtesting.ValidKeyOne.Key + " user@host"
   134  	key2 := sshtesting.ValidKeyTwo.Key
   135  	s.setAuthorizedKeys(c, key1, key2, "bad key")
   136  
   137  	newKey := sshtesting.ValidKeyThree.Key + " newuser@host"
   138  	newLineKey := sshtesting.ValidKeyFour.Key + " line1\nline2"
   139  
   140  	newAttrs := map[string]interface{}{
   141  		config.AuthorizedKeysKey: strings.Join([]string{key1, key2, "bad key", newKey}, "\n"),
   142  	}
   143  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   144  
   145  	args := params.ModifyUserSSHKeys{
   146  		User: names.NewUserTag("admin").Name(),
   147  		Keys: []string{key2, newKey, newKey, "invalid-key", newLineKey},
   148  	}
   149  	results, err := s.api.AddKeys(args)
   150  	c.Assert(err, jc.ErrorIsNil)
   151  
   152  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   153  		Results: []params.ErrorResult{
   154  			{Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))},
   155  			{Error: nil},
   156  			{Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", newKey))},
   157  			{Error: apiservertesting.ServerError("invalid ssh key: invalid-key")},
   158  			{Error: apiservertesting.ServerError(fmt.Sprintf("invalid ssh key: %s", newLineKey))},
   159  		},
   160  	})
   161  }
   162  
   163  func (s *keyManagerSuite) TestAddKeys(c *gc.C) {
   164  	defer s.setup(c).Finish()
   165  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   166  	s.assertAddKeys(c)
   167  }
   168  
   169  func (s *keyManagerSuite) TestAddKeysSuperUser(c *gc.C) {
   170  	s.apiUser = names.NewUserTag("superuser-fred")
   171  	defer s.setup(c).Finish()
   172  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   173  	s.assertAddKeys(c)
   174  }
   175  
   176  func (s *keyManagerSuite) TestAddKeysModelAdmin(c *gc.C) {
   177  	s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String())
   178  	defer s.setup(c).Finish()
   179  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   180  	s.assertAddKeys(c)
   181  }
   182  
   183  func (s *keyManagerSuite) TestAddKeysNonAuthorised(c *gc.C) {
   184  	s.apiUser = names.NewUserTag("fred")
   185  	defer s.setup(c).Finish()
   186  
   187  	_, err := s.api.AddKeys(params.ModifyUserSSHKeys{})
   188  	c.Assert(err, gc.ErrorMatches, "permission denied")
   189  	c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized)
   190  }
   191  
   192  func (s *keyManagerSuite) TestBlockAddKeys(c *gc.C) {
   193  	defer s.setup(c).Finish()
   194  	s.blockChecker.EXPECT().ChangeAllowed().Return(errors.OperationBlockedError("TestAddKeys"))
   195  
   196  	_, err := s.api.AddKeys(params.ModifyUserSSHKeys{})
   197  
   198  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue)
   199  }
   200  
   201  func (s *keyManagerSuite) TestAddJujuSystemKey(c *gc.C) {
   202  	defer s.setup(c).Finish()
   203  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   204  	s.setAuthorizedKeys(c, sshtesting.ValidKeyOne.Key)
   205  
   206  	newAttrs := map[string]interface{}{
   207  		config.AuthorizedKeysKey: sshtesting.ValidKeyOne.Key,
   208  	}
   209  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   210  
   211  	newKey := sshtesting.ValidKeyThree.Key + " " + config.JujuSystemKey
   212  	args := params.ModifyUserSSHKeys{
   213  		User: names.NewUserTag("admin").Name(),
   214  		Keys: []string{newKey},
   215  	}
   216  	results, err := s.api.AddKeys(args)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   219  		Results: []params.ErrorResult{
   220  			{Error: apiservertesting.ServerError("may not add key with comment juju-system-key: " + newKey)},
   221  		},
   222  	})
   223  }
   224  
   225  func (s *keyManagerSuite) assertDeleteKeys(c *gc.C) {
   226  	key1 := sshtesting.ValidKeyOne.Key + " user@host"
   227  	key2 := sshtesting.ValidKeyTwo.Key
   228  	s.setAuthorizedKeys(c, key1, key2, "bad key 1", "bad key 2")
   229  
   230  	newAttrs := map[string]interface{}{
   231  		config.AuthorizedKeysKey: strings.Join([]string{key1, "bad key 1"}, "\n"),
   232  	}
   233  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   234  
   235  	args := params.ModifyUserSSHKeys{
   236  		User: names.NewUserTag("admin").String(),
   237  		Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, sshtesting.ValidKeyThree.Fingerprint, "invalid-key", "bad key 2"},
   238  	}
   239  	results, err := s.api.DeleteKeys(args)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   242  		Results: []params.ErrorResult{
   243  			{Error: nil},
   244  			{Error: apiservertesting.ServerError("key not found: " + sshtesting.ValidKeyThree.Fingerprint)},
   245  			{Error: apiservertesting.ServerError("key not found: invalid-key")},
   246  			{Error: nil},
   247  		},
   248  	})
   249  }
   250  
   251  func (s *keyManagerSuite) TestDeleteKeys(c *gc.C) {
   252  	defer s.setup(c).Finish()
   253  	s.blockChecker.EXPECT().RemoveAllowed().Return(nil)
   254  	s.assertDeleteKeys(c)
   255  }
   256  
   257  func (s *keyManagerSuite) TestDeleteKeysSuperUser(c *gc.C) {
   258  	s.apiUser = names.NewUserTag("superuser-fred")
   259  	defer s.setup(c).Finish()
   260  	s.blockChecker.EXPECT().RemoveAllowed().Return(nil)
   261  	s.assertDeleteKeys(c)
   262  }
   263  
   264  func (s *keyManagerSuite) TestDeleteKeysModelAdmin(c *gc.C) {
   265  	s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String())
   266  	defer s.setup(c).Finish()
   267  	s.blockChecker.EXPECT().RemoveAllowed().Return(nil)
   268  	s.assertDeleteKeys(c)
   269  }
   270  
   271  func (s *keyManagerSuite) TestDeleteKeysNonAuthorised(c *gc.C) {
   272  	s.apiUser = names.NewUserTag("fred")
   273  	defer s.setup(c).Finish()
   274  
   275  	_, err := s.api.DeleteKeys(params.ModifyUserSSHKeys{})
   276  	c.Assert(err, gc.ErrorMatches, "permission denied")
   277  	c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized)
   278  }
   279  
   280  func (s *keyManagerSuite) TestBlockDeleteKeys(c *gc.C) {
   281  	defer s.setup(c).Finish()
   282  	s.blockChecker.EXPECT().RemoveAllowed().Return(errors.OperationBlockedError("TestDeleteKeys"))
   283  
   284  	_, err := s.api.DeleteKeys(params.ModifyUserSSHKeys{})
   285  
   286  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue)
   287  }
   288  
   289  func (s *keyManagerSuite) TestDeleteJujuSystemKey(c *gc.C) {
   290  	defer s.setup(c).Finish()
   291  	s.blockChecker.EXPECT().RemoveAllowed().Return(nil)
   292  
   293  	key1 := sshtesting.ValidKeyOne.Key + " juju-client-key"
   294  	key2 := sshtesting.ValidKeyTwo.Key + " " + config.JujuSystemKey
   295  	key3 := sshtesting.ValidKeyThree.Key + " a user key"
   296  	s.setAuthorizedKeys(c, key1, key2, key3)
   297  
   298  	newAttrs := map[string]interface{}{
   299  		config.AuthorizedKeysKey: strings.Join([]string{key1, key2, key3}, "\n"),
   300  	}
   301  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   302  
   303  	args := params.ModifyUserSSHKeys{
   304  		User: names.NewUserTag("admin").Name(),
   305  		Keys: []string{"juju-client-key", config.JujuSystemKey},
   306  	}
   307  	results, err := s.api.DeleteKeys(args)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   310  		Results: []params.ErrorResult{
   311  			{Error: apiservertesting.ServerError("may not delete internal key: juju-client-key")},
   312  			{Error: apiservertesting.ServerError("may not delete internal key: " + config.JujuSystemKey)},
   313  		},
   314  	})
   315  }
   316  
   317  // This should be impossible to do anyway since it's impossible to request
   318  // to remove the client and system key
   319  func (s *keyManagerSuite) TestCannotDeleteAllKeys(c *gc.C) {
   320  	defer s.setup(c).Finish()
   321  	s.blockChecker.EXPECT().RemoveAllowed().Return(nil)
   322  
   323  	key1 := sshtesting.ValidKeyOne.Key + " user@host"
   324  	key2 := sshtesting.ValidKeyTwo.Key
   325  	s.setAuthorizedKeys(c, key1, key2)
   326  
   327  	args := params.ModifyUserSSHKeys{
   328  		User: names.NewUserTag("admin").String(),
   329  		Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, "user@host"},
   330  	}
   331  	_, err := s.api.DeleteKeys(args)
   332  	c.Assert(err, gc.ErrorMatches, "cannot delete all keys")
   333  }
   334  
   335  func (s *keyManagerSuite) assertImportKeys(c *gc.C) {
   336  	key1 := sshtesting.ValidKeyOne.Key + " user@host"
   337  	key2 := sshtesting.ValidKeyTwo.Key
   338  	key3 := sshtesting.ValidKeyThree.Key
   339  	key4 := sshtesting.ValidKeyFour.Key
   340  	keymv := strings.Split(sshtesting.ValidKeyMulti, "\n")
   341  	keymp := strings.Split(sshtesting.PartValidKeyMulti, "\n")
   342  	keymi := strings.Split(sshtesting.MultiInvalid, "\n")
   343  	s.setAuthorizedKeys(c, key1, key2, "bad key")
   344  
   345  	newAttrs := map[string]interface{}{
   346  		config.AuthorizedKeysKey: strings.Join([]string{
   347  			key1, key2, "bad key", key3, keymv[0], keymv[1], keymp[0], key4,
   348  		}, "\n"),
   349  	}
   350  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   351  
   352  	args := params.ModifyUserSSHKeys{
   353  		User: names.NewUserTag("admin").String(),
   354  		Keys: []string{
   355  			"lp:existing",
   356  			"lp:validuser",
   357  			"invalid-key",
   358  			"lp:multi",
   359  			"lp:multiempty",
   360  			"lp:multipartial",
   361  			"lp:multiinvalid",
   362  			"lp:multionedup",
   363  		},
   364  	}
   365  	results, err := s.api.ImportKeys(args)
   366  
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	c.Assert(results.Results, gc.HasLen, 8)
   369  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   370  		Results: []params.ErrorResult{
   371  			{Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))},
   372  			{Error: nil},
   373  			{Error: apiservertesting.ServerError("invalid ssh key id: invalid-key")},
   374  			{Error: nil},
   375  			{Error: apiservertesting.ServerError("invalid ssh key id: lp:multiempty")},
   376  			{Error: apiservertesting.ServerError(fmt.Sprintf(
   377  				`invalid ssh key for lp:multipartial: `+
   378  					`generating key fingerprint: `+
   379  					`invalid authorized_key "%s"`, keymp[1]))},
   380  			{Error: apiservertesting.ServerError(fmt.Sprintf(
   381  				`invalid ssh key for lp:multiinvalid: `+
   382  					`generating key fingerprint: `+
   383  					`invalid authorized_key "%s"`+"\n"+
   384  					`invalid ssh key for lp:multiinvalid: `+
   385  					`generating key fingerprint: `+
   386  					`invalid authorized_key "%s"`, keymi[0], keymi[1]))},
   387  			{Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", key2))},
   388  		},
   389  	})
   390  }
   391  
   392  func (s *keyManagerSuite) TestImportKeys(c *gc.C) {
   393  	defer s.setup(c).Finish()
   394  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   395  	s.assertImportKeys(c)
   396  }
   397  
   398  func (s *keyManagerSuite) TestImportKeysSuperUser(c *gc.C) {
   399  	s.apiUser = names.NewUserTag("superuser-fred")
   400  	defer s.setup(c).Finish()
   401  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   402  	s.assertImportKeys(c)
   403  }
   404  
   405  func (s *keyManagerSuite) TestImportKeysModelAdmin(c *gc.C) {
   406  	s.apiUser = names.NewUserTag("admin" + coretesting.ModelTag.String())
   407  	defer s.setup(c).Finish()
   408  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   409  	s.assertImportKeys(c)
   410  }
   411  
   412  func (s *keyManagerSuite) TestImportKeysNonAuthorised(c *gc.C) {
   413  	s.apiUser = names.NewUserTag("fred")
   414  	defer s.setup(c).Finish()
   415  
   416  	_, err := s.api.ImportKeys(params.ModifyUserSSHKeys{})
   417  	c.Assert(err, gc.ErrorMatches, "permission denied")
   418  	c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized)
   419  }
   420  
   421  func (s *keyManagerSuite) TestImportJujuSystemKey(c *gc.C) {
   422  	defer s.setup(c).Finish()
   423  	s.blockChecker.EXPECT().ChangeAllowed().Return(nil)
   424  
   425  	key1 := sshtesting.ValidKeyOne.Key
   426  	s.setAuthorizedKeys(c, key1)
   427  	newAttrs := map[string]interface{}{
   428  		config.AuthorizedKeysKey: key1,
   429  	}
   430  	s.model.EXPECT().UpdateModelConfig(newAttrs, nil)
   431  
   432  	args := params.ModifyUserSSHKeys{
   433  		User: names.NewUserTag("admin").String(),
   434  		Keys: []string{"lp:systemkey"},
   435  	}
   436  	results, err := s.api.ImportKeys(args)
   437  	c.Assert(err, gc.IsNil)
   438  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   439  		Results: []params.ErrorResult{
   440  			{Error: apiservertesting.ServerError("may not add key with comment juju-system-key: " + keymanagertesting.SystemKey)},
   441  		},
   442  	})
   443  }
   444  
   445  func (s *keyManagerSuite) TestBlockImportKeys(c *gc.C) {
   446  	defer s.setup(c).Finish()
   447  	s.blockChecker.EXPECT().ChangeAllowed().Return(errors.OperationBlockedError("TestImportKeys"))
   448  
   449  	_, err := s.api.ImportKeys(params.ModifyUserSSHKeys{})
   450  
   451  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue)
   452  }