mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-21 23:16:49 +00:00
engine/gRPC proxy: Fix mux regression and add test coverage (#1456)
* engine/gRPC proxy: Fix mux regression and enhance test coverage * Use a temp dir for TLS creds and add credentials test tables * Update GetRPCEndpoints grpcProxyName ListenAddr field * Log unauthorised access attempts
This commit is contained in:
@@ -2,12 +2,20 @@ package engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -16,6 +24,7 @@ import (
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/key"
|
||||
@@ -4139,3 +4148,145 @@ func TestGetOpenInterest(t *testing.T) {
|
||||
_, err = s.GetOpenInterest(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestStartRPCRESTProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tempDir := filepath.Join(os.TempDir(), "gct-grpc-proxy-test")
|
||||
tempDirTLS := filepath.Join(tempDir, "tls")
|
||||
|
||||
t.Cleanup(func() {
|
||||
assert.NoErrorf(t, os.RemoveAll(tempDir), "RemoveAll should not error, manual directory deletion required for TempDir: %s", tempDir)
|
||||
})
|
||||
|
||||
if !assert.NoError(t, genCert(tempDirTLS), "genCert should not error") {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
gRPCPort := rand.Intn(65535-42069) + 42069 //nolint:gosec // Don't require crypto/rand usage here
|
||||
gRPCProxyPort := gRPCPort + 1
|
||||
|
||||
e := &Engine{
|
||||
Config: &config.Config{
|
||||
RemoteControl: config.RemoteControlConfig{
|
||||
Username: "bobmarley",
|
||||
Password: "Sup3rdup3rS3cr3t",
|
||||
GRPC: config.GRPCConfig{
|
||||
Enabled: true,
|
||||
ListenAddress: "localhost:" + strconv.Itoa(gRPCPort),
|
||||
GRPCProxyListenAddress: "localhost:" + strconv.Itoa(gRPCProxyPort),
|
||||
},
|
||||
},
|
||||
},
|
||||
Settings: Settings{
|
||||
DataDir: tempDir,
|
||||
CoreSettings: CoreSettings{EnableGRPCProxy: true},
|
||||
},
|
||||
}
|
||||
|
||||
fakeTime := time.Now().Add(-time.Hour)
|
||||
e.uptime = fakeTime
|
||||
|
||||
StartRPCServer(e)
|
||||
|
||||
// Give the proxy time to start
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
certFile := filepath.Join(tempDirTLS, "cert.pem")
|
||||
caCert, err := os.ReadFile(certFile)
|
||||
require.NoError(t, err, "ReadFile should not error")
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(caCert)
|
||||
require.True(t, ok, "AppendCertsFromPEM should return true")
|
||||
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: caCertPool, MinVersion: tls.VersionTLS12}}}
|
||||
|
||||
for _, creds := range []struct {
|
||||
testDescription string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{"Valid credentials", "bobmarley", "Sup3rdup3rS3cr3t"},
|
||||
{"Valid username but invalid password", "bobmarley", "wrongpass"},
|
||||
{"Invalid username but valid password", "bonk", "Sup3rdup3rS3cr3t"},
|
||||
{"Invalid username and password despite glorious credentials", "bonk", "wif"},
|
||||
} {
|
||||
creds := creds
|
||||
t.Run(creds.testDescription, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://localhost:"+strconv.Itoa(gRPCProxyPort)+"/v1/getinfo", http.NoBody)
|
||||
require.NoError(t, err, "NewRequestWithContext should not error")
|
||||
req.SetBasicAuth(creds.username, creds.password)
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err, "Do should not error")
|
||||
defer resp.Body.Close()
|
||||
|
||||
if creds.username == "bobmarley" && creds.password == "Sup3rdup3rS3cr3t" {
|
||||
var info gctrpc.GetInfoResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
||||
require.NoError(t, err, "Decode should not error")
|
||||
|
||||
uptimeDuration, err := time.ParseDuration(info.Uptime)
|
||||
require.NoError(t, err, "ParseDuration should not error")
|
||||
assert.InDelta(t, time.Since(fakeTime).Seconds(), uptimeDuration.Seconds(), 1.0, "Uptime should be within 1 second of the expected duration")
|
||||
} else {
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err, "ReadAll should not error")
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "HTTP status code should be 401")
|
||||
assert.Equal(t, "Access denied\n", string(respBody), "Response body should be 'Access denied\n'")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPCProxyAuthClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := new(RPCServer)
|
||||
s.Engine = &Engine{
|
||||
Config: &config.Config{
|
||||
RemoteControl: config.RemoteControlConfig{
|
||||
Username: "bobmarley",
|
||||
Password: "Sup3rdup3rS3cr3t",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte("MEOW"))
|
||||
require.NoError(t, err, "Write should not error")
|
||||
})
|
||||
|
||||
handler := s.authClient(dummyHandler)
|
||||
|
||||
for _, creds := range []struct {
|
||||
testDescription string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{"Valid credentials", "bobmarley", "Sup3rdup3rS3cr3t"},
|
||||
{"Valid username but invalid password", "bobmarley", "wrongpass"},
|
||||
{"Invalid username but valid password", "bonk", "Sup3rdup3rS3cr3t"},
|
||||
{"Invalid username and password despite glorious credentials", "bonk", "wif"},
|
||||
} {
|
||||
creds := creds
|
||||
t.Run(creds.testDescription, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody)
|
||||
require.NoError(t, err, "NewRequestWithContext should not error")
|
||||
req.SetBasicAuth(creds.username, creds.password)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if creds.username == "bobmarley" && creds.password == "Sup3rdup3rS3cr3t" {
|
||||
assert.Equal(t, http.StatusOK, rr.Code, "HTTP status code should be 200")
|
||||
assert.Equal(t, "MEOW", rr.Body.String(), "Response body should be 'MEOW'")
|
||||
} else {
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code, "HTTP status code should be 401")
|
||||
assert.Equal(t, "Access denied\n", rr.Body.String(), "Response body should be 'Access denied\n'")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user