mirror of
https://github.com/fatedier/frp.git
synced 2026-10-18 18:53:27 +08:00
Move the frpc-only TLS option into the shared TLSConfig as transport.tls.useSystemCa, replacing the unpublished useSystemRoots. When set, frpc verifies the server with the system CAs; when trustedCaFile is also set, its CAs are appended to the system pool so both are trusted. Failures to load either source abort the connection instead of falling back. Enabling the option without effective TLS is a configuration error, and the field is rejected in frps transport.tls and in both webServer.tls sections. Fixes #5545
350 lines
11 KiB
Go
350 lines
11 KiB
Go
package basic
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/onsi/ginkgo/v2"
|
|
|
|
"github.com/fatedier/frp/test/e2e/framework"
|
|
"github.com/fatedier/frp/test/e2e/framework/consts"
|
|
"github.com/fatedier/frp/test/e2e/pkg/cert"
|
|
"github.com/fatedier/frp/test/e2e/pkg/port"
|
|
)
|
|
|
|
type generalTestConfigures struct {
|
|
server string
|
|
client string
|
|
clientPrefix string
|
|
client2 string
|
|
client2Prefix string
|
|
testDelay time.Duration
|
|
expectError bool
|
|
}
|
|
|
|
func renderBindPortConfig(protocol string) string {
|
|
switch protocol {
|
|
case "kcp":
|
|
return fmt.Sprintf(`kcpBindPort = {{ .%s }}`, consts.PortServerName)
|
|
case "quic":
|
|
return fmt.Sprintf(`quicBindPort = {{ .%s }}`, consts.PortServerName)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
|
serverConf := consts.DefaultServerConfig
|
|
clientConf := consts.DefaultClientConfig
|
|
if configures.clientPrefix != "" {
|
|
clientConf = configures.clientPrefix
|
|
}
|
|
|
|
serverConf += fmt.Sprintf(`
|
|
%s
|
|
`, configures.server)
|
|
|
|
tcpPortName := port.GenName("TCP")
|
|
udpPortName := port.GenName("UDP")
|
|
clientConf += fmt.Sprintf(`
|
|
%s
|
|
|
|
[[proxies]]
|
|
name = "tcp"
|
|
type = "tcp"
|
|
localPort = {{ .%s }}
|
|
remotePort = {{ .%s }}
|
|
|
|
[[proxies]]
|
|
name = "udp"
|
|
type = "udp"
|
|
localPort = {{ .%s }}
|
|
remotePort = {{ .%s }}
|
|
`, configures.client,
|
|
framework.TCPEchoServerPort, tcpPortName,
|
|
framework.UDPEchoServerPort, udpPortName,
|
|
)
|
|
|
|
clientConfs := []string{clientConf}
|
|
if configures.client2 != "" {
|
|
client2Conf := consts.DefaultClientConfig
|
|
if configures.client2Prefix != "" {
|
|
client2Conf = configures.client2Prefix
|
|
}
|
|
client2Conf += fmt.Sprintf(`
|
|
%s
|
|
`, configures.client2)
|
|
clientConfs = append(clientConfs, client2Conf)
|
|
}
|
|
|
|
f.RunProcesses(serverConf, clientConfs)
|
|
|
|
if configures.testDelay > 0 {
|
|
time.Sleep(configures.testDelay)
|
|
}
|
|
|
|
framework.NewRequestExpect(f).PortName(tcpPortName).ExpectError(configures.expectError).Explain("tcp proxy").Ensure()
|
|
framework.NewRequestExpect(f).Protocol("udp").
|
|
PortName(udpPortName).ExpectError(configures.expectError).Explain("udp proxy").Ensure()
|
|
}
|
|
|
|
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
|
|
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
|
|
ginkgo.It(desc, func() {
|
|
runClientServerTest(f, configures)
|
|
})
|
|
}
|
|
|
|
var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
|
|
f := framework.NewDefaultFramework()
|
|
|
|
ginkgo.Describe("Protocol", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
for _, protocol := range supportProtocols {
|
|
configures := &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
%s
|
|
`, renderBindPortConfig(protocol)),
|
|
client: fmt.Sprintf(`transport.protocol = "%s"`, protocol),
|
|
}
|
|
defineClientServerTest(protocol, f, configures)
|
|
}
|
|
})
|
|
|
|
// wss is special, it needs to be tested separately.
|
|
// frps only supports ws, so there should be a proxy to terminate TLS before frps.
|
|
ginkgo.Describe("Protocol wss", func() {
|
|
wssPort := f.AllocPort()
|
|
configures := &generalTestConfigures{
|
|
clientPrefix: fmt.Sprintf(`
|
|
serverAddr = "127.0.0.1"
|
|
serverPort = %d
|
|
loginFailExit = false
|
|
transport.protocol = "wss"
|
|
log.level = "trace"
|
|
`, wssPort),
|
|
// Due to the fact that frps cannot directly accept wss connections, we use the https2http plugin of another frpc to terminate TLS.
|
|
client2: fmt.Sprintf(`
|
|
[[proxies]]
|
|
name = "wss2ws"
|
|
type = "tcp"
|
|
remotePort = %d
|
|
[proxies.plugin]
|
|
type = "https2http"
|
|
localAddr = "127.0.0.1:{{ .%s }}"
|
|
`, wssPort, consts.PortServerName),
|
|
testDelay: 10 * time.Second,
|
|
}
|
|
|
|
defineClientServerTest("wss", f, configures)
|
|
})
|
|
|
|
ginkgo.Describe("Authentication", func() {
|
|
defineClientServerTest("Token Correct", f, &generalTestConfigures{
|
|
server: `auth.token = "123456"`,
|
|
client: `auth.token = "123456"`,
|
|
})
|
|
|
|
defineClientServerTest("Token Incorrect", f, &generalTestConfigures{
|
|
server: `auth.token = "123456"`,
|
|
client: `auth.token = "invalid"`,
|
|
expectError: true,
|
|
})
|
|
})
|
|
|
|
ginkgo.Describe("TLS", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
for _, protocol := range supportProtocols {
|
|
tmp := protocol
|
|
// Since v0.50.0, the default value of tls_enable has been changed to true.
|
|
// Therefore, here it needs to be set as false to test the scenario of turning it off.
|
|
defineClientServerTest("Disable TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
%s
|
|
`, renderBindPortConfig(protocol)),
|
|
client: fmt.Sprintf(`transport.tls.enable = false
|
|
transport.protocol = "%s"
|
|
`, protocol),
|
|
})
|
|
}
|
|
|
|
defineClientServerTest("enable tls force, client with TLS", f, &generalTestConfigures{
|
|
server: "transport.tls.force = true",
|
|
})
|
|
defineClientServerTest("enable tls force, client without TLS", f, &generalTestConfigures{
|
|
server: "transport.tls.force = true",
|
|
client: "transport.tls.enable = false",
|
|
expectError: true,
|
|
})
|
|
defineClientServerTest("useSystemCa rejects the default self-signed server certificate", f, &generalTestConfigures{
|
|
client: "transport.tls.useSystemCa = true",
|
|
expectError: true,
|
|
})
|
|
})
|
|
|
|
ginkgo.Describe("TLS with custom certificate", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
|
|
var (
|
|
caCrtPath string
|
|
serverCrtPath, serverKeyPath string
|
|
clientCrtPath, clientKeyPath string
|
|
)
|
|
ginkgo.JustBeforeEach(func() {
|
|
generator := &cert.SelfSignedCertGenerator{}
|
|
artifacts, err := generator.Generate("127.0.0.1")
|
|
framework.ExpectNoError(err)
|
|
|
|
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
|
|
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
|
|
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
|
|
generator.SetCA(artifacts.CACert, artifacts.CAKey)
|
|
_, err = generator.Generate("127.0.0.1")
|
|
framework.ExpectNoError(err)
|
|
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
|
|
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
|
|
})
|
|
|
|
for _, protocol := range supportProtocols {
|
|
tmp := protocol
|
|
|
|
ginkgo.It("one-way authentication: "+tmp, func() {
|
|
runClientServerTest(f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
%s
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, renderBindPortConfig(tmp), caCrtPath),
|
|
client: fmt.Sprintf(`
|
|
transport.protocol = "%s"
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
`, tmp, clientCrtPath, clientKeyPath),
|
|
})
|
|
})
|
|
|
|
ginkgo.It("mutual authentication: "+tmp, func() {
|
|
runClientServerTest(f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
%s
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, renderBindPortConfig(tmp), serverCrtPath, serverKeyPath, caCrtPath),
|
|
client: fmt.Sprintf(`
|
|
transport.protocol = "%s"
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, tmp, clientCrtPath, clientKeyPath, caCrtPath),
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
ginkgo.Describe("TLS with custom certificate and specified server name", func() {
|
|
var (
|
|
caCrtPath string
|
|
serverCrtPath, serverKeyPath string
|
|
clientCrtPath, clientKeyPath string
|
|
)
|
|
ginkgo.JustBeforeEach(func() {
|
|
generator := &cert.SelfSignedCertGenerator{}
|
|
artifacts, err := generator.Generate("example.com")
|
|
framework.ExpectNoError(err)
|
|
|
|
caCrtPath = f.WriteTempFile("ca.crt", string(artifacts.CACert))
|
|
serverCrtPath = f.WriteTempFile("server.crt", string(artifacts.Cert))
|
|
serverKeyPath = f.WriteTempFile("server.key", string(artifacts.Key))
|
|
generator.SetCA(artifacts.CACert, artifacts.CAKey)
|
|
_, err = generator.Generate("example.com")
|
|
framework.ExpectNoError(err)
|
|
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
|
|
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
|
|
})
|
|
|
|
ginkgo.It("mutual authentication", func() {
|
|
runClientServerTest(f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, serverCrtPath, serverKeyPath, caCrtPath),
|
|
client: fmt.Sprintf(`
|
|
transport.tls.serverName = "example.com"
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, clientCrtPath, clientKeyPath, caCrtPath),
|
|
})
|
|
})
|
|
|
|
ginkgo.It("mutual authentication with incorrect server name", func() {
|
|
runClientServerTest(f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, serverCrtPath, serverKeyPath, caCrtPath),
|
|
client: fmt.Sprintf(`
|
|
transport.tls.serverName = "invalid.com"
|
|
transport.tls.certFile = "%s"
|
|
transport.tls.keyFile = "%s"
|
|
transport.tls.trustedCaFile = "%s"
|
|
`, clientCrtPath, clientKeyPath, caCrtPath),
|
|
expectError: true,
|
|
})
|
|
})
|
|
})
|
|
|
|
ginkgo.Describe("TLS with disableCustomTLSFirstByte set to false", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
for _, protocol := range supportProtocols {
|
|
tmp := protocol
|
|
defineClientServerTest("TLS over "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
%s
|
|
`, renderBindPortConfig(protocol)),
|
|
client: fmt.Sprintf(`
|
|
transport.protocol = "%s"
|
|
transport.tls.disableCustomTLSFirstByte = false
|
|
`, protocol),
|
|
})
|
|
}
|
|
})
|
|
|
|
ginkgo.Describe("IPv6 bind address", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
for _, protocol := range supportProtocols {
|
|
tmp := protocol
|
|
defineClientServerTest("IPv6 bind address: "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
bindAddr = "::"
|
|
%s
|
|
`, renderBindPortConfig(protocol)),
|
|
client: fmt.Sprintf(`
|
|
transport.protocol = "%s"
|
|
`, protocol),
|
|
})
|
|
}
|
|
})
|
|
|
|
ginkgo.Describe("Use same port for bindPort and vhostHTTPSPort", func() {
|
|
supportProtocols := []string{"tcp", "kcp", "quic", "websocket"}
|
|
for _, protocol := range supportProtocols {
|
|
tmp := protocol
|
|
defineClientServerTest("Use same port for bindPort and vhostHTTPSPort: "+strings.ToUpper(tmp), f, &generalTestConfigures{
|
|
server: fmt.Sprintf(`
|
|
vhostHTTPSPort = {{ .%s }}
|
|
%s
|
|
`, consts.PortServerName, renderBindPortConfig(protocol)),
|
|
// transport.tls.disableCustomTLSFirstByte should set to false when vhostHTTPSPort is same as bindPort
|
|
client: fmt.Sprintf(`
|
|
transport.protocol = "%s"
|
|
transport.tls.disableCustomTLSFirstByte = false
|
|
`, protocol),
|
|
})
|
|
}
|
|
})
|
|
})
|