github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/secretbackendmanager/backends_test.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package secretbackendmanager_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "go.uber.org/mock/gomock" 15 gc "gopkg.in/check.v1" 16 17 facademocks "github.com/juju/juju/apiserver/facade/mocks" 18 "github.com/juju/juju/apiserver/facades/controller/secretbackendmanager" 19 "github.com/juju/juju/apiserver/facades/controller/secretbackendmanager/mocks" 20 coresecrets "github.com/juju/juju/core/secrets" 21 corewatcher "github.com/juju/juju/core/watcher" 22 "github.com/juju/juju/rpc/params" 23 "github.com/juju/juju/secrets/provider" 24 "github.com/juju/juju/state" 25 ) 26 27 type SecretsManagerSuite struct { 28 testing.IsolationSuite 29 30 authorizer *facademocks.MockAuthorizer 31 resources *facademocks.MockResources 32 33 provider *mocks.MockSecretBackendProvider 34 backendState *mocks.MockBackendState 35 backendRotate *mocks.MockBackendRotate 36 backendRotateWatcher *mocks.MockSecretBackendRotateWatcher 37 clock clock.Clock 38 39 facade *secretbackendmanager.SecretBackendsManagerAPI 40 } 41 42 var _ = gc.Suite(&SecretsManagerSuite{}) 43 44 func (s *SecretsManagerSuite) setup(c *gc.C) *gomock.Controller { 45 ctrl := gomock.NewController(c) 46 47 s.authorizer = facademocks.NewMockAuthorizer(ctrl) 48 s.resources = facademocks.NewMockResources(ctrl) 49 50 s.provider = mocks.NewMockSecretBackendProvider(ctrl) 51 s.backendState = mocks.NewMockBackendState(ctrl) 52 s.backendRotate = mocks.NewMockBackendRotate(ctrl) 53 s.backendRotateWatcher = mocks.NewMockSecretBackendRotateWatcher(ctrl) 54 s.expectAuthController() 55 56 s.clock = testclock.NewClock(time.Now()) 57 58 var err error 59 s.facade, err = secretbackendmanager.NewTestAPI( 60 s.authorizer, s.resources, s.backendState, s.backendRotate, s.clock) 61 c.Assert(err, jc.ErrorIsNil) 62 63 return ctrl 64 } 65 66 func (s *SecretsManagerSuite) expectAuthController() { 67 s.authorizer.EXPECT().AuthController().Return(true) 68 } 69 70 func ptr[T any](v T) *T { 71 return &v 72 } 73 74 func (s *SecretsManagerSuite) TestWatchBackendRotateChanges(c *gc.C) { 75 defer s.setup(c).Finish() 76 77 s.backendRotate.EXPECT().WatchSecretBackendRotationChanges().Return( 78 s.backendRotateWatcher, nil, 79 ) 80 s.resources.EXPECT().Register(s.backendRotateWatcher).Return("1") 81 82 next := time.Now().Add(time.Hour) 83 rotateChan := make(chan []corewatcher.SecretBackendRotateChange, 1) 84 rotateChan <- []corewatcher.SecretBackendRotateChange{{ 85 ID: "backend-id", 86 Name: "myvault", 87 NextTriggerTime: next, 88 }} 89 s.backendRotateWatcher.EXPECT().Changes().Return(rotateChan) 90 91 result, err := s.facade.WatchSecretBackendsRotateChanges() 92 c.Assert(err, jc.ErrorIsNil) 93 c.Assert(result, jc.DeepEquals, params.SecretBackendRotateWatchResult{ 94 WatcherId: "1", 95 Changes: []params.SecretBackendRotateChange{{ 96 ID: "backend-id", 97 Name: "myvault", 98 NextTriggerTime: next, 99 }}, 100 }) 101 } 102 103 type providerWithRefresh struct { 104 provider.ProviderConfig 105 provider.SupportAuthRefresh 106 provider.SecretBackendProvider 107 } 108 109 func (providerWithRefresh) RefreshAuth(adminCfg *provider.ModelBackendConfig, validFor time.Duration) (*provider.BackendConfig, error) { 110 result := *adminCfg 111 result.Config["token"] = validFor.String() 112 return &result.BackendConfig, nil 113 } 114 115 func (s *SecretsManagerSuite) TestRotateBackendTokens(c *gc.C) { 116 ctrl := s.setup(c) 117 defer ctrl.Finish() 118 119 backend := &coresecrets.SecretBackend{ 120 BackendType: "vault", 121 TokenRotateInterval: ptr(200 * time.Minute), 122 Config: map[string]interface{}{"foo": "bar"}, 123 } 124 s.backendState.EXPECT().GetSecretBackendByID("backend-id").Return(backend, nil) 125 126 p := mocks.NewMockSecretBackendProvider(ctrl) 127 s.PatchValue(&secretbackendmanager.GetProvider, func(string) (provider.SecretBackendProvider, error) { 128 return providerWithRefresh{ 129 SecretBackendProvider: p, 130 }, nil 131 }) 132 s.backendState.EXPECT().UpdateSecretBackend(state.UpdateSecretBackendParams{ 133 ID: "backend-id", 134 Config: map[string]interface{}{ 135 "foo": "bar", 136 "token": "3h20m0s", 137 }, 138 }).Return(nil) 139 140 nextRotateTime := s.clock.Now().Add(150 * time.Minute) 141 s.backendState.EXPECT().SecretBackendRotated("backend-id", nextRotateTime).Return(errors.New("boom")) 142 143 result, err := s.facade.RotateBackendTokens(params.RotateSecretBackendArgs{ 144 BackendIDs: []string{"backend-id"}}) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Assert(result, jc.DeepEquals, params.ErrorResults{ 147 Results: []params.ErrorResult{ 148 { 149 Error: ¶ms.Error{Code: "", Message: `boom`}, 150 }, 151 }, 152 }) 153 } 154 155 func (s *SecretsManagerSuite) TestRotateBackendTokensRetry(c *gc.C) { 156 ctrl := s.setup(c) 157 defer ctrl.Finish() 158 159 backend := &coresecrets.SecretBackend{ 160 BackendType: "vault", 161 TokenRotateInterval: ptr(200 * time.Minute), 162 Config: map[string]interface{}{"foo": "bar"}, 163 } 164 s.backendState.EXPECT().GetSecretBackendByID("backend-id").Return(backend, nil) 165 166 p := mocks.NewMockSecretBackendProvider(ctrl) 167 s.PatchValue(&secretbackendmanager.GetProvider, func(string) (provider.SecretBackendProvider, error) { 168 return providerWithRefresh{ 169 SecretBackendProvider: p, 170 }, nil 171 }) 172 s.backendState.EXPECT().UpdateSecretBackend(state.UpdateSecretBackendParams{ 173 ID: "backend-id", 174 Config: map[string]interface{}{ 175 "foo": "bar", 176 "token": "3h20m0s", 177 }, 178 }).Return(errors.New("BOOM")) 179 180 // On error, try again after a short time. 181 nextRotateTime := s.clock.Now().Add(2 * time.Minute) 182 183 s.backendState.EXPECT().SecretBackendRotated("backend-id", nextRotateTime).Return(errors.New("boom")) 184 185 result, err := s.facade.RotateBackendTokens(params.RotateSecretBackendArgs{ 186 BackendIDs: []string{"backend-id"}}) 187 c.Assert(err, jc.ErrorIsNil) 188 c.Assert(result, jc.DeepEquals, params.ErrorResults{ 189 Results: []params.ErrorResult{ 190 { 191 Error: ¶ms.Error{Code: "", Message: `boom`}, 192 }, 193 }, 194 }) 195 }