Skip to content

Commit 878fad5

Browse files
FEATURE (encryption): Add encyption for secrets in notifiers and storages
1 parent 6ff3096 commit 878fad5

File tree

41 files changed

+1571
-392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1571
-392
lines changed

backend/internal/features/backups/backups/background_service.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"postgresus-backend/internal/config"
66
backups_config "postgresus-backend/internal/features/backups/config"
77
"postgresus-backend/internal/features/storages"
8+
"postgresus-backend/internal/util/encryption"
89
"postgresus-backend/internal/util/period"
910
"time"
1011
)
@@ -131,7 +132,8 @@ func (s *BackupBackgroundService) cleanOldBackups() error {
131132
continue
132133
}
133134

134-
err = storage.DeleteFile(backup.ID)
135+
encryptor := encryption.GetFieldEncryptor()
136+
err = storage.DeleteFile(encryptor, backup.ID)
135137
if err != nil {
136138
s.logger.Error("Failed to delete backup file", "backupId", backup.ID, "error", err)
137139
}

backend/internal/features/backups/backups/controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
users_testing "postgresus-backend/internal/features/users/testing"
2727
workspaces_models "postgresus-backend/internal/features/workspaces/models"
2828
workspaces_testing "postgresus-backend/internal/features/workspaces/testing"
29+
"postgresus-backend/internal/util/encryption"
2930
test_utils "postgresus-backend/internal/util/testing"
3031
"postgresus-backend/internal/util/tools"
3132
)
@@ -700,7 +701,7 @@ func createTestBackup(
700701
dummyContent := []byte("dummy backup content for testing")
701702
reader := strings.NewReader(string(dummyContent))
702703
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
703-
if err := storages[0].SaveFile(logger, backup.ID, reader); err != nil {
704+
if err := storages[0].SaveFile(encryption.GetFieldEncryptor(), logger, backup.ID, reader); err != nil {
704705
panic(fmt.Sprintf("Failed to create test backup file: %v", err))
705706
}
706707

backend/internal/features/backups/backups/di.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"postgresus-backend/internal/features/storages"
1010
users_repositories "postgresus-backend/internal/features/users/repositories"
1111
workspaces_services "postgresus-backend/internal/features/workspaces/services"
12+
"postgresus-backend/internal/util/encryption"
1213
"postgresus-backend/internal/util/logger"
1314
"time"
1415
)
@@ -25,6 +26,7 @@ var backupService = &BackupService{
2526
notifiers.GetNotifierService(),
2627
backups_config.GetBackupConfigService(),
2728
users_repositories.GetSecretKeyRepository(),
29+
encryption.GetFieldEncryptor(),
2830
usecases.GetCreateBackupUsecase(),
2931
logger.GetLogger(),
3032
[]BackupRemoveListener{},

backend/internal/features/backups/backups/service.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
users_models "postgresus-backend/internal/features/users/models"
1717
users_repositories "postgresus-backend/internal/features/users/repositories"
1818
workspaces_services "postgresus-backend/internal/features/workspaces/services"
19+
util_encryption "postgresus-backend/internal/util/encryption"
1920
"slices"
2021
"strings"
2122
"time"
@@ -31,6 +32,7 @@ type BackupService struct {
3132
notificationSender NotificationSender
3233
backupConfigService *backups_config.BackupConfigService
3334
secretKeyRepo *users_repositories.SecretKeyRepository
35+
fieldEncryptor util_encryption.FieldEncryptor
3436

3537
createBackupUseCase CreateBackupUsecase
3638

@@ -284,7 +286,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID, isLastTry bool) {
284286
// Delete partial backup from storage
285287
storage, storageErr := s.storageService.GetStorageByID(backup.StorageID)
286288
if storageErr == nil {
287-
if deleteErr := storage.DeleteFile(backup.ID); deleteErr != nil {
289+
if deleteErr := storage.DeleteFile(s.fieldEncryptor, backup.ID); deleteErr != nil {
288290
s.logger.Error(
289291
"Failed to delete partial backup file",
290292
"backupId",
@@ -545,7 +547,7 @@ func (s *BackupService) deleteBackup(backup *Backup) error {
545547
return err
546548
}
547549

548-
err = storage.DeleteFile(backup.ID)
550+
err = storage.DeleteFile(s.fieldEncryptor, backup.ID)
549551
if err != nil {
550552
// we do not return error here, because sometimes clean up performed
551553
// before unavailable storage removal or change - therefore we should
@@ -599,7 +601,7 @@ func (s *BackupService) getBackupReader(backupID uuid.UUID) (io.ReadCloser, erro
599601
return nil, fmt.Errorf("failed to get storage: %w", err)
600602
}
601603

602-
fileReader, err := storage.GetFile(backup.ID)
604+
fileReader, err := storage.GetFile(s.fieldEncryptor, backup.ID)
603605
if err != nil {
604606
return nil, fmt.Errorf("failed to get backup file: %w", err)
605607
}

backend/internal/features/backups/backups/service_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
users_testing "postgresus-backend/internal/features/users/testing"
1414
workspaces_services "postgresus-backend/internal/features/workspaces/services"
1515
workspaces_testing "postgresus-backend/internal/features/workspaces/testing"
16+
"postgresus-backend/internal/util/encryption"
1617
"postgresus-backend/internal/util/logger"
1718
"strings"
1819
"testing"
@@ -56,11 +57,12 @@ func Test_BackupExecuted_NotificationSent(t *testing.T) {
5657
mockNotificationSender,
5758
backups_config.GetBackupConfigService(),
5859
users_repositories.GetSecretKeyRepository(),
60+
encryption.GetFieldEncryptor(),
5961
&CreateFailedBackupUsecase{},
6062
logger.GetLogger(),
6163
[]BackupRemoveListener{},
6264
workspaces_services.GetWorkspaceService(),
63-
nil, // auditLogService
65+
nil,
6466
NewBackupContextManager(),
6567
}
6668

@@ -103,11 +105,12 @@ func Test_BackupExecuted_NotificationSent(t *testing.T) {
103105
mockNotificationSender,
104106
backups_config.GetBackupConfigService(),
105107
users_repositories.GetSecretKeyRepository(),
108+
encryption.GetFieldEncryptor(),
106109
&CreateSuccessBackupUsecase{},
107110
logger.GetLogger(),
108111
[]BackupRemoveListener{},
109112
workspaces_services.GetWorkspaceService(),
110-
nil, // auditLogService
113+
nil,
111114
NewBackupContextManager(),
112115
}
113116

@@ -127,11 +130,12 @@ func Test_BackupExecuted_NotificationSent(t *testing.T) {
127130
mockNotificationSender,
128131
backups_config.GetBackupConfigService(),
129132
users_repositories.GetSecretKeyRepository(),
133+
encryption.GetFieldEncryptor(),
130134
&CreateSuccessBackupUsecase{},
131135
logger.GetLogger(),
132136
[]BackupRemoveListener{},
133137
workspaces_services.GetWorkspaceService(),
134-
nil, // auditLogService
138+
nil,
135139
NewBackupContextManager(),
136140
}
137141

backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ import (
1515
"time"
1616

1717
"postgresus-backend/internal/config"
18-
"postgresus-backend/internal/features/backups/backups/encryption"
18+
backup_encryption "postgresus-backend/internal/features/backups/backups/encryption"
1919
backups_config "postgresus-backend/internal/features/backups/config"
2020
"postgresus-backend/internal/features/databases"
2121
pgtypes "postgresus-backend/internal/features/databases/databases/postgresql"
2222
"postgresus-backend/internal/features/storages"
2323
users_repositories "postgresus-backend/internal/features/users/repositories"
24+
"postgresus-backend/internal/util/encryption"
2425
"postgresus-backend/internal/util/tools"
2526

2627
"github.com/google/uuid"
@@ -40,8 +41,9 @@ const (
4041
)
4142

4243
type CreatePostgresqlBackupUsecase struct {
43-
logger *slog.Logger
44-
secretKeyRepo *users_repositories.SecretKeyRepository
44+
logger *slog.Logger
45+
secretKeyRepo *users_repositories.SecretKeyRepository
46+
fieldEncryptor encryption.FieldEncryptor
4547
}
4648

4749
// Execute creates a backup of the database
@@ -166,7 +168,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
166168
// Start streaming into storage in its own goroutine
167169
saveErrCh := make(chan error, 1)
168170
go func() {
169-
saveErr := storage.SaveFile(uc.logger, backupID, storageReader)
171+
saveErr := storage.SaveFile(uc.fieldEncryptor, uc.logger, backupID, storageReader)
170172
saveErrCh <- saveErr
171173
}()
172174

@@ -440,7 +442,7 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption(
440442
backupID uuid.UUID,
441443
backupConfig *backups_config.BackupConfig,
442444
storageWriter io.WriteCloser,
443-
) (io.Writer, *encryption.EncryptionWriter, BackupMetadata, error) {
445+
) (io.Writer, *backup_encryption.EncryptionWriter, BackupMetadata, error) {
444446
metadata := BackupMetadata{}
445447

446448
if backupConfig.Encryption != backups_config.BackupEncryptionEncrypted {
@@ -449,12 +451,12 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption(
449451
return storageWriter, nil, metadata, nil
450452
}
451453

452-
salt, err := encryption.GenerateSalt()
454+
salt, err := backup_encryption.GenerateSalt()
453455
if err != nil {
454456
return nil, nil, metadata, fmt.Errorf("failed to generate salt: %w", err)
455457
}
456458

457-
nonce, err := encryption.GenerateNonce()
459+
nonce, err := backup_encryption.GenerateNonce()
458460
if err != nil {
459461
return nil, nil, metadata, fmt.Errorf("failed to generate nonce: %w", err)
460462
}
@@ -464,7 +466,7 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption(
464466
return nil, nil, metadata, fmt.Errorf("failed to get master key: %w", err)
465467
}
466468

467-
encWriter, err := encryption.NewEncryptionWriter(
469+
encWriter, err := backup_encryption.NewEncryptionWriter(
468470
storageWriter,
469471
masterKey,
470472
backupID,
@@ -486,7 +488,7 @@ func (uc *CreatePostgresqlBackupUsecase) setupBackupEncryption(
486488
}
487489

488490
func (uc *CreatePostgresqlBackupUsecase) cleanupOnCancellation(
489-
encryptionWriter *encryption.EncryptionWriter,
491+
encryptionWriter *backup_encryption.EncryptionWriter,
490492
storageWriter io.WriteCloser,
491493
saveErrCh chan error,
492494
) {
@@ -510,7 +512,7 @@ func (uc *CreatePostgresqlBackupUsecase) cleanupOnCancellation(
510512
}
511513

512514
func (uc *CreatePostgresqlBackupUsecase) closeWriters(
513-
encryptionWriter *encryption.EncryptionWriter,
515+
encryptionWriter *backup_encryption.EncryptionWriter,
514516
storageWriter io.WriteCloser,
515517
) error {
516518
encryptionCloseErrCh := make(chan error, 1)

backend/internal/features/backups/backups/usecases/postgresql/di.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package usecases_postgresql
22

33
import (
44
users_repositories "postgresus-backend/internal/features/users/repositories"
5+
"postgresus-backend/internal/util/encryption"
56
"postgresus-backend/internal/util/logger"
67
)
78

89
var createPostgresqlBackupUsecase = &CreatePostgresqlBackupUsecase{
910
logger.GetLogger(),
1011
users_repositories.GetSecretKeyRepository(),
12+
encryption.GetFieldEncryptor(),
1113
}
1214

1315
func GetCreatePostgresqlBackupUsecase() *CreatePostgresqlBackupUsecase {

backend/internal/features/databases/controller_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
users_testing "postgresus-backend/internal/features/users/testing"
1717
workspaces_controllers "postgresus-backend/internal/features/workspaces/controllers"
1818
workspaces_testing "postgresus-backend/internal/features/workspaces/testing"
19+
"postgresus-backend/internal/util/encryption"
1920
test_utils "postgresus-backend/internal/util/testing"
2021
"postgresus-backend/internal/util/tools"
2122
)
@@ -769,6 +770,71 @@ func createTestDatabaseViaAPI(
769770
return &database
770771
}
771772

773+
func Test_CreateDatabase_PasswordIsEncryptedInDB(t *testing.T) {
774+
router := createTestRouter()
775+
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
776+
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
777+
778+
testDbName := "test_db"
779+
plainPassword := "my-super-secret-password-123"
780+
request := Database{
781+
Name: "Test Database",
782+
WorkspaceID: &workspace.ID,
783+
Type: DatabaseTypePostgres,
784+
Postgresql: &postgresql.PostgresqlDatabase{
785+
Version: tools.PostgresqlVersion16,
786+
Host: "localhost",
787+
Port: 5432,
788+
Username: "postgres",
789+
Password: plainPassword,
790+
Database: &testDbName,
791+
},
792+
}
793+
794+
var createdDatabase Database
795+
test_utils.MakePostRequestAndUnmarshal(
796+
t,
797+
router,
798+
"/api/v1/databases/create",
799+
"Bearer "+owner.Token,
800+
request,
801+
http.StatusCreated,
802+
&createdDatabase,
803+
)
804+
805+
repository := &DatabaseRepository{}
806+
databaseFromDB, err := repository.FindByID(createdDatabase.ID)
807+
assert.NoError(t, err)
808+
assert.NotNil(t, databaseFromDB)
809+
assert.NotNil(t, databaseFromDB.Postgresql)
810+
811+
assert.True(
812+
t,
813+
strings.HasPrefix(databaseFromDB.Postgresql.Password, "enc:"),
814+
"Password should be encrypted in database with 'enc:' prefix, got: %s",
815+
databaseFromDB.Postgresql.Password,
816+
)
817+
818+
encryptor := encryption.GetFieldEncryptor()
819+
decryptedPassword, err := encryptor.Decrypt(
820+
databaseFromDB.ID,
821+
databaseFromDB.Postgresql.Password,
822+
)
823+
assert.NoError(t, err)
824+
assert.Equal(t, plainPassword, decryptedPassword,
825+
"Decrypted password should match original plaintext password")
826+
827+
test_utils.MakeDeleteRequest(
828+
t,
829+
router,
830+
"/api/v1/databases/"+createdDatabase.ID.String(),
831+
"Bearer "+owner.Token,
832+
http.StatusNoContent,
833+
)
834+
835+
workspaces_testing.RemoveTestWorkspace(workspace, router)
836+
}
837+
772838
func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) {
773839
testCases := []struct {
774840
name string
@@ -815,7 +881,15 @@ func Test_DatabaseSensitiveDataLifecycle_AllTypes(t *testing.T) {
815881
}
816882
},
817883
verifySensitiveData: func(t *testing.T, database *Database) {
818-
assert.Equal(t, "original-password-secret", database.Postgresql.Password)
884+
// Verify password is encrypted
885+
assert.True(t, strings.HasPrefix(database.Postgresql.Password, "enc:"),
886+
"Password should be encrypted in database")
887+
888+
// Verify it can be decrypted back to original
889+
encryptor := encryption.GetFieldEncryptor()
890+
decrypted, err := encryptor.Decrypt(database.ID, database.Postgresql.Password)
891+
assert.NoError(t, err)
892+
assert.Equal(t, "original-password-secret", decrypted)
819893
},
820894
verifyHiddenData: func(t *testing.T, database *Database) {
821895
assert.Equal(t, "", database.Postgresql.Password)

0 commit comments

Comments
 (0)