Skip to content

Commit 683d4bd

Browse files
nipunn1313jovermier
authored andcommitted
convex-backend PR 198: feat(aws_s3): Add S3 compatibility environment variables for non-AWS services (#40557)
## Summary - Add environment variables to disable AWS-specific S3 headers for compatibility with non-AWS S3 services like MinIO and Rook Ceph RGW - `AWS_S3_DISABLE_SSE=true` disables server-side encryption headers (`x-amz-server-side-encryption`) - `AWS_S3_DISABLE_CHECKSUMS=true` disables checksum algorithm headers (`x-amz-checksum-algorithm`) ## Problem The current Convex backend hard-codes AWS-specific headers in S3 multipart upload operations: - `ServerSideEncryption::Aes256` - `ChecksumAlgorithm::Crc32` These headers cause compatibility issues with S3-compatible services that don't support AWS-specific features, leading to multipart upload failures. ## Solution - Added two new environment variables in `aws_utils` crate for S3 compatibility configuration - Modified `aws_s3` storage implementation to conditionally add AWS headers based on environment variables - Maintains backward compatibility (headers are added by default when variables are not set) ## Files Changed - `crates/aws_utils/src/lib.rs`: Added environment variable configuration and accessor functions - `crates/aws_s3/src/storage.rs`: Updated multipart upload methods to conditionally add headers ## Testing - Code compiles successfully with all dependencies - Backward compatibility maintained (default behavior unchanged) - Ready for testing with MinIO and other S3-compatible services ## Benefits - Enables self-hosted Convex to work with a wider range of S3-compatible storage backends - Improves compatibility with MinIO, Rook Ceph RGW, and other S3 implementations - Maintains full backward compatibility with existing AWS S3 deployments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Jason Overmier <jason.r.overmier@gmail.com> GitOrigin-RevId: f854dba3a108deb6e9d407c50f6e8181e91bb70a
1 parent 388aef0 commit 683d4bd

File tree

3 files changed

+70
-14
lines changed

3 files changed

+70
-14
lines changed

crates/aws_s3/src/storage.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use async_trait::async_trait;
99
use aws_config::retry::RetryConfig;
1010
use aws_sdk_s3::{
1111
operation::{
12+
create_multipart_upload::builders::CreateMultipartUploadFluentBuilder,
1213
head_object::{
1314
HeadObjectError,
1415
HeadObjectOutput,
@@ -26,6 +27,8 @@ use aws_sdk_s3::{
2627
Client,
2728
};
2829
use aws_utils::{
30+
are_checksums_disabled,
31+
is_sse_disabled,
2932
must_s3_config_from_env,
3033
s3::S3Client,
3134
};
@@ -150,6 +153,28 @@ impl<RT: Runtime> S3Storage<RT> {
150153
let bucket_name = s3_bucket_name(&use_case)?;
151154
S3Storage::new_with_prefix(bucket_name, key_prefix, runtime).await
152155
}
156+
157+
/// Helper method to configure multipart upload builder with optional AWS
158+
/// headers for S3 compatibility with non-AWS services
159+
fn configure_multipart_upload_builder(
160+
&self,
161+
mut upload_builder: CreateMultipartUploadFluentBuilder,
162+
) -> CreateMultipartUploadFluentBuilder {
163+
// Add server-side encryption if not disabled for S3 compatibility
164+
if !is_sse_disabled() {
165+
upload_builder = upload_builder.server_side_encryption(ServerSideEncryption::Aes256);
166+
}
167+
168+
// Add checksum algorithm if not disabled for S3 compatibility
169+
if !are_checksums_disabled() {
170+
// Because we're using multipart uploads, we're really specifying the part
171+
// checksum algorithm here, so it needs to match what we use for
172+
// each part.
173+
upload_builder = upload_builder.checksum_algorithm(ChecksumAlgorithm::Crc32);
174+
}
175+
176+
upload_builder
177+
}
153178
}
154179

155180
async fn s3_client() -> Result<Client, anyhow::Error> {
@@ -215,15 +240,15 @@ impl<RT: Runtime> Storage for S3Storage<RT> {
215240
async fn start_upload(&self) -> anyhow::Result<Box<BufferedUpload>> {
216241
let key: ObjectKey = self.runtime.new_uuid_v4().to_string().try_into()?;
217242
let s3_key = S3Key(self.key_prefix.clone() + &key);
218-
let output = self
243+
let upload_builder = self
219244
.client
220245
.create_multipart_upload()
221246
.bucket(self.bucket.clone())
222-
.key(&s3_key.0)
223-
.server_side_encryption(ServerSideEncryption::Aes256)
224-
// Because we're using multipart uploads, we're really specifying the part checksum
225-
// algorithm here, so it needs to match what we use for each part.
226-
.checksum_algorithm(ChecksumAlgorithm::Crc32)
247+
.key(&s3_key.0);
248+
249+
let upload_builder = self.configure_multipart_upload_builder(upload_builder);
250+
251+
let output = upload_builder
227252
.send()
228253
.await
229254
.context("Failed to create multipart upload")?;
@@ -253,15 +278,15 @@ impl<RT: Runtime> Storage for S3Storage<RT> {
253278
async fn start_client_driven_upload(&self) -> anyhow::Result<ClientDrivenUploadToken> {
254279
let key: ObjectKey = self.runtime.new_uuid_v4().to_string().try_into()?;
255280
let s3_key = S3Key(self.key_prefix.clone() + &key);
256-
let output = self
281+
let upload_builder = self
257282
.client
258283
.create_multipart_upload()
259284
.bucket(self.bucket.clone())
260-
.key(&s3_key.0)
261-
.server_side_encryption(ServerSideEncryption::Aes256)
262-
// Because we're using multipart uploads, we're really specifying the part checksum
263-
// algorithm here, so it needs to match what we use for each part.
264-
.checksum_algorithm(ChecksumAlgorithm::Crc32)
285+
.key(&s3_key.0);
286+
287+
let upload_builder = self.configure_multipart_upload_builder(upload_builder);
288+
289+
let output = upload_builder
265290
.send()
266291
.await
267292
.context("Failed to create multipart upload")?;
@@ -552,15 +577,20 @@ impl<RT: Runtime> S3Upload<RT> {
552577
let part_number = self.next_part_number()?;
553578
crate::metrics::log_aws_s3_part_upload_size_bytes(data.len());
554579

555-
let builder = self
580+
let mut builder = self
556581
.client
557582
.upload_part()
558-
.checksum_algorithm(ChecksumAlgorithm::Crc32)
559583
.body(ByteStream::from(data))
560584
.bucket(self.bucket.clone())
561585
.key(&self.s3_key.0)
562586
.part_number(Into::<u16>::into(part_number) as i32)
563587
.upload_id(self.upload_id.to_string());
588+
589+
// Add checksum algorithm if not disabled for S3 compatibility
590+
if !are_checksums_disabled() {
591+
builder = builder.checksum_algorithm(ChecksumAlgorithm::Crc32);
592+
}
593+
564594
Ok(UploadPart {
565595
part_number,
566596
builder,

crates/aws_utils/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ static AWS_S3_FORCE_PATH_STYLE: LazyLock<bool> = LazyLock::new(|| {
3333
.unwrap_or_default()
3434
});
3535

36+
static AWS_S3_DISABLE_SSE: LazyLock<bool> = LazyLock::new(|| {
37+
env::var("AWS_S3_DISABLE_SSE")
38+
.ok()
39+
.and_then(|s| s.parse().ok())
40+
.unwrap_or_default()
41+
});
42+
43+
static AWS_S3_DISABLE_CHECKSUMS: LazyLock<bool> = LazyLock::new(|| {
44+
env::var("AWS_S3_DISABLE_CHECKSUMS")
45+
.ok()
46+
.and_then(|s| s.parse().ok())
47+
.unwrap_or_default()
48+
});
49+
3650
/// Similar aws_config::from_env but returns an error if credentials or
3751
/// region is are not. It also doesn't spew out log lines every time
3852
/// credentials are accessed.
@@ -62,3 +76,13 @@ pub async fn must_s3_config_from_env() -> anyhow::Result<S3ConfigBuilder> {
6276
s3_config_builder = s3_config_builder.force_path_style(*AWS_S3_FORCE_PATH_STYLE);
6377
Ok(s3_config_builder)
6478
}
79+
80+
/// Returns true if server-side encryption headers should be disabled
81+
pub fn is_sse_disabled() -> bool {
82+
*AWS_S3_DISABLE_SSE
83+
}
84+
85+
/// Returns true if checksum headers should be disabled
86+
pub fn are_checksums_disabled() -> bool {
87+
*AWS_S3_DISABLE_CHECKSUMS
88+
}

self-hosted/docker/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ services:
2828
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
2929
- AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN:-}
3030
- AWS_S3_FORCE_PATH_STYLE=${AWS_S3_FORCE_PATH_STYLE:-}
31+
- AWS_S3_DISABLE_SSE=${AWS_S3_DISABLE_SSE:-}
32+
- AWS_S3_DISABLE_CHECKSUMS=${AWS_S3_DISABLE_CHECKSUMS:-}
3133
- S3_STORAGE_EXPORTS_BUCKET=${S3_STORAGE_EXPORTS_BUCKET:-}
3234
- S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET=${S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET:-}
3335
- S3_STORAGE_MODULES_BUCKET=${S3_STORAGE_MODULES_BUCKET:-}

0 commit comments

Comments
 (0)