Volume encryption follows the filer: weed s3, weed-vector, weed-worker, weed-vector-cli (#12487)

* s3: encrypt volume data when the filer is configured to

weed s3 read the filer's configuration at startup (buckets dir, filer
group, masters, metrics, maxMB) but not its -encryptVolumeData, so a
cluster that turned encryption on at the filer still had S3 uploads
stored plain, while uploads through the filer itself were encrypted.
The gateway now encrypts when either its own -encryptVolumeData or the
filer's is set; weed-vector, weed-worker and weed-vector-cli already
follow the filer's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* vector: weed-vector, weed-worker and the CLI take volume encryption from the filer

They already encrypted chunks for the volume servers when the filer's
configuration said so, or when their own --encryptVolumeData did. With
weed s3 following the filer too, the filer's -encryptVolumeData is the
one switch for every writer, so the three flags are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* S3 Vectors tests: turn volume encryption on at the filer

VECTOR_ENCRYPT_VOLUMES=1 set -encryptVolumeData on the gateway, the
engine and the worker. It now sets it on the filer alone, so the leg
checks that each of them follows the filer: the gateway's stored chunks
carry a cipher key (with a gateway that ignores the filer, the leg fails
on "the gateway stored a chunk without a cipher key"), and so do the
engine's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* weed-worker README: volume encryption follows the filer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UpvafvK8JpagF94MHWqwK

* s3: help text says the filer's -encryptVolumeData turns S3 encryption on

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* S3 Vectors tests: plain legs check the engine stored no cipher key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 authored and GitHub committed 2026-11-08 09:17:30 -08:00
1 parent 87627c2d72
commit 4e12c13416
13 files changed
+36 -64

No files matched your search

+8 -7
View File
@@ -301,9 +301,13 @@ def run_cluster(param, tmp_path_factory, identities):
"max": 64, "preStopSeconds": 0,
}.items()])
wait_for(lambda: listening(vol_grpc), processes, "volume not ready")
# VECTOR_ENCRYPT_VOLUMES=1 turns on the filer's volume encryption, which
# the gateway, the engine and the worker all follow: every chunk they
# store is encrypted, and direct reads must undo it.
encrypt = ["-encryptVolumeData"] if os.environ.get("VECTOR_ENCRYPT_VOLUMES") else []
start("filer", [weed, "filer", f"-master={master_addr}", "-ip=127.0.0.1",
"-ip.bind=127.0.0.1", f"-port={filer}", f"-port.grpc={filer_grpc}",
f"-defaultStoreDir={directory / 'filer'}"])
f"-defaultStoreDir={directory / 'filer'}", *encrypt])
wait_for(lambda: listening(filer_grpc), processes, "filer not ready")
endpoint = f"http://127.0.0.1:{s3}"
# weed s3 turns AWS_ACCESS_KEY_ID into an admin identity.
@@ -311,10 +315,7 @@ def run_cluster(param, tmp_path_factory, identities):
config, unset = [f"-config={HERE / 's3.json'}"], ()
else:
config, unset = [], ("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY")
# VECTOR_ENCRYPT_VOLUMES=1 has the gateway encrypt every chunk it
# stores, which direct reads must undo.
encrypt = ["-encryptVolumeData"] if os.environ.get("VECTOR_ENCRYPT_VOLUMES") else []
start("s3", [weed, "-v=1", "s3", f"-filer={filer_addr}", *config, *encrypt,
start("s3", [weed, "-v=1", "s3", f"-filer={filer_addr}", *config,
"-ip=127.0.0.1", "-ip.bind=127.0.0.1", f"-port={s3}",
f"-port.grpc={s3_grpc}", "-port.iceberg=0", f"-port.lance={lance}"], unset)
wait_for(lambda: http_ready(f"http://127.0.0.1:{lance}/v1/namespace/root/list"), processes, "catalog not ready")
@@ -324,7 +325,7 @@ def run_cluster(param, tmp_path_factory, identities):
# The engine keeps tables on the filers and volume servers itself; it
# has no S3 credentials to be given, and encrypts its chunks for the
# volume servers as the gateway does.
start("vector", [engine, "--masters", master_addr, "--listen", f"127.0.0.1:{vector}", *encrypt])
start("vector", [engine, "--masters", master_addr, "--listen", f"127.0.0.1:{vector}"])
wait_for(lambda: listening(vector), processes, "vector engine not ready")
if os.environ.get("VECTOR_TLS"):
wait_for(lambda: listening(s3_grpc), processes, "s3 gRPC not ready")
@@ -362,7 +363,7 @@ def run_cluster(param, tmp_path_factory, identities):
wait_for(lambda: http_ready(f"{admin_url}/api/plugin/job-types"), processes, "admin not ready")
start("lance-worker", [lance_worker, "--admin", f"127.0.0.1:{admin}.{admin_grpc}",
"--namespace", f"http://127.0.0.1:{lance}", "--id", "vectors-test-worker",
"--masters", master_addr, *(["--encryptVolumeData"] if encrypt else [])])
"--masters", master_addr])
wait_for(lambda: "lance_vector_index" in
[jt["job_type"] for jt in json.load(urllib.request.urlopen(f"{admin_url}/api/plugin/job-types", timeout=2))],
processes, "the Lance worker never registered with admin")
+5 -2
View File
@@ -118,8 +118,9 @@ def index(vectors):
def assert_engine_wrote_like_the_gateway(cluster, index):
"""The engine writes the table's files itself: the filer holds entries the
gateway could have written (an ETag, chunks, no inline content), and under
VECTOR_ENCRYPT_VOLUMES every chunk carries a cipher key."""
gateway could have written (an ETag, chunks, no inline content), and every
chunk carries a cipher key exactly when VECTOR_ENCRYPT_VOLUMES encrypts at
the filer."""
import os
import urllib.request
@@ -135,6 +136,8 @@ def assert_engine_wrote_like_the_gateway(cluster, index):
assert "Seaweed-X-Amz-ETag" in (meta.get("Extended") or {}), meta.get("Extended")
if os.environ.get("VECTOR_ENCRYPT_VOLUMES"):
assert all(chunk.get("cipher_key") for chunk in meta["chunks"]), meta["chunks"]
else:
assert not any(chunk.get("cipher_key") for chunk in meta["chunks"]), meta["chunks"]
def run_cli(cluster, *args):