mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-18 14:40:26 +01:00
08cbbf8134
* Adds custom dial func for HTTP-Connect and socks5 proxy tunnels. Standard golang http.transport exposes GetProxyConnectHeader function, but it doesn't allow to use separate tls config for proxy. It also not possible to enforce HTTP-Connect with standard http lib. * For http scrape targets, by default http.Transport.Proxy function must be used. Since it has special case with full uri forward. * Adds proxy.URL json methods that allow to properly copy internal fields, like User/Password. It should fix bug with proxy_url. When credentials specified at URL was ignored. * Adds tests for scrape client proxy requests related issue https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6771
190 lines
5.6 KiB
Go
190 lines
5.6 KiB
Go
package promscrape
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
|
|
)
|
|
|
|
func copyHeader(dst, src http.Header) {
|
|
for k, vv := range src {
|
|
for _, v := range vv {
|
|
dst.Add(k, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func proxyTunnel(w http.ResponseWriter, r *http.Request) {
|
|
transfer := func(src io.ReadCloser, dst io.WriteCloser) {
|
|
defer dst.Close()
|
|
defer src.Close()
|
|
io.Copy(dst, src) //nolint
|
|
}
|
|
destConn, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
hijacker, ok := w.(http.Hijacker)
|
|
if !ok {
|
|
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
clientConn, _, err := hijacker.Hijack()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
}
|
|
go transfer(clientConn, destConn)
|
|
transfer(destConn, clientConn)
|
|
}
|
|
|
|
type testProxyServer struct {
|
|
ba *promauth.BasicAuthConfig
|
|
receivedProxyRequest bool
|
|
}
|
|
|
|
func checkBasicAuthHeader(w http.ResponseWriter, headerValue string, ba *promauth.BasicAuthConfig) bool {
|
|
userPasswordEncoded := base64.StdEncoding.EncodeToString([]byte(ba.Username + ":" + ba.Password.String()))
|
|
expectedAuthValue := "Basic " + userPasswordEncoded
|
|
if headerValue != expectedAuthValue {
|
|
w.WriteHeader(403)
|
|
fmt.Fprintf(w, "Proxy Requires authorization got header value=%q, want=%q", headerValue, expectedAuthValue)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (tps *testProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
tps.receivedProxyRequest = true
|
|
if tps.ba != nil {
|
|
if !checkBasicAuthHeader(w, r.Header.Get("Proxy-Authorization"), tps.ba) {
|
|
return
|
|
}
|
|
}
|
|
if r.Method == http.MethodConnect {
|
|
proxyTunnel(w, r)
|
|
return
|
|
}
|
|
|
|
resp, err := http.DefaultTransport.RoundTrip(r)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
copyHeader(w.Header(), resp.Header)
|
|
w.WriteHeader(resp.StatusCode)
|
|
io.Copy(w, resp.Body) //nolint
|
|
}
|
|
|
|
func newClientTestServer(useTLS bool, rh http.Handler) *httptest.Server {
|
|
var s *httptest.Server
|
|
if useTLS {
|
|
s = httptest.NewTLSServer(rh)
|
|
} else {
|
|
s = httptest.NewServer(rh)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func newTestAuthConfig(t *testing.T, isTLS bool, ba *promauth.BasicAuthConfig) *promauth.Config {
|
|
a := promauth.Options{
|
|
BasicAuth: ba,
|
|
}
|
|
if isTLS {
|
|
a.TLSConfig = &promauth.TLSConfig{InsecureSkipVerify: true}
|
|
}
|
|
ac, err := a.NewConfig()
|
|
if err != nil {
|
|
t.Fatalf("cannot setup promauth.Confg: %s", err)
|
|
}
|
|
return ac
|
|
}
|
|
|
|
func TestClientProxyReadOk(t *testing.T) {
|
|
ctx := context.Background()
|
|
f := func(isBackendTLS, isProxyTLS bool, backendAuth, proxyAuth *promauth.BasicAuthConfig) {
|
|
t.Helper()
|
|
|
|
proxyHandler := &testProxyServer{ba: proxyAuth}
|
|
ps := newClientTestServer(isProxyTLS, proxyHandler)
|
|
|
|
expectedBackendResponse := `metric_name{key="value"} 123\n`
|
|
|
|
backend := newClientTestServer(isBackendTLS, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if backendAuth != nil && !checkBasicAuthHeader(w, r.Header.Get("Authorization"), backendAuth) {
|
|
return
|
|
}
|
|
w.Write([]byte(expectedBackendResponse))
|
|
}))
|
|
|
|
defer backend.Close()
|
|
defer ps.Close()
|
|
|
|
c, err := newClient(ctx, &ScrapeWork{
|
|
ScrapeURL: backend.URL,
|
|
ProxyURL: proxy.MustNewURL(ps.URL),
|
|
ScrapeTimeout: 2 * time.Second,
|
|
AuthConfig: newTestAuthConfig(t, isBackendTLS, backendAuth),
|
|
ProxyAuthConfig: newTestAuthConfig(t, isProxyTLS, proxyAuth),
|
|
MaxScrapeSize: 16000,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to create client: %s", err)
|
|
}
|
|
var bb bytesutil.ByteBuffer
|
|
if err := c.ReadData(&bb); err != nil {
|
|
t.Fatalf("unexpected error at ReadData: %s", err)
|
|
}
|
|
got, err := io.ReadAll(bb.NewReader())
|
|
if err != nil {
|
|
t.Fatalf("err read: %s", err)
|
|
}
|
|
|
|
if !proxyHandler.receivedProxyRequest {
|
|
t.Fatalf("proxy server didn't recieved request")
|
|
}
|
|
if string(got) != expectedBackendResponse {
|
|
t.Fatalf("not expected response: ")
|
|
}
|
|
}
|
|
|
|
// no tls
|
|
f(false, false, nil, nil)
|
|
// both tls no auth
|
|
f(true, true, nil, nil)
|
|
// backend tls, proxy http no auth
|
|
f(true, false, nil, nil)
|
|
// backend http, proxy tls no auth
|
|
f(false, true, nil, nil)
|
|
|
|
// no tls with auth
|
|
f(false, false, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, &promauth.BasicAuthConfig{Username: "proxy-test"})
|
|
// proxy tls and auth
|
|
f(false, true, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, &promauth.BasicAuthConfig{Username: "proxy-test"})
|
|
// backend tls and auth
|
|
f(true, false, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, &promauth.BasicAuthConfig{Username: "proxy-test"})
|
|
// tls with auth
|
|
f(true, true, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, &promauth.BasicAuthConfig{Username: "proxy-test"})
|
|
|
|
// tls with backend auth
|
|
f(true, true, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, nil)
|
|
// tls with proxy auth
|
|
f(true, true, nil, &promauth.BasicAuthConfig{Username: "proxy-test", Password: promauth.NewSecret("1234")})
|
|
// proxy tls with backend auth
|
|
f(false, true, &promauth.BasicAuthConfig{Username: "test", Password: promauth.NewSecret("1234")}, nil)
|
|
// backend tls and proxy auth
|
|
f(true, false, nil, &promauth.BasicAuthConfig{Username: "proxy-test", Password: promauth.NewSecret("1234")})
|
|
}
|