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

     1  // Copyright 2022 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secretexpire_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock/testclock"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names/v5"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/worker/v3/workertest"
    14  	"go.uber.org/mock/gomock"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/core/secrets"
    18  	corewatcher "github.com/juju/juju/core/watcher"
    19  	"github.com/juju/juju/testing"
    20  	"github.com/juju/juju/worker/secretexpire"
    21  	"github.com/juju/juju/worker/secretexpire/mocks"
    22  	rotatemocks "github.com/juju/juju/worker/secretrotate/mocks"
    23  )
    24  
    25  type workerSuite struct {
    26  	testing.BaseSuite
    27  
    28  	clock  *testclock.Clock
    29  	config secretexpire.Config
    30  
    31  	facade              *mocks.MockSecretManagerFacade
    32  	triggerWatcher      *rotatemocks.MockSecretTriggerWatcher
    33  	expiryConfigChanges chan []corewatcher.SecretTriggerChange
    34  	expiredSecrets      chan []string
    35  }
    36  
    37  var _ = gc.Suite(&workerSuite{})
    38  
    39  func (s *workerSuite) SetUpTest(c *gc.C) {
    40  	s.BaseSuite.SetUpTest(c)
    41  }
    42  
    43  func (s *workerSuite) TearDownTest(c *gc.C) {
    44  	s.BaseSuite.TearDownTest(c)
    45  }
    46  
    47  func (s *workerSuite) setup(c *gc.C) *gomock.Controller {
    48  	ctrl := gomock.NewController(c)
    49  
    50  	s.clock = testclock.NewClock(time.Now())
    51  	s.facade = mocks.NewMockSecretManagerFacade(ctrl)
    52  	s.triggerWatcher = rotatemocks.NewMockSecretTriggerWatcher(ctrl)
    53  	s.expiryConfigChanges = make(chan []corewatcher.SecretTriggerChange)
    54  	s.expiredSecrets = make(chan []string, 5)
    55  	s.config = secretexpire.Config{
    56  		Clock:               s.clock,
    57  		SecretManagerFacade: s.facade,
    58  		Logger:              loggo.GetLogger("test"),
    59  		SecretOwners:        []names.Tag{names.NewApplicationTag("mariadb")},
    60  		ExpireRevisions:     s.expiredSecrets,
    61  	}
    62  	return ctrl
    63  }
    64  
    65  func (s *workerSuite) TestValidateConfig(c *gc.C) {
    66  	_ = s.setup(c)
    67  
    68  	s.testValidateConfig(c, func(config *secretexpire.Config) {
    69  		config.SecretManagerFacade = nil
    70  	}, `nil Facade not valid`)
    71  
    72  	s.testValidateConfig(c, func(config *secretexpire.Config) {
    73  		config.SecretOwners = nil
    74  	}, `empty SecretOwners not valid`)
    75  
    76  	s.testValidateConfig(c, func(config *secretexpire.Config) {
    77  		config.ExpireRevisions = nil
    78  	}, `nil ExpireRevisionsChannel not valid`)
    79  
    80  	s.testValidateConfig(c, func(config *secretexpire.Config) {
    81  		config.Logger = nil
    82  	}, `nil Logger not valid`)
    83  
    84  	s.testValidateConfig(c, func(config *secretexpire.Config) {
    85  		config.Clock = nil
    86  	}, `nil Clock not valid`)
    87  }
    88  
    89  func (s *workerSuite) testValidateConfig(c *gc.C, f func(*secretexpire.Config), expect string) {
    90  	config := s.config
    91  	f(&config)
    92  	c.Check(config.Validate(), gc.ErrorMatches, expect)
    93  }
    94  
    95  func (s *workerSuite) expectWorker() {
    96  	s.facade.EXPECT().WatchSecretRevisionsExpiryChanges(s.config.SecretOwners).Return(s.triggerWatcher, nil)
    97  	s.triggerWatcher.EXPECT().Changes().AnyTimes().Return(s.expiryConfigChanges)
    98  	s.triggerWatcher.EXPECT().Kill().MaxTimes(1)
    99  	s.triggerWatcher.EXPECT().Wait().Return(nil).MinTimes(1)
   100  }
   101  
   102  func (s *workerSuite) TestStartStop(c *gc.C) {
   103  	ctrl := s.setup(c)
   104  	defer ctrl.Finish()
   105  
   106  	s.expectWorker()
   107  
   108  	w, err := secretexpire.New(s.config)
   109  	c.Assert(err, jc.ErrorIsNil)
   110  
   111  	workertest.CheckAlive(c, w)
   112  	workertest.CleanKill(c, w)
   113  }
   114  
   115  func (s *workerSuite) advanceClock(c *gc.C, d time.Duration) {
   116  	err := s.clock.WaitAdvance(d, testing.LongWait, 1)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  }
   119  
   120  func (s *workerSuite) expectNoExpiry(c *gc.C) {
   121  	select {
   122  	case uris := <-s.expiredSecrets:
   123  		c.Fatalf("got unexpected secret expiry %q", uris)
   124  	case <-time.After(testing.ShortWait):
   125  	}
   126  }
   127  
   128  func (s *workerSuite) expectExpired(c *gc.C, expected ...string) {
   129  	select {
   130  	case uris, ok := <-s.expiredSecrets:
   131  		c.Assert(ok, jc.IsTrue)
   132  		c.Assert(uris, jc.SameContents, expected)
   133  	case <-time.After(testing.LongWait):
   134  		c.Fatal("timed out waiting for secrets to be expired")
   135  	}
   136  }
   137  
   138  func (s *workerSuite) TestExpires(c *gc.C) {
   139  	ctrl := s.setup(c)
   140  	defer ctrl.Finish()
   141  
   142  	s.expectWorker()
   143  
   144  	w, err := secretexpire.New(s.config)
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	defer workertest.CleanKill(c, w)
   147  
   148  	now := s.clock.Now()
   149  	next := now.Add(time.Hour)
   150  	uri := secrets.NewURI()
   151  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   152  		URI:             uri,
   153  		Revision:        666,
   154  		NextTriggerTime: next,
   155  	}}
   156  	s.advanceClock(c, time.Hour)
   157  	s.expectExpired(c, uri.ID+"/666")
   158  }
   159  
   160  func (s *workerSuite) TestRetrigger(c *gc.C) {
   161  	ctrl := s.setup(c)
   162  	defer ctrl.Finish()
   163  
   164  	s.expectWorker()
   165  
   166  	w, err := secretexpire.New(s.config)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	defer workertest.CleanKill(c, w)
   169  
   170  	now := s.clock.Now()
   171  	next := now.Add(time.Hour)
   172  	uri := secrets.NewURI()
   173  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   174  		URI:             uri,
   175  		Revision:        666,
   176  		NextTriggerTime: next,
   177  	}}
   178  	s.advanceClock(c, time.Hour)
   179  	s.expectExpired(c, uri.ID+"/666")
   180  
   181  	// Secret not removed, will retrigger in 5 minutes.
   182  	s.advanceClock(c, 2*time.Minute)
   183  	s.expectNoExpiry(c)
   184  
   185  	s.advanceClock(c, 3*time.Minute)
   186  	s.expectExpired(c, uri.ID+"/666")
   187  
   188  	// Remove secret, will not retrigger.
   189  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   190  		URI:      uri,
   191  		Revision: 666,
   192  	}}
   193  	s.advanceClock(c, 5*time.Minute)
   194  	s.expectNoExpiry(c)
   195  }
   196  
   197  func (s *workerSuite) TestSecretUpdateBeforeExpires(c *gc.C) {
   198  	ctrl := s.setup(c)
   199  	defer ctrl.Finish()
   200  
   201  	s.expectWorker()
   202  
   203  	w, err := secretexpire.New(s.config)
   204  	c.Assert(err, jc.ErrorIsNil)
   205  	defer workertest.CleanKill(c, w)
   206  
   207  	now := s.clock.Now()
   208  	uri := secrets.NewURI()
   209  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   210  		URI:             uri,
   211  		Revision:        666,
   212  		NextTriggerTime: now.Add(2 * time.Hour),
   213  	}}
   214  	s.advanceClock(c, time.Hour)
   215  	s.expectNoExpiry(c)
   216  
   217  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   218  		URI:             uri,
   219  		Revision:        666,
   220  		NextTriggerTime: now.Add(time.Hour),
   221  	}}
   222  	s.advanceClock(c, 2*time.Hour)
   223  	s.expectExpired(c, uri.ID+"/666")
   224  }
   225  
   226  func (s *workerSuite) TestSecretUpdateBeforeExpiresNotTriggered(c *gc.C) {
   227  	ctrl := s.setup(c)
   228  	defer ctrl.Finish()
   229  
   230  	s.expectWorker()
   231  
   232  	w, err := secretexpire.New(s.config)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	defer workertest.CleanKill(c, w)
   235  
   236  	now := s.clock.Now()
   237  	uri := secrets.NewURI()
   238  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   239  		URI:             uri,
   240  		Revision:        666,
   241  		NextTriggerTime: now.Add(time.Hour),
   242  	}}
   243  	s.advanceClock(c, 30*time.Minute)
   244  	s.expectNoExpiry(c)
   245  
   246  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   247  		URI:             uri,
   248  		Revision:        666,
   249  		NextTriggerTime: now.Add(2 * time.Hour),
   250  	}}
   251  	s.advanceClock(c, 30*time.Minute)
   252  	s.expectNoExpiry(c)
   253  
   254  	// Final sanity check.
   255  	s.advanceClock(c, time.Hour)
   256  	s.expectExpired(c, uri.ID+"/666")
   257  }
   258  
   259  func (s *workerSuite) TestNewSecretTriggersBefore(c *gc.C) {
   260  	ctrl := s.setup(c)
   261  	defer ctrl.Finish()
   262  
   263  	s.expectWorker()
   264  
   265  	w, err := secretexpire.New(s.config)
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	defer workertest.CleanKill(c, w)
   268  
   269  	now := s.clock.Now()
   270  	uri := secrets.NewURI()
   271  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   272  		URI:             uri,
   273  		Revision:        666,
   274  		NextTriggerTime: now.Add(time.Hour),
   275  	}}
   276  	s.advanceClock(c, 15*time.Minute)
   277  	s.expectNoExpiry(c)
   278  
   279  	// New secret with earlier expiry time triggers first.
   280  	uri2 := secrets.NewURI()
   281  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   282  		URI:             uri2,
   283  		Revision:        667,
   284  		NextTriggerTime: now.Add(30 * time.Minute),
   285  	}}
   286  	time.Sleep(testing.ShortWait) // ensure advanceClock happens after timer is updated
   287  	s.advanceClock(c, 15*time.Minute)
   288  	s.expectExpired(c, uri2.ID+"/667")
   289  
   290  	s.advanceClock(c, 30*time.Minute)
   291  	s.expectExpired(c, uri.ID+"/666", uri2.ID+"/667")
   292  }
   293  
   294  func (s *workerSuite) TestManySecretsTrigger(c *gc.C) {
   295  	ctrl := s.setup(c)
   296  	defer ctrl.Finish()
   297  
   298  	s.expectWorker()
   299  
   300  	w, err := secretexpire.New(s.config)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	defer workertest.CleanKill(c, w)
   303  
   304  	now := s.clock.Now()
   305  	next := now.Add(time.Hour)
   306  	uri := secrets.NewURI()
   307  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   308  		URI:             uri,
   309  		Revision:        666,
   310  		NextTriggerTime: next,
   311  	}}
   312  	s.advanceClock(c, time.Second) // ensure some fake time has elapsed
   313  
   314  	uri2 := secrets.NewURI()
   315  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   316  		URI:             uri2,
   317  		Revision:        667,
   318  		NextTriggerTime: next,
   319  	}}
   320  
   321  	s.advanceClock(c, 90*time.Minute)
   322  	s.expectExpired(c, uri.ID+"/666", uri2.ID+"/667")
   323  }
   324  
   325  func (s *workerSuite) TestDeleteSecretExpiry(c *gc.C) {
   326  	ctrl := s.setup(c)
   327  	defer ctrl.Finish()
   328  
   329  	s.expectWorker()
   330  
   331  	w, err := secretexpire.New(s.config)
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	defer workertest.CleanKill(c, w)
   334  
   335  	now := s.clock.Now()
   336  	uri := secrets.NewURI()
   337  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   338  		URI:             uri,
   339  		Revision:        666,
   340  		NextTriggerTime: now.Add(time.Hour),
   341  	}}
   342  	s.advanceClock(c, 30*time.Minute)
   343  	s.expectNoExpiry(c)
   344  
   345  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   346  		URI:      uri,
   347  		Revision: 666,
   348  	}}
   349  	s.advanceClock(c, 30*time.Hour)
   350  	s.expectNoExpiry(c)
   351  }
   352  
   353  func (s *workerSuite) TestManySecretsDeleteOne(c *gc.C) {
   354  	ctrl := s.setup(c)
   355  	defer ctrl.Finish()
   356  
   357  	s.expectWorker()
   358  
   359  	w, err := secretexpire.New(s.config)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	defer workertest.CleanKill(c, w)
   362  
   363  	now := s.clock.Now()
   364  	next := now.Add(time.Hour)
   365  	uri := secrets.NewURI()
   366  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   367  		URI:             uri,
   368  		Revision:        666,
   369  		NextTriggerTime: next,
   370  	}}
   371  	s.advanceClock(c, time.Second) // ensure some fake time has elapsed
   372  
   373  	uri2 := secrets.NewURI()
   374  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   375  		URI:             uri2,
   376  		Revision:        667,
   377  		NextTriggerTime: next,
   378  	}}
   379  	s.advanceClock(c, 15*time.Minute)
   380  	s.expectNoExpiry(c)
   381  
   382  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   383  		URI:      uri2,
   384  		Revision: 667,
   385  	}}
   386  	s.advanceClock(c, 15*time.Minute)
   387  	// Secret 2 would have expired here.
   388  	s.expectNoExpiry(c)
   389  
   390  	s.advanceClock(c, 30*time.Minute)
   391  	s.expectExpired(c, uri.ID+"/666")
   392  }
   393  
   394  func (s *workerSuite) TestExpiryGranularity(c *gc.C) {
   395  	ctrl := s.setup(c)
   396  	defer ctrl.Finish()
   397  
   398  	s.expectWorker()
   399  
   400  	w, err := secretexpire.New(s.config)
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	defer workertest.CleanKill(c, w)
   403  
   404  	now := s.clock.Now()
   405  	uri := secrets.NewURI()
   406  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   407  		URI:             uri,
   408  		Revision:        666,
   409  		NextTriggerTime: now.Add(25 * time.Second),
   410  	}}
   411  	s.advanceClock(c, time.Second) // ensure some fake time has elapsed
   412  
   413  	uri2 := secrets.NewURI()
   414  	s.expiryConfigChanges <- []corewatcher.SecretTriggerChange{{
   415  		URI:             uri2,
   416  		Revision:        667,
   417  		NextTriggerTime: now.Add(39 * time.Second),
   418  	}}
   419  	// First secret won't expire before the one minute granularity.
   420  	s.advanceClock(c, 46*time.Second)
   421  	s.expectExpired(c, uri.ID+"/666", uri2.ID+"/667")
   422  }