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

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secretbackendrotate_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock/testclock"
    10  	"github.com/juju/loggo"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/worker/v3/workertest"
    13  	"go.uber.org/mock/gomock"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	corewatcher "github.com/juju/juju/core/watcher"
    17  	"github.com/juju/juju/testing"
    18  	"github.com/juju/juju/worker/secretbackendrotate"
    19  	"github.com/juju/juju/worker/secretbackendrotate/mocks"
    20  )
    21  
    22  type workerSuite struct {
    23  	testing.BaseSuite
    24  
    25  	clock  testclock.AdvanceableClock
    26  	config secretbackendrotate.Config
    27  
    28  	facade              *mocks.MockSecretBackendManagerFacade
    29  	rotateWatcher       *mocks.MockSecretBackendRotateWatcher
    30  	rotateConfigChanges chan []corewatcher.SecretBackendRotateChange
    31  	rotatedTokens       chan []string
    32  }
    33  
    34  var _ = gc.Suite(&workerSuite{})
    35  
    36  func (s *workerSuite) SetUpTest(c *gc.C) {
    37  	s.BaseSuite.SetUpTest(c)
    38  }
    39  
    40  func (s *workerSuite) TearDownTest(c *gc.C) {
    41  	s.BaseSuite.TearDownTest(c)
    42  }
    43  
    44  func (s *workerSuite) setup(c *gc.C) *gomock.Controller {
    45  	ctrl := gomock.NewController(c)
    46  
    47  	s.clock = testclock.NewDilatedWallClock(100 * time.Millisecond)
    48  	s.facade = mocks.NewMockSecretBackendManagerFacade(ctrl)
    49  	s.rotateWatcher = mocks.NewMockSecretBackendRotateWatcher(ctrl)
    50  	s.rotateConfigChanges = make(chan []corewatcher.SecretBackendRotateChange)
    51  	s.rotatedTokens = make(chan []string, 5)
    52  	s.config = secretbackendrotate.Config{
    53  		Clock:                      s.clock,
    54  		SecretBackendManagerFacade: s.facade,
    55  		Logger:                     loggo.GetLogger("test"),
    56  	}
    57  	return ctrl
    58  }
    59  
    60  func (s *workerSuite) TestValidateConfig(c *gc.C) {
    61  	_ = s.setup(c)
    62  
    63  	s.testValidateConfig(c, func(config *secretbackendrotate.Config) {
    64  		config.SecretBackendManagerFacade = nil
    65  	}, `nil Facade not valid`)
    66  
    67  	s.testValidateConfig(c, func(config *secretbackendrotate.Config) {
    68  		config.Logger = nil
    69  	}, `nil Logger not valid`)
    70  
    71  	s.testValidateConfig(c, func(config *secretbackendrotate.Config) {
    72  		config.Clock = nil
    73  	}, `nil Clock not valid`)
    74  }
    75  
    76  func (s *workerSuite) testValidateConfig(c *gc.C, f func(*secretbackendrotate.Config), expect string) {
    77  	config := s.config
    78  	f(&config)
    79  	c.Check(config.Validate(), gc.ErrorMatches, expect)
    80  }
    81  
    82  func (s *workerSuite) expectWorker() {
    83  	s.facade.EXPECT().WatchTokenRotationChanges().Return(s.rotateWatcher, nil)
    84  	s.rotateWatcher.EXPECT().Changes().AnyTimes().Return(s.rotateConfigChanges)
    85  	s.rotateWatcher.EXPECT().Kill().MaxTimes(1)
    86  	s.rotateWatcher.EXPECT().Wait().Return(nil).MinTimes(1)
    87  
    88  	s.facade.EXPECT().RotateBackendTokens(gomock.Any()).DoAndReturn(
    89  		func(ids ...string) error {
    90  			s.rotatedTokens <- ids
    91  			return nil
    92  		},
    93  	).AnyTimes()
    94  }
    95  
    96  func (s *workerSuite) TestStartStop(c *gc.C) {
    97  	ctrl := s.setup(c)
    98  	defer ctrl.Finish()
    99  
   100  	s.expectWorker()
   101  
   102  	w, err := secretbackendrotate.NewWorker(s.config)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  
   105  	workertest.CheckAlive(c, w)
   106  	workertest.CleanKill(c, w)
   107  }
   108  
   109  func (s *workerSuite) expectRotated(c *gc.C, expected ...string) {
   110  	select {
   111  	case ids, ok := <-s.rotatedTokens:
   112  		c.Assert(ok, jc.IsTrue)
   113  		c.Assert(ids, jc.SameContents, expected)
   114  	case <-time.After(testing.LongWait):
   115  		c.Fatal("timed out waiting for token to be rotated")
   116  	}
   117  }
   118  
   119  func (s *workerSuite) expectNoRotates(c *gc.C) {
   120  	select {
   121  	case ids := <-s.rotatedTokens:
   122  		c.Fatalf("got unexpected secret rotation %q", ids)
   123  	case <-time.After(testing.ShortWait):
   124  	}
   125  }
   126  
   127  func (s *workerSuite) TestFirstToken(c *gc.C) {
   128  	ctrl := s.setup(c)
   129  	defer ctrl.Finish()
   130  
   131  	s.expectWorker()
   132  
   133  	w, err := secretbackendrotate.NewWorker(s.config)
   134  	c.Assert(err, jc.ErrorIsNil)
   135  	defer workertest.CleanKill(c, w)
   136  
   137  	now := s.clock.Now()
   138  	next := now.Add(time.Hour)
   139  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   140  		ID:              "some-backend-id",
   141  		Name:            "some-backend",
   142  		NextTriggerTime: next,
   143  	}}
   144  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   145  	s.clock.Advance(time.Hour)
   146  
   147  	s.expectRotated(c, "some-backend-id")
   148  }
   149  
   150  func (s *workerSuite) TestBackendUpdateBeforeRotate(c *gc.C) {
   151  	ctrl := s.setup(c)
   152  	defer ctrl.Finish()
   153  
   154  	s.expectWorker()
   155  
   156  	w, err := secretbackendrotate.NewWorker(s.config)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	defer workertest.CleanKill(c, w)
   159  
   160  	now := s.clock.Now()
   161  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   162  		ID:              "some-backend-id",
   163  		Name:            "some-backend",
   164  		NextTriggerTime: now.Add(2 * time.Hour),
   165  	}}
   166  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   167  	s.clock.Advance(time.Hour)
   168  	s.expectNoRotates(c)
   169  
   170  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   171  		ID:              "some-backend-id",
   172  		Name:            "some-backend",
   173  		NextTriggerTime: now.Add(time.Hour),
   174  	}}
   175  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   176  	s.clock.Advance(2 * time.Hour)
   177  	s.expectRotated(c, "some-backend-id")
   178  }
   179  
   180  func (s *workerSuite) TestUpdateBeforeRotateNotTriggered(c *gc.C) {
   181  	ctrl := s.setup(c)
   182  	defer ctrl.Finish()
   183  
   184  	s.expectWorker()
   185  
   186  	w, err := secretbackendrotate.NewWorker(s.config)
   187  	c.Assert(err, jc.ErrorIsNil)
   188  	defer workertest.CleanKill(c, w)
   189  
   190  	now := s.clock.Now()
   191  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   192  		ID:              "some-backend-id",
   193  		Name:            "some-backend",
   194  		NextTriggerTime: now.Add(time.Hour),
   195  	}}
   196  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   197  	s.clock.Advance(30 * time.Minute)
   198  	s.expectNoRotates(c)
   199  
   200  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   201  		ID:              "some-backend-id",
   202  		Name:            "some-backend",
   203  		NextTriggerTime: now.Add(2 * time.Hour),
   204  	}}
   205  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   206  	s.clock.Advance(30 * time.Minute)
   207  	s.expectNoRotates(c)
   208  
   209  	// Final sanity check.
   210  	s.clock.Advance(time.Hour)
   211  	s.expectRotated(c, "some-backend-id")
   212  }
   213  
   214  func (s *workerSuite) TestNewBackendTriggersBefore(c *gc.C) {
   215  	ctrl := s.setup(c)
   216  	defer ctrl.Finish()
   217  
   218  	s.expectWorker()
   219  
   220  	w, err := secretbackendrotate.NewWorker(s.config)
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	defer workertest.CleanKill(c, w)
   223  
   224  	now := s.clock.Now()
   225  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   226  		ID:              "some-backend-id",
   227  		Name:            "some-backend",
   228  		NextTriggerTime: now.Add(time.Hour),
   229  	}}
   230  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   231  	s.clock.Advance(15 * time.Minute)
   232  	s.expectNoRotates(c)
   233  
   234  	// New secret with earlier rotate time triggers first.
   235  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   236  		ID:              "some-backend-id2",
   237  		Name:            "some-backend2",
   238  		NextTriggerTime: now.Add(30 * time.Minute),
   239  	}}
   240  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   241  	s.clock.Advance(15 * time.Minute)
   242  	s.expectRotated(c, "some-backend-id2")
   243  
   244  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   245  	s.clock.Advance(30 * time.Minute)
   246  	s.expectRotated(c, "some-backend-id")
   247  }
   248  
   249  func (s *workerSuite) TestManyBackendsTrigger(c *gc.C) {
   250  	ctrl := s.setup(c)
   251  	defer ctrl.Finish()
   252  
   253  	s.expectWorker()
   254  
   255  	w, err := secretbackendrotate.NewWorker(s.config)
   256  	c.Assert(err, jc.ErrorIsNil)
   257  	defer workertest.CleanKill(c, w)
   258  
   259  	now := s.clock.Now()
   260  	next := now.Add(time.Hour)
   261  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   262  		ID:              "some-backend-id",
   263  		Name:            "some-backend",
   264  		NextTriggerTime: next,
   265  	}}
   266  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   267  
   268  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   269  		ID:              "some-backend-id2",
   270  		Name:            "some-backend2",
   271  		NextTriggerTime: next,
   272  	}}
   273  
   274  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   275  	s.clock.Advance(90 * time.Minute)
   276  	s.expectRotated(c, "some-backend-id", "some-backend-id2")
   277  }
   278  
   279  func (s *workerSuite) TestDeleteBackendRotation(c *gc.C) {
   280  	ctrl := s.setup(c)
   281  	defer ctrl.Finish()
   282  
   283  	s.expectWorker()
   284  
   285  	w, err := secretbackendrotate.NewWorker(s.config)
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	defer workertest.CleanKill(c, w)
   288  
   289  	now := s.clock.Now()
   290  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   291  		ID:              "some-backend-id",
   292  		Name:            "some-backend",
   293  		NextTriggerTime: now.Add(time.Hour),
   294  	}}
   295  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   296  	s.clock.Advance(30 * time.Minute)
   297  	s.expectNoRotates(c)
   298  
   299  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   300  		ID:   "some-backend-id",
   301  		Name: "some-backend",
   302  	}}
   303  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   304  	s.clock.Advance(30 * time.Hour)
   305  	s.expectNoRotates(c)
   306  }
   307  
   308  func (s *workerSuite) TestManyBackendsDeleteOne(c *gc.C) {
   309  	ctrl := s.setup(c)
   310  	defer ctrl.Finish()
   311  
   312  	s.expectWorker()
   313  
   314  	w, err := secretbackendrotate.NewWorker(s.config)
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	defer workertest.CleanKill(c, w)
   317  
   318  	now := s.clock.Now()
   319  	next := now.Add(time.Hour)
   320  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   321  		ID:              "some-backend-id",
   322  		Name:            "some-backend",
   323  		NextTriggerTime: next,
   324  	}}
   325  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   326  
   327  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   328  		ID:              "some-backend-id2",
   329  		Name:            "some-backend2",
   330  		NextTriggerTime: next,
   331  	}}
   332  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   333  	s.clock.Advance(15 * time.Minute)
   334  	s.expectNoRotates(c)
   335  
   336  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   337  		ID:   "some-backend-id2",
   338  		Name: "some-backend2",
   339  	}}
   340  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   341  	s.clock.Advance(15 * time.Minute)
   342  	// Secret 2 would have rotated here.
   343  	s.expectNoRotates(c)
   344  
   345  	s.clock.Advance(30 * time.Minute)
   346  	s.expectRotated(c, "some-backend-id")
   347  }
   348  
   349  func (s *workerSuite) TestRotateGranularity(c *gc.C) {
   350  	ctrl := s.setup(c)
   351  	defer ctrl.Finish()
   352  
   353  	s.expectWorker()
   354  
   355  	w, err := secretbackendrotate.NewWorker(s.config)
   356  	c.Assert(err, jc.ErrorIsNil)
   357  	defer workertest.CleanKill(c, w)
   358  
   359  	now := s.clock.Now()
   360  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   361  		ID:              "some-backend-id",
   362  		Name:            "some-backend",
   363  		NextTriggerTime: now.Add(25 * time.Second),
   364  	}}
   365  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   366  
   367  	s.rotateConfigChanges <- []corewatcher.SecretBackendRotateChange{{
   368  		ID:              "some-backend-id2",
   369  		Name:            "some-backend2",
   370  		NextTriggerTime: now.Add(39 * time.Second),
   371  	}}
   372  	time.Sleep(100 * time.Millisecond) // ensure advanceClock happens after timer is updated
   373  	// First secret won't rotate before the one minute granularity.
   374  	s.clock.Advance(46 * time.Second)
   375  	s.expectRotated(c, "some-backend-id", "some-backend-id2")
   376  }