Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions hadoop-hdds/docs/content/design/s3-multi-chunks-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: S3 Multi Chunks Verification
summary: Add signature verification support for AWS Signature V4 streaming chunked uploads in S3G.
date: 2026-04-29
jira: HDDS-12542
status: proposed
author: Chung-En Lee
---
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. See accompanying LICENSE file.
-->

# Context & Motivation

Ozone S3 Gateway (S3G) currently utilizes SignedChunksInputStream to handle aws-chunked content-encoding for AWS Signature V4. However, it doesn’t do any signature verification now. This proposal aims to complete the existing SignedChunksInputStream to make sure signature verification is correct and minimize performance overhead.

# Goal & Non-Goal

## Goal

Support signature verification for AWS Signature Version 4 streaming chunked uploads with the following algorithms:
- STREAMING-AWS4-HMAC-SHA256-PAYLOAD
- STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER

## Non-Goal

The following algorithms are out of scope for this proposal. We may consider supporting them in the future if there is demand, but there is not any use case for them currently in Ozone.
- STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD
- STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER

# Proposed Solution

Currently, the SignedChunksInputStream successfully parses the S3 chunked upload payload but lacks the actual signature verification. This proposal enhances the existing stream to perform real-time signature verification, while ensuring the output remains fully compatible with Ozone's native, high-throughput write APIs.

## 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.


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?

- **Defense in Depth:** Even in the unlikely event that an S3G instance is compromised or the internal network is intercepted, the Secret Key remains safely isolated within OM.
- **Limited Blast Radius:** The exposed Derived Key is strictly scoped to a specific date, region, and service. An attacker cannot use it to forge arbitrary requests for other days or different services.

## HMAC-SHA256 Implementation

We add `ChunksValidator` to handle related verification tasks, such as updating hashing, building `strToSign`, and verifying signatures. To achieve this with minimal overhead, we will extract the reusable SigV4 HMAC logic currently embedded in `AWSV4AuthValidator` into a shared utility module that both `ozone-manager` and `s3gateway` can depend on.
The current `AWSV4AuthValidator` class is package-private and lives in the `ozone-manager` module, so `SignedChunksInputStream` must not depend on it directly. Instead, the refactoring will move common operations such as signing-key derivation and signature calculation into a buildable shared component, while `AWSV4AuthValidator` and `SignedChunksInputStream` each call that shared code from their respective modules.
This still allows `SignedChunksInputStream` to compute the derived key strictly once per request, avoiding expensive HMAC recalculations per chunk and preserving reuse of the existing highly-optimized `ThreadLocal Mac`-based implementation.

## SignedChunksInputStream flow

```mermaid
graph TD
classDef state fill:#f9f9fa,stroke:#333,stroke-width:2px;
classDef action fill:#e1f5fe,stroke:#0288d1,stroke-width:1px;
classDef check fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
classDef error fill:#ffebee,stroke:#c62828,stroke-width:2px;
classDef endpoint fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;

Start((Start InputStream)):::endpoint --> GetDerivedKey[Get Derived Key from KeyOutputStream<br>#40;Created during createKey#41;]:::action
GetDerivedKey --> Init[Initiate Validator with Derived Key & SignatureInfo]:::action
Init --> ReadHeader[Read Chunk Header until CRLF]:::action

ReadHeader --> ParseHeader{Extract Payload Size <br>& Expected Signature}:::check
ParseHeader -- Invalid Format --> ThrowError((Throw IOException)):::error

ParseHeader -- Valid --> CheckSize{Is Size == 0?}:::check

%% Final Chunk Flow
CheckSize -- "Yes (Final Chunk)" --> ValidateFinal[Validate Final Signature]:::action
ValidateFinal -- Valid --> CheckTailer{Is Trailer}:::check


CheckTailer -- "Yes (Trailer?)" --> ValidateTrailer[Validate Trailer]:::action
ValidateTrailer --> ReturnEOF((End of the read)):::endpoint

CheckTailer -- "No (Trailer?)" --> ReturnEOF((End of the read)):::endpoint

%% Data Chunk Flow
CheckSize -- "No (Data Chunk)" --> ReadPayload[Read Payload Bytes]:::action
ReadPayload --> UpdateHash[Incrementally update <br> SHA-256 Hash]:::action

UpdateHash --> CheckChunkEnd{Is Chunk fully read?}:::check
CheckChunkEnd -- "No" --> ReadPayload

CheckChunkEnd -- "Yes" --> ValidateChunk[Verify Chunk Signature via HMAC]:::action

ValidateChunk -- "Signature Mismatch" --> ThrowError
ValidateChunk -- "Signature Matches" --> ConsumeCRLF[Consume trailing CRLF]:::action
ConsumeCRLF --> ReadHeader
```
# Trade-offs

## Verification in S3 Gateway (Fail-Fast vs. Backend Processing)

Rather than offloading the verification to the backend Ozone cluster or introducing complex asynchronous pre-fetching, we decided to maintain the current stream-based architecture and execute the verification process entirely within the S3 Gateway (S3G). The primary reasons for this architectural choice are:

- Fail-Fast: This allows us to immediately reject requests with invalid signatures at the edge, preventing malformed data from consuming DataNode I/O, network bandwidth, and storage capacity.
- Stateless Scalability: Signature verification is a CPU-intensive task. Since the S3 Gateway is stateless and horizontally scalable, offloading the verification computations to S3G prevents the backend Ozone Managers (OM) or DataNodes (DN) from becoming CPU bottlenecks. S3G instances can be scaled out independently as compute demands increase.

## 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.

Doesn't the signed multi chunks payload verification check per chunk indenpently? or could you explain what is the incremental hashing for?

Comment thread
peterxcli marked this conversation as resolved.

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).


# Performance

To ensure that introducing the real-time signature verification process does not significantly degrade the overall upload throughput, the architecture is designed with the following optimizations in mind. Furthermore, we plan to conduct simple benchmarks in the future to validate these performance expectations:

- Constant Memory: Incremental hashing processes byte streams on the fly. This prevents large memory allocations and avoids GC spikes during massive uploads.
- CPU Offloading & Scalability: Verification computation is isolated in the stateless, horizontally scalable S3G instances. This allows verification throughput to scale easily by adding more S3G nodes, protecting backend OM and DataNodes from CPU bottlenecks.

# Compatibility

A core principle of this design is to introduce robust security enhancements without breaking existing workflows or requiring modifications from end-users. The proposed architecture ensures seamless integration with current S3 clients and minimal impact on Ozone's internal backend components.

- Client Compatibility: Standard S3 clients (e.g., AWS SDK) require no changes to use the signature verification.
- Backend Compatibility: No changes to existing Ozone data layouts or core RPC protocols. Only a lightweight OM API is added for S3G to retrieve the key.
Comment thread
chungen0126 marked this conversation as resolved.

# Test

To guarantee the correctness, stability, and security of the newly introduced chunk verification logic, a comprehensive testing strategy will be executed. This plan covers both granular unit testing for the stream parsing logic and end-to-end integration testing using official AWS SDKs.

- Unit Tests:
- There is an existing test class for SignedChunksInputStream.
- We would likely add TestChunkSignatureValidator to cover various scenarios, including:
- Validating correct signatures for both HMAC algorithms.
- Simulating signature verification failures due to signature doesn't match.
- Integration Tests:
- We would likely add TestS3SDKInSecureCluster to cover tests for S3 signed APIs in a secure cluster.
Loading