mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #4867 from coo1white/fix-backup-s3-secret-reencrypt
fix(backup): keep the S3 secret encrypted when a save inherits it
This commit is contained in:
@@ -357,19 +357,25 @@ func (s *BackupService) GetS3Config(ctx context.Context) (*BackupS3Config, error
|
||||
}
|
||||
|
||||
func (s *BackupService) UpdateS3Config(ctx context.Context, cfg BackupS3Config) (*BackupS3Config, error) {
|
||||
// 如果没提供 secret,保留原有值
|
||||
// 如果没提供 secret,保留原有值。loadS3Config 会解密,所以这里拿到的是明文,
|
||||
// 和调用方新填的 secret 一样,都必须走下面的加密再落库。
|
||||
if cfg.SecretAccessKey == "" {
|
||||
old, _ := s.loadS3Config(ctx)
|
||||
if old != nil {
|
||||
cfg.SecretAccessKey = old.SecretAccessKey
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
// 加密 SecretAccessKey。这一步不能只在“调用方填了新 secret”时做:表单保存成功
|
||||
// 后本函数会清空返回值里的 secret,所以第二次保存(改端点、存定时配置)送上来的
|
||||
// secret 是空的,会走上面的分支继承旧值。若那条路径跳过加密,明文就会覆盖掉库里
|
||||
// 的密文,而读取侧“兼容未加密旧数据”的回退会把这件事永久掩盖成一条日志。
|
||||
if cfg.SecretAccessKey != "" {
|
||||
// 拒绝用自动生成的临时密钥加密:该密钥每次重启都会变化,落库的密文在
|
||||
// 重启/升级后无法解密(#4524)。与支付、TOTP 的处理保持一致。
|
||||
if !s.encryptionKeyConfigured {
|
||||
return nil, ErrSecretEncryptionKeyNotConfigured
|
||||
}
|
||||
// 加密 SecretAccessKey
|
||||
encrypted, err := s.encryptor.Encrypt(cfg.SecretAccessKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt secret: %w", err)
|
||||
|
||||
@@ -372,6 +372,53 @@ func TestBackupService_S3ConfigKeepExistingSecret(t *testing.T) {
|
||||
require.Equal(t, "AKID-NEW", internal.AccessKeyID)
|
||||
}
|
||||
|
||||
// 一次不带 secret 的保存(表单第二次提交、改端点、存定时配置)继承的是 loadS3Config
|
||||
// 解密后的明文。若那条路径跳过加密,明文就会覆盖库里的密文,而读取侧“兼容未加密旧
|
||||
// 数据”的回退会把它掩盖成一条日志,功能照常,密钥却是明文落库的。
|
||||
func TestBackupService_S3ConfigStaysEncryptedAfterSecondSave(t *testing.T) {
|
||||
repo := newMockSettingRepo()
|
||||
svc := newTestBackupService(repo, &mockDumper{}, newMockObjectStore())
|
||||
|
||||
_, err := svc.UpdateS3Config(context.Background(), BackupS3Config{
|
||||
Bucket: "my-bucket",
|
||||
AccessKeyID: "AKID",
|
||||
SecretAccessKey: "original-secret",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
storedSecret := func() string {
|
||||
raw, _ := repo.GetValue(context.Background(), settingKeyBackupS3Config)
|
||||
var stored BackupS3Config
|
||||
require.NoError(t, json.Unmarshal([]byte(raw), &stored))
|
||||
return stored.SecretAccessKey
|
||||
}
|
||||
require.Equal(t, "ENC:original-secret", storedSecret())
|
||||
|
||||
// 第二次保存不带 secret,只改别的字段。
|
||||
_, err = svc.UpdateS3Config(context.Background(), BackupS3Config{
|
||||
Bucket: "my-bucket",
|
||||
AccessKeyID: "AKID-NEW",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "ENC:original-secret", storedSecret(),
|
||||
"secret must stay encrypted at rest after a save that inherits it")
|
||||
require.NotEqual(t, "original-secret", storedSecret(), "secret must never be stored as plaintext")
|
||||
|
||||
// 第三次保存,确认不会反复套壳加密。
|
||||
_, err = svc.UpdateS3Config(context.Background(), BackupS3Config{
|
||||
Bucket: "my-bucket",
|
||||
AccessKeyID: "AKID-THIRD",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ENC:original-secret", storedSecret(), "secret must not be double-encrypted")
|
||||
|
||||
internal, err := svc.loadS3Config(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "original-secret", internal.SecretAccessKey)
|
||||
require.Equal(t, "AKID-THIRD", internal.AccessKeyID)
|
||||
}
|
||||
|
||||
func TestBackupService_UpdateS3Config_RejectsEphemeralKey(t *testing.T) {
|
||||
repo := newMockSettingRepo()
|
||||
svc := newTestBackupServiceEphemeralKey(repo)
|
||||
|
||||
Reference in New Issue
Block a user