@@ -38,6 +38,7 @@ import (
3838const (
3939 headerNameUserAgent = "User-Agent"
4040 sdkName = "ibm-go-sdk-core"
41+ maxRedirects = 10
4142)
4243
4344// ServiceOptions is a struct of configuration values for a service.
@@ -117,7 +118,7 @@ func (service *BaseService) Clone() *BaseService {
117118 // First, copy the service options struct.
118119 serviceOptions := * service .Options
119120
120- // Next, make a copy the service struct, then use the copy of the service options.
121+ // Next, make a copy of the service struct, then use the copy of the service options.
121122 // Note, we'll re-use the "Client" instance from the original BaseService instance.
122123 clone := * service
123124 clone .Options = & serviceOptions
@@ -234,7 +235,7 @@ func (service *BaseService) SetDefaultHeaders(headers http.Header) {
234235// the retryable client; otherwise "client" will be stored
235236// directly on "service".
236237func (service * BaseService ) SetHTTPClient (client * http.Client ) {
237- setMinimumTLSVersion (client )
238+ setupHTTPClient (client )
238239
239240 if isRetryableClient (service .Client ) {
240241 // If "service" is currently holding a retryable client,
@@ -298,15 +299,74 @@ func (service *BaseService) IsSSLDisabled() bool {
298299 return false
299300}
300301
301- // setMinimumTLSVersion sets the minimum TLS version required by the client to TLS v1.2
302- func setMinimumTLSVersion (client * http.Client ) {
303- if tr , ok := client .Transport .(* http.Transport ); tr != nil && ok {
304- if tr .TLSClientConfig == nil {
305- tr .TLSClientConfig = & tls.Config {} // #nosec G402
302+ // setupHTTPClient will configure "client" for use with the BaseService object.
303+ func setupHTTPClient (client * http.Client ) {
304+ // Set our "CheckRedirect" function to allow safe headers to be included
305+ // in redirected requests under certain conditions.
306+ if client .CheckRedirect == nil {
307+ client .CheckRedirect = checkRedirect
308+ }
309+ }
310+
311+ // checkRedirect is used as an override for the default "CheckRedirect" function supplied
312+ // by the net/http package and implements some additional logic required by IBM SDKs.
313+ func checkRedirect (req * http.Request , via []* http.Request ) error {
314+
315+ // The net/http module is implemented such that it will only include "safe" headers
316+ // ("Authorization", "WWW-Authenticate", "Cookie", "Cookie2") when redirecting a request
317+ // if the redirected host is the same host or a sub-domain of the original request's host.
318+ // Example: foo.com redirected to foo.com or bar.foo.com would work, but bar.com would not.
319+ // This "CheckRedirect" implementation will propagate "safe" headers in a redirected request
320+ // only in situations where the hosts associated with the original and redirected request URLs
321+ // are both located within the ".cloud.ibm.com" domain.
322+
323+ // First, perform the check that is done by the default CheckRedirect function
324+ // to ensure we don't exhaust our max redirect limit.
325+ if len (via ) >= maxRedirects {
326+ GetLogger ().Debug ("Exceeded max redirects: %d" , maxRedirects )
327+ return fmt .Errorf ("stopped after %d redirects" , maxRedirects )
328+ }
329+
330+ if len (via ) > 0 {
331+ GetLogger ().Debug ("Detected %d prior request(s)" , len (via ))
332+ originalReq := via [0 ]
333+ redirectedReq := req
334+ GetLogger ().Debug ("Redirecting request from %s to %s" , originalReq .URL .String (), redirectedReq .URL .String ())
335+ redirectedHeader := req .Header
336+ originalHeader := via [0 ].Header
337+
338+ originalHost := originalReq .URL .Hostname ()
339+ redirectedHost := redirectedReq .URL .Hostname ()
340+
341+ if shouldCopySafeHeadersOnRedirect (originalHost , redirectedHost ) {
342+
343+ // We're only concerned with "safe" headers since these are the ones that are not
344+ // propagated automatically by net/http for a "cross-site" redirect.
345+ for _ , headerKey := range []string {"Authorization" , "WWW-Authenticate" , "Cookie" , "Cookie2" } {
346+ // If the original request contains a value for "headerKey"
347+ // *and* this header is not already present in the redirected request,
348+ // then copy the value from the original request to the redirected request.
349+ if v , inOriginalRequest := originalHeader [headerKey ]; inOriginalRequest {
350+ if _ , inRedirectedRequest := redirectedHeader [headerKey ]; ! inRedirectedRequest {
351+ redirectedHeader [headerKey ] = v
352+ GetLogger ().Debug ("Propagating header '%s' in redirected request" , headerKey )
353+ }
354+ }
355+ }
356+ } else {
357+ GetLogger ().Debug ("Redirected request is not within the trusted domain." )
306358 }
307-
308- tr . TLSClientConfig . MinVersion = tls . VersionTLS12
359+ } else {
360+ GetLogger (). Debug ( "Detected no prior requests!" )
309361 }
362+ return nil
363+ }
364+
365+ // shouldCopySafeHeadersOnRedirect returns true iff safe headers should be copied
366+ // to a redirected request.
367+ func shouldCopySafeHeadersOnRedirect (fromHost , toHost string ) bool {
368+ GetLogger ().Debug ("hosts: %s %s" , fromHost , toHost )
369+ return strings .HasSuffix (fromHost , ".cloud.ibm.com" ) && strings .HasSuffix (toHost , ".cloud.ibm.com" )
310370}
311371
312372// SetEnableGzipCompression sets the service's EnableGzipCompression field
@@ -693,7 +753,7 @@ func (service *BaseService) DisableRetries() {
693753// DefaultHTTPClient returns a non-retryable http client with default configuration.
694754func DefaultHTTPClient () * http.Client {
695755 client := cleanhttp .DefaultPooledClient ()
696- setMinimumTLSVersion (client )
756+ setupHTTPClient (client )
697757 return client
698758}
699759
@@ -731,7 +791,7 @@ func NewRetryableClientWithHTTPClient(httpClient *http.Client) *retryablehttp.Cl
731791 // as our embedded client used to invoke individual requests.
732792 client .HTTPClient = httpClient
733793 } else {
734- // Otherwise, we'll use construct a default HTTP client and use that
794+ // Otherwise, we'll construct a default HTTP client and use that
735795 client .HTTPClient = DefaultHTTPClient ()
736796 }
737797
0 commit comments