mirror of https://github.com/go-gost/gost.git
189 lines
6.2 KiB
Go
189 lines
6.2 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
)
|
|
|
|
// UTLSSuite exercises the utls dialer in a forward-proxy chain.
|
|
//
|
|
// Topology:
|
|
//
|
|
// curl -> client gost (http proxy, :8080)
|
|
// -> chain node (http connector + utls dialer)
|
|
// -> server gost (http proxy over TLS listener, :8443)
|
|
// -> tcp-echo
|
|
type UTLSSuite struct {
|
|
suite.Suite
|
|
ctx context.Context
|
|
echoC testcontainers.Container
|
|
echoIP string
|
|
certDir string
|
|
}
|
|
|
|
func (s *UTLSSuite) SetupSuite() {
|
|
s.ctx = context.Background()
|
|
|
|
certDir, err := os.MkdirTemp("", "gost-utls-certs-*")
|
|
s.Require().NoError(err)
|
|
s.certDir = certDir
|
|
s.generateCerts()
|
|
|
|
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
|
|
s.Require().NoError(err)
|
|
s.echoC = echoC
|
|
|
|
echoIP, err := echoC.ContainerIP(s.ctx)
|
|
s.Require().NoError(err)
|
|
s.echoIP = echoIP
|
|
}
|
|
|
|
func (s *UTLSSuite) TearDownSuite() {
|
|
if s.echoC != nil {
|
|
s.echoC.Terminate(s.ctx)
|
|
}
|
|
os.RemoveAll(s.certDir)
|
|
}
|
|
|
|
// generateCerts creates a self-signed CA and a server cert for the
|
|
// "utls-server" hostname, writing PEM files to s.certDir.
|
|
func (s *UTLSSuite) generateCerts() {
|
|
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
s.Require().NoError(err)
|
|
caTmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{CommonName: "Test CA"},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
|
|
s.Require().NoError(err)
|
|
s.writeCertPEM(s.certDir+"/ca.pem", "CERTIFICATE", caDER)
|
|
s.writeKeyPEM(s.certDir+"/ca-key.pem", "EC PRIVATE KEY", caKey)
|
|
|
|
serverKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
s.Require().NoError(err)
|
|
serverTmpl := &x509.Certificate{
|
|
SerialNumber: big.NewInt(2),
|
|
Subject: pkix.Name{CommonName: "utls-server"},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
DNSNames: []string{"utls-server"},
|
|
}
|
|
serverDER, err := x509.CreateCertificate(rand.Reader, serverTmpl, caTmpl, &serverKey.PublicKey, caKey)
|
|
s.Require().NoError(err)
|
|
s.writeCertPEM(s.certDir+"/server.pem", "CERTIFICATE", serverDER)
|
|
s.writeKeyPEM(s.certDir+"/server-key.pem", "EC PRIVATE KEY", serverKey)
|
|
}
|
|
|
|
// TestUTLSInsecure is the regression test for go-gost/gost#887.
|
|
//
|
|
// A utls dialer with `secure: false` must still complete the handshake
|
|
// (InsecureSkipVerify must be honoured). The previous unsafe.Pointer cast
|
|
// read garbage for InsecureSkipVerify, so it came back false and the
|
|
// handshake failed with "at least one of ServerName, InsecureSkipVerify or
|
|
// InsecureServerNameToVerify must be specified".
|
|
func (s *UTLSSuite) TestUTLSInsecure() {
|
|
rendered, err := RenderConfig("testdata/utls/client_insecure.yaml",
|
|
ConfigData{ServerAddr: "utls-server:8443"})
|
|
s.Require().NoError(err)
|
|
defer os.Remove(rendered)
|
|
|
|
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName,
|
|
"testdata/utls/server_auto.yaml", []string{"utls-server"}, []string{"8443/tcp"})
|
|
s.Require().NoError(err)
|
|
defer serverC.Terminate(s.ctx)
|
|
|
|
clientC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, rendered, "8080/tcp")
|
|
s.Require().NoError(err)
|
|
defer clientC.Terminate(s.ctx)
|
|
|
|
s.requireProxyWorks(clientC, serverC)
|
|
}
|
|
|
|
// TestUTLSSecureWithCA verifies the converter's RootCAs/ServerName path:
|
|
// with `secure: true` and a CA file, the utls dialer must verify the
|
|
// server's CA-signed certificate and still reach the echo server.
|
|
func (s *UTLSSuite) TestUTLSSecureWithCA() {
|
|
rendered, err := RenderConfig("testdata/utls/client_secure.yaml",
|
|
ConfigData{ServerAddr: "utls-server:8443"})
|
|
s.Require().NoError(err)
|
|
defer os.Remove(rendered)
|
|
|
|
serverC, err := runGostContainer(s.ctx, SharedNetworkName,
|
|
"testdata/utls/server_ca.yaml",
|
|
[]string{"utls-server"}, []string{"8443/tcp"},
|
|
[]testcontainers.ContainerFile{
|
|
{HostFilePath: s.certDir + "/server.pem", ContainerFilePath: "/certs/server.pem", FileMode: 0644},
|
|
{HostFilePath: s.certDir + "/server-key.pem", ContainerFilePath: "/certs/server-key.pem", FileMode: 0644},
|
|
})
|
|
s.Require().NoError(err)
|
|
defer serverC.Terminate(s.ctx)
|
|
|
|
clientC, err := RunGostContainerWithFiles(s.ctx, SharedNetworkName, rendered,
|
|
[]testcontainers.ContainerFile{
|
|
{HostFilePath: s.certDir + "/ca.pem", ContainerFilePath: "/certs/ca.pem", FileMode: 0644},
|
|
},
|
|
"8080/tcp")
|
|
s.Require().NoError(err)
|
|
defer clientC.Terminate(s.ctx)
|
|
|
|
s.requireProxyWorks(clientC, serverC)
|
|
}
|
|
|
|
// requireProxyWorks drives curl through the client proxy and asserts the
|
|
// echo server's "hello-gost" response is returned.
|
|
func (s *UTLSSuite) requireProxyWorks(clientC, serverC testcontainers.Container) {
|
|
cmd := []string{"curl", "-v", "-s", "--connect-timeout", "5",
|
|
"-x", "http://127.0.0.1:8080",
|
|
fmt.Sprintf("http://%s:5678", s.echoIP)}
|
|
code, out, err := clientC.Exec(s.ctx, cmd)
|
|
body, err2 := io.ReadAll(out)
|
|
if err != nil || err2 != nil || code != 0 || !strings.Contains(string(body), "hello-gost") {
|
|
DumpLogs(s.T(), s.ctx, "utls client logs", clientC)
|
|
DumpLogs(s.T(), s.ctx, "utls server logs", serverC)
|
|
}
|
|
s.Require().NoError(err)
|
|
s.Require().NoError(err2)
|
|
s.Require().Equal(0, code)
|
|
s.Require().Contains(string(body), "hello-gost")
|
|
}
|
|
|
|
func TestUTLSSuite(t *testing.T) {
|
|
suite.Run(t, new(UTLSSuite))
|
|
}
|
|
|
|
// --- helpers (mirror mtls_test.go) ---
|
|
|
|
func (s *UTLSSuite) writeCertPEM(path, blockType string, der []byte) {
|
|
err := os.WriteFile(path, pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: der}), 0644)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *UTLSSuite) writeKeyPEM(path, blockType string, key *ecdsa.PrivateKey) {
|
|
b, err := x509.MarshalECPrivateKey(key)
|
|
s.Require().NoError(err)
|
|
err = os.WriteFile(path, pem.EncodeToMemory(&pem.Block{Type: blockType, Bytes: b}), 0600)
|
|
s.Require().NoError(err)
|
|
}
|