ssh_tunnel.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "os"
  6. "os/exec"
  7. "sync"
  8. "time"
  9. )
  10. // TunnelConfig contains configuration for a specific tunnel
  11. type TunnelConfig struct {
  12. Enabled bool
  13. Server string
  14. Port int
  15. User string
  16. RemotePort int
  17. KeepaliveInterval int
  18. ReconnectDelay int
  19. }
  20. // SSHTunnel manages reverse SSH tunnel to server
  21. type SSHTunnel struct {
  22. name string // "ssh" or "dashboard"
  23. localPort int // Local port to forward (22 for SSH, 8080 for dashboard)
  24. cfg *TunnelConfig
  25. cmd *exec.Cmd
  26. stopChan chan struct{}
  27. mu sync.Mutex
  28. }
  29. // NewSSHTunnel creates a new SSH tunnel manager
  30. func NewSSHTunnel(name string, localPort int, cfg *TunnelConfig) *SSHTunnel {
  31. return &SSHTunnel{
  32. name: name,
  33. localPort: localPort,
  34. cfg: cfg,
  35. stopChan: make(chan struct{}),
  36. }
  37. }
  38. // Start initiates the SSH tunnel
  39. func (t *SSHTunnel) Start() error {
  40. if !t.cfg.Enabled {
  41. log.Printf("[%s-tunnel] Tunnel disabled", t.name)
  42. return nil
  43. }
  44. if t.cfg.RemotePort == 0 {
  45. log.Printf("[%s-tunnel] Remote port not allocated yet, waiting...", t.name)
  46. return nil
  47. }
  48. keyPath := "/opt/mybeacon/etc/ssh_tunnel_ed25519"
  49. // Verify key exists
  50. if _, err := os.Stat(keyPath); os.IsNotExist(err) {
  51. return fmt.Errorf("SSH key not found: %s", keyPath)
  52. }
  53. // Build reverse tunnel string: remote_port:localhost:local_port
  54. reverseSpec := fmt.Sprintf("%d:localhost:%d", t.cfg.RemotePort, t.localPort)
  55. args := []string{
  56. "-N", // No command execution
  57. "-R", reverseSpec, // Reverse tunnel with fixed port
  58. "-o", fmt.Sprintf("ServerAliveInterval=%d", t.cfg.KeepaliveInterval),
  59. "-o", "ServerAliveCountMax=3",
  60. "-o", "ExitOnForwardFailure=yes",
  61. "-o", "StrictHostKeyChecking=accept-new",
  62. "-i", keyPath,
  63. "-p", fmt.Sprintf("%d", t.cfg.Port),
  64. fmt.Sprintf("%s@%s", t.cfg.User, t.cfg.Server),
  65. }
  66. t.cmd = exec.Command("ssh", args...)
  67. if err := t.cmd.Start(); err != nil {
  68. return err
  69. }
  70. log.Printf("[%s-tunnel] Started: %s:%d -> localhost:%d (remote_port=%d)",
  71. t.name, t.cfg.Server, t.cfg.Port, t.localPort, t.cfg.RemotePort)
  72. // Monitor process
  73. go t.monitor()
  74. return nil
  75. }
  76. // monitor watches SSH process and handles reconnection
  77. func (t *SSHTunnel) monitor() {
  78. err := t.cmd.Wait()
  79. if err != nil {
  80. log.Printf("[%s-tunnel] Tunnel exited with error: %v", t.name, err)
  81. } else {
  82. log.Printf("[%s-tunnel] Tunnel exited", t.name)
  83. }
  84. // Auto-reconnect after delay
  85. select {
  86. case <-t.stopChan:
  87. log.Printf("[%s-tunnel] Stopped, not reconnecting", t.name)
  88. return
  89. case <-time.After(time.Duration(t.cfg.ReconnectDelay) * time.Second):
  90. log.Printf("[%s-tunnel] Reconnecting in %ds...", t.name, t.cfg.ReconnectDelay)
  91. if err := t.Start(); err != nil {
  92. log.Printf("[%s-tunnel] Reconnect failed: %v", t.name, err)
  93. // Will retry after another delay via monitor()
  94. }
  95. }
  96. }
  97. // Stop terminates the SSH tunnel
  98. func (t *SSHTunnel) Stop() {
  99. log.Printf("[%s-tunnel] Stopping...", t.name)
  100. t.mu.Lock()
  101. defer t.mu.Unlock()
  102. select {
  103. case <-t.stopChan:
  104. // Already closed
  105. return
  106. default:
  107. close(t.stopChan)
  108. }
  109. if t.cmd != nil && t.cmd.Process != nil {
  110. t.cmd.Process.Kill()
  111. }
  112. }
  113. // Restart restarts the tunnel (useful when config changes)
  114. func (t *SSHTunnel) Restart() error {
  115. t.Stop()
  116. time.Sleep(2 * time.Second)
  117. // Recreate stop channel
  118. t.mu.Lock()
  119. t.stopChan = make(chan struct{})
  120. t.mu.Unlock()
  121. return t.Start()
  122. }
  123. // UpdateConfig updates the tunnel configuration
  124. func (t *SSHTunnel) UpdateConfig(cfg *TunnelConfig) {
  125. t.mu.Lock()
  126. defer t.mu.Unlock()
  127. t.cfg = cfg
  128. }