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 }