Merge current v2.11 into v3.0
This commit is contained in:
commit
bc84fdd006
143 changed files with 8736 additions and 6601 deletions
|
@ -4,9 +4,9 @@ import (
|
|||
"container/heap"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
@ -276,5 +276,5 @@ func hash(input string) string {
|
|||
// We purposely ignore the error because the implementation always returns nil.
|
||||
_, _ = hasher.Write([]byte(input))
|
||||
|
||||
return fmt.Sprintf("%x", hasher.Sum64())
|
||||
return strconv.FormatUint(hasher.Sum64(), 16)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
@ -8,6 +9,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -180,10 +182,71 @@ func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport)
|
|||
|
||||
// Return directly HTTP/1.1 transport when HTTP/2 is disabled
|
||||
if cfg.DisableHTTP2 {
|
||||
return transport, nil
|
||||
return &KerberosRoundTripper{
|
||||
OriginalRoundTripper: transport,
|
||||
new: func() http.RoundTripper {
|
||||
return transport.Clone()
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return newSmartRoundTripper(transport, cfg.ForwardingTimeouts)
|
||||
rt, err := newSmartRoundTripper(transport, cfg.ForwardingTimeouts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KerberosRoundTripper{
|
||||
OriginalRoundTripper: rt,
|
||||
new: func() http.RoundTripper {
|
||||
return rt.Clone()
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type KerberosRoundTripper struct {
|
||||
new func() http.RoundTripper
|
||||
OriginalRoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
type stickyRoundTripper struct {
|
||||
RoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
type transportKeyType string
|
||||
|
||||
var transportKey transportKeyType = "transport"
|
||||
|
||||
func AddTransportOnContext(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, transportKey, &stickyRoundTripper{})
|
||||
}
|
||||
|
||||
func (k *KerberosRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
value, ok := request.Context().Value(transportKey).(*stickyRoundTripper)
|
||||
if !ok {
|
||||
return k.OriginalRoundTripper.RoundTrip(request)
|
||||
}
|
||||
|
||||
if value.RoundTripper != nil {
|
||||
return value.RoundTripper.RoundTrip(request)
|
||||
}
|
||||
|
||||
resp, err := k.OriginalRoundTripper.RoundTrip(request)
|
||||
|
||||
// If we found that we are authenticating with Kerberos (Negotiate) or NTLM.
|
||||
// We put a dedicated roundTripper in the ConnContext.
|
||||
// This will stick the next calls to the same connection with the backend.
|
||||
if err == nil && containsNTLMorNegotiate(resp.Header.Values("WWW-Authenticate")) {
|
||||
value.RoundTripper = k.new()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func containsNTLMorNegotiate(h []string) bool {
|
||||
for _, s := range h {
|
||||
if strings.HasPrefix(s, "NTLM") || strings.HasPrefix(s, "Negotiate") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
|
@ -549,3 +550,80 @@ func (s *fakeSpiffeSource) GetX509BundleForTrustDomain(trustDomain spiffeid.Trus
|
|||
func (s *fakeSpiffeSource) GetX509SVID() (*x509svid.SVID, error) {
|
||||
return s.svid, nil
|
||||
}
|
||||
|
||||
type roundTripperFn func(req *http.Request) (*http.Response, error)
|
||||
|
||||
func (r roundTripperFn) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
return r(request)
|
||||
}
|
||||
|
||||
func TestKerberosRoundTripper(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
||||
originalRoundTripperHeaders map[string][]string
|
||||
|
||||
expectedStatusCode []int
|
||||
expectedDedicatedCount int
|
||||
expectedOriginalCount int
|
||||
}{
|
||||
{
|
||||
desc: "without special header",
|
||||
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized},
|
||||
expectedOriginalCount: 3,
|
||||
},
|
||||
{
|
||||
desc: "with Negotiate (Kerberos)",
|
||||
originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"Negotiate"}},
|
||||
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK},
|
||||
expectedOriginalCount: 1,
|
||||
expectedDedicatedCount: 2,
|
||||
},
|
||||
{
|
||||
desc: "with NTLM",
|
||||
originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"NTLM"}},
|
||||
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK},
|
||||
expectedOriginalCount: 1,
|
||||
expectedDedicatedCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
origCount := 0
|
||||
dedicatedCount := 0
|
||||
rt := KerberosRoundTripper{
|
||||
new: func() http.RoundTripper {
|
||||
return roundTripperFn(func(req *http.Request) (*http.Response, error) {
|
||||
dedicatedCount++
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}, nil
|
||||
})
|
||||
},
|
||||
OriginalRoundTripper: roundTripperFn(func(req *http.Request) (*http.Response, error) {
|
||||
origCount++
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Header: test.originalRoundTripperHeaders,
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
ctx := AddTransportOnContext(context.Background())
|
||||
for _, expected := range test.expectedStatusCode {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1", http.NoBody)
|
||||
require.NoError(t, err)
|
||||
resp, err := rt.RoundTrip(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, resp.StatusCode)
|
||||
}
|
||||
|
||||
require.Equal(t, test.expectedOriginalCount, origCount)
|
||||
require.Equal(t, test.expectedDedicatedCount, dedicatedCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (http.RoundTripper, error) {
|
||||
func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (*smartRoundTripper, error) {
|
||||
transportHTTP1 := transport.Clone()
|
||||
|
||||
transportHTTP2, err := http2.ConfigureTransports(transport)
|
||||
|
@ -53,6 +53,12 @@ type smartRoundTripper struct {
|
|||
http *http.Transport
|
||||
}
|
||||
|
||||
func (m *smartRoundTripper) Clone() http.RoundTripper {
|
||||
h := m.http.Clone()
|
||||
h2 := m.http2.Clone()
|
||||
return &smartRoundTripper{http: h, http2: h2}
|
||||
}
|
||||
|
||||
func (m *smartRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// If we have a connection upgrade, we don't use HTTP/2
|
||||
if httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue