From cd616a48a809614c971bea9ff69d2d7c63b687e6 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 8 Nov 2026 05:13:19 -0800 Subject: [PATCH] S3 Vectors: refuse an SSE-S3 index when the engine has no key for it (#12484) vector: refuse an SSE-S3 index when the engine has no key for it The engine writes and reads an SSE-S3 bucket's files itself, so without s3.sse.kek or s3.sse.key it failed inside Lance and the client saw only InternalFailure. weed mini makes this easy to hit: it generates its key for its own S3 server only. A table the gateway marks sse_s3 is now refused up front with FailedPrecondition, which the gateway answers as ValidationException with the reason. Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK Co-authored-by: Claude Opus 5.5 --- seaweed-vector/crates/server/src/service.rs | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/seaweed-vector/crates/server/src/service.rs b/seaweed-vector/crates/server/src/service.rs index 68a1dd2038..d9ec782793 100644 --- a/seaweed-vector/crates/server/src/service.rs +++ b/seaweed-vector/crates/server/src/service.rs @@ -366,6 +366,18 @@ fn rows_as_vectors( out } +/// Refuses a table of an SSE-S3 bucket when the engine's storage has no key +/// to read or write it with, rather than failing inside Lance. +fn sse_s3_ready(sse_s3: bool, key_configured: Option) -> Result<(), Status> { + if sse_s3 && key_configured == Some(false) { + return Err(Status::failed_precondition( + "the bucket encrypts with SSE-S3, which needs s3.sse.kek or s3.sse.key for the \ + engine as for the S3 gateway", + )); + } + Ok(()) +} + /// The description a gateway sent with the table. Only a vector index at its /// colocated path is taken, since the engine is that path's writer already; /// any other location would point the engine at storage the catalog does not @@ -458,6 +470,10 @@ impl VectorServer { table.id.join("/") )) })?; + sse_s3_ready( + table.sse_s3, + self.storage.as_ref().map(|s| s.sse_s3_configured()), + )?; Ok(description) } @@ -1040,6 +1056,21 @@ mod tests { } } + /// A table of an SSE-S3 bucket is refused before Lance opens it when the + /// storage holds no key. + #[test] + fn an_sse_s3_table_needs_the_key() { + assert!(sse_s3_ready(false, Some(false)).is_ok()); + assert!(sse_s3_ready(true, Some(true)).is_ok()); + assert!( + sse_s3_ready(true, None).is_ok(), + "in-process stores need no key" + ); + let err = sse_s3_ready(true, Some(false)).unwrap_err(); + assert_eq!(err.code(), tonic::Code::FailedPrecondition); + assert!(err.message().contains("s3.sse.kek or s3.sse.key")); + } + /// Only a vector index at its colocated path is taken from the request. #[test] fn resolved_takes_only_a_colocated_vector_index() {