Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3b7fe86
Add smux dependancy
jisung-02 Dec 8, 2025
c04d717
Add WebSocket adapter and buffer pool for smux
jisung-02 Dec 8, 2025
e2e8a09
Implement TCP tunnel with opentunnel/closetunnel commands
jisung-02 Dec 8, 2025
b69c489
Simplify opentunnel command by removing unused fields
jisung-02 Dec 12, 2025
8927b9f
Add user credential demotion to tunnel connections for security
jisung-02 Dec 12, 2025
75b1648
Merge branch 'main' into 145-tunneling
jisung-02 Dec 15, 2025
c7c75c4
Remove unused codes in conn.go and pool.go
jisung-02 Dec 15, 2025
c21f8e6
Minor fix
jisung-02 Dec 15, 2025
22bcafa
Add strict security validations to tunnel functionality
jisung-02 Dec 15, 2025
df6ce04
Modify to run tunnel worker as nobody user instead of specified user
jisung-02 Dec 19, 2025
d6d0869
Unify duplicate tunnel_id to session_id
jisung-02 Dec 19, 2025
a554688
Merge branch 'main' into 145-tunneling
jisung-02 Dec 19, 2025
4ed0c6f
Minor fix
jisung-02 Dec 19, 2025
e1f67aa
Fix buffer pool to use pointer indirection for proper reuse
jisung-02 Dec 19, 2025
0889a1a
Define constants for readability and place type definitions at the top
jisung-02 Dec 22, 2025
94d914f
Move constant definitions to the top to improve readability
jisung-02 Dec 22, 2025
3d4494e
Fix to prevent CPU spin in tunnel stream handler
jisung-02 Dec 22, 2025
d933cdf
Fix to prevent data loss by ensuring all buffered data is read from b…
jisung-02 Dec 22, 2025
f6581a3
Suppress redundant error logs when killing finished processes
jisung-02 Dec 22, 2025
146ebf0
Protect WebSocket writes with mutex for concurrent stream safety
jisung-02 Dec 22, 2025
0291e0f
Synchronize tunnel deletion to prevent race condition in cleanup
jisung-02 Dec 22, 2025
63c7c31
Add port validation and fix potential issues in tunnel client
jisung-02 Dec 22, 2025
15c253e
Merge branch 'main' into 145-tunneling
jisung-02 Dec 22, 2025
cad3df2
Suppress CodeQL SSRF warning for trusted tunnel URL
jisung-02 Dec 23, 2025
dbcb7b0
Reorder const and var declarations to follow Go conventions
jisung-02 Dec 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/alpamon/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/alpacax/alpamon/cmd/alpamon/command/ftp"
"github.com/alpacax/alpamon/cmd/alpamon/command/setup"
"github.com/alpacax/alpamon/cmd/alpamon/command/tunnel"
"github.com/alpacax/alpamon/pkg/collector"
"github.com/alpacax/alpamon/pkg/config"
"github.com/alpacax/alpamon/pkg/db"
Expand Down Expand Up @@ -38,7 +39,7 @@ var RootCmd = &cobra.Command{

func init() {
setup.SetConfigPaths(name)
RootCmd.AddCommand(setup.SetupCmd, ftp.FtpCmd)
RootCmd.AddCommand(setup.SetupCmd, ftp.FtpCmd, tunnel.TunnelWorkerCmd)
}

func runAgent() {
Expand Down
18 changes: 18 additions & 0 deletions cmd/alpamon/command/tunnel/tunnel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tunnel

import (
"github.com/alpacax/alpamon/pkg/runner"
"github.com/spf13/cobra"
)

// TunnelWorkerCmd is the subcommand for running the tunnel worker subprocess.
// It is invoked by the main alpamon process with demoted user credentials.
var TunnelWorkerCmd = &cobra.Command{
Use: "tunnel-worker <targetAddr>",
Short: "Tunnel worker subprocess for TCP relay",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
targetAddr := args[0] // e.g., "127.0.0.1:3306"
runner.RunTunnelWorker(targetAddr)
},
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/shirou/gopsutil/v4 v4.24.8
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/xtaci/smux v1.5.44
golang.org/x/term v0.14.0
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/ini.v1 v1.67.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xtaci/smux v1.5.44 h1:7T61zLfFX1jokXj6d+lPaxHnVwgYiJ7EN94DAudKqpg=
github.com/xtaci/smux v1.5.44/go.mod h1:IGQ9QYrBphmb/4aTnLEcJby0TNr3NV+OslIOMrX825Q=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
Expand Down
13 changes: 13 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/xtaci/smux"
"gopkg.in/ini.v1"
)

Expand All @@ -22,6 +23,17 @@ const (
MaxConnectInterval = 300 * time.Second
)

// GetSmuxConfig returns optimized smux configuration for tunnel connections.
func GetSmuxConfig() *smux.Config {
cfg := smux.DefaultConfig()
cfg.KeepAliveInterval = 10 * time.Second
cfg.KeepAliveTimeout = 30 * time.Second
cfg.MaxFrameSize = 32768 // 32KB
cfg.MaxReceiveBuffer = 4194304 // 4MB
cfg.MaxStreamBuffer = 65536 // 64KB per stream
return cfg
}

func InitSettings(settings Settings) {
GlobalSettings = settings
}
Expand Down Expand Up @@ -133,6 +145,7 @@ func validateConfig(config Config, wsPath string) (bool, Settings) {
}
}
}

return valid, settings
}

Expand Down
49 changes: 49 additions & 0 deletions pkg/runner/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,55 @@ func (cr *CommandRunner) handleInternalCmd() (int, string) {
}

return 0, "Spawned a ftp terminal."
case "opentunnel":
log.Debug().
Str("sessionID", cr.data.SessionID).
Int("targetPort", cr.data.TargetPort).
Str("url", cr.data.URL).
Msg("Received opentunnel command")

// Validate port range (1-65535, 0 is reserved)
if cr.data.TargetPort < 1 || cr.data.TargetPort > 65535 {
return 1, fmt.Sprintf("opentunnel: Invalid target port %d. Must be between 1 and 65535.", cr.data.TargetPort)
}

data := openTunnelData{
SessionID: cr.data.SessionID,
TargetPort: cr.data.TargetPort,
URL: cr.data.URL,
}
err := cr.validateData(data)
if err != nil {
return 1, fmt.Sprintf("opentunnel: Not enough information. %s", err.Error())
}

// Check if tunnel already exists
if _, exists := GetActiveTunnel(cr.data.SessionID); exists {
return 1, fmt.Sprintf("opentunnel: Tunnel session %s already exists.", cr.data.SessionID)
}

tunnelClient := NewTunnelClient(
cr.data.SessionID,
cr.data.TargetPort,
cr.data.URL,
)
go tunnelClient.RunTunnelBackground()

return 0, fmt.Sprintf("Spawned a tunnel for session %s, target port %d.", cr.data.SessionID, cr.data.TargetPort)
case "closetunnel":
data := closeTunnelData{
SessionID: cr.data.SessionID,
}
err := cr.validateData(data)
if err != nil {
return 1, fmt.Sprintf("closetunnel: Not enough information. %s", err.Error())
}

if err := CloseTunnel(cr.data.SessionID); err != nil {
return 1, fmt.Sprintf("closetunnel: %s", err.Error())
}

return 0, fmt.Sprintf("Closed tunnel session %s.", cr.data.SessionID)
case "resizepty":
if terminals[cr.data.SessionID] != nil {
err := terminals[cr.data.SessionID].resize(cr.data.Rows, cr.data.Cols)
Expand Down
11 changes: 11 additions & 0 deletions pkg/runner/command_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type CommandData struct {
AssignmentID string `json:"assignment_id"`
ServerID string `json:"server_id"`
ChainNames []string `json:"chain_names"` // for firewall-reorder-chains
TargetPort int `json:"target_port"` // for tunneling
}

type firewallData struct {
Expand Down Expand Up @@ -158,6 +159,16 @@ type openFtpData struct {
HomeDirectory string `validate:"required"`
}

type openTunnelData struct {
SessionID string `validate:"required"`
TargetPort int `validate:"required"`
URL string `validate:"required"`
}

type closeTunnelData struct {
SessionID string `validate:"required"`
}

type commandFin struct {
Success bool `json:"success"`
Result string `json:"result"`
Expand Down
Loading
Loading