@@ -3,7 +3,10 @@ package openstack
33import (
44 "context"
55 "fmt"
6+ "github.com/gophercloud/gophercloud/openstack/identity/v3/users"
7+ "github.com/hashicorp/go-multierror"
68 "github.com/opentelekomcloud/vault-plugin-secrets-openstack/openstack/common"
9+ "net/http"
710 "sync"
811 "time"
912
@@ -32,8 +35,8 @@ type sharedCloud struct {
3235
3336type backend struct {
3437 * framework.Backend
35-
36- clouds map [ string ] * sharedCloud
38+ clouds map [ string ] * sharedCloud
39+ checkAutoRotateAfter time. Time
3740}
3841
3942func Factory (ctx context.Context , conf * logical.BackendConfig ) (logical.Backend , error ) {
@@ -62,7 +65,8 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
6265 secretToken (b ),
6366 secretUser (b ),
6467 },
65- BackendType : logical .TypeLogical ,
68+ BackendType : logical .TypeLogical ,
69+ PeriodicFunc : b .periodicFunc ,
6670 }
6771
6872 if err := b .Setup (ctx , conf ); err != nil {
@@ -147,3 +151,82 @@ func (c *sharedCloud) initClient(ctx context.Context, s logical.Storage) error {
147151
148152 return nil
149153}
154+
155+ func (b * backend ) periodicFunc (ctx context.Context , req * logical.Request ) error {
156+ // Check for autorotation once an hour to avoid unnecessarily iterating
157+ // over all keys too frequently.
158+ if time .Now ().Before (b .checkAutoRotateAfter ) {
159+ return nil
160+ }
161+ b .Logger ().Debug ("periodic func" , "rotate-root" , "rotation cycle in progress" )
162+ b .checkAutoRotateAfter = time .Now ().Add (1 * time .Hour )
163+
164+ return b .autoRotateKeys (ctx , req )
165+ }
166+
167+ func (b * backend ) autoRotateKeys (ctx context.Context , req * logical.Request ) error {
168+ keys , err := req .Storage .List (ctx , "clouds/" )
169+ if err != nil {
170+ return err
171+ }
172+
173+ // Collect errors in a multierror to ensure a single failure doesn't prevent
174+ // all keys from being rotated.
175+ var errs * multierror.Error
176+
177+ for _ , key := range keys {
178+ cloudEntry := b .getSharedCloud (key )
179+ if cloudEntry == nil {
180+ continue
181+ }
182+
183+ err = b .rotateIfRequired (ctx , req , cloudEntry )
184+ if err != nil {
185+ errs = multierror .Append (errs , err )
186+ }
187+ }
188+ b .Logger ().Debug ("periodic func" , "rotate-root" , "rotation cycle complete" )
189+ return errs .ErrorOrNil ()
190+ }
191+
192+ func (b * backend ) rotateIfRequired (ctx context.Context , req * logical.Request , sCloud * sharedCloud ) error {
193+ cloudConfig , err := sCloud .getCloudConfig (ctx , req .Storage )
194+ if err != nil {
195+ return err
196+ }
197+ if time .Now ().After (cloudConfig .RootPasswordExpirationDate ) {
198+ client , err := sCloud .getClient (ctx , req .Storage )
199+ if err != nil {
200+ return logical .CodedError (http .StatusConflict , common .LogHttpError (err ).Error ())
201+ }
202+ newPassword , err := sCloud .passwords .Generate (ctx )
203+ if err != nil {
204+ return err
205+ }
206+
207+ // make sure we don't use this cloud until the password is changed
208+ sCloud .lock .Lock ()
209+ defer sCloud .lock .Unlock ()
210+
211+ user , err := tokens .Get (client , client .Token ()).ExtractUser ()
212+ if err != nil {
213+ return logical .CodedError (http .StatusConflict , common .LogHttpError (err ).Error ())
214+ }
215+ err = users .ChangePassword (client , user .ID , users.ChangePasswordOpts {
216+ Password : newPassword ,
217+ OriginalPassword : cloudConfig .Password ,
218+ }).ExtractErr ()
219+ if err != nil {
220+ errorMessage := fmt .Sprintf ("error changing root password: %s" , common .LogHttpError (err ).Error ())
221+ return logical .CodedError (http .StatusConflict , errorMessage )
222+ }
223+ cloudConfig .Password = newPassword
224+ cloudConfig .RootPasswordExpirationDate = time .Now ().Add (cloudConfig .RootPasswordTTL )
225+
226+ if err := cloudConfig .save (ctx , req .Storage ); err != nil {
227+ return err
228+ }
229+ b .Logger ().Debug ("password rotated" , "cloud" , cloudConfig .Name )
230+ }
231+ return nil
232+ }
0 commit comments