Skip to content

s3client: sign storage requests with the configured default region - #277

Merged
arttor merged 5 commits into
clyso:mainfrom
den-dw:fix/storage-signing-region
Oct 27, 2026
Merged

arttor merged 5 commits into
clyso:mainfrom
den-dw:fix/storage-signing-region

Conversation

@den-dw

@den-dw den-dw commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #276

Requests to storages were signed with us-east-1 regardless of the storage defaultRegion. Providers that validate the SigV4 credential scope (e.g. Cloud.ru Object Storage) answer AuthorizationHeaderMalformed: unexpected region, expecting '<region>' (or a bare 400 Bad Request for HEAD), so replication to such a storage fails: first bucket:create, then object:sync (unable to get to object stat: 400 Bad Request) and tag sync.

Three places:

  • the AWS SDK client in pkg/s3client/aws_wrapper.go (worker HeadBucket/CreateBucket, ACL, tagging) — hardcoded us-east-1;
  • proxy request forwarding in pkg/s3client/http_forward.go — hardcoded us-east-1;
  • the minio client in pkg/s3client/client.go — created without Region, so minio-go looks the region up with GetBucketLocation signed with us-east-1, which those providers reject.

Changes:

  • signingRegion() returns the storage defaultRegion, or us-east-1 when it is empty; the AWS SDK client and the proxy forwarding use it;
  • the minio client gets Region: defaultRegion; when it is empty the lookup works as before (no us-east-1 is forced, so AWS buckets in other regions keep working);
  • isOnline uses BucketExists (HEAD): with a configured region GetBucketLocation answers locally and would no longer check that the storage is reachable.

Tests: region choice, signature scope of the AWS client, and an httptest storage asserting that with defaultRegion the minio client signs with that region, never calls ?location, and the online check still reaches the storage (fails without the change). go test ./pkg/s3client/... ./pkg/s3/... ./pkg/objstore/... ./service/proxy/... ./service/worker/handler/..., go vet, gofmt.

@arttor arttor 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.

please also rebase onto main to make tests pass on CI

Comment thread pkg/s3client/region_test.go Outdated
* limitations under the License.
*/

package s3client

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.

Thanks for adding tests. In this repo storage behaviour is tested with e2e tests under test/ against real storages running in containers, not with a hand-written fake S3 server. test/env/components.go already provides Garage, SeaweedFS, MinIO, gofakes3 and Ceph RGW. test/minio/presign_test.go is an example of such a test.

Can you replace region_test.go with an e2e test against one of them configured with a region? You'll probably need to add an option in test/env to set the region on the container (e.g. via an env var). The test should cover both cases from the issue - replication to that storage and requests through the proxy - and fail on main.

@den-dw
den-dw force-pushed the fix/storage-signing-region branch from ec02a59 to 550265d Compare October 26, 2026 13:47
Comment thread pkg/s3client/client.go Outdated
func isOnline(ctx context.Context, c *client) error {
_, err := c.mc.GetBucketLocation(ctx, "probe-health-test")
// HEAD request: with a configured region GetBucketLocation answers without contacting the storage
_, err := c.mc.BucketExists(ctx, "probe-health-test")

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.

Let's not switch the health check to HEAD: HEAD errors have no body, so wrong credentials come back as a plain 403 which will now make the check pass for AccessDenied. Am I correct that the problem with keeping GetBucketLocation is that minio answers it from the configured region without sending a request?

Can you use GetBucketVersioning("probe-health-test") instead, which always reaches the storage? Also, to be safe with storages that don't implement every API, can the check treat any S3 error response as online, and fail only on network errors and credential/signature errors (InvalidAccessKeyId, SignatureDoesNotMatch, AuthorizationHeaderMalformed)?

@den-dw
den-dw force-pushed the fix/storage-signing-region branch from 550265d to 1fe249f Compare October 27, 2026 08:17
@den-dw

den-dw commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

Yes, that's right: when the client has a region, minio-go's getBucketLocation returns c.region without sending a request, so the online check never reached the storage.

  • Online check: it now calls GetBucketVersioning("probe-health-test"). Network errors, and InvalidAccessKeyId / SignatureDoesNotMatch / AuthorizationHeaderMalformed, mean the storage is offline. Any other S3 error response means it is online. A storage that requires a region but has no defaultRegion is now reported offline at startup, instead of failing on every request later.

  • Tests: I removed region_test.go. test/env has a new WithRegion option that starts MinIO with MINIO_SITE_REGION, so it rejects requests signed for any other region. The new Test_e2e_storage_region in test/minio covers:

    • replication to that storage;
    • requests through the proxy with it as main;
    • the online check with valid and wrong credentials.

    On main the replication case fails: bucket:create keeps being retried with AuthorizationHeaderMalformed: ... expecting 'test-region-1'. The proxy case fails too. Both pass with this PR.

I also rebased the branch onto main.

den-dw and others added 5 commits October 27, 2026 14:56
Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
Without a region minio-go resolves it with GetBucketLocation signed with us-east-1, which providers validating the credential scope reject (400 on object stat during replication). The online check now uses HEAD bucket: with a region set, GetBucketLocation no longer reaches the storage.

Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
With a configured region minio-go answers GetBucketLocation without a
request, and a HEAD error has no body, so a bare 403 for wrong credentials
looked like an online storage. GetBucketVersioning always reaches the
storage: any S3 error response means it is up; network errors and
InvalidAccessKeyId, SignatureDoesNotMatch, AuthorizationHeaderMalformed
mean it is not.

Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
Replaces the fake-S3 unit test with a MinIO started with a region
(env.WithRegion), which rejects requests signed for any other region.
Covers replication to that storage, requests through the proxy with it as
main, and the online check with valid and wrong credentials.

Signed-off-by: Denis Ivanov <dendw.work@gmail.com>
Signed-off-by: Artem Torubarov <artem.torubarov@clyso.com>
@arttor
arttor force-pushed the fix/storage-signing-region branch from 1fe249f to 38e0f19 Compare October 27, 2026 13:57
@arttor
arttor merged commit 1d11638 into clyso:main Oct 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Requests to storages are always signed with us-east-1, ignoring defaultRegion

2 participants