From fdcfc267a348fef5e1d668c8c3f2334f5cece279 Mon Sep 17 00:00:00 2001 From: Josh Friend Date: Wed, 29 Apr 2026 20:44:42 -0400 Subject: [PATCH] feat: add CRC64-NVME upload checksums to S3 cache Enable CRC64-NVME checksums on S3 uploads for server-side integrity validation. Also refactor S3 test helpers and fix soak test TTL. --- internal/cache/s3.go | 4 ++- internal/cache/s3_test.go | 50 +++++++++++++++++------------------ internal/s3client/s3client.go | 7 ++--- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/internal/cache/s3.go b/internal/cache/s3.go index 6cee936..f43387a 100644 --- a/internal/cache/s3.go +++ b/internal/cache/s3.go @@ -418,9 +418,11 @@ func (w *s3Writer) upload(pr *io.PipeReader, r io.Reader) { userMetadata["Headers"] = string(headersJSON) } - // Configure upload options + // Configure upload options. CRC64-NVME is computed as data streams through and sent as a + // trailing header so S3 validates integrity server-side, preventing corrupt or truncated uploads. opts := minio.PutObjectOptions{ UserMetadata: userMetadata, + AutoChecksum: minio.ChecksumCRC64NVME, } // Enable concurrent streaming for multi-part uploads if configured diff --git a/internal/cache/s3_test.go b/internal/cache/s3_test.go index ede16eb..8a60467 100644 --- a/internal/cache/s3_test.go +++ b/internal/cache/s3_test.go @@ -15,29 +15,29 @@ import ( "github.com/block/cachew/internal/s3client/s3clienttest" ) -// TestS3Cache tests the S3 cache implementation using MinIO in Docker. -func TestS3Cache(t *testing.T) { - bucket := s3clienttest.Start(t) +func newS3Cache(t *testing.T, bucket string) cache.Cache { + t.Helper() + s3clienttest.CleanBucket(t, bucket) + _, ctx := logging.Configure(t.Context(), logging.Config{Level: slog.LevelDebug}) - cachetest.Suite(t, func(t *testing.T) cache.Cache { - s3clienttest.CleanBucket(t, bucket) - _, ctx := logging.Configure(t.Context(), logging.Config{Level: slog.LevelDebug}) + clientProvider := s3client.NewClientProvider(ctx, s3client.Config{ + Endpoint: s3clienttest.Addr, + UseSSL: false, + }) - clientProvider := s3client.NewClientProvider(ctx, s3client.Config{ - Endpoint: s3clienttest.Addr, - Region: "", - UseSSL: false, - SkipSSLVerify: false, - }) + c, err := cache.NewS3(ctx, cache.S3Config{ + Bucket: bucket, + MaxTTL: 500 * time.Millisecond, + UploadPartSizeMB: 16, + }, clientProvider) + assert.NoError(t, err) + return c +} - c, err := cache.NewS3(ctx, cache.S3Config{ - Bucket: bucket, - MaxTTL: 500 * time.Millisecond, - UploadPartSizeMB: 16, - }, clientProvider) - assert.NoError(t, err) - return c - }) +// TestS3Cache tests the S3 cache implementation using MinIO in Docker. +func TestS3Cache(t *testing.T) { + bucket := s3clienttest.Start(t) + cachetest.Suite(t, func(t *testing.T) cache.Cache { return newS3Cache(t, bucket) }) } func TestS3CacheSoak(t *testing.T) { @@ -46,14 +46,12 @@ func TestS3CacheSoak(t *testing.T) { } bucket := s3clienttest.Start(t) - - _, ctx := logging.Configure(t.Context(), logging.Config{Level: slog.LevelError}) + s3clienttest.CleanBucket(t, bucket) + _, ctx := logging.Configure(t.Context(), logging.Config{Level: slog.LevelDebug}) clientProvider := s3client.NewClientProvider(ctx, s3client.Config{ - Endpoint: s3clienttest.Addr, - Region: "", - UseSSL: false, - SkipSSLVerify: false, + Endpoint: s3clienttest.Addr, + UseSSL: false, }) c, err := cache.NewS3(ctx, cache.S3Config{ diff --git a/internal/s3client/s3client.go b/internal/s3client/s3client.go index 81d11f9..2dab2ae 100644 --- a/internal/s3client/s3client.go +++ b/internal/s3client/s3client.go @@ -86,9 +86,10 @@ func NewClient(ctx context.Context, config Config) (*minio.Client, error) { }) options := &minio.Options{ - Creds: creds, - Secure: config.UseSSL, - Region: config.Region, + Creds: creds, + Secure: config.UseSSL, + Region: config.Region, + TrailingHeaders: true, } if transport != nil {