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: &params.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: &params.Error{Code: "", Message: `boom`},
   192  			},
   193  		},
   194  	})
   195  }