Compare commits

...

6 Commits

Author SHA1 Message Date
Chris An 46337942d5 fix(test): backport scanner wait correction for 1.0.4 (#8930)
* fix(test): follow scanner schedules during multi-pool recovery

* fix(test): retain active scanner completion budget

* test: preserve E2E membership for scanner wait regression
2026-10-25 18:21:25 +08:00
Hauser 46e56019eb chore(deps): refresh dependencies and pin Pulsar 6.9.0 (#8923) 2026-10-25 14:41:41 +08:00
Chris An e02c27feaf chore(release): prepare 1.0.4 2026-10-25 13:28:03 +08:00
Chris An 440f831d12 ci(release): pin the 1.0.3 console for the security patch
Retain Console v0.1.37 and its original verified archive digest.
Reject mismatched release metadata rather than selecting newer UI changes.
2026-10-25 13:28:00 +08:00
Chris An 0ad3afcf0a fix(security): backport emSign distrust to 1.0.3
Update the patched verifier and bundled roots without unrelated dependency changes.
Disable the distrusted G3 native root and validate both Docker trust stores.

Refs #8819
2026-10-25 13:27:56 +08:00
overtrue 331fb89628 chore(release): prepare 1.0.3 2026-10-17 15:57:35 +08:00
13 changed files with 958 additions and 763 deletions
+1
View File
@@ -27,6 +27,7 @@ script-tests: ## Run shell script tests
trap 'status=$$?; printf "ERROR: script-tests failed (exit %s): %s\n" "$$status" "$$BASH_COMMAND" >&2; exit "$$status"' ERR
./scripts/test_build_rustfs_options.sh
./scripts/test_docker_runtime_timezone.sh
sh ./scripts/test_ca_bundle.sh
./scripts/test_entrypoint_credentials.sh
./scripts/test_internode_grpc_ab_bench.sh
./scripts/test_object_batch_bench_enhanced.sh
+6 -2
View File
@@ -322,7 +322,8 @@ jobs:
download_console_assets() {
local curl_bin="curl"
local python_bin="python3"
local console_api="https://api.github.com/repos/rustfs/console/releases/latest"
# Preserve the reviewed 1.0.3 Console in this security-only release branch.
local console_api="https://api.github.com/repos/rustfs/console/releases/tags/v0.1.37"
local console_json="console-release.json"
local console_url
local console_sha256
@@ -359,12 +360,15 @@ jobs:
release = json.load(handle)
tag = release.get("tag_name", "") or "unknown"
if tag != "v0.1.37":
raise SystemExit("Console release does not match the security-release pin")
expected_digest = "sha256:e6ff2c7e7695de824ec4cda91d8611d1aba5d8462e6809592c92a50b23f5b9ab"
for asset in release.get("assets", []):
name = asset.get("name", "")
digest = asset.get("digest", "")
url = asset.get("browser_download_url", "")
if name.startswith("rustfs-console-") and name.endswith(".zip") and digest.startswith("sha256:") and url:
if name == "rustfs-console-v0.1.37.zip" and digest == expected_digest and url:
sha256 = digest.split(":", 1)[1]
if not re.fullmatch(r"[0-9a-fA-F]{64}", sha256):
raise SystemExit(f"console zip asset has invalid sha256 digest: {sha256}")
Generated
+341 -382
View File
File diff suppressed because it is too large Load Diff
+81 -81
View File
@@ -74,7 +74,7 @@ edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/rustfs/rustfs"
rust-version = "1.99.0"
version = "1.0.2"
version = "1.0.4"
homepage = "https://rustfs.com"
description = "RustFS is a high-performance distributed object storage software built using Rust, one of the most popular languages worldwide. "
keywords = ["RustFS", "Minio", "object-storage", "filesystem", "s3"]
@@ -91,62 +91,62 @@ redundant_clone = "warn"
[workspace.dependencies]
# RustFS Internal Crates
rustfs = { path = "./rustfs", version = "1.0.2" }
rustfs-heal = { path = "crates/heal", version = "1.0.2" }
rustfs-heal-contracts = { path = "crates/heal-contracts", version = "1.0.2" }
rustfs-scanner-metrics = { path = "crates/scanner-metrics", version = "1.0.2" }
rustfs-audit = { path = "crates/audit", version = "1.0.2" }
rustfs-checksums = { path = "crates/checksums", version = "1.0.2" }
rustfs-common = { path = "crates/common", version = "1.0.2" }
rustfs-data-usage = { path = "crates/data-usage", version = "1.0.2" }
rustfs-config = { path = "./crates/config", version = "1.0.2" }
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.2" }
rustfs-credentials = { path = "crates/credentials", version = "1.0.2" }
rustfs-crypto = { path = "crates/crypto", version = "1.0.2" }
rustfs-ecstore = { path = "crates/ecstore", version = "1.0.2" }
rustfs-filemeta = { path = "crates/filemeta", version = "1.0.2" }
rustfs-iam = { path = "crates/iam", version = "1.0.2" }
rustfs-keystone = { path = "crates/keystone", version = "1.0.2" }
rustfs-license = { path = "crates/license", version = "1.0.2" }
rustfs-lifecycle = { path = "crates/lifecycle", version = "1.0.2" }
rustfs-kms = { path = "crates/kms", version = "1.0.2" }
rustfs-lock = { path = "crates/lock", version = "1.0.2" }
rustfs-madmin = { path = "crates/madmin", version = "1.0.2" }
rustfs-notify = { path = "crates/notify", version = "1.0.2" }
rustfs-io-metrics = { path = "crates/io-metrics", version = "1.0.2" }
rustfs-io-core = { path = "crates/io-core", version = "1.0.2" }
rustfs-object-capacity = { path = "crates/object-capacity", version = "1.0.2" }
rustfs-object-data-cache = { path = "crates/object-data-cache", version = "1.0.2", default-features = false }
rustfs-log-analyzer = { path = "crates/log-analyzer", version = "1.0.2" }
rustfs-obs = { path = "crates/obs", version = "1.0.2" }
rustfs-policy = { path = "crates/policy", version = "1.0.2" }
rustfs-protos = { path = "crates/protos", version = "1.0.2" }
rustfs-protocols = { path = "crates/protocols", version = "1.0.2" }
rustfs-replication = { path = "crates/replication", version = "1.0.2" }
rustfs-rio = { path = "crates/rio", version = "1.0.2" }
rustfs-rio-v2 = { path = "crates/rio-v2", version = "1.0.2" }
rustfs-s3-client = { path = "crates/s3-client", version = "1.0.2" }
rustfs-s3-types = { path = "crates/s3-types", version = "1.0.2" }
rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.2" }
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.2" }
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.2" }
rustfs-scanner = { path = "crates/scanner", version = "1.0.2" }
rustfs-security-governance = { path = "crates/security-governance", version = "1.0.2" }
rustfs-extension-schema = { path = "crates/extension-schema", version = "1.0.2" }
rustfs-signer = { path = "crates/signer", version = "1.0.2" }
rustfs-storage-api = { path = "crates/storage-api", version = "1.0.2" }
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.2" }
rustfs-targets = { path = "crates/targets", version = "1.0.2" }
rustfs-test-utils = { path = "crates/test-utils", version = "1.0.2" }
rustfs-tls-runtime = { path = "crates/tls-runtime", version = "1.0.2" }
rustfs-utils = { path = "crates/utils", version = "1.0.2" }
rustfs-zip = { path = "./crates/zip", version = "1.0.2" }
rustfs = { path = "./rustfs", version = "1.0.4" }
rustfs-heal = { path = "crates/heal", version = "1.0.4" }
rustfs-heal-contracts = { path = "crates/heal-contracts", version = "1.0.4" }
rustfs-scanner-metrics = { path = "crates/scanner-metrics", version = "1.0.4" }
rustfs-audit = { path = "crates/audit", version = "1.0.4" }
rustfs-checksums = { path = "crates/checksums", version = "1.0.4" }
rustfs-common = { path = "crates/common", version = "1.0.4" }
rustfs-data-usage = { path = "crates/data-usage", version = "1.0.4" }
rustfs-config = { path = "./crates/config", version = "1.0.4" }
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.4" }
rustfs-credentials = { path = "crates/credentials", version = "1.0.4" }
rustfs-crypto = { path = "crates/crypto", version = "1.0.4" }
rustfs-ecstore = { path = "crates/ecstore", version = "1.0.4" }
rustfs-filemeta = { path = "crates/filemeta", version = "1.0.4" }
rustfs-iam = { path = "crates/iam", version = "1.0.4" }
rustfs-keystone = { path = "crates/keystone", version = "1.0.4" }
rustfs-license = { path = "crates/license", version = "1.0.4" }
rustfs-lifecycle = { path = "crates/lifecycle", version = "1.0.4" }
rustfs-kms = { path = "crates/kms", version = "1.0.4" }
rustfs-lock = { path = "crates/lock", version = "1.0.4" }
rustfs-madmin = { path = "crates/madmin", version = "1.0.4" }
rustfs-notify = { path = "crates/notify", version = "1.0.4" }
rustfs-io-metrics = { path = "crates/io-metrics", version = "1.0.4" }
rustfs-io-core = { path = "crates/io-core", version = "1.0.4" }
rustfs-object-capacity = { path = "crates/object-capacity", version = "1.0.4" }
rustfs-object-data-cache = { path = "crates/object-data-cache", version = "1.0.4", default-features = false }
rustfs-log-analyzer = { path = "crates/log-analyzer", version = "1.0.4" }
rustfs-obs = { path = "crates/obs", version = "1.0.4" }
rustfs-policy = { path = "crates/policy", version = "1.0.4" }
rustfs-protos = { path = "crates/protos", version = "1.0.4" }
rustfs-protocols = { path = "crates/protocols", version = "1.0.4" }
rustfs-replication = { path = "crates/replication", version = "1.0.4" }
rustfs-rio = { path = "crates/rio", version = "1.0.4" }
rustfs-rio-v2 = { path = "crates/rio-v2", version = "1.0.4" }
rustfs-s3-client = { path = "crates/s3-client", version = "1.0.4" }
rustfs-s3-types = { path = "crates/s3-types", version = "1.0.4" }
rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.4" }
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.4" }
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.4" }
rustfs-scanner = { path = "crates/scanner", version = "1.0.4" }
rustfs-security-governance = { path = "crates/security-governance", version = "1.0.4" }
rustfs-extension-schema = { path = "crates/extension-schema", version = "1.0.4" }
rustfs-signer = { path = "crates/signer", version = "1.0.4" }
rustfs-storage-api = { path = "crates/storage-api", version = "1.0.4" }
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.4" }
rustfs-targets = { path = "crates/targets", version = "1.0.4" }
rustfs-test-utils = { path = "crates/test-utils", version = "1.0.4" }
rustfs-tls-runtime = { path = "crates/tls-runtime", version = "1.0.4" }
rustfs-utils = { path = "crates/utils", version = "1.0.4" }
rustfs-zip = { path = "./crates/zip", version = "1.0.4" }
# Async Runtime and Networking
async-channel = "2.5.0"
async_zip = { default-features = false, version = "0.0.19" }
mysql_async = { default-features = false, version = "0.37.1" }
async-compression = { version = "0.4.50" }
async-compression = { version = "0.4.51" }
async-trait = "0.1.92"
async-nats = { version = "0.50.0", default-features = false }
axum = "0.8.9"
@@ -155,8 +155,8 @@ futures-core = "0.3.34"
futures-lite = "2.6.1"
futures-util = "0.3.34"
pollster = "1.0.1"
pulsar = { default-features = false, version = "6.9.0" }
lapin = { default-features = false, version = "4.12.2" }
pulsar = { default-features = false, version = "=6.9.0" }
lapin = { default-features = false, version = "4.12.3" }
hyper = { version = "1.12.0" }
h2 = { version = "0.4.20" }
hyper-rustls = { default-features = false, version = "0.27.10" }
@@ -169,7 +169,7 @@ minlz = "1.2.3"
reqwest = "0.13.5"
rustfs-kafka-async = { version = "1.4.0" }
socket2 = { version = "0.6.5" }
tokio = { version = "1.53.3" }
tokio = { version = "1.54.1" }
tokio-rustls = { default-features = false, version = "0.26.6" }
tokio-stream = { version = "0.1.19" }
tokio-test = "0.4.6"
@@ -220,7 +220,7 @@ openidconnect = { default-features = false, version = "4.0" }
pbkdf2 = "0.13.0"
p256 = { version = "0.14.0", features = ["ecdsa", "pkcs8"] }
rsa = { version = "=0.10.0-rc.18" }
rustls = { default-features = false, version = "0.23.45" }
rustls = { default-features = false, version = "0.23.46" }
rustls-native-certs = "0.8"
rustls-pki-types = "1.15.1"
x509-parser = "0.18.1"
@@ -243,22 +243,22 @@ tokio-postgres-rustls = "0.14.0"
# Utilities and Tools
anyhow = "1.0.104"
arc-swap = "1.9.2"
arc-swap = "1.9.3"
rustfs-tokio-tar = { version = "0.7.1" }
# Candidate Snowball parser versions exercised by rustfs-zip compatibility fixtures.
tar-codec = "0.0.14"
tar-framing = "0.0.14"
atoi = "3.1.0"
atoi = "3.1.1"
atomic_enum = "0.3.0"
aws-config = { version = "1.12.0" }
aws-credential-types = { version = "1.3.0" }
aws-sdk-kms = { default-features = false, version = "1.123.0" }
aws-sdk-s3 = { default-features = false, version = "1.152.0" }
aws-sdk-sts = { default-features = false, version = "1.119.0" }
aws-smithy-async = { version = "1.3.0" }
aws-smithy-http-client = { default-features = false, version = "1.5.0" }
aws-smithy-runtime-api = { version = "1.19.0" }
aws-smithy-types = { version = "1.8.1" }
aws-config = { version = "1.13.0" }
aws-credential-types = { version = "1.3.1" }
aws-sdk-kms = { default-features = false, version = "1.126.0" }
aws-sdk-s3 = { default-features = false, version = "1.155.0" }
aws-sdk-sts = { default-features = false, version = "1.122.0" }
aws-smithy-async = { version = "1.3.1" }
aws-smithy-http-client = { default-features = false, version = "1.5.1" }
aws-smithy-runtime-api = { version = "1.19.1" }
aws-smithy-types = { version = "1.9.0" }
base64-simd = "0.8.0"
brotli = "9.0.0"
clap = { version = "4.6.7" }
@@ -272,12 +272,12 @@ crossbeam-utils = "0.8.23"
datafusion = { default-features = false, version = "55.2.0" }
derive_builder = "0.20.2"
ed25519-dalek = "3.0.0"
enumset = "1.1.14"
enumset = "1.1.15"
faster-hex = "1.0.0"
flate2 = "1.1.10"
glob = "0.3.4"
google-cloud-storage = "1.20.0"
google-cloud-auth = "1.17.0"
google-cloud-storage = "1.21.0"
google-cloud-auth = "1.18.0"
hashbrown = { version = "0.17.1" }
# Base32 for RFC 6238 TOTP shared secrets (RFC 4648 unpadded, the alphabet
# every authenticator app expects). Already in the graph transitively.
@@ -287,11 +287,11 @@ highway = { version = "1.3.0" }
hostname = "0.4.2"
ipnetwork = { version = "0.21.1" }
lazy_static = "1.5.1"
libc = "0.2.190"
libc = "0.2.191"
libsystemd = "0.7.2"
local-ip-address = "0.6.13"
log = "0.4"
memmap2 = "0.9.11"
memmap2 = "0.9.12"
lz4 = "1.28.1"
matchit = "0.9.2"
md-5 = "0.11.0"
@@ -317,7 +317,7 @@ rustfs-erasure-codec = { version = "9.0.0" }
reed-solomon-simd = "3.1.0"
regex = { version = "1.13.1" }
rumqttc = { package = "rumqttc-next", version = "0.34.0" }
redis = { version = "1.7.1" }
redis = { version = "1.8.0" }
rustify = { version = "0.7", default-features = false }
rustix = { version = "1.1.5" }
rust-embed = { version = "8.13.0" }
@@ -327,7 +327,7 @@ s3s-sigv4 = { git = "https://github.com/s3s-project/s3s", rev = "5761ddfe4c509fc
serial_test = "4.0.1"
shadow-rs = { default-features = false, version = "2.0.0" }
siphasher = "1.0.4"
smallvec = { version = "1.16.3" }
smallvec = { version = "1.16.4" }
compact_str = "0.10.0"
snap = "1.1.2"
starshard = { version = "2.4.0" }
@@ -346,7 +346,7 @@ tracing-subscriber = { version = "0.3.23" }
transform-stream = "0.3.1"
url = "2.5.8"
urlencoding = "2.1.3"
uuid = { version = "1.28.0" }
uuid = { version = "1.29.0" }
vaultrs = { version = "0.8.0" }
tar = "0.4.46"
walkdir = "2.5.0"
@@ -354,13 +354,13 @@ winapi-util = "0.1.11"
windows = { version = "0.62.2" }
windows-sys = "0.61.2"
xxhash-rust = { version = "0.8.19" }
zip = "9.0.3"
zstd = "0.14.2"
zip = "9.1.0"
zstd = "0.14.3"
# Observability and Metrics
metrics = "0.24.6"
metrics-util = "0.20"
dial9 = { version = "0.5.4", default-features = false }
dial9 = { version = "0.5.5", default-features = false }
opentelemetry = { version = "0.33.1" }
opentelemetry-appender-tracing = { version = "0.33.1" }
opentelemetry-otlp = { version = "0.33.1" }
@@ -368,12 +368,12 @@ opentelemetry-proto = { version = "0.33.1", default-features = false, features =
opentelemetry_sdk = { version = "0.33.1" }
opentelemetry-semantic-conventions = { version = "0.33.1" }
opentelemetry-stdout = { version = "0.33.1" }
pyroscope = { version = "2.1.2" }
pyroscope = { version = "2.1.5" }
# FTP and SFTP
libunftp = { version = "0.23.1" }
unftp-core = "0.1.0"
suppaftp = { version = "12.2.0" }
suppaftp = { version = "12.2.1" }
rcgen = { version = "0.14.10", default-features = false, features = ["aws_lc_rs", "crypto", "pem"] }
russh = { version = "0.64.1" }
russh-sftp = "3.0.1"
@@ -384,7 +384,7 @@ dav-server = "0.11.0"
# Performance Analysis and Memory Profiling
rustfs-mimalloc = { version = "0.5.6" }
# Preserve Unicode focus filters until rustfs/backlog#2302 is resolved.
hotpath = { version = "0.28.6", default-features = false }
hotpath = { version = "0.28.7", default-features = false }
# Snapshot testing for output format regression detection
insta = { version = "1.49" }
+14 -1
View File
@@ -18,6 +18,13 @@ ARG TARGETARCH
ARG RELEASE=latest
RUN apk add --no-cache ca-certificates curl jq unzip
# Keep the native store safe even if the distribution CA package lags distrust.
COPY scripts/check_ca_bundle.sh /tmp/check_ca_bundle.sh
RUN sed -i '/^mozilla\/emSign_ECC_Root_CA_-_G3.crt$/s/^/!/' /etc/ca-certificates.conf && \
update-ca-certificates --fresh && \
sh /tmp/check_ca_bundle.sh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs && \
rm /tmp/check_ca_bundle.sh
WORKDIR /build
RUN set -eux; \
@@ -98,7 +105,13 @@ RUN apk upgrade --no-cache && \
tzdata \
&& test "$(TZ=Asia/Kolkata date +%z)" = "+0530"
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Keep the native store safe even if the distribution CA package lags distrust.
COPY scripts/check_ca_bundle.sh /tmp/check_ca_bundle.sh
RUN sed -i '/^mozilla\/emSign_ECC_Root_CA_-_G3.crt$/s/^/!/' /etc/ca-certificates.conf && \
update-ca-certificates --fresh && \
sh /tmp/check_ca_bundle.sh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs && \
rm /tmp/check_ca_bundle.sh
COPY --from=build /build/rustfs /usr/bin/rustfs
COPY entrypoint.sh /entrypoint.sh
+14
View File
@@ -24,6 +24,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Keep the native store safe even if the distribution CA package lags distrust.
COPY scripts/check_ca_bundle.sh /tmp/check_ca_bundle.sh
RUN sed -i '/^mozilla\/emSign_ECC_Root_CA_-_G3.crt$/s/^/!/' /etc/ca-certificates.conf && \
update-ca-certificates --fresh && \
sh /tmp/check_ca_bundle.sh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs && \
rm /tmp/check_ca_bundle.sh
WORKDIR /build
RUN set -eux; \
@@ -103,6 +110,13 @@ RUN apt-get update && apt-get upgrade -y \
&& test "$(TZ=Asia/Kolkata date +%z)" = "+0530" \
&& rm -rf /var/lib/apt/lists/*
# Keep the native store safe even if the distribution CA package lags distrust.
COPY scripts/check_ca_bundle.sh /tmp/check_ca_bundle.sh
RUN sed -i '/^mozilla\/emSign_ECC_Root_CA_-_G3.crt$/s/^/!/' /etc/ca-certificates.conf && \
update-ca-certificates --fresh && \
sh /tmp/check_ca_bundle.sh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs && \
rm /tmp/check_ca_bundle.sh
COPY --from=build /build/rustfs /usr/bin/rustfs
COPY entrypoint.sh /entrypoint.sh
@@ -1206,7 +1206,9 @@ mod tests {
let interval = interval
.filter(|interval| *interval != 0)
.ok_or("scanner schedule has no positive interval")?;
let next_cycle = cycle_end
// A new leader may already be sleeping before completing its first cycle.
let schedule_start = if cycle_end == 0 { now } else { cycle_end };
let next_cycle = schedule_start
.checked_add(interval)
.ok_or("scanner schedule timestamp overflows")?;
let budget = next_cycle
@@ -1223,6 +1225,11 @@ mod tests {
Ok(Duration::from_secs(259)),
);
assert_eq!(scanner_cycle_completion_budget(100, Some(1), 110, false), Ok(Duration::from_secs(60)));
// A newly elected leader can advertise a sleep before its first completed cycle.
assert_eq!(
scanner_cycle_completion_budget(0, Some(289), 1_792_913_211, false),
Ok(Duration::from_secs(349)),
);
}
#[test]
@@ -1243,7 +1250,7 @@ mod tests {
previous_cycle_end: u64,
) -> Result<u64, Box<dyn Error + Send + Sync>> {
let mut deadline = Instant::now() + Duration::from_secs(60);
let mut schedule_accounted = false;
let mut accounted_schedules = HashSet::new();
loop {
let mut latest_cycle_end = 0;
let mut versions_observed = false;
@@ -1290,15 +1297,27 @@ mod tests {
return Err("scanner cycle timestamp is in the future".into());
}
let schedule = &status["cycle_schedule"];
if !schedule_accounted && schedule["execution_role"].as_str() == Some("leader") {
if schedule["execution_role"].as_str() == Some("leader") {
let active = metrics["current_cycle_active"]
.as_bool()
.ok_or("scanner status is missing its active-cycle flag")?;
if active || (cycle_end != 0 && schedule["effective_interval_available"].as_bool() == Some(true)) {
let interval = schedule["effective_interval_seconds"].as_u64();
let budget = scanner_cycle_completion_budget(cycle_end, interval, now, active)?;
deadline = Instant::now().checked_add(budget).ok_or("scanner deadline overflows")?;
schedule_accounted = true;
if active || schedule["effective_interval_available"].as_bool() == Some(true) {
let interval = if active {
None
} else {
Some(
schedule["effective_interval_seconds"]
.as_u64()
.ok_or("scanner schedule is missing its effective interval")?,
)
};
// Reconsider a changed leader or retry plan, but never renew
// the deadline on repeated observations of the same sleep.
if accounted_schedules.insert((node_index, cycle_end, interval)) {
let budget = scanner_cycle_completion_budget(cycle_end, interval, now, active)?;
let scheduled_deadline = Instant::now().checked_add(budget).ok_or("scanner deadline overflows")?;
deadline = deadline.max(scheduled_deadline);
}
}
}
// Superseded walks complete all sets while withholding quota authority.
+1 -1
View File
@@ -51,7 +51,7 @@ thiserror = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
# Keep XZ decoding self-contained in macOS binaries instead of linking Homebrew liblzma.
liblzma = { version = "0.4.8", features = ["static"] }
liblzma = { version = "0.4.9", features = ["static"] }
[dev-dependencies]
rustfs-tokio-tar = { workspace = true }
+345 -288
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
#!/bin/sh
# Reject the distrusted emSign ECC Root CA - G3 in native trust stores.
set -eu
bundle=${1:?Usage: check_ca_bundle.sh BUNDLE CERT_DIRECTORY}
cert_directory=${2:?Usage: check_ca_bundle.sh BUNDLE CERT_DIRECTORY}
distrusted_sha256=86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b
scratch=$(mktemp -d)
trap 'rm -rf "$scratch"' EXIT HUP INT TERM
check_file() {
# Hash DER, not subject text or PEM formatting. Also inspect hashed links:
# native trust consumers may use the directory rather than the bundle.
awk '
/-----BEGIN CERTIFICATE-----/ { if (inside) exit 1; inside=1; cert=""; next }
/-----END CERTIFICATE-----/ { if (!inside) exit 1; print cert; inside=0; next }
inside { gsub(/[[:space:]]/, ""); cert=cert $0 }
END { if (inside) exit 1 }
' "$1" > "$scratch/certificates"
while IFS= read -r encoded; do
printf '%s' "$encoded" | base64 -d > "$scratch/certificate.der"
if [ ! -s "$scratch/certificate.der" ]; then
echo "Empty certificate in $1" >&2
return 1
fi
digest=$(sha256sum "$scratch/certificate.der")
digest=${digest%% *}
if [ "$digest" = "$distrusted_sha256" ]; then
echo "Distrusted emSign ECC Root CA - G3 found in $1" >&2
return 1
fi
done < "$scratch/certificates"
}
check_file "$bundle"
if [ ! -s "$scratch/certificates" ]; then
echo "CA bundle contains no certificates: $bundle" >&2
exit 1
fi
find -L "$cert_directory" -type f > "$scratch/files"
while IFS= read -r file; do
check_file "$file"
done < "$scratch/files"
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
scratch=$(mktemp -d)
trap 'rm -rf "$scratch"' EXIT HUP INT TERM
mkdir "$scratch/certs"
# A public root from webpki-roots 1.0.9, not a private key or active trust anchor.
distrusted="$repo_root/scripts/fixtures/distrusted-emsign-g3.pem"
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj /CN=ca-bundle-test \
-keyout "$scratch/key.pem" -out "$scratch/valid.pem" >/dev/null 2>&1
cp "$scratch/valid.pem" "$scratch/bundle.pem"
check() {
sh "$repo_root/scripts/check_ca_bundle.sh" "$scratch/bundle.pem" "$scratch/certs"
}
reject() {
if check > "$scratch/output" 2>&1; then
echo "Expected CA rejection: $1" >&2
exit 1
fi
}
check
cat "$distrusted" >> "$scratch/bundle.pem"
reject 'G3 after a valid certificate in the bundle'
cp "$scratch/valid.pem" "$scratch/bundle.pem"
cp "$distrusted" "$scratch/certs/renamed-root.pem"
reject 'G3 in directory with an unrelated name'
rm "$scratch/certs/renamed-root.pem"
ln -s "$distrusted" "$scratch/certs/12345678.0"
reject 'G3 through a hashed trust-directory symlink'
rm "$scratch/certs/12345678.0"
# Equivalent PEM encodings must not defeat DER-based detection.
printf '%s\n' '-----BEGIN CERTIFICATE-----' > "$scratch/bundle.pem"
sed '/-----/d' "$distrusted" | tr -d '\n' >> "$scratch/bundle.pem"
printf '\n%s\n' '-----END CERTIFICATE-----' >> "$scratch/bundle.pem"
reject 'G3 with unwrapped PEM body'
printf '%s\n' '-----BEGIN CERTIFICATE-----' > "$scratch/bundle.pem"
reject 'truncated bundle'
printf '%s\n' '-----BEGIN CERTIFICATE-----' '-----END CERTIFICATE-----' > "$scratch/bundle.pem"
reject 'empty PEM body'
: > "$scratch/bundle.pem"
reject 'empty bundle'
cp "$scratch/valid.pem" "$scratch/bundle.pem"
check
printf '%s\n' 'CA bundle regression checks passed'
+26
View File
@@ -45,6 +45,32 @@ def archive(path, members, binary):
class ReleaseBuildTests(unittest.TestCase):
def test_security_release_console_pin(self):
script = shell_body(STEPS['Download static console assets'])
self.assertIn('releases/tags/v0.1.37', script)
self.assertNotIn('console/releases/latest', script)
parser = re.search(r"<<'PY'\n(.*?)\nPY", script, re.S)[1]
expected_digest = 'sha256:e6ff2c7e7695de824ec4cda91d8611d1aba5d8462e6809592c92a50b23f5b9ab'
asset = {'name': 'rustfs-console-v0.1.37.zip', 'digest': expected_digest,
'browser_download_url': 'https://github.com/rustfs/console/releases/download/v0.1.37/rustfs-console-v0.1.37.zip'}
cases = [
('exact pin', 'v0.1.37', asset, True),
('different tag', 'v0.1.38', asset, False),
('different name', 'v0.1.37', {**asset, 'name': 'rustfs-console-v0.1.38.zip'}, False),
('different digest', 'v0.1.37', {**asset, 'digest': 'sha256:' + '0' * 64}, False),
('missing URL', 'v0.1.37', {**asset, 'browser_download_url': ''}, False),
]
for name, tag, entry, accepted in cases:
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
metadata = Path(directory) / 'release.json'
metadata.write_text(json.dumps({'tag_name': tag, 'assets': [entry]}))
result = subprocess.run([sys.executable, '-c', parser, str(metadata)],
capture_output=True, text=True)
self.assertEqual(result.returncode == 0, accepted, result.stderr)
if accepted:
self.assertEqual(result.stdout.split(),
['v0.1.37', asset['browser_download_url'], expected_digest.split(':')[1]])
def test_every_matrix_target_builds_only_server(self):
mock_cargo = r'''
cargo() {