mirror of
https://github.com/TronoSfera/backupy-agent.git
synced 2026-05-18 10:03:30 +03:00
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/ — Go agent (WSS client, persistent queue, Docker
discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/ — protobuf wire format (generated .pb.go committed
so the repo builds without protoc)
- docs/ — agent spec + wire-protocol contract
Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
116 lines
2.9 KiB
Go
116 lines
2.9 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newDEK(t *testing.T) []byte {
|
|
t.Helper()
|
|
dek := make([]byte, 32)
|
|
_, err := rand.Read(dek)
|
|
require.NoError(t, err)
|
|
return dek
|
|
}
|
|
|
|
func TestEncryptor_RoundTrip_5MB(t *testing.T) {
|
|
plaintext := make([]byte, 5<<20)
|
|
_, err := rand.Read(plaintext)
|
|
require.NoError(t, err)
|
|
|
|
e, err := NewEncryptor(newDEK(t))
|
|
require.NoError(t, err)
|
|
|
|
var ct bytes.Buffer
|
|
consumed, err := e.Stream(bytes.NewReader(plaintext), &ct)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(len(plaintext)), consumed)
|
|
|
|
var round bytes.Buffer
|
|
pt, err := e.Decrypt(&ct, &round)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(len(plaintext)), pt)
|
|
require.Equal(t, plaintext, round.Bytes())
|
|
}
|
|
|
|
func TestEncryptor_RoundTrip_Empty(t *testing.T) {
|
|
e, err := NewEncryptor(newDEK(t))
|
|
require.NoError(t, err)
|
|
|
|
var ct bytes.Buffer
|
|
consumed, err := e.Stream(bytes.NewReader(nil), &ct)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), consumed)
|
|
|
|
var round bytes.Buffer
|
|
pt, err := e.Decrypt(&ct, &round)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), pt)
|
|
require.Empty(t, round.Bytes())
|
|
}
|
|
|
|
func TestEncryptor_WrongKeyFails(t *testing.T) {
|
|
plaintext := bytes.Repeat([]byte("hello"), 1000)
|
|
e1, err := NewEncryptor(newDEK(t))
|
|
require.NoError(t, err)
|
|
e2, err := NewEncryptor(newDEK(t))
|
|
require.NoError(t, err)
|
|
|
|
var ct bytes.Buffer
|
|
_, err = e1.Stream(bytes.NewReader(plaintext), &ct)
|
|
require.NoError(t, err)
|
|
|
|
var round bytes.Buffer
|
|
_, err = e2.Decrypt(&ct, &round)
|
|
require.Error(t, err, "decrypt with a different DEK must fail GCM tag check")
|
|
}
|
|
|
|
func TestEncryptor_TamperingDetected(t *testing.T) {
|
|
dek := newDEK(t)
|
|
e, err := NewEncryptor(dek)
|
|
require.NoError(t, err)
|
|
|
|
plaintext := bytes.Repeat([]byte("abcd"), 4096)
|
|
var ct bytes.Buffer
|
|
_, err = e.Stream(bytes.NewReader(plaintext), &ct)
|
|
require.NoError(t, err)
|
|
|
|
// Flip a bit inside the first ciphertext chunk (skip header + nonce).
|
|
tampered := ct.Bytes()
|
|
flipAt := chunkHeaderSize + nonceSize + 1
|
|
require.Greater(t, len(tampered), flipAt)
|
|
tampered[flipAt] ^= 0x01
|
|
|
|
var round bytes.Buffer
|
|
_, err = e.Decrypt(bytes.NewReader(tampered), &round)
|
|
require.Error(t, err, "bit-flip in ciphertext must trigger a GCM tag failure")
|
|
}
|
|
|
|
func TestEncryptor_RejectsBadDEKLength(t *testing.T) {
|
|
_, err := NewEncryptor(make([]byte, 16))
|
|
require.Error(t, err)
|
|
_, err = NewEncryptor(make([]byte, 31))
|
|
require.Error(t, err)
|
|
_, err = NewEncryptor(make([]byte, 33))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestEncryptor_TruncatedStreamFails(t *testing.T) {
|
|
e, err := NewEncryptor(newDEK(t))
|
|
require.NoError(t, err)
|
|
|
|
var ct bytes.Buffer
|
|
_, err = e.Stream(bytes.NewReader(bytes.Repeat([]byte("X"), 4096)), &ct)
|
|
require.NoError(t, err)
|
|
|
|
// Drop the last 4 bytes of the EOF marker.
|
|
b := ct.Bytes()
|
|
truncated := b[:len(b)-2]
|
|
|
|
var round bytes.Buffer
|
|
_, err = e.Decrypt(bytes.NewReader(truncated), &round)
|
|
require.Error(t, err, "missing EOF marker must be detected")
|
|
}
|