Auth: encrypt secrets for oauth providers in SSO settings API service (#79081)
encrypt secrets for oauth providers
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings/api"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings/database"
|
||||
@@ -27,10 +28,12 @@ type SSOSettingsService struct {
|
||||
store ssosettings.Store
|
||||
ac ac.AccessControl
|
||||
fbStrategies []ssosettings.FallbackStrategy
|
||||
secrets secrets.Service
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl,
|
||||
routeRegister routing.RouteRegister, features *featuremgmt.FeatureManager) *SSOSettingsService {
|
||||
routeRegister routing.RouteRegister, features *featuremgmt.FeatureManager,
|
||||
secrets secrets.Service) *SSOSettingsService {
|
||||
strategies := []ssosettings.FallbackStrategy{
|
||||
strategies.NewOAuthStrategy(cfg),
|
||||
// register other strategies here, for example SAML
|
||||
@@ -44,6 +47,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, ac ac.AccessControl,
|
||||
store: store,
|
||||
ac: ac,
|
||||
fbStrategies: strategies,
|
||||
secrets: secrets,
|
||||
}
|
||||
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) {
|
||||
@@ -114,6 +118,15 @@ func (s *SSOSettingsService) List(ctx context.Context, requester identity.Reques
|
||||
|
||||
func (s *SSOSettingsService) Upsert(ctx context.Context, settings models.SSOSettings) error {
|
||||
// TODO: validation (configurable provider? Contains the required fields? etc)
|
||||
|
||||
if isOAuthProvider(settings.Provider) {
|
||||
encryptedClientSecret, err := s.secrets.Encrypt(ctx, []byte(settings.OAuthSettings.ClientSecret), secrets.WithoutScope())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.OAuthSettings.ClientSecret = string(encryptedClientSecret)
|
||||
}
|
||||
|
||||
err := s.store.Upsert(ctx, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -182,3 +195,13 @@ func (s *SSOSettingsService) getFallBackstrategyFor(provider string) (ssosetting
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func isOAuthProvider(provider string) bool {
|
||||
for _, oAuthProvider := range ssosettings.AllOAuthProviders {
|
||||
if oAuthProvider == provider {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,17 +6,20 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/login/social"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
secretsFakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings/models"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSSOSettingsService_GetForProvider(t *testing.T) {
|
||||
@@ -308,6 +311,66 @@ func TestSSOSettingsService_List(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSOSettingsService_Upsert(t *testing.T) {
|
||||
t.Run("successfully upsert SSO settings", func(t *testing.T) {
|
||||
env := setupTestEnv(t)
|
||||
|
||||
settings := models.SSOSettings{
|
||||
Provider: "azuread",
|
||||
OAuthSettings: &social.OAuthInfo{
|
||||
ClientId: "client-id",
|
||||
ClientSecret: "client-secret",
|
||||
Enabled: true,
|
||||
},
|
||||
IsDeleted: false,
|
||||
}
|
||||
|
||||
env.secrets.On("Encrypt", mock.Anything, []byte(settings.OAuthSettings.ClientSecret), mock.Anything).Return([]byte("encrypted-client-secret"), nil).Once()
|
||||
|
||||
err := env.service.Upsert(context.Background(), settings)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("returns error if secrets encryption failed", func(t *testing.T) {
|
||||
env := setupTestEnv(t)
|
||||
|
||||
settings := models.SSOSettings{
|
||||
Provider: "azuread",
|
||||
OAuthSettings: &social.OAuthInfo{
|
||||
ClientId: "client-id",
|
||||
ClientSecret: "client-secret",
|
||||
Enabled: true,
|
||||
},
|
||||
IsDeleted: false,
|
||||
}
|
||||
|
||||
env.secrets.On("Encrypt", mock.Anything, []byte(settings.OAuthSettings.ClientSecret), mock.Anything).Return(nil, errors.New("encryption failed")).Once()
|
||||
|
||||
err := env.service.Upsert(context.Background(), settings)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("returns error if store failed to upsert settings", func(t *testing.T) {
|
||||
env := setupTestEnv(t)
|
||||
|
||||
settings := models.SSOSettings{
|
||||
Provider: "azuread",
|
||||
OAuthSettings: &social.OAuthInfo{
|
||||
ClientId: "client-id",
|
||||
ClientSecret: "client-secret",
|
||||
Enabled: true,
|
||||
},
|
||||
IsDeleted: false,
|
||||
}
|
||||
|
||||
env.secrets.On("Encrypt", mock.Anything, []byte(settings.OAuthSettings.ClientSecret), mock.Anything).Return([]byte("encrypted-client-secret"), nil).Once()
|
||||
env.store.ExpectedError = errors.New("upsert failed")
|
||||
|
||||
err := env.service.Upsert(context.Background(), settings)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSSOSettingsService_Delete(t *testing.T) {
|
||||
t.Run("successfully delete SSO settings", func(t *testing.T) {
|
||||
env := setupTestEnv(t)
|
||||
@@ -345,19 +408,23 @@ func TestSSOSettingsService_Delete(t *testing.T) {
|
||||
func setupTestEnv(t *testing.T) testEnv {
|
||||
store := ssosettingstests.NewFakeStore()
|
||||
fallbackStrategy := ssosettingstests.NewFakeFallbackStrategy()
|
||||
|
||||
secrets := secretsFakes.NewMockService(t)
|
||||
accessControl := acimpl.ProvideAccessControl(setting.NewCfg())
|
||||
|
||||
svc := &SSOSettingsService{
|
||||
log: log.NewNopLogger(),
|
||||
store: store,
|
||||
ac: accessControl,
|
||||
fbStrategies: []ssosettings.FallbackStrategy{fallbackStrategy},
|
||||
secrets: secrets,
|
||||
}
|
||||
|
||||
return testEnv{
|
||||
service: svc,
|
||||
store: store,
|
||||
ac: accessControl,
|
||||
fallbackStrategy: fallbackStrategy,
|
||||
secrets: secrets,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,4 +433,5 @@ type testEnv struct {
|
||||
store *ssosettingstests.FakeStore
|
||||
ac accesscontrol.AccessControl
|
||||
fallbackStrategy *ssosettingstests.FakeFallbackStrategy
|
||||
secrets *secretsFakes.MockService
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user