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 }