2024-08-19 20:31:18 +00:00
|
|
|
package promscrape
|
|
|
|
|
2024-10-03 16:27:15 +00:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2024-08-19 20:31:18 +00:00
|
|
|
func copyHeader(dst, src http.Header) {
|
|
|
|
for k, vv := range src {
|
|
|
|
for _, v := range vv {
|
|
|
|
dst.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-03 16:27:15 +00:00
|
|
|
type connReadWriteCloser struct {
|
|
|
|
io.Reader
|
|
|
|
io.WriteCloser
|
|
|
|
}
|
|
|
|
|
2024-08-19 20:31:18 +00:00
|
|
|
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
|
|
|
|
}
|
2024-10-03 16:27:15 +00:00
|
|
|
server, err := net.DialTimeout("tcp", r.Host, 10*time.Second)
|
2024-08-19 20:31:18 +00:00
|
|
|
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
|
|
|
|
}
|
2024-10-03 16:27:15 +00:00
|
|
|
clientConn, clientBuf, err := hijacker.Hijack()
|
2024-08-19 20:31:18 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
|
|
|
}
|
2024-10-03 16:27:15 +00:00
|
|
|
// For hijacked connections, one has to read from the connection buffer, but
|
|
|
|
// still write directly to the connection.
|
|
|
|
client := &connReadWriteCloser{clientBuf, clientConn}
|
|
|
|
|
|
|
|
go transfer(client, server)
|
|
|
|
transfer(server, client)
|
2024-08-19 20:31:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-09-24 13:12:55 +00:00
|
|
|
func TestClientProxyReadOk(t *testing.T) {
|
2024-08-19 20:31:18 +00:00
|
|
|
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{
|
2024-08-30 11:23:32 +00:00
|
|
|
ScrapeURL: backend.URL,
|
|
|
|
ProxyURL: proxy.MustNewURL(ps.URL),
|
|
|
|
// bump timeout for slow CIs
|
|
|
|
ScrapeTimeout: 5 * time.Second,
|
|
|
|
// force connection re-creating to avoid broken conns in slow CIs
|
|
|
|
DisableKeepAlive: true,
|
|
|
|
AuthConfig: newTestAuthConfig(t, isBackendTLS, backendAuth),
|
|
|
|
ProxyAuthConfig: newTestAuthConfig(t, isProxyTLS, proxyAuth),
|
|
|
|
MaxScrapeSize: 16000,
|
2024-08-19 20:31:18 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to create client: %s", err)
|
|
|
|
}
|
2024-08-30 11:23:32 +00:00
|
|
|
|
2024-08-19 20:31:18 +00:00
|
|
|
var bb bytesutil.ByteBuffer
|
2024-10-03 16:27:15 +00:00
|
|
|
if err = c.ReadData(&bb); err != nil {
|
2024-08-19 20:31:18 +00:00
|
|
|
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")})
|
2024-09-24 13:12:55 +00:00
|
|
|
}
|