Ceph RGW rejects quoted ETags in If-Match (412) — relay CAS / S3 conformance probe fails on Ceph-backed object storage
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 70/100
Research direction
Start in crates/buzz-relay/src/api/git/store.rs, in put_pointer() and its Precond::IfMatch(ETag(tag)) arm. Trace how the ETag reaches the If-Match header and review the S3 conformance probe and cas_publish.rs paths mentioned in the issue. Reproduce against Ceph RGW if available; done when conditional writes and the startup probe succeed without breaking other S3 backends.
Written by the indexing model from the issue text.
Description
Summary
When buzz-relay is backed by an S3-compatible object store implemented by Ceph RGW, every conditional-write (CAS) — including the startup S3 conformance probe — fails with HTTP 412, causing the relay to CrashLoop on startup.
Root cause
Ceph RGW returns ETags in quoted form (e.g. "deadbeef", per RFC 7232) in GET responses, but rejects quoted ETags in the If-Match precondition header (returns 412) while accepting the unquoted form (e.g. deadbeef). This is non-compliant with RFC 7232, which requires If-Match to carry a quoted entity-tag.
buzz-relay takes the ETag it received from a GET response and forwards it verbatim into the If-Match header of the subsequent PUT. Because the stored ETag is quoted, every If-Match carries a quoted value that Ceph RGW rejects → 412 → CAS fails.
Relevant code: crates/buzz-relay/src/api/git/store.rs, put_pointer(), the Precond::IfMatch(ETag(tag)) arm:
Precond::IfMatch(ETag(tag)) => {
headers.insert(
axum::http::header::IF_MATCH,
tag.parse().map_err(|_| {
StoreError::Backend(S3Error::HttpFailWithBody(
400,
format!("invalid etag {tag}"),
))
})?,
);
}
This single path covers both the conformance probe (etag_consistency phase, which calls get_pointer → put_pointer(IfMatch)) and cas_publish.rs (Precond::IfMatch(e.clone())).
Reproduction
- S3 backend: Ceph RGW ( Reef / any recent release).
- Configure buzz-relay against a Ceph-backed bucket (
BUZZ_S3_ENDPOINT,BUZZ_S3_BUCKET, credentials). - Start the relay: the startup S3 conformance probe fails with 412; the relay exits / CrashLoops.
Empirically confirmed with a stdlib SigV4 probe against our Ceph RGW:
- GET returns
ETag: "deadbeef"(quoted). If-Match: "deadbeef"(quoted) → 412 Precondition Failed.If-Match: deadbeef(unquoted) → 200 OK.
Suggested fix
Normalize the ETag before inserting it into If-Match — strip surrounding double quotes:
Precond::IfMatch(ETag(tag)) => {
// Ceph RGW rejects quoted If-Match values (RFC 7232 non-compliant) but
// accepts the unquoted form. AWS S3 and MinIO accept both, so stripping
// is safe across backends.
let unquoted = tag.trim_matches('"');
headers.insert(
axum::http::header::IF_MATCH,
unquoted.parse().map_err(|_| {
StoreError::Backend(S3Error::HttpFailWithBody(
400,
format!("invalid etag {unquoted}"),
))
})?,
);
}
This is harmless for AWS S3 and MinIO, which accept both the quoted and unquoted forms, so it does not regress those backends.
Alternatively, a more general fix could normalize the ETag on read (when received from GET) so callers always see the unquoted form, but that is a larger change with broader blast radius.
Environment
- buzz-relay built from chart tag
chart-v0.1.6 - S3 backend: Ceph RGW
Happy to open a PR if this approach is acceptable.
- Dominant language
- Rust
- Stars
- 33.7k
- Forks
- 4.4k
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 239
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from block/buzz
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
workflow_sink's mention parser never masks code regions — @name inside a code span wakes the agent Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 Half a day Newbie friendliness 88/100
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gitbutlerapp/gitbutler#15998 · 1 comment ·
-
bug triage:deciding
Difficulty 1/5 Under an hour Newbie friendliness 88/100
open-telemetry/otel-arrow#4132 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
bitcoindevkit/bdk-ffi#1125 ·