mirror of
https://github.com/fatedier/frp.git
synced 2026-07-17 17:59:18 +08:00
Compare commits
1 Commits
v0.66.0
...
33ab7eedd6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33ab7eedd6 |
22
README.md
22
README.md
@@ -13,14 +13,6 @@ frp is an open source project with its ongoing development made possible entirel
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
|
||||
@@ -58,6 +50,20 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and
|
||||
<sub>Available for macOS, Linux and Windows</sub>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<br>
|
||||
<b>Secure and Elastic Infrastructure for Running Your AI-Generated Code</b>
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## What is frp?
|
||||
|
||||
22
README_zh.md
22
README_zh.md
@@ -15,14 +15,6 @@ frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者
|
||||
|
||||
<h3 align="center">Gold Sponsors</h3>
|
||||
<!--gold sponsors start-->
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/beclab/Olares" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_olares.jpeg">
|
||||
@@ -59,6 +51,20 @@ an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and
|
||||
<sub>Available for macOS, Linux and Windows</sub>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://jb.gg/frp" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||
<br>
|
||||
<b>The complete IDE crafted for professional Go developers</b>
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||
<br>
|
||||
<b>Secure and Elastic Infrastructure for Running Your AI-Generated Code</b>
|
||||
</a>
|
||||
</p>
|
||||
<!--gold sponsors end-->
|
||||
|
||||
## 为什么使用 frp ?
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
* HTTPS proxies now support load balancing groups. Multiple HTTPS proxies can be configured with the same `loadBalancer.group` and `loadBalancer.groupKey` to share the same custom domain and distribute traffic across multiple backend services, similar to the existing TCP and HTTP load balancing capabilities.
|
||||
* Individual frpc proxies and visitors now accept an `enabled` flag (defaults to true), letting you disable specific entries without relying on the global `start` list—disabled blocks are skipped when client configs load.
|
||||
* OIDC authentication now supports a `tokenSource` field to dynamically obtain tokens from external sources. You can use `type = "file"` to read a token from a file, or `type = "exec"` to run an external command (e.g., a cloud CLI or secrets manager) and capture its stdout as the token. The `exec` type requires the `--allow-unsafe=TokenSourceExec` CLI flag for security reasons.
|
||||
|
||||
## Improvements
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ type SessionContext struct {
|
||||
Conn net.Conn
|
||||
// Indicates whether the connection is encrypted.
|
||||
ConnEncrypted bool
|
||||
// Auth runtime used for login, heartbeats, and encryption.
|
||||
Auth *auth.ClientAuth
|
||||
// Sets authentication based on selected method
|
||||
AuthSetter auth.Setter
|
||||
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||
Connector Connector
|
||||
// Virtual net controller
|
||||
@@ -91,7 +91,7 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
||||
ctl.lastPong.Store(time.Now())
|
||||
|
||||
if sessionCtx.ConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, sessionCtx.Auth.EncryptionKey())
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(sessionCtx.Conn, []byte(sessionCtx.Common.Auth.Token))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
||||
ctl.registerMsgHandlers()
|
||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher)
|
||||
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, sessionCtx.Auth.EncryptionKey(), ctl.msgTransporter, sessionCtx.VnetController)
|
||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController)
|
||||
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||
return ctl, nil
|
||||
@@ -133,7 +133,7 @@ func (ctl *Control) handleReqWorkConn(_ msg.Message) {
|
||||
m := &msg.NewWorkConn{
|
||||
RunID: ctl.sessionCtx.RunID,
|
||||
}
|
||||
if err = ctl.sessionCtx.Auth.Setter.SetNewWorkConn(m); err != nil {
|
||||
if err = ctl.sessionCtx.AuthSetter.SetNewWorkConn(m); err != nil {
|
||||
xl.Warnf("error during NewWorkConn authentication: %v", err)
|
||||
workConn.Close()
|
||||
return
|
||||
@@ -243,7 +243,7 @@ func (ctl *Control) heartbeatWorker() {
|
||||
sendHeartBeat := func() (bool, error) {
|
||||
xl.Debugf("send heartbeat to server")
|
||||
pingMsg := &msg.Ping{}
|
||||
if err := ctl.sessionCtx.Auth.Setter.SetPing(pingMsg); err != nil {
|
||||
if err := ctl.sessionCtx.AuthSetter.SetPing(pingMsg); err != nil {
|
||||
xl.Warnf("error during ping authentication: %v, skip sending ping message", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ func NewProxy(
|
||||
ctx context.Context,
|
||||
pxyConf v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
) (pxy Proxy) {
|
||||
@@ -70,7 +69,6 @@ func NewProxy(
|
||||
baseProxy := BaseProxy{
|
||||
baseCfg: pxyConf.GetBaseConfig(),
|
||||
clientCfg: clientCfg,
|
||||
encryptionKey: encryptionKey,
|
||||
limiter: limiter,
|
||||
msgTransporter: msgTransporter,
|
||||
vnetController: vnetController,
|
||||
@@ -88,7 +86,6 @@ func NewProxy(
|
||||
type BaseProxy struct {
|
||||
baseCfg *v1.ProxyBaseConfig
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
encryptionKey []byte
|
||||
msgTransporter transport.MessageTransporter
|
||||
vnetController *vnet.Controller
|
||||
limiter *rate.Limiter
|
||||
@@ -132,7 +129,7 @@ func (pxy *BaseProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pxy.HandleTCPWorkConnection(conn, m, pxy.encryptionKey)
|
||||
pxy.HandleTCPWorkConnection(conn, m, []byte(pxy.clientCfg.Auth.Token))
|
||||
}
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
|
||||
@@ -40,8 +40,7 @@ type Manager struct {
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
|
||||
encryptionKey []byte
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
clientCfg *v1.ClientCommonConfig
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -49,7 +48,6 @@ type Manager struct {
|
||||
func NewManager(
|
||||
ctx context.Context,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
) *Manager {
|
||||
@@ -58,7 +56,6 @@ func NewManager(
|
||||
msgTransporter: msgTransporter,
|
||||
vnetController: vnetController,
|
||||
closed: false,
|
||||
encryptionKey: encryptionKey,
|
||||
clientCfg: clientCfg,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -166,7 +163,7 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
||||
for _, cfg := range proxyCfgs {
|
||||
name := cfg.GetBaseConfig().Name
|
||||
if _, ok := pm.proxies[name]; !ok {
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.encryptionKey, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||
if pm.inWorkConnCallback != nil {
|
||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@ func NewWrapper(
|
||||
ctx context.Context,
|
||||
cfg v1.ProxyConfigurer,
|
||||
clientCfg *v1.ClientCommonConfig,
|
||||
encryptionKey []byte,
|
||||
eventHandler event.Handler,
|
||||
msgTransporter transport.MessageTransporter,
|
||||
vnetController *vnet.Controller,
|
||||
@@ -123,7 +122,7 @@ func NewWrapper(
|
||||
xl.Tracef("enable health check monitor")
|
||||
}
|
||||
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, encryptionKey, pw.msgTransporter, pw.vnetController)
|
||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||
return pw
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ func (pxy *SUDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
|
||||
@@ -102,7 +102,7 @@ func (pxy *UDPProxy) InWorkConn(conn net.Conn, _ *msg.StartWorkConn) {
|
||||
})
|
||||
}
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.clientCfg.Auth.Token))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
|
||||
@@ -111,8 +111,8 @@ type Service struct {
|
||||
// Uniq id got from frps, it will be attached to loginMsg.
|
||||
runID string
|
||||
|
||||
// Auth runtime and encryption materials
|
||||
auth *auth.ClientAuth
|
||||
// Sets authentication based on selected method
|
||||
authSetter auth.Setter
|
||||
|
||||
// web server for admin UI and apis
|
||||
webServer *httppkg.Server
|
||||
@@ -155,14 +155,14 @@ func NewService(options ServiceOptions) (*Service, error) {
|
||||
webServer = ws
|
||||
}
|
||||
|
||||
authRuntime, err := auth.BuildClientAuth(&options.Common.Auth)
|
||||
authSetter, err := auth.NewAuthSetter(options.Common.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Service{
|
||||
ctx: context.Background(),
|
||||
auth: authRuntime,
|
||||
authSetter: authSetter,
|
||||
webServer: webServer,
|
||||
common: options.Common,
|
||||
configFilePath: options.ConfigFilePath,
|
||||
@@ -296,7 +296,7 @@ func (svr *Service) login() (conn net.Conn, connector Connector, err error) {
|
||||
}
|
||||
|
||||
// Add auth
|
||||
if err = svr.auth.Setter.SetLogin(loginMsg); err != nil {
|
||||
if err = svr.authSetter.SetLogin(loginMsg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
||||
RunID: svr.runID,
|
||||
Conn: conn,
|
||||
ConnEncrypted: connEncrypted,
|
||||
Auth: svr.auth,
|
||||
AuthSetter: svr.authSetter,
|
||||
Connector: connector,
|
||||
VnetController: svr.vnetController,
|
||||
}
|
||||
|
||||
@@ -80,8 +80,7 @@ func NewProxyCommand(name string, c v1.ProxyConfigurer, clientCfg *v1.ClientComm
|
||||
}
|
||||
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
if _, err := validator.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg, unsafeFeatures); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -111,8 +110,7 @@ func NewVisitorCommand(name string, c v1.VisitorConfigurer, clientCfg *v1.Client
|
||||
os.Exit(1)
|
||||
}
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
if _, err := validator.ValidateClientCommonConfig(clientCfg); err != nil {
|
||||
if _, err := validation.ValidateClientCommonConfig(clientCfg, unsafeFeatures); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ func runClient(cfgFilePath string, unsafeFeatures *security.UnsafeFeatures) erro
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return startService(cfg, proxyCfgs, visitorCfgs, unsafeFeatures, cfgFilePath)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
"github.com/fatedier/frp/pkg/util/log"
|
||||
"github.com/fatedier/frp/pkg/util/version"
|
||||
"github.com/fatedier/frp/server"
|
||||
@@ -35,7 +33,6 @@ var (
|
||||
cfgFile string
|
||||
showVersion bool
|
||||
strictConfigMode bool
|
||||
allowUnsafe []string
|
||||
|
||||
serverCfg v1.ServerConfig
|
||||
)
|
||||
@@ -44,8 +41,6 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&strictConfigMode, "strict_config", "", true, "strict config parsing mode, unknown fields will cause errors")
|
||||
rootCmd.PersistentFlags().StringSliceVarP(&allowUnsafe, "allow-unsafe", "", []string{},
|
||||
fmt.Sprintf("allowed unsafe features, one or more of: %s", strings.Join(security.ServerUnsafeFeatures, ", ")))
|
||||
|
||||
config.RegisterServerConfigFlags(rootCmd, &serverCfg)
|
||||
}
|
||||
@@ -82,9 +77,7 @@ var rootCmd = &cobra.Command{
|
||||
svrCfg = &serverCfg
|
||||
}
|
||||
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
warning, err := validator.ValidateServerConfig(svrCfg)
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
"github.com/fatedier/frp/pkg/config"
|
||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -43,9 +42,7 @@ var verifyCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
unsafeFeatures := security.NewUnsafeFeatures(allowUnsafe)
|
||||
validator := validation.NewConfigValidator(unsafeFeatures)
|
||||
warning, err := validator.ValidateServerConfig(svrCfg)
|
||||
warning, err := validation.ValidateServerConfig(svrCfg)
|
||||
if warning != nil {
|
||||
fmt.Printf("WARNING: %v\n", warning)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
@@ -28,39 +27,6 @@ type Setter interface {
|
||||
SetNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
type ClientAuth struct {
|
||||
Setter Setter
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (a *ClientAuth) EncryptionKey() []byte {
|
||||
return a.key
|
||||
}
|
||||
|
||||
// BuildClientAuth resolves any dynamic auth values and returns a prepared auth runtime.
|
||||
// Caller must run validation before calling this function.
|
||||
func BuildClientAuth(cfg *v1.AuthClientConfig) (*ClientAuth, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("auth config is nil")
|
||||
}
|
||||
resolved := *cfg
|
||||
if resolved.Method == v1.AuthMethodToken && resolved.TokenSource != nil {
|
||||
token, err := resolved.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
resolved.Token = token
|
||||
}
|
||||
setter, err := NewAuthSetter(resolved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ClientAuth{
|
||||
Setter: setter,
|
||||
key: []byte(resolved.Token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAuthSetter(cfg v1.AuthClientConfig) (authProvider Setter, err error) {
|
||||
switch cfg.Method {
|
||||
case v1.AuthMethodToken:
|
||||
@@ -86,35 +52,6 @@ type Verifier interface {
|
||||
VerifyNewWorkConn(*msg.NewWorkConn) error
|
||||
}
|
||||
|
||||
type ServerAuth struct {
|
||||
Verifier Verifier
|
||||
key []byte
|
||||
}
|
||||
|
||||
func (a *ServerAuth) EncryptionKey() []byte {
|
||||
return a.key
|
||||
}
|
||||
|
||||
// BuildServerAuth resolves any dynamic auth values and returns a prepared auth runtime.
|
||||
// Caller must run validation before calling this function.
|
||||
func BuildServerAuth(cfg *v1.AuthServerConfig) (*ServerAuth, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("auth config is nil")
|
||||
}
|
||||
resolved := *cfg
|
||||
if resolved.Method == v1.AuthMethodToken && resolved.TokenSource != nil {
|
||||
token, err := resolved.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
resolved.Token = token
|
||||
}
|
||||
return &ServerAuth{
|
||||
Verifier: NewAuthVerifier(resolved),
|
||||
key: []byte(resolved.Token),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
||||
switch cfg.Method {
|
||||
case v1.AuthMethodToken:
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -196,6 +198,17 @@ type AuthClientConfig struct {
|
||||
|
||||
func (c *AuthClientConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -36,9 +38,68 @@ func TestClientConfigComplete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthClientConfig_Complete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cfg := &AuthClientConfig{}
|
||||
err := cfg.Complete()
|
||||
require.NoError(err)
|
||||
require.EqualValues("token", cfg.Method)
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "client-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthClientConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthClientConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/fatedier/frp/pkg/config/types"
|
||||
@@ -135,6 +138,17 @@ type AuthServerConfig struct {
|
||||
|
||||
func (c *AuthServerConfig) Complete() error {
|
||||
c.Method = util.EmptyOr(c.Method, "token")
|
||||
|
||||
// Resolve tokenSource during configuration loading
|
||||
if c.Method == AuthMethodToken && c.TokenSource != nil {
|
||||
token, err := c.TokenSource.Resolve(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve auth.tokenSource: %w", err)
|
||||
}
|
||||
// Move the resolved token to the Token field and clear TokenSource
|
||||
c.Token = token
|
||||
c.TokenSource = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -33,9 +35,68 @@ func TestServerConfigComplete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthServerConfig_Complete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
cfg := &AuthServerConfig{}
|
||||
err := cfg.Complete()
|
||||
require.NoError(err)
|
||||
require.EqualValues("token", cfg.Method)
|
||||
// Create a temporary file for testing
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test_token")
|
||||
testContent := "file-token-value"
|
||||
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config AuthServerConfig
|
||||
expectToken string
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "tokenSource resolved to token",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: testFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectToken: testContent,
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "direct token unchanged",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
Token: "direct-token",
|
||||
},
|
||||
expectToken: "direct-token",
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "invalid tokenSource should panic",
|
||||
config: AuthServerConfig{
|
||||
Method: AuthMethodToken,
|
||||
TokenSource: &ValueSource{
|
||||
Type: "file",
|
||||
File: &FileSource{
|
||||
Path: "/non/existent/file",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.expectPanic {
|
||||
err := tt.config.Complete()
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
err := tt.config.Complete()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectToken, tt.config.Token)
|
||||
require.Nil(t, tt.config.TokenSource, "TokenSource should be cleared after resolution")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func (v *ConfigValidator) ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
func ValidateClientCommonConfig(c *v1.ClientCommonConfig, unsafeFeatures *security.UnsafeFeatures) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
@@ -35,15 +35,15 @@ func (v *ConfigValidator) ValidateClientCommonConfig(c *v1.ClientCommonConfig) (
|
||||
|
||||
validators := []func() (Warning, error){
|
||||
func() (Warning, error) { return validateFeatureGates(c) },
|
||||
func() (Warning, error) { return v.validateAuthConfig(&c.Auth) },
|
||||
func() (Warning, error) { return validateAuthConfig(&c.Auth, unsafeFeatures) },
|
||||
func() (Warning, error) { return nil, validateLogConfig(&c.Log) },
|
||||
func() (Warning, error) { return nil, validateWebServerConfig(&c.WebServer) },
|
||||
func() (Warning, error) { return validateTransportConfig(&c.Transport) },
|
||||
func() (Warning, error) { return validateIncludeFiles(c.IncludeConfigFiles) },
|
||||
}
|
||||
|
||||
for _, validator := range validators {
|
||||
w, err := validator()
|
||||
for _, v := range validators {
|
||||
w, err := v()
|
||||
warnings = AppendError(warnings, w)
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func validateFeatureGates(c *v1.ClientCommonConfig) (Warning, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Warning, error) {
|
||||
func validateAuthConfig(c *v1.AuthClientConfig, unsafeFeatures *security.UnsafeFeatures) (Warning, error) {
|
||||
var errs error
|
||||
if !slices.Contains(SupportedAuthMethods, c.Method) {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||
@@ -76,8 +76,9 @@ func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Warning, e
|
||||
// Validate tokenSource if specified
|
||||
if c.TokenSource != nil {
|
||||
if c.TokenSource.Type == "exec" {
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
if !unsafeFeatures.IsEnabled(security.TokenSourceExec) {
|
||||
errs = AppendError(errs, fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, start frpc with '--allow-unsafe %s'", security.TokenSourceExec, security.TokenSourceExec))
|
||||
}
|
||||
}
|
||||
if err := c.TokenSource.Validate(); err != nil {
|
||||
@@ -85,13 +86,13 @@ func (v *ConfigValidator) validateAuthConfig(c *v1.AuthClientConfig) (Warning, e
|
||||
}
|
||||
}
|
||||
|
||||
if err := v.validateOIDCConfig(&c.OIDC); err != nil {
|
||||
if err := validateOIDCConfig(&c.OIDC, unsafeFeatures); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
func (v *ConfigValidator) validateOIDCConfig(c *v1.AuthOIDCClientConfig) error {
|
||||
func validateOIDCConfig(c *v1.AuthOIDCClientConfig, unsafeFeatures *security.UnsafeFeatures) error {
|
||||
if c.TokenSource == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -103,8 +104,9 @@ func (v *ConfigValidator) validateOIDCConfig(c *v1.AuthOIDCClientConfig) error {
|
||||
errs = AppendError(errs, fmt.Errorf("cannot specify both auth.oidc.tokenSource and any other field of auth.oidc"))
|
||||
}
|
||||
if c.TokenSource.Type == "exec" {
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
if !unsafeFeatures.IsEnabled(security.TokenSourceExec) {
|
||||
errs = AppendError(errs, fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, start frpc with '--allow-unsafe %s'", security.TokenSourceExec, security.TokenSourceExec))
|
||||
}
|
||||
}
|
||||
if err := c.TokenSource.Validate(); err != nil {
|
||||
@@ -165,10 +167,9 @@ func ValidateAllClientConfig(
|
||||
visitorCfgs []v1.VisitorConfigurer,
|
||||
unsafeFeatures *security.UnsafeFeatures,
|
||||
) (Warning, error) {
|
||||
validator := NewConfigValidator(unsafeFeatures)
|
||||
var warnings Warning
|
||||
if c != nil {
|
||||
warning, err := validator.ValidateClientCommonConfig(c)
|
||||
warning, err := ValidateClientCommonConfig(c, unsafeFeatures)
|
||||
warnings = AppendError(warnings, warning)
|
||||
if err != nil {
|
||||
return warnings, err
|
||||
|
||||
@@ -21,10 +21,9 @@ import (
|
||||
"github.com/samber/lo"
|
||||
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
func (v *ConfigValidator) ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
func ValidateServerConfig(c *v1.ServerConfig) (Warning, error) {
|
||||
var (
|
||||
warnings Warning
|
||||
errs error
|
||||
@@ -43,11 +42,6 @@ func (v *ConfigValidator) ValidateServerConfig(c *v1.ServerConfig) (Warning, err
|
||||
|
||||
// Validate tokenSource if specified
|
||||
if c.Auth.TokenSource != nil {
|
||||
if c.Auth.TokenSource.Type == "exec" {
|
||||
if err := v.ValidateUnsafeFeature(security.TokenSourceExec); err != nil {
|
||||
errs = AppendError(errs, err)
|
||||
}
|
||||
}
|
||||
if err := c.Auth.TokenSource.Validate(); err != nil {
|
||||
errs = AppendError(errs, fmt.Errorf("invalid auth.tokenSource: %v", err))
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatedier/frp/pkg/policy/security"
|
||||
)
|
||||
|
||||
// ConfigValidator holds the context dependencies for configuration validation.
|
||||
type ConfigValidator struct {
|
||||
unsafeFeatures *security.UnsafeFeatures
|
||||
}
|
||||
|
||||
// NewConfigValidator creates a new ConfigValidator instance.
|
||||
func NewConfigValidator(unsafeFeatures *security.UnsafeFeatures) *ConfigValidator {
|
||||
return &ConfigValidator{
|
||||
unsafeFeatures: unsafeFeatures,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUnsafeFeature checks if a specific unsafe feature is enabled.
|
||||
func (v *ConfigValidator) ValidateUnsafeFeature(feature string) error {
|
||||
if !v.unsafeFeatures.IsEnabled(feature) {
|
||||
return fmt.Errorf("unsafe feature %q is not enabled. "+
|
||||
"To enable it, ensure it is allowed in the configuration or command line flags", feature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
package version
|
||||
|
||||
var version = "0.66.0"
|
||||
var version = "0.65.0"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
||||
@@ -106,8 +106,6 @@ type Control struct {
|
||||
|
||||
// verifies authentication based on selected method
|
||||
authVerifier auth.Verifier
|
||||
// key used for connection encryption
|
||||
encryptionKey []byte
|
||||
|
||||
// other components can use this to communicate with client
|
||||
msgTransporter transport.MessageTransporter
|
||||
@@ -159,7 +157,6 @@ func NewControl(
|
||||
pxyManager *proxy.Manager,
|
||||
pluginManager *plugin.Manager,
|
||||
authVerifier auth.Verifier,
|
||||
encryptionKey []byte,
|
||||
ctlConn net.Conn,
|
||||
ctlConnEncrypted bool,
|
||||
loginMsg *msg.Login,
|
||||
@@ -174,7 +171,6 @@ func NewControl(
|
||||
pxyManager: pxyManager,
|
||||
pluginManager: pluginManager,
|
||||
authVerifier: authVerifier,
|
||||
encryptionKey: encryptionKey,
|
||||
conn: ctlConn,
|
||||
loginMsg: loginMsg,
|
||||
workConnCh: make(chan net.Conn, poolCount+10),
|
||||
@@ -190,7 +186,7 @@ func NewControl(
|
||||
ctl.lastPing.Store(time.Now())
|
||||
|
||||
if ctlConnEncrypted {
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, ctl.encryptionKey)
|
||||
cryptoRW, err := netpkg.NewCryptoReadWriter(ctl.conn, []byte(ctl.serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -482,7 +478,6 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
|
||||
GetWorkConnFn: ctl.GetWorkConn,
|
||||
Configurer: pxyConf,
|
||||
ServerCfg: ctl.serverCfg,
|
||||
EncryptionKey: ctl.encryptionKey,
|
||||
})
|
||||
if err != nil {
|
||||
return remoteAddr, err
|
||||
|
||||
@@ -165,7 +165,7 @@ func (pxy *HTTPProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err err
|
||||
|
||||
var rwc io.ReadWriteCloser = tmpConn
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
return
|
||||
|
||||
@@ -68,7 +68,6 @@ type BaseProxy struct {
|
||||
poolCount int
|
||||
getWorkConnFn GetWorkConnFn
|
||||
serverCfg *v1.ServerConfig
|
||||
encryptionKey []byte
|
||||
limiter *rate.Limiter
|
||||
userInfo plugin.UserInfo
|
||||
loginMsg *msg.Login
|
||||
@@ -214,6 +213,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
xl := xlog.FromContextSafe(pxy.Context())
|
||||
defer userConn.Close()
|
||||
|
||||
serverCfg := pxy.serverCfg
|
||||
cfg := pxy.configurer.GetBaseConfig()
|
||||
// server plugin hook
|
||||
rc := pxy.GetResourceController()
|
||||
@@ -240,7 +240,7 @@ func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
|
||||
xl.Tracef("handler user tcp connection, use_encryption: %t, use_compression: %t",
|
||||
cfg.Transport.UseEncryption, cfg.Transport.UseCompression)
|
||||
if cfg.Transport.UseEncryption {
|
||||
local, err = libio.WithEncryption(local, pxy.encryptionKey)
|
||||
local, err = libio.WithEncryption(local, []byte(serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
return
|
||||
@@ -279,7 +279,6 @@ type Options struct {
|
||||
GetWorkConnFn GetWorkConnFn
|
||||
Configurer v1.ProxyConfigurer
|
||||
ServerCfg *v1.ServerConfig
|
||||
EncryptionKey []byte
|
||||
}
|
||||
|
||||
func NewProxy(ctx context.Context, options *Options) (pxy Proxy, err error) {
|
||||
@@ -299,7 +298,6 @@ func NewProxy(ctx context.Context, options *Options) (pxy Proxy, err error) {
|
||||
poolCount: options.PoolCount,
|
||||
getWorkConnFn: options.GetWorkConnFn,
|
||||
serverCfg: options.ServerCfg,
|
||||
encryptionKey: options.EncryptionKey,
|
||||
limiter: limiter,
|
||||
xl: xl,
|
||||
ctx: xlog.NewContext(ctx, xl),
|
||||
|
||||
@@ -205,7 +205,7 @@ func (pxy *UDPProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
var rwc io.ReadWriteCloser = workConn
|
||||
if pxy.cfg.Transport.UseEncryption {
|
||||
rwc, err = libio.WithEncryption(rwc, pxy.encryptionKey)
|
||||
rwc, err = libio.WithEncryption(rwc, []byte(pxy.serverCfg.Auth.Token))
|
||||
if err != nil {
|
||||
xl.Errorf("create encryption stream error: %v", err)
|
||||
workConn.Close()
|
||||
|
||||
@@ -113,8 +113,8 @@ type Service struct {
|
||||
|
||||
sshTunnelGateway *ssh.Gateway
|
||||
|
||||
// Auth runtime and encryption materials
|
||||
auth *auth.ServerAuth
|
||||
// Verifies authentication based on selected method
|
||||
authVerifier auth.Verifier
|
||||
|
||||
tlsConfig *tls.Config
|
||||
|
||||
@@ -149,11 +149,6 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
}
|
||||
}
|
||||
|
||||
authRuntime, err := auth.BuildServerAuth(&cfg.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svr := &Service{
|
||||
ctlManager: NewControlManager(),
|
||||
pxyManager: proxy.NewManager(),
|
||||
@@ -165,7 +160,7 @@ func NewService(cfg *v1.ServerConfig) (*Service, error) {
|
||||
},
|
||||
sshTunnelListener: netpkg.NewInternalListener(),
|
||||
httpVhostRouter: vhost.NewRouters(),
|
||||
auth: authRuntime,
|
||||
authVerifier: auth.NewAuthVerifier(cfg.Auth),
|
||||
webServer: webServer,
|
||||
tlsConfig: tlsConfig,
|
||||
cfg: cfg,
|
||||
@@ -591,7 +586,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
|
||||
ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
|
||||
|
||||
// Check auth.
|
||||
authVerifier := svr.auth.Verifier
|
||||
authVerifier := svr.authVerifier
|
||||
if internal && loginMsg.ClientSpec.AlwaysAuthPass {
|
||||
authVerifier = auth.AlwaysPassVerifier
|
||||
}
|
||||
@@ -600,7 +595,7 @@ func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login, inter
|
||||
}
|
||||
|
||||
// TODO(fatedier): use SessionContext
|
||||
ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, svr.auth.EncryptionKey(), ctlConn, !internal, loginMsg, svr.cfg)
|
||||
ctl, err := NewControl(ctx, svr.rc, svr.pxyManager, svr.pluginManager, authVerifier, ctlConn, !internal, loginMsg, svr.cfg)
|
||||
if err != nil {
|
||||
xl.Warnf("create new controller error: %v", err)
|
||||
// don't return detailed errors to client
|
||||
|
||||
@@ -29,18 +29,6 @@ import (
|
||||
var _ = ginkgo.Describe("[Feature: TokenSource]", func() {
|
||||
f := framework.NewDefaultFramework()
|
||||
|
||||
createExecTokenScript := func(name string) string {
|
||||
scriptPath := filepath.Join(f.TempDirectory, name)
|
||||
scriptContent := `#!/bin/sh
|
||||
printf '%s\n' "$1"
|
||||
`
|
||||
err := os.WriteFile(scriptPath, []byte(scriptContent), 0o600)
|
||||
framework.ExpectNoError(err)
|
||||
err = os.Chmod(scriptPath, 0o700)
|
||||
framework.ExpectNoError(err)
|
||||
return scriptPath
|
||||
}
|
||||
|
||||
ginkgo.Describe("File-based token loading", func() {
|
||||
ginkgo.It("should work with file tokenSource", func() {
|
||||
// Create a temporary token file
|
||||
@@ -226,154 +214,4 @@ auth.tokenSource.file.path = "%s"
|
||||
f.RunProcesses([]string{serverConf}, []string{})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("Exec-based token loading", func() {
|
||||
ginkgo.It("should work with server tokenSource", func() {
|
||||
execValue := "exec-server-value"
|
||||
scriptPath := createExecTokenScript("server_token_exec.sh")
|
||||
|
||||
serverPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
|
||||
serverConf := fmt.Sprintf(`
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = %d
|
||||
|
||||
auth.tokenSource.type = "exec"
|
||||
auth.tokenSource.exec.command = %q
|
||||
auth.tokenSource.exec.args = [%q]
|
||||
`, serverPort, scriptPath, execValue)
|
||||
|
||||
clientConf := fmt.Sprintf(`
|
||||
serverAddr = "127.0.0.1"
|
||||
serverPort = %d
|
||||
loginFailExit = false
|
||||
auth.token = %q
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = %d
|
||||
remotePort = %d
|
||||
`, serverPort, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||
|
||||
serverConfigPath := f.GenerateConfigFile(serverConf)
|
||||
clientConfigPath := f.GenerateConfigFile(clientConf)
|
||||
|
||||
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should work with client tokenSource", func() {
|
||||
execValue := "exec-client-value"
|
||||
scriptPath := createExecTokenScript("client_token_exec.sh")
|
||||
|
||||
serverPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
|
||||
serverConf := fmt.Sprintf(`
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = %d
|
||||
|
||||
auth.token = %q
|
||||
`, serverPort, execValue)
|
||||
|
||||
clientConf := fmt.Sprintf(`
|
||||
serverAddr = "127.0.0.1"
|
||||
serverPort = %d
|
||||
loginFailExit = false
|
||||
|
||||
auth.tokenSource.type = "exec"
|
||||
auth.tokenSource.exec.command = %q
|
||||
auth.tokenSource.exec.args = [%q]
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = %d
|
||||
remotePort = %d
|
||||
`, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||
|
||||
serverConfigPath := f.GenerateConfigFile(serverConf)
|
||||
clientConfigPath := f.GenerateConfigFile(clientConf)
|
||||
|
||||
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should work with both server and client tokenSource", func() {
|
||||
execValue := "exec-shared-value"
|
||||
scriptPath := createExecTokenScript("shared_token_exec.sh")
|
||||
|
||||
serverPort := f.AllocPort()
|
||||
remotePort := f.AllocPort()
|
||||
|
||||
serverConf := fmt.Sprintf(`
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = %d
|
||||
|
||||
auth.tokenSource.type = "exec"
|
||||
auth.tokenSource.exec.command = %q
|
||||
auth.tokenSource.exec.args = [%q]
|
||||
`, serverPort, scriptPath, execValue)
|
||||
|
||||
clientConf := fmt.Sprintf(`
|
||||
serverAddr = "127.0.0.1"
|
||||
serverPort = %d
|
||||
loginFailExit = false
|
||||
|
||||
auth.tokenSource.type = "exec"
|
||||
auth.tokenSource.exec.command = %q
|
||||
auth.tokenSource.exec.args = [%q]
|
||||
|
||||
[[proxies]]
|
||||
name = "tcp"
|
||||
type = "tcp"
|
||||
localPort = %d
|
||||
remotePort = %d
|
||||
`, serverPort, scriptPath, execValue, f.PortByName(framework.TCPEchoServerPort), remotePort)
|
||||
|
||||
serverConfigPath := f.GenerateConfigFile(serverConf)
|
||||
clientConfigPath := f.GenerateConfigFile(clientConf)
|
||||
|
||||
_, _, err := f.RunFrps("-c", serverConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
_, _, err = f.RunFrpc("-c", clientConfigPath, "--allow-unsafe=TokenSourceExec")
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
framework.NewRequestExpect(f).Port(remotePort).Ensure()
|
||||
})
|
||||
|
||||
ginkgo.It("should fail validation without allow-unsafe", func() {
|
||||
execValue := "exec-unsafe-value"
|
||||
scriptPath := createExecTokenScript("unsafe_token_exec.sh")
|
||||
|
||||
serverPort := f.AllocPort()
|
||||
serverConf := fmt.Sprintf(`
|
||||
bindAddr = "0.0.0.0"
|
||||
bindPort = %d
|
||||
|
||||
auth.tokenSource.type = "exec"
|
||||
auth.tokenSource.exec.command = %q
|
||||
auth.tokenSource.exec.args = [%q]
|
||||
`, serverPort, scriptPath, execValue)
|
||||
|
||||
serverConfigPath := f.GenerateConfigFile(serverConf)
|
||||
|
||||
_, output, err := f.RunFrps("verify", "-c", serverConfigPath)
|
||||
framework.ExpectNoError(err)
|
||||
framework.ExpectContainSubstring(output, "unsafe feature \"TokenSourceExec\" is not enabled")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user