mirror of
https://github.com/fatedier/frp.git
synced 2025-07-27 07:35:07 +00:00
add more e2e test (#2505)
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/streamserver"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
@@ -172,6 +175,106 @@ var _ = Describe("[Feature: Basic]", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("HTTPS", func() {
|
||||
It("proxy to HTTPS server", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
vhostHTTPSPort := f.AllocPort()
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_https_port = %d
|
||||
`, vhostHTTPSPort)
|
||||
|
||||
localPort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig
|
||||
getProxyConf := func(proxyName string, customDomains string, extra string) string {
|
||||
return fmt.Sprintf(`
|
||||
[%s]
|
||||
type = https
|
||||
local_port = %d
|
||||
custom_domains = %s
|
||||
`+extra, proxyName, localPort, customDomains)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
proxyName string
|
||||
customDomains string
|
||||
extraConfig string
|
||||
}{
|
||||
{
|
||||
proxyName: "normal",
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption",
|
||||
extraConfig: "use_encryption = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-compression",
|
||||
extraConfig: "use_compression = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption-and-compression",
|
||||
extraConfig: `
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
`,
|
||||
},
|
||||
{
|
||||
proxyName: "multiple-custom-domains",
|
||||
customDomains: "a.example.com, b.example.com",
|
||||
},
|
||||
}
|
||||
|
||||
// build all client config
|
||||
for i, test := range tests {
|
||||
if tests[i].customDomains == "" {
|
||||
tests[i].customDomains = test.proxyName + ".example.com"
|
||||
}
|
||||
clientConf += getProxyConf(test.proxyName, tests[i].customDomains, test.extraConfig) + "\n"
|
||||
}
|
||||
// run frps and frpc
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
framework.ExpectNoError(err)
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithTlsConfig(tlsConfig),
|
||||
httpserver.WithResponse([]byte("test")),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
for _, test := range tests {
|
||||
for _, domain := range strings.Split(test.customDomains, ",") {
|
||||
domain = strings.TrimSpace(domain)
|
||||
framework.NewRequestExpect(f).
|
||||
Explain(test.proxyName + "-" + domain).
|
||||
Port(vhostHTTPSPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTPS().HTTPHost(domain).TLSConfig(&tls.Config{
|
||||
ServerName: domain,
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}).
|
||||
ExpectResp([]byte("test")).
|
||||
Ensure()
|
||||
}
|
||||
}
|
||||
|
||||
// not exist host
|
||||
notExistDomain := "not-exist.example.com"
|
||||
framework.NewRequestExpect(f).
|
||||
Explain("not exist host").
|
||||
Port(vhostHTTPSPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTPS().HTTPHost(notExistDomain).TLSConfig(&tls.Config{
|
||||
ServerName: notExistDomain,
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}).
|
||||
ExpectError(true).
|
||||
Ensure()
|
||||
})
|
||||
})
|
||||
|
||||
Describe("STCP && SUDP", func() {
|
||||
types := []string{"stcp", "sudp"}
|
||||
for _, t := range types {
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"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"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -17,19 +18,17 @@ type generalTestConfigures struct {
|
||||
expectError bool
|
||||
}
|
||||
|
||||
// defineClientServerTest test a normal tcp and udp proxy with specified TestConfigures.
|
||||
func defineClientServerTest(desc string, f *framework.Framework, configures *generalTestConfigures) {
|
||||
It(desc, func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
func runClientServerTest(f *framework.Framework, configures *generalTestConfigures) {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
serverConf += fmt.Sprintf(`
|
||||
serverConf += fmt.Sprintf(`
|
||||
%s
|
||||
`, configures.server)
|
||||
|
||||
tcpPortName := port.GenName("TCP")
|
||||
udpPortName := port.GenName("UDP")
|
||||
clientConf += fmt.Sprintf(`
|
||||
tcpPortName := port.GenName("TCP")
|
||||
udpPortName := port.GenName("UDP")
|
||||
clientConf += fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
[tcp]
|
||||
@@ -42,15 +41,21 @@ func defineClientServerTest(desc string, f *framework.Framework, configures *gen
|
||||
local_port = {{ .%s }}
|
||||
remote_port = {{ .%s }}
|
||||
`, configures.client,
|
||||
framework.TCPEchoServerPort, tcpPortName,
|
||||
framework.UDPEchoServerPort, udpPortName,
|
||||
)
|
||||
framework.TCPEchoServerPort, tcpPortName,
|
||||
framework.UDPEchoServerPort, udpPortName,
|
||||
)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
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()
|
||||
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) {
|
||||
It(desc, func() {
|
||||
runClientServerTest(f, configures)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,4 +113,122 @@ var _ = Describe("[Feature: Client-Server]", func() {
|
||||
expectError: true,
|
||||
})
|
||||
})
|
||||
|
||||
Describe("TLS with custom certificate", func() {
|
||||
supportProtocols := []string{"tcp", "kcp", "websocket"}
|
||||
|
||||
var (
|
||||
caCrtPath string
|
||||
serverCrtPath, serverKeyPath string
|
||||
clientCrtPath, clientKeyPath string
|
||||
)
|
||||
JustBeforeEach(func() {
|
||||
generator := &cert.SelfSignedCertGenerator{}
|
||||
artifacts, err := generator.Generate("0.0.0.0")
|
||||
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)
|
||||
generator.Generate("0.0.0.0")
|
||||
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
|
||||
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
|
||||
})
|
||||
|
||||
for _, protocol := range supportProtocols {
|
||||
tmp := protocol
|
||||
|
||||
It("one-way authentication: "+tmp, func() {
|
||||
runClientServerTest(f, &generalTestConfigures{
|
||||
server: fmt.Sprintf(`
|
||||
protocol = %s
|
||||
kcp_bind_port = {{ .%s }}
|
||||
tls_trusted_ca_file = %s
|
||||
`, tmp, consts.PortServerName, caCrtPath),
|
||||
client: fmt.Sprintf(`
|
||||
protocol = %s
|
||||
tls_enable = true
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
`, tmp, clientCrtPath, clientKeyPath),
|
||||
})
|
||||
})
|
||||
|
||||
It("mutual authentication: "+tmp, func() {
|
||||
runClientServerTest(f, &generalTestConfigures{
|
||||
server: fmt.Sprintf(`
|
||||
protocol = %s
|
||||
kcp_bind_port = {{ .%s }}
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, tmp, consts.PortServerName, serverCrtPath, serverKeyPath, caCrtPath),
|
||||
client: fmt.Sprintf(`
|
||||
protocol = %s
|
||||
tls_enable = true
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, tmp, clientCrtPath, clientKeyPath, caCrtPath),
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Describe("TLS with custom certificate and specified server name", func() {
|
||||
var (
|
||||
caCrtPath string
|
||||
serverCrtPath, serverKeyPath string
|
||||
clientCrtPath, clientKeyPath string
|
||||
)
|
||||
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)
|
||||
generator.Generate("example.com")
|
||||
clientCrtPath = f.WriteTempFile("client.crt", string(artifacts.Cert))
|
||||
clientKeyPath = f.WriteTempFile("client.key", string(artifacts.Key))
|
||||
})
|
||||
|
||||
It("mutual authentication", func() {
|
||||
runClientServerTest(f, &generalTestConfigures{
|
||||
server: fmt.Sprintf(`
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, serverCrtPath, serverKeyPath, caCrtPath),
|
||||
client: fmt.Sprintf(`
|
||||
tls_enable = true
|
||||
tls_server_name = example.com
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, clientCrtPath, clientKeyPath, caCrtPath),
|
||||
})
|
||||
})
|
||||
|
||||
It("mutual authentication with incorrect server name", func() {
|
||||
runClientServerTest(f, &generalTestConfigures{
|
||||
server: fmt.Sprintf(`
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, serverCrtPath, serverKeyPath, caCrtPath),
|
||||
client: fmt.Sprintf(`
|
||||
tls_enable = true
|
||||
tls_server_name = invalid.com
|
||||
tls_cert_file = %s
|
||||
tls_key_file = %s
|
||||
tls_trusted_ca_file = %s
|
||||
`, clientCrtPath, clientKeyPath, caCrtPath),
|
||||
expectError: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -86,16 +86,16 @@ var _ = Describe("[Feature: Server Manager]", func() {
|
||||
|
||||
adminPort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
admin_port = %d
|
||||
admin_port = %d
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
|
||||
[udp]
|
||||
type = udp
|
||||
local_port = {{ .%s }}
|
||||
`, adminPort, framework.TCPEchoServerPort, framework.UDPEchoServerPort)
|
||||
[udp]
|
||||
type = udp
|
||||
local_port = {{ .%s }}
|
||||
`, adminPort, framework.TCPEchoServerPort, framework.UDPEchoServerPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
@@ -123,4 +123,25 @@ var _ = Describe("[Feature: Server Manager]", func() {
|
||||
|
||||
framework.NewRequestExpect(f).Protocol("udp").Port(port).Ensure()
|
||||
})
|
||||
|
||||
It("Port Reuse", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
// Use same port as PortServer
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_http_port = {{ .%s }}
|
||||
`, consts.PortServerName)
|
||||
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[http]
|
||||
type = http
|
||||
local_port = {{ .%s }}
|
||||
custom_domains = example.com
|
||||
`, framework.HTTPSimpleServerPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("example.com")
|
||||
}).PortName(consts.PortServerName).Ensure()
|
||||
})
|
||||
})
|
||||
|
52
test/e2e/features/monitor.go
Normal file
52
test/e2e/features/monitor.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[Feature: Monitor]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
It("Prometheus metrics", func() {
|
||||
dashboardPort := f.AllocPort()
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
enable_prometheus = true
|
||||
dashboard_addr = 0.0.0.0
|
||||
dashboard_port = %d
|
||||
`, dashboardPort)
|
||||
|
||||
clientConf := consts.DefaultClientConfig
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
framework.NewRequestExpect(f).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().Port(dashboardPort).HTTPPath("/metrics")
|
||||
}).Ensure(func(resp *request.Response) bool {
|
||||
log.Trace("prometheus metrics response: \n%s", resp.Content)
|
||||
if resp.Code != 200 {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(string(resp.Content), "traffic_in") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
})
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -256,3 +257,10 @@ func (f *Framework) RunServer(portName string, s server.Server) {
|
||||
func (f *Framework) SetEnvs(envs []string) {
|
||||
f.osEnvs = envs
|
||||
}
|
||||
|
||||
func (f *Framework) WriteTempFile(name string, content string) string {
|
||||
filePath := filepath.Join(f.TempDirectory, name)
|
||||
err := ioutil.WriteFile(filePath, []byte(content), 0766)
|
||||
ExpectNoError(err)
|
||||
return filePath
|
||||
}
|
||||
|
@@ -55,6 +55,11 @@ func NewRequestExpect(f *Framework) *RequestExpect {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *RequestExpect) Request(req *request.Request) *RequestExpect {
|
||||
e.req = req
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *RequestExpect) RequestModify(f func(r *request.Request)) *RequestExpect {
|
||||
f(e.req)
|
||||
return e
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -12,8 +13,9 @@ type Server struct {
|
||||
bindPort int
|
||||
hanlder http.Handler
|
||||
|
||||
l net.Listener
|
||||
hs *http.Server
|
||||
l net.Listener
|
||||
tlsConfig *tls.Config
|
||||
hs *http.Server
|
||||
}
|
||||
|
||||
type Option func(*Server) *Server
|
||||
@@ -43,6 +45,13 @@ func WithBindPort(port int) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithTlsConfig(tlsConfig *tls.Config) Option {
|
||||
return func(s *Server) *Server {
|
||||
s.tlsConfig = tlsConfig
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func WithHandler(h http.Handler) Option {
|
||||
return func(s *Server) *Server {
|
||||
s.hanlder = h
|
||||
@@ -50,6 +59,15 @@ func WithHandler(h http.Handler) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithResponse(resp []byte) Option {
|
||||
return func(s *Server) *Server {
|
||||
s.hanlder = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(resp)
|
||||
})
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Run() error {
|
||||
if err := s.initListener(); err != nil {
|
||||
return err
|
||||
@@ -57,11 +75,17 @@ func (s *Server) Run() error {
|
||||
|
||||
addr := net.JoinHostPort(s.bindAddr, strconv.Itoa(s.bindPort))
|
||||
hs := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.hanlder,
|
||||
Addr: addr,
|
||||
Handler: s.hanlder,
|
||||
TLSConfig: s.tlsConfig,
|
||||
}
|
||||
|
||||
s.hs = hs
|
||||
go hs.Serve(s.l)
|
||||
if s.tlsConfig == nil {
|
||||
go hs.Serve(s.l)
|
||||
} else {
|
||||
go hs.ServeTLS(s.l, "", "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
68
test/e2e/pkg/cert/generator.go
Normal file
68
test/e2e/pkg/cert/generator.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Artifacts hosts a private key, its corresponding serving certificate and
|
||||
// the CA certificate that signs the serving certificate.
|
||||
type Artifacts struct {
|
||||
// PEM encoded private key
|
||||
Key []byte
|
||||
// PEM encoded serving certificate
|
||||
Cert []byte
|
||||
// PEM encoded CA private key
|
||||
CAKey []byte
|
||||
// PEM encoded CA certificate
|
||||
CACert []byte
|
||||
// Resource version of the certs
|
||||
ResourceVersion string
|
||||
}
|
||||
|
||||
// CertGenerator is an interface to provision the serving certificate.
|
||||
type CertGenerator interface {
|
||||
// Generate returns a Artifacts struct.
|
||||
Generate(CommonName string) (*Artifacts, error)
|
||||
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||
SetCA(caKey, caCert []byte)
|
||||
}
|
||||
|
||||
// ValidCACert think cert and key are valid if they meet the following requirements:
|
||||
// - key and cert are valid pair
|
||||
// - caCert is the root ca of cert
|
||||
// - cert is for dnsName
|
||||
// - cert won't expire before time
|
||||
func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool {
|
||||
if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 {
|
||||
return false
|
||||
}
|
||||
// Verify key and cert are valid pair
|
||||
_, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify cert is valid for at least 1 year.
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(caCert) {
|
||||
return false
|
||||
}
|
||||
block, _ := pem.Decode(cert)
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
c, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ops := x509.VerifyOptions{
|
||||
DNSName: dnsName,
|
||||
Roots: pool,
|
||||
CurrentTime: time,
|
||||
}
|
||||
_, err = c.Verify(ops)
|
||||
return err == nil
|
||||
}
|
169
test/e2e/pkg/cert/selfsigned.go
Normal file
169
test/e2e/pkg/cert/selfsigned.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/util/cert"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
)
|
||||
|
||||
type SelfSignedCertGenerator struct {
|
||||
caKey []byte
|
||||
caCert []byte
|
||||
}
|
||||
|
||||
var _ CertGenerator = &SelfSignedCertGenerator{}
|
||||
|
||||
// SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert.
|
||||
func (cp *SelfSignedCertGenerator) SetCA(caKey, caCert []byte) {
|
||||
cp.caKey = caKey
|
||||
cp.caCert = caCert
|
||||
}
|
||||
|
||||
// Generate creates and returns a CA certificate, certificate and
|
||||
// key for the server or client. Key and Cert are used by the server or client
|
||||
// to establish trust for others, CA certificate is used by the
|
||||
// client or server to verify the other's authentication chain.
|
||||
// The cert will be valid for 365 days.
|
||||
func (cp *SelfSignedCertGenerator) Generate(commonName string) (*Artifacts, error) {
|
||||
var signingKey *rsa.PrivateKey
|
||||
var signingCert *x509.Certificate
|
||||
var valid bool
|
||||
var err error
|
||||
|
||||
valid, signingKey, signingCert = cp.validCACert()
|
||||
if !valid {
|
||||
signingKey, err = NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the CA private key: %v", err)
|
||||
}
|
||||
signingCert, err = cert.NewSelfSignedCACert(cert.Config{CommonName: commonName}, signingKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the CA cert: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
hostIP := net.ParseIP(commonName)
|
||||
var altIPs []net.IP
|
||||
DNSNames := []string{"localhost"}
|
||||
if hostIP.To4() != nil {
|
||||
altIPs = append(altIPs, hostIP.To4())
|
||||
} else {
|
||||
DNSNames = append(DNSNames, commonName)
|
||||
}
|
||||
|
||||
key, err := NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the private key: %v", err)
|
||||
}
|
||||
signedCert, err := NewSignedCert(
|
||||
cert.Config{
|
||||
CommonName: commonName,
|
||||
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
AltNames: cert.AltNames{IPs: altIPs, DNSNames: DNSNames},
|
||||
},
|
||||
key, signingCert, signingKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the cert: %v", err)
|
||||
}
|
||||
return &Artifacts{
|
||||
Key: EncodePrivateKeyPEM(key),
|
||||
Cert: EncodeCertPEM(signedCert),
|
||||
CAKey: EncodePrivateKeyPEM(signingKey),
|
||||
CACert: EncodeCertPEM(signingCert),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cp *SelfSignedCertGenerator) validCACert() (bool, *rsa.PrivateKey, *x509.Certificate) {
|
||||
if !ValidCACert(cp.caKey, cp.caCert, cp.caCert, "",
|
||||
time.Now().AddDate(1, 0, 0)) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
key, err := keyutil.ParsePrivateKeyPEM(cp.caKey)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
privateKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
certs, err := cert.ParseCertsPEM(cp.caCert)
|
||||
if err != nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
if len(certs) != 1 {
|
||||
return false, nil, nil
|
||||
}
|
||||
return true, privateKey, certs[0]
|
||||
}
|
||||
|
||||
// NewPrivateKey creates an RSA private key
|
||||
func NewPrivateKey() (*rsa.PrivateKey, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
}
|
||||
|
||||
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
||||
func NewSignedCert(cfg cert.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cfg.CommonName) == 0 {
|
||||
return nil, errors.New("must specify a CommonName")
|
||||
}
|
||||
if len(cfg.Usages) == 0 {
|
||||
return nil, errors.New("must specify at least one ExtKeyUsage")
|
||||
}
|
||||
|
||||
certTmpl := x509.Certificate{
|
||||
Subject: pkix.Name{
|
||||
CommonName: cfg.CommonName,
|
||||
Organization: cfg.Organization,
|
||||
},
|
||||
DNSNames: cfg.AltNames.DNSNames,
|
||||
IPAddresses: cfg.AltNames.IPs,
|
||||
SerialNumber: serial,
|
||||
NotBefore: caCert.NotBefore,
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: cfg.Usages,
|
||||
}
|
||||
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
}
|
||||
|
||||
// EncodePrivateKeyPEM returns PEM-encoded private key data
|
||||
func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
|
||||
block := pem.Block{
|
||||
Type: keyutil.RSAPrivateKeyBlockType,
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
}
|
||||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
// EncodeCertPEM returns PEM-encoded certificate data
|
||||
func EncodeCertPEM(ct *x509.Certificate) []byte {
|
||||
block := pem.Block{
|
||||
Type: cert.CertificateBlockType,
|
||||
Bytes: ct.Raw,
|
||||
}
|
||||
return pem.EncodeToMemory(&block)
|
||||
}
|
@@ -3,6 +3,7 @@ package request
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -25,11 +26,12 @@ type Request struct {
|
||||
body []byte
|
||||
timeout time.Duration
|
||||
|
||||
// for http
|
||||
method string
|
||||
host string
|
||||
path string
|
||||
headers map[string]string
|
||||
// for http or https
|
||||
method string
|
||||
host string
|
||||
path string
|
||||
headers map[string]string
|
||||
tlsConfig *tls.Config
|
||||
|
||||
proxyURL string
|
||||
}
|
||||
@@ -64,6 +66,11 @@ func (r *Request) HTTP() *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) HTTPS() *Request {
|
||||
r.protocol = "https"
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) Proxy(url string) *Request {
|
||||
r.proxyURL = url
|
||||
return r
|
||||
@@ -102,6 +109,11 @@ func (r *Request) HTTPHeaders(headers map[string]string) *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) TLSConfig(tlsConfig *tls.Config) *Request {
|
||||
r.tlsConfig = tlsConfig
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) Timeout(timeout time.Duration) *Request {
|
||||
r.timeout = timeout
|
||||
return r
|
||||
@@ -119,10 +131,10 @@ func (r *Request) Do() (*Response, error) {
|
||||
)
|
||||
|
||||
addr := net.JoinHostPort(r.addr, strconv.Itoa(r.port))
|
||||
// for protocol http
|
||||
if r.protocol == "http" {
|
||||
return r.sendHTTPRequest(r.method, fmt.Sprintf("http://%s%s", addr, r.path),
|
||||
r.host, r.headers, r.proxyURL, r.body)
|
||||
// for protocol http and https
|
||||
if r.protocol == "http" || r.protocol == "https" {
|
||||
return r.sendHTTPRequest(r.method, fmt.Sprintf("%s://%s%s", r.protocol, addr, r.path),
|
||||
r.host, r.headers, r.proxyURL, r.body, r.tlsConfig)
|
||||
}
|
||||
|
||||
// for protocol tcp and udp
|
||||
@@ -165,7 +177,10 @@ type Response struct {
|
||||
Content []byte
|
||||
}
|
||||
|
||||
func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers map[string]string, proxy string, body []byte) (*Response, error) {
|
||||
func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers map[string]string,
|
||||
proxy string, body []byte, tlsConfig *tls.Config,
|
||||
) (*Response, error) {
|
||||
|
||||
var inBody io.Reader
|
||||
if len(body) != 0 {
|
||||
inBody = bytes.NewReader(body)
|
||||
@@ -190,6 +205,7 @@ func (r *Request) sendHTTPRequest(method, urlstr string, host string, headers ma
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
if len(proxy) != 0 {
|
||||
tr.Proxy = func(req *http.Request) (*url.URL, error) {
|
||||
|
316
test/e2e/plugin/client.go
Normal file
316
test/e2e/plugin/client.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/cert"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/utils"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[Feature: Client-Plugins]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
Describe("UnixDomainSocket", func() {
|
||||
It("Expose a unix domain socket echo server", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
getProxyConf := func(proxyName string, portName string, extra string) string {
|
||||
return fmt.Sprintf(`
|
||||
[%s]
|
||||
type = tcp
|
||||
remote_port = {{ .%s }}
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = {{ .%s }}
|
||||
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
proxyName string
|
||||
portName string
|
||||
extraConfig string
|
||||
}{
|
||||
{
|
||||
proxyName: "normal",
|
||||
portName: port.GenName("Normal"),
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption",
|
||||
portName: port.GenName("WithEncryption"),
|
||||
extraConfig: "use_encryption = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-compression",
|
||||
portName: port.GenName("WithCompression"),
|
||||
extraConfig: "use_compression = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption-and-compression",
|
||||
portName: port.GenName("WithEncryptionAndCompression"),
|
||||
extraConfig: `
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// build all client config
|
||||
for _, test := range tests {
|
||||
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
|
||||
}
|
||||
// run frps and frpc
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
for _, test := range tests {
|
||||
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("http_proxy", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
remote_port = %d
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = 123
|
||||
`, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// http proxy, no auth info
|
||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).Ensure(framework.ExpectResponseCode(407))
|
||||
|
||||
// http proxy, correct auth
|
||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).Ensure()
|
||||
|
||||
// connect TCP server by CONNECT method
|
||||
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||
r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
})
|
||||
})
|
||||
|
||||
It("socks5 proxy", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
remote_port = %d
|
||||
plugin = socks5
|
||||
plugin_user = abc
|
||||
plugin_passwd = 123
|
||||
`, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// http proxy, no auth info
|
||||
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||
r.TCP().Proxy("socks5://127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).ExpectError(true).Ensure()
|
||||
|
||||
// http proxy, correct auth
|
||||
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||
r.TCP().Proxy("socks5://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).Ensure()
|
||||
})
|
||||
|
||||
It("static_file", func() {
|
||||
vhostPort := f.AllocPort()
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
vhost_http_port = %d
|
||||
`, vhostPort)
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
f.WriteTempFile("test_static_file", "foo")
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
remote_port = %d
|
||||
plugin = static_file
|
||||
plugin_local_path = %s
|
||||
|
||||
[http]
|
||||
type = http
|
||||
custom_domains = example.com
|
||||
plugin = static_file
|
||||
plugin_local_path = %s
|
||||
|
||||
[http-with-auth]
|
||||
type = http
|
||||
custom_domains = other.example.com
|
||||
plugin = static_file
|
||||
plugin_local_path = %s
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = 123
|
||||
`, remotePort, f.TempDirectory, f.TempDirectory, f.TempDirectory)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// from tcp proxy
|
||||
framework.NewRequestExpect(f).Request(
|
||||
framework.NewHTTPRequest().HTTPPath("/test_static_file").Port(remotePort),
|
||||
).ExpectResp([]byte("foo")).Ensure()
|
||||
|
||||
// from http proxy without auth
|
||||
framework.NewRequestExpect(f).Request(
|
||||
framework.NewHTTPRequest().HTTPHost("example.com").HTTPPath("/test_static_file").Port(vhostPort),
|
||||
).ExpectResp([]byte("foo")).Ensure()
|
||||
|
||||
// from http proxy with auth
|
||||
framework.NewRequestExpect(f).Request(
|
||||
framework.NewHTTPRequest().HTTPHost("other.example.com").HTTPPath("/test_static_file").Port(vhostPort).HTTPHeaders(map[string]string{
|
||||
"Authorization": utils.BasicAuth("abc", "123"),
|
||||
}),
|
||||
).ExpectResp([]byte("foo")).Ensure()
|
||||
})
|
||||
|
||||
It("http2https", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
vhostHTTPPort := f.AllocPort()
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_http_port = %d
|
||||
`, vhostHTTPPort)
|
||||
|
||||
localPort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[http2https]
|
||||
type = http
|
||||
custom_domains = example.com
|
||||
plugin = http2https
|
||||
plugin_local_addr = 127.0.0.1:%d
|
||||
`, localPort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
framework.ExpectNoError(err)
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithTlsConfig(tlsConfig),
|
||||
httpserver.WithResponse([]byte("test")),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
Port(vhostHTTPPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTP().HTTPHost("example.com")
|
||||
}).
|
||||
ExpectResp([]byte("test")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
It("https2http", func() {
|
||||
generator := &cert.SelfSignedCertGenerator{}
|
||||
artifacts, err := generator.Generate("example.com")
|
||||
framework.ExpectNoError(err)
|
||||
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
|
||||
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
vhostHTTPSPort := f.AllocPort()
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_https_port = %d
|
||||
`, vhostHTTPSPort)
|
||||
|
||||
localPort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[https2http]
|
||||
type = https
|
||||
custom_domains = example.com
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:%d
|
||||
plugin_crt_path = %s
|
||||
plugin_key_path = %s
|
||||
`, localPort, crtPath, keyPath)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithResponse([]byte("test")),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
Port(vhostHTTPSPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
|
||||
ServerName: "example.com",
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}).
|
||||
ExpectResp([]byte("test")).
|
||||
Ensure()
|
||||
})
|
||||
|
||||
It("https2https", func() {
|
||||
generator := &cert.SelfSignedCertGenerator{}
|
||||
artifacts, err := generator.Generate("example.com")
|
||||
framework.ExpectNoError(err)
|
||||
crtPath := f.WriteTempFile("server.crt", string(artifacts.Cert))
|
||||
keyPath := f.WriteTempFile("server.key", string(artifacts.Key))
|
||||
|
||||
serverConf := consts.DefaultServerConfig
|
||||
vhostHTTPSPort := f.AllocPort()
|
||||
serverConf += fmt.Sprintf(`
|
||||
vhost_https_port = %d
|
||||
`, vhostHTTPSPort)
|
||||
|
||||
localPort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[https2https]
|
||||
type = https
|
||||
custom_domains = example.com
|
||||
plugin = https2https
|
||||
plugin_local_addr = 127.0.0.1:%d
|
||||
plugin_crt_path = %s
|
||||
plugin_key_path = %s
|
||||
`, localPort, crtPath, keyPath)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
framework.ExpectNoError(err)
|
||||
localServer := httpserver.New(
|
||||
httpserver.WithBindPort(localPort),
|
||||
httpserver.WithResponse([]byte("test")),
|
||||
httpserver.WithTlsConfig(tlsConfig),
|
||||
)
|
||||
f.RunServer("", localServer)
|
||||
|
||||
framework.NewRequestExpect(f).
|
||||
Port(vhostHTTPSPort).
|
||||
RequestModify(func(r *request.Request) {
|
||||
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
|
||||
ServerName: "example.com",
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
}).
|
||||
ExpectResp([]byte("test")).
|
||||
Ensure()
|
||||
})
|
||||
})
|
@@ -1,106 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/port"
|
||||
"github.com/fatedier/frp/test/e2e/pkg/request"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[Feature: Client-Plugins]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
Describe("UnixDomainSocket", func() {
|
||||
It("Expose a unix domain socket echo server", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
getProxyConf := func(proxyName string, portName string, extra string) string {
|
||||
return fmt.Sprintf(`
|
||||
[%s]
|
||||
type = tcp
|
||||
remote_port = {{ .%s }}
|
||||
plugin = unix_domain_socket
|
||||
plugin_unix_path = {{ .%s }}
|
||||
`+extra, proxyName, portName, framework.UDSEchoServerAddr)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
proxyName string
|
||||
portName string
|
||||
extraConfig string
|
||||
}{
|
||||
{
|
||||
proxyName: "normal",
|
||||
portName: port.GenName("Normal"),
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption",
|
||||
portName: port.GenName("WithEncryption"),
|
||||
extraConfig: "use_encryption = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-compression",
|
||||
portName: port.GenName("WithCompression"),
|
||||
extraConfig: "use_compression = true",
|
||||
},
|
||||
{
|
||||
proxyName: "with-encryption-and-compression",
|
||||
portName: port.GenName("WithEncryptionAndCompression"),
|
||||
extraConfig: `
|
||||
use_encryption = true
|
||||
use_compression = true
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// build all client config
|
||||
for _, test := range tests {
|
||||
clientConf += getProxyConf(test.proxyName, test.portName, test.extraConfig) + "\n"
|
||||
}
|
||||
// run frps and frpc
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
for _, test := range tests {
|
||||
framework.NewRequestExpect(f).Port(f.PortByName(test.portName)).Ensure()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("plugin http_proxy", func() {
|
||||
serverConf := consts.DefaultServerConfig
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
remote_port = %d
|
||||
plugin = http_proxy
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = 123
|
||||
`, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
// http proxy, no auth info
|
||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().Proxy("http://127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).Ensure(framework.ExpectResponseCode(407))
|
||||
|
||||
// http proxy, correct auth
|
||||
framework.NewRequestExpect(f).PortName(framework.HTTPSimpleServerPort).RequestModify(func(r *request.Request) {
|
||||
r.HTTP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
}).Ensure()
|
||||
|
||||
// connect TCP server by CONNECT method
|
||||
framework.NewRequestExpect(f).PortName(framework.TCPEchoServerPort).RequestModify(func(r *request.Request) {
|
||||
r.TCP().Proxy("http://abc:123@127.0.0.1:" + strconv.Itoa(remotePort))
|
||||
})
|
||||
})
|
||||
})
|
341
test/e2e/plugin/server.go
Normal file
341
test/e2e/plugin/server.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
"github.com/fatedier/frp/test/e2e/framework"
|
||||
"github.com/fatedier/frp/test/e2e/framework/consts"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
)
|
||||
|
||||
var _ = Describe("[Feature: Server-Plugins]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
Describe("Login", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.LoginContent{}
|
||||
return &r
|
||||
}
|
||||
|
||||
It("Auth for custom meta token", func() {
|
||||
localPort := f.AllocPort()
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.LoginContent)
|
||||
if content.Metas["token"] == "123" {
|
||||
ret.Unchange = true
|
||||
} else {
|
||||
ret.Reject = true
|
||||
ret.RejectReason = "invalid token"
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.user-manager]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = Login
|
||||
`, localPort)
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
meta_token = 123
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
remotePort2 := f.AllocPort()
|
||||
invalidTokenClientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||
[tcp2]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort2)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf, invalidTokenClientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
framework.NewRequestExpect(f).Port(remotePort2).ExpectError(true).Ensure()
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NewProxy", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.NewProxyContent{}
|
||||
return &r
|
||||
}
|
||||
|
||||
It("Validate Info", func() {
|
||||
localPort := f.AllocPort()
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewProxyContent)
|
||||
if content.ProxyName == "tcp" {
|
||||
ret.Unchange = true
|
||||
} else {
|
||||
ret.Reject = true
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
`, localPort)
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
|
||||
It("Mofify RemotePort", func() {
|
||||
localPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewProxyContent)
|
||||
content.RemotePort = remotePort
|
||||
ret.Content = content
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = NewProxy
|
||||
`, localPort)
|
||||
clientConf := consts.DefaultClientConfig
|
||||
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = 0
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Ping", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.PingContent{}
|
||||
return &r
|
||||
}
|
||||
|
||||
It("Validate Info", func() {
|
||||
localPort := f.AllocPort()
|
||||
|
||||
var record string
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.PingContent)
|
||||
record = content.Ping.PrivilegeKey
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = Ping
|
||||
`, localPort)
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
heartbeat_interval = 1
|
||||
authenticate_heartbeats = true
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
framework.ExpectNotEqual("", record)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NewWorkConn", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.NewWorkConnContent{}
|
||||
return &r
|
||||
}
|
||||
|
||||
It("Validate Info", func() {
|
||||
localPort := f.AllocPort()
|
||||
|
||||
var record string
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewWorkConnContent)
|
||||
record = content.NewWorkConn.RunID
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = NewWorkConn
|
||||
`, localPort)
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
framework.ExpectNotEqual("", record)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NewUserConn", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.NewUserConnContent{}
|
||||
return &r
|
||||
}
|
||||
It("Validate Info", func() {
|
||||
localPort := f.AllocPort()
|
||||
|
||||
var record string
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewUserConnContent)
|
||||
record = content.RemoteAddr
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, nil)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = 127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = NewUserConn
|
||||
`, localPort)
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
framework.ExpectNotEqual("", record)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("HTTPS Protocol", func() {
|
||||
newFunc := func() *plugin.Request {
|
||||
var r plugin.Request
|
||||
r.Content = &plugin.NewUserConnContent{}
|
||||
return &r
|
||||
}
|
||||
It("Validate Login Info, disable tls verify", func() {
|
||||
localPort := f.AllocPort()
|
||||
|
||||
var record string
|
||||
handler := func(req *plugin.Request) *plugin.Response {
|
||||
var ret plugin.Response
|
||||
content := req.Content.(*plugin.NewUserConnContent)
|
||||
record = content.RemoteAddr
|
||||
ret.Unchange = true
|
||||
return &ret
|
||||
}
|
||||
tlsConfig, err := transport.NewServerTLSConfig("", "", "")
|
||||
framework.ExpectNoError(err)
|
||||
pluginServer := NewHTTPPluginServer(localPort, newFunc, handler, tlsConfig)
|
||||
|
||||
f.RunServer("", pluginServer)
|
||||
|
||||
serverConf := consts.DefaultServerConfig + fmt.Sprintf(`
|
||||
[plugin.test]
|
||||
addr = https://127.0.0.1:%d
|
||||
path = /handler
|
||||
ops = NewUserConn
|
||||
`, localPort)
|
||||
|
||||
remotePort := f.AllocPort()
|
||||
clientConf := consts.DefaultClientConfig
|
||||
clientConf += fmt.Sprintf(`
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = {{ .%s }}
|
||||
remote_port = %d
|
||||
`, framework.TCPEchoServerPort, remotePort)
|
||||
|
||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
|
||||
framework.ExpectNotEqual("", record)
|
||||
})
|
||||
})
|
||||
})
|
41
test/e2e/plugin/utils.go
Normal file
41
test/e2e/plugin/utils.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
plugin "github.com/fatedier/frp/pkg/plugin/server"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/test/e2e/mock/server/httpserver"
|
||||
)
|
||||
|
||||
type PluginHandler func(req *plugin.Request) *plugin.Response
|
||||
|
||||
type NewPluginRequest func() *plugin.Request
|
||||
|
||||
func NewHTTPPluginServer(port int, newFunc NewPluginRequest, handler PluginHandler, tlsConfig *tls.Config) *httpserver.Server {
|
||||
return httpserver.New(
|
||||
httpserver.WithBindPort(port),
|
||||
httpserver.WithTlsConfig(tlsConfig),
|
||||
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
r := newFunc()
|
||||
buf, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
log.Trace("plugin request: %s", string(buf))
|
||||
err = json.Unmarshal(buf, &r)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
resp := handler(r)
|
||||
buf, _ = json.Marshal(resp)
|
||||
log.Trace("plugin response: %s", string(buf))
|
||||
w.Write(buf)
|
||||
})),
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user