client: allow injecting a custom *http.Client (SSRF-guardable)

NewClient hardcoded its *http.Client, so a server-side caller making
requests to an attacker-influenced baseURL (a tenant's prime_endpoint)
had no way to attach an SSRF policy — the transport followed redirects
and dialed any resolved IP, reachable being the cloud metadata service.

Add NewClientWithHTTPClient + NewDragonchainSDKWithHTTPClient so callers
can supply a client whose transport enforces a dial-time resolved-IP guard
and redirect policy. Existing constructors delegate with the prior default
(30s timeout), so this is backward compatible — the guard itself lives in
the consuming server (e.g. brill-api/pkg/prime), not in this client lib.
This commit is contained in:
2026-06-04 12:32:33 -04:00
parent d945029f33
commit bc2b622873
2 changed files with 27 additions and 8 deletions

View File

@@ -23,14 +23,23 @@ type Client struct {
} }
func NewClient(publicID, authKeyID, authKey, baseURL string) *Client { func NewClient(publicID, authKeyID, authKey, baseURL string) *Client {
return NewClientWithHTTPClient(publicID, authKeyID, authKey, baseURL, nil)
}
// NewClientWithHTTPClient is like NewClient but uses a caller-supplied
// *http.Client — e.g. one whose transport is SSRF-guarded, or one with a
// non-default timeout. A nil hc falls back to the package default (30s
// timeout, default transport), so existing callers are unaffected.
func NewClientWithHTTPClient(publicID, authKeyID, authKey, baseURL string, hc *http.Client) *Client {
if hc == nil {
hc = &http.Client{Timeout: 30 * time.Second}
}
return &Client{ return &Client{
publicID: publicID, publicID: publicID,
authKeyID: authKeyID, authKeyID: authKeyID,
authKey: authKey, authKey: authKey,
baseURL: strings.TrimSuffix(baseURL, "/"), baseURL: strings.TrimSuffix(baseURL, "/"),
httpClient: &http.Client{ httpClient: hc,
Timeout: 30 * time.Second,
},
} }
} }

View File

@@ -50,6 +50,8 @@
package sdk package sdk
import ( import (
"net/http"
"git.dragonchain.com/dragonchain/prime-sdk-go/block" "git.dragonchain.com/dragonchain/prime-sdk-go/block"
"git.dragonchain.com/dragonchain/prime-sdk-go/client" "git.dragonchain.com/dragonchain/prime-sdk-go/client"
"git.dragonchain.com/dragonchain/prime-sdk-go/contract" "git.dragonchain.com/dragonchain/prime-sdk-go/contract"
@@ -80,7 +82,15 @@ type DragonchainSDK struct {
// Returns a configured SDK client ready to make API calls. // Returns a configured SDK client ready to make API calls.
// All API methods on the returned client require a context.Context parameter. // All API methods on the returned client require a context.Context parameter.
func NewDragonchainSDK(publicID, authKeyID, authKey, baseURL string) *DragonchainSDK { func NewDragonchainSDK(publicID, authKeyID, authKey, baseURL string) *DragonchainSDK {
c := client.NewClient(publicID, authKeyID, authKey, baseURL) return NewDragonchainSDKWithHTTPClient(publicID, authKeyID, authKey, baseURL, nil)
}
// NewDragonchainSDKWithHTTPClient is like NewDragonchainSDK but routes every
// request through the caller-supplied *http.Client. Pass a client whose
// transport enforces an SSRF policy (guarded dialer + redirect checks) when
// the baseURL is attacker-influenced. A nil hc falls back to the SDK default.
func NewDragonchainSDKWithHTTPClient(publicID, authKeyID, authKey, baseURL string, hc *http.Client) *DragonchainSDK {
c := client.NewClientWithHTTPClient(publicID, authKeyID, authKey, baseURL, hc)
return &DragonchainSDK{ return &DragonchainSDK{
client: c, client: c,
Transaction: transaction.NewTransactionClient(c), Transaction: transaction.NewTransactionClient(c),