Fix managed http/https transport failures #858

Open
darkowlzz wants to merge 1 commits from darkowlzz/http-go-transport-fix into main
2 changed files with 72 additions and 20 deletions

61
http.go
View File

@ -1,6 +1,8 @@
package git package git
import ( import (
"crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -190,8 +192,19 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
var resp *http.Response var resp *http.Response
var err error var err error
var userName string
var password string // Obtain the credentials and use them.
cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext)
if err != nil {
return err
}
defer cred.Free()
userName, password, err := cred.GetUserpassPlaintext()
if err != nil {
return err
}
for { for {
req := &http.Request{ req := &http.Request{
Method: self.req.Method, Method: self.req.Method,
@ -204,7 +217,32 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
} }
req.SetBasicAuth(userName, password) req.SetBasicAuth(userName, password)
resp, err = http.DefaultClient.Do(req)
c := http.Client{}
cap := x509.NewCertPool()
// NOTE: self.req.URL.Host returns only host without port. To be
// able to fetch the correct certs from the global certs, parse again
// and get host+port with url.Host.
u, err := url.Parse(self.req.URL.String())
if err != nil {
return fmt.Errorf("failed to parse URL: %v", err)
}
// Use CA cert if found.
if cert, found := globalCACertPool.certPool[u.Host]; found {
if ok := cap.AppendCertsFromPEM(cert); !ok {
return fmt.Errorf("failed to parse CA cert")
}
c.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: cap,
},
}
}
resp, err = c.Do(req)
if err != nil { if err != nil {
return err return err
} }
@ -213,23 +251,6 @@ func (self *httpSmartSubtransportStream) sendRequest() error {
break break
} }
if resp.StatusCode == http.StatusUnauthorized {
resp.Body.Close()
cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext)
if err != nil {
return err
}
defer cred.Free()
userName, password, err = cred.GetUserpassPlaintext()
if err != nil {
return err
}
continue
}
// Any other error we treat as a hard error and punt back to the caller // Any other error we treat as a hard error and punt back to the caller
resp.Body.Close() resp.Body.Close()
return fmt.Errorf("Unhandled HTTP error %s", resp.Status) return fmt.Errorf("Unhandled HTTP error %s", resp.Status)

View File

@ -24,6 +24,7 @@ import "C"
import ( import (
"fmt" "fmt"
"io" "io"
"net/url"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
@ -39,8 +40,38 @@ var (
}{ }{
transports: make(map[string]*RegisteredSmartTransport), transports: make(map[string]*RegisteredSmartTransport),
} }
// globalCACertPool is a mapping of global CA certs used by git2go-managed
// transports. The map's key is hostname+port and the value is a
// corresponding CA cert.
// Since the git2go-managed transports aren't public, this can be used to
// provide certs to the subtransports that can be looked up for a given
// host.
globalCACertPool = struct {
sync.Mutex
certPool map[string][]byte
}{
certPool: make(map[string][]byte),
}
) )
// RegisterCACerts registers CA cert associated with an address in the
// globalCACertPool.
func RegisterCACerts(address string, caBundle []byte) error {
globalCACertPool.Lock()
defer globalCACertPool.Unlock()
// Ignore empty CA bundles.
if len(caBundle) == 0 {
return nil
}
u, err := url.Parse(address)
if err != nil {
return err
}
// Store the certificate based on host+port, e.g.: 127.0.0.1:42107.
globalCACertPool.certPool[u.Host] = caBundle
return nil
}
// unregisterManagedTransports unregisters all git2go-managed transports. // unregisterManagedTransports unregisters all git2go-managed transports.
func unregisterManagedTransports() error { func unregisterManagedTransports() error {
globalRegisteredSmartTransports.Lock() globalRegisteredSmartTransports.Lock()