Files
seaweedfs/weed/remote_storage/s3/endpoint_test.go
T
Chris Lu 5b519489c1 remote_storage: build all S3-compatible clients through one constructor (#10720)
* remote_storage: build S3-compatible clients through one constructor

The eight non-s3 S3-SDK providers each duplicated the AWS session setup
and only the s3 maker could take a custom *http.Client. Route every
S3-compatible type (s3, wasabi, b2, aliyun, tencent, baidu, filebase,
storj, contabo) through MakeWithHTTPClient with a single options table,
and add S3CompatibleEndpoint so callers can resolve the endpoint a given
type dials. No behavior change.

* volume: apply the remote-endpoint check to all S3-compatible providers

FetchAndWriteNeedle validated the endpoint and used the pinned dialer only
for type "s3". Every S3-SDK backend (wasabi, b2, aliyun, tencent, baidu,
filebase, storj, contabo) dials a caller-supplied endpoint through the same
client, so gate on S3CompatibleEndpoint to apply the same check uniformly.
-volume.allowUntrustedRemoteEndpoints still opts out.

* volume: don't route the guarded remote-endpoint client through a proxy

The guarded client exists to dial the validated endpoint directly and
re-check the resolved IP at connect time. With http.ProxyFromEnvironment
set, the dialer only validates the proxy's address while the proxy
re-resolves the endpoint host, which reopens the rebinding window. Drop
the proxy on this path; operators that need one can opt out with
-volume.allowUntrustedRemoteEndpoints.
2026-08-11 19:06:12 -07:00

45 lines
2.0 KiB
Go

package s3
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/remote_pb"
)
// TestS3CompatibleEndpointCoversAllProviders locks the type-to-endpoint mapping
// the volume server's SSRF guard relies on. Every S3-SDK-backed provider dials
// a caller-supplied endpoint, so each must surface it here; a new sibling that
// forgets to would silently bypass the guard.
func TestS3CompatibleEndpointCoversAllProviders(t *testing.T) {
cases := []struct {
conf *remote_pb.RemoteConf
want string
}{
{&remote_pb.RemoteConf{Type: "s3", S3Endpoint: "http://s3.internal"}, "http://s3.internal"},
{&remote_pb.RemoteConf{Type: "wasabi", WasabiEndpoint: "http://wasabi.internal"}, "http://wasabi.internal"},
{&remote_pb.RemoteConf{Type: "b2", BackblazeEndpoint: "http://b2.internal"}, "http://b2.internal"},
{&remote_pb.RemoteConf{Type: "aliyun", AliyunEndpoint: "http://aliyun.internal"}, "http://aliyun.internal"},
{&remote_pb.RemoteConf{Type: "tencent", TencentEndpoint: "http://tencent.internal"}, "http://tencent.internal"},
{&remote_pb.RemoteConf{Type: "baidu", BaiduEndpoint: "http://baidu.internal"}, "http://baidu.internal"},
{&remote_pb.RemoteConf{Type: "filebase", FilebaseEndpoint: "http://filebase.internal"}, "http://filebase.internal"},
{&remote_pb.RemoteConf{Type: "storj", StorjEndpoint: "http://storj.internal"}, "http://storj.internal"},
{&remote_pb.RemoteConf{Type: "contabo", ContaboEndpoint: "http://contabo.internal"}, "http://contabo.internal"},
}
for _, tc := range cases {
endpoint, ok := S3CompatibleEndpoint(tc.conf)
if !ok {
t.Errorf("type %q: expected an S3-compatible endpoint, got ok=false", tc.conf.Type)
continue
}
if endpoint != tc.want {
t.Errorf("type %q: endpoint = %q, want %q", tc.conf.Type, endpoint, tc.want)
}
}
// Every registered maker must be an S3-compatible type; the guard treats
// anything else (gcs, azure, ...) as not dialing a caller-supplied URL.
if _, ok := S3CompatibleEndpoint(&remote_pb.RemoteConf{Type: "gcs"}); ok {
t.Error("gcs must not be reported as an S3-compatible endpoint")
}
}