Skip to content

HDDS-12542. Support S3 signed multi chunks payload verification#10006

Open
chungen0126 wants to merge 4 commits into
apache:masterfrom
chungen0126:HDDS-12542-design-doc
Open

HDDS-12542. Support S3 signed multi chunks payload verification#10006
chungen0126 wants to merge 4 commits into
apache:masterfrom
chungen0126:HDDS-12542-design-doc

Conversation

@chungen0126

@chungen0126 chungen0126 commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

This is a design doc for supporting s3 multi chunks upload verification.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-12542

How was this patch tested?

None.

@chungen0126 chungen0126 changed the title s3 multi-chunks verification design doc HDDS-12542. Support S3 signed multi chunks payload verification Mar 30, 2026
@errose28 errose28 added s3 S3 Gateway design labels Mar 30, 2026
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new design document proposing how Ozone S3 Gateway (S3G) should verify AWS SigV4 streaming chunked payload signatures (currently parsed by SignedChunksInputStream but not cryptographically verified).

Changes:

  • Introduces a design doc describing chunk-signature verification approach and performance considerations (buffering + incremental hashing).
  • Outlines planned algorithm support (HMAC-SHA256 and mentions ECDSA/SigV4a), plus a testing plan.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md
Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md Outdated
@chungen0126 chungen0126 marked this pull request as ready for review April 29, 2026 08:09
@chungen0126 chungen0126 requested review from ChenSammi, Russole, henrybear327, ivandika3 and jojochuang and removed request for henrybear327 April 29, 2026 08:09
peterxcli

This comment was marked as outdated.

@peterxcli peterxcli dismissed their stale review April 29, 2026 10:58

accident

@peterxcli peterxcli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking if we could reuse the multipart upload? data chunk goes to mpu request, while the final chunk trigger a mpu complete request.

## Accumulative Buffering

The chunk size defined by AWS S3 Signature V4 (e.g., 64KB) is fundamentally different from—and typically much smaller than—Ozone's optimal internal transmission chunk size (e.g., 4MB).
To bridge this impedance mismatch, we introduce an internal buffering mechanism within the SignedChunksInputStream. The stream will aggressively read, verify, and accumulate multiple small S3 chunks in its buffer until the data reaches Ozone's preferred transmission length. This significantly reduces the number of micro-writes and system calls made to the downstream OzoneOutputStream, maximizing Ratis pipeline throughput.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we introduce an internal buffering mechanism within the SignedChunksInputStream. The stream will aggressively read, verify, and accumulate multiple small S3 chunks in its buffer until the data reaches Ozone's preferred transmission length.

Could you explain how would this buffer guarantee a ack chunk write success means it's durable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, the internal buffer in S3G does not acknowledge individual AWS SigV4 chunks. In the S3 protocol, the client does not receive an ACK for each chunk, it only receives a single HTTP 200 OK at the very end of the entire PUT request (or the specific multipart upload part).

The durability is strictly guaranteed by how we handle the downstream stream lifecycle. The S3 Gateway will only return the final HTTP 200 OK to the S3 client after OzoneOutputStream.close() is successfully called. This close() operation ensures that all buffered data is fully flushed, safely committed across DataNodes via the Ratis pipeline, and the metadata is finalized in the Ozone Manager.

If the S3G crashes or restarts while data is still in this 4MB memory buffer, no ACK is ever sent to the client. The connection will be dropped, and the S3 client (like the AWS SDK) will simply treat it as a network failure and automatically retry the upload. Therefore, there is no risk of data loss.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v1, v2 pipeline are already support buffering.

v1:

try (OzoneOutputStream output = openKeyForPut(
volume.getName(), bucketName, keyPath, length,
replicationConfig, customMetadata, tags, writeConditions)) {
long metadataLatencyNs =
getMetrics().updatePutKeyMetadataStats(startNanos);
perf.appendMetaLatencyNanos(metadataLatencyNs);
putLength = IOUtils.copyLarge(multiDigestInputStream, output, 0, length,
new byte[getIOBufferSize(length)]);

v2:

if (isDatastreamEnabled() && !enableEC && length > getDatastreamMinLength()) {
perf.appendStreamMode();
Pair<String, Long> keyWriteResult = ObjectEndpointStreaming
.put(bucket, keyPath, length, replicationConfig, getChunkSize(),
customMetadata, tags, multiDigestInputStream, getHeaders(),
signatureInfo.isSignPayload(), perf, writeConditions);
md5Hash = keyWriteResult.getKey();
putLength = keyWriteResult.getValue();

private static long writeToStreamOutput(OzoneDataStreamOutput streamOutput,
InputStream body, int bufferSize,
long length)
throws IOException {
final byte[] buffer = new byte[bufferSize];
long n = 0;
while (n < length) {
final int toRead = Math.toIntExact(Math.min(bufferSize, length - n));
final int readLength = body.read(buffer, 0, toRead);
if (readLength == -1) {
break;
}
streamOutput.write(ByteBuffer.wrap(buffer, 0, readLength));
n += readLength;
}
return n;

Comment thread hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md
@chungen0126

Copy link
Copy Markdown
Contributor Author

I'm thinking if we could reuse the multipart upload? data chunk goes to mpu request, while the final chunk trigger a mpu complete request.

Yes, MPU can reuse this.

In S3, an MPU UploadPart request behaves exactly like a standard single PUT request. If a client uses chunked encoding for an MPU part, it will go through this exact same SignedChunksInputStream logic. The stream will verify and buffer the chunks for that specific part just like it does for a single PUT.

@peterxcli

Copy link
Copy Markdown
Member

In S3, an MPU UploadPart request behaves exactly like a standard single PUT request. If a client uses chunked encoding for an MPU part, it will go through this exact same SignedChunksInputStream logic. The stream will verify and buffer the chunks for that specific part just like it does for a single PUT.

Actually I’m asking whether this multi‑chunk payload can reuse the existing MPU infrastructure. Sorry, I didn’t phrase it clearly earlier.

@peterxcli peterxcli left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only thing we need to add is, make the SignedChunksInputStream support materialised the chunked content if it bytes in the inputBody reach the length set in the x-amz-decoded-content-length header, reaming hash (each chunk signature depending on the previous chunk / prior state)

## Accumulative Buffering

The chunk size defined by AWS S3 Signature V4 (e.g., 64KB) is fundamentally different from—and typically much smaller than—Ozone's optimal internal transmission chunk size (e.g., 4MB).
To bridge this impedance mismatch, we introduce an internal buffering mechanism within the SignedChunksInputStream. The stream will aggressively read, verify, and accumulate multiple small S3 chunks in its buffer until the data reaches Ozone's preferred transmission length. This significantly reduces the number of micro-writes and system calls made to the downstream OzoneOutputStream, maximizing Ratis pipeline throughput.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v1, v2 pipeline are already support buffering.

v1:

try (OzoneOutputStream output = openKeyForPut(
volume.getName(), bucketName, keyPath, length,
replicationConfig, customMetadata, tags, writeConditions)) {
long metadataLatencyNs =
getMetrics().updatePutKeyMetadataStats(startNanos);
perf.appendMetaLatencyNanos(metadataLatencyNs);
putLength = IOUtils.copyLarge(multiDigestInputStream, output, 0, length,
new byte[getIOBufferSize(length)]);

v2:

if (isDatastreamEnabled() && !enableEC && length > getDatastreamMinLength()) {
perf.appendStreamMode();
Pair<String, Long> keyWriteResult = ObjectEndpointStreaming
.put(bucket, keyPath, length, replicationConfig, getChunkSize(),
customMetadata, tags, multiDigestInputStream, getHeaders(),
signatureInfo.isSignPayload(), perf, writeConditions);
md5Hash = keyWriteResult.getKey();
putLength = keyWriteResult.getValue();

private static long writeToStreamOutput(OzoneDataStreamOutput streamOutput,
InputStream body, int bufferSize,
long length)
throws IOException {
final byte[] buffer = new byte[bufferSize];
long n = 0;
while (n < length) {
final int toRead = Math.toIntExact(Math.min(bufferSize, length - n));
final int readLength = body.read(buffer, 0, toRead);
if (readLength == -1) {
break;
}
streamOutput.write(ByteBuffer.wrap(buffer, 0, readLength));
n += readLength;
}
return n;

@chungen0126

Copy link
Copy Markdown
Contributor Author

I think the only thing we need to add is, make the SignedChunksInputStream support materialised the chunked content if it bytes in the inputBody reach the length set in the x-amz-decoded-content-length header, reaming hash (each chunk signature depending on the previous chunk / prior state)

Thank you for the catch. You are right. The buffers work well now. I'll update the design doc.

@chungen0126 chungen0126 requested a review from peterxcli May 5, 2026 14:22

## Secret Key

Currently, the AWS Secret Keys are securely stored and managed exclusively within the Ozone Manager (OM). To enable the S3 Gateway (S3G) to independently verify chunked payloads, it requires access to verification materials. We propose adding a new internal OM API specifically for S3G to retrieve this data.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users will bring their s3 secret when they are sending s3 request, so can we just use it to calculate the derived key?

@chungen0126 chungen0126 May 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. However, under the AWS Signature V4 protocol, the client never transmits the Secret Key over the network. They only send the Access Key and the calculated Signature.

Since S3G never actually receives the user's Secret Key in the request, it is impossible for S3G to calculate the derived key locally. It must query Ozone Manager (OM), which securely holds the master keys, to perform the derivation.

@ivandika3 ivandika3 May 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to get AWS Secret key at all, AFAIK the information sent in the S3 request is enough for S3G to calculate the chunk signature (I might be wrong).

Also querying OM for each write will affect performance so it should not be done. Transmitting the secret key securely from OM to S3G is also another security concern. So is there a way to do some chunk signature validation without knowing the user secret key?

Since this will affect the existing S3 chunked uploads, we need to think about the carefully think about the side-effect. IMO if we need to get the secret key, this feature might be more difficult than expected.

@chungen0126 chungen0126 May 6, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ivandika3 for the review.

I don't think we need to get AWS Secret key at all, AFAIK the information sent in the S3 request is enough for S3G to calculate the chunk signature (I might be wrong).

Just to clarify, the verification process require the secret key. The official AWS documentation explains how the signing key is generated. See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html.
At step 2.

signing key = HMAC-SHA256(HMAC-SHA256(HMAC-SHA256(HMAC-SHA256("AWS4" + "<YourSecretAccessKey>","20130524"),"us-east-1"),"s3"),"aws4_request")

As shown here, the signing key is derived from the secret key.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also querying OM for each write will affect performance so it should not be done. Transmitting the secret key securely from OM to S3G is also another security concern. So is there a way to do some chunk signature validation without knowing the user secret key?

Retrieving the signing key from OM is secure due to the following reasons:

  1. One-way Hashing: The key S3G receives is a derived key computed through multiple iterations of HMAC-SHA256. Since HMAC is a one-way cryptographic function, even if this derived key is compromised, it is impossible to get the original secret key
  2. Time-bound Scoping: The derived key is strictly bound to a specific date. Because the date is a required input for the HMAC calculation, the key is only valid for that particular day and cannot be reused for requests on any other date.

As mentioned in the design doc:

From a security perspective, this new API will not expose the raw AWS Secret Key to the S3G. Instead, S3G will provide the request context (Date, Region, Service), and OM will compute and return the Derived Key.

Regarding the performance concern of fetching the signing key from OM.
For a multi-chunk streaming upload, this only adds one additional RPC per request. I believe the impact on overall performance will be small. Or maybe we could piggyback the derived key onto the metadata returned by the createKey call. This would eliminate the extra RPC entirely by providing the key upfront for all subsequent chunk uploads.

@ivandika3 ivandika3 May 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, the verification process require the secret key. The official AWS documentation explains how the signing key is generated. See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html.
At step 2.

Yes you're right we do need the secret key. Thanks for the clarification.

One-way Hashing: The key S3G receives is a derived key computed through multiple iterations of HMAC-SHA256. Since HMAC is a one-way cryptographic function, even if this derived key is compromised, it is impossible to get the original secret key

Yes, I think we can use the signing key to mask the secret key (since it's hashed). The main security point is that the response returned by the OM should not include a plain text secret key.

For a multi-chunk streaming upload, this only adds one additional RPC per request. I believe the impact on overall performance will be small. Or maybe we could piggyback the derived key onto the metadata returned by the createKey call. This would eliminate the extra RPC entirely by providing the key upfront for all subsequent chunk uploads.

Hm, to be consistent (handle secret revocation change), S3G need to send an additional RPC per PutObject. However, I think this might have some performance implication since each PutObject will generate around 5+ RPC request (S3 secret fetch, get volume, get bucket, open key, commit key). Additionally, the S3GetSecretRequest is implemented as a Ratis transaction so the latency should be higher than normal read request (without Ratis). One idea is to cache the signing key and refresh periodically, but this has consistency and security implication since the secret key might already be revoked but we might still allow the requests, etc. Unless we have a way to notify S3G about the revocation (looks like Minio implemented this way through when using etcd or it will try to broadcast the changes)

The piggyback logic might work to amortize this secret key (I can see the OMKeyCreateRequest can return the user signingKey if requested). We can think further on this.

Our cluster uses a different strategy so I'm not that well-versed about this. We might need to get people more familiar with this. You can also research other systems like Minio and SeaweedFS.


## Incremental Hashing

To maintain a low memory footprint during the continuous buffering process, the system utilizes incremental hashing (e.g., MessageDigest.update()) on the incoming byte streams to calculate the payload digest on the fly. This prevents allocating massive temporary byte arrays and avoids Garbage Collection (GC) spikes during large multi-gigabyte uploads. The computed digest is then used to construct the required StringToSign, which dictates the final signature calculation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please take a look if the multi digest already had this incremental hashing capability?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although we use MessageDigest here as well, reusing the one from MultiDigestInputStream isn't a good fit. MultiDigestInputStream validates the entire object at once, whereas this implementation requires chunk-by-chunk validation. Trying to combine these two different mechanism would just make the code clunky and less elegant.

@ivandika3 ivandika3 May 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current MessageDigest is simplistic since it does not rely on the previous data chunks so might not be appropriate. You might want to implement your own MessageDigest

From below

image

The next chunk digest depends on the previous signature, so that is the only state you can store.

The implementation can be left up to @chungen0126. The main point (as mentioned in the design doc) is that we should not buffer anything than necessary in S3G (the data needs to be streamed seamlessly).


## ECDSA-SHA256 Implementation

Currently, the Ozone S3 Gateway exclusively supports HMAC for AWS Signature V4. To implement ECDSA (SigV4a), we will extend the authentication flow across the Ozone Manager (OM) and S3 Gateway (S3G) through three key updates:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we need to implement ECDSA in Sigv4a. ECDSA is slow compared with sha256. AWS introduced Sigv4a for some special endpoints. We don't have this case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'll move ECDSA to future work. I agree that there is no use case for it in Ozone.


Currently, the AWS Secret Keys are securely stored and managed exclusively within the Ozone Manager (OM). To enable the S3 Gateway (S3G) to independently verify chunked payloads, it requires access to verification materials. We propose adding a new internal OM API specifically for S3G to retrieve this data.

From a security perspective, this new API **will not expose the raw AWS Secret Key** to the S3G. Instead, S3G will provide the request context (Date, Region, Service), and OM will compute and return the **Derived Key**. This architectural choice provides significant security benefits:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "Devrived key" looks good to me. Can it be piggy backed when S3 sends RPC to OM to create the file/object?

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

This PR has been marked as stale due to 21 days of inactivity. Please comment or remove the stale label to keep it open. Otherwise, it will be automatically closed in 7 days.

@github-actions github-actions Bot added the stale label Jun 4, 2026
@github-actions

Copy link
Copy Markdown

Thank you for your contribution. This PR is being closed due to inactivity. Please contact a maintainer if you would like to reopen it.

@github-actions github-actions Bot closed this Jun 12, 2026
@chungen0126 chungen0126 changed the title HDDS-12542. Support S3 signed multi chunks payload verification HDDS-15652. Support S3 signed multi chunks payload verification Jun 24, 2026
@chungen0126 chungen0126 reopened this Jun 24, 2026
@chungen0126 chungen0126 changed the title HDDS-15652. Support S3 signed multi chunks payload verification HDDS-12542. Support S3 signed multi chunks payload verification Jun 24, 2026
@chungen0126

Copy link
Copy Markdown
Contributor Author

Updated and will start to create some pr for this. @ChenSammi @ivandika3 @peterxcli @henrybear327 PTAL.

@github-actions github-actions Bot removed the stale label Jun 25, 2026

@ivandika3 ivandika3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chungen0126 LGTM +1. Please go ahead with the subtasks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

design s3 S3 Gateway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants