ipsec.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package vpn
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strings"
  9. "sync"
  10. )
  11. const (
  12. SwanctlConfPath = "/etc/swanctl/conf.d/tunnels.conf"
  13. IPSecStorePath = "/etc/proxmox-router/vpn/ipsec.json"
  14. )
  15. type IPSecManager struct {
  16. mu sync.Mutex
  17. config IPSecConfig
  18. }
  19. func NewIPSecManager() *IPSecManager {
  20. m := &IPSecManager{
  21. config: IPSecConfig{
  22. Connections: make([]IPSecConnection, 0),
  23. },
  24. }
  25. m.load()
  26. return m
  27. }
  28. func (m *IPSecManager) load() {
  29. data, err := os.ReadFile(IPSecStorePath)
  30. if err == nil {
  31. _ = json.Unmarshal(data, &m.config)
  32. if m.config.Connections == nil {
  33. m.config.Connections = make([]IPSecConnection, 0)
  34. }
  35. }
  36. }
  37. func (m *IPSecManager) save() error {
  38. _ = os.MkdirAll(filepath.Dir(IPSecStorePath), 0755)
  39. data, err := json.MarshalIndent(m.config, "", " ")
  40. if err != nil {
  41. return err
  42. }
  43. return os.WriteFile(IPSecStorePath, data, 0600)
  44. }
  45. func (m *IPSecManager) GetConfig() IPSecConfig {
  46. m.mu.Lock()
  47. defer m.mu.Unlock()
  48. return m.config
  49. }
  50. func (m *IPSecManager) SetConfig(cfg IPSecConfig) error {
  51. m.mu.Lock()
  52. defer m.mu.Unlock()
  53. if cfg.Connections == nil {
  54. cfg.Connections = make([]IPSecConnection, 0)
  55. }
  56. m.config = cfg
  57. if err := m.save(); err != nil {
  58. return err
  59. }
  60. return m.generateSwanctlConfig()
  61. }
  62. func (m *IPSecManager) SaveConnection(conn IPSecConnection) error {
  63. m.mu.Lock()
  64. defer m.mu.Unlock()
  65. if conn.Name == "" {
  66. return fmt.Errorf("connection name is required")
  67. }
  68. if conn.IKEProposals == "" {
  69. conn.IKEProposals = "aes256-sha256-modp2048,default"
  70. }
  71. if conn.ESPProposals == "" {
  72. conn.ESPProposals = "aes256-sha256-modp2048,default"
  73. }
  74. if conn.LocalAddrs == "" {
  75. conn.LocalAddrs = "%any"
  76. }
  77. found := false
  78. for i, c := range m.config.Connections {
  79. if c.Name == conn.Name {
  80. m.config.Connections[i] = conn
  81. found = true
  82. break
  83. }
  84. }
  85. if !found {
  86. m.config.Connections = append(m.config.Connections, conn)
  87. }
  88. if err := m.save(); err != nil {
  89. return err
  90. }
  91. return m.generateSwanctlConfig()
  92. }
  93. func (m *IPSecManager) DeleteConnection(name string) error {
  94. m.mu.Lock()
  95. defer m.mu.Unlock()
  96. var updated []IPSecConnection
  97. for _, c := range m.config.Connections {
  98. if c.Name != name {
  99. updated = append(updated, c)
  100. }
  101. }
  102. m.config.Connections = updated
  103. if err := m.save(); err != nil {
  104. return err
  105. }
  106. return m.generateSwanctlConfig()
  107. }
  108. func (m *IPSecManager) generateSwanctlConfig() error {
  109. _ = os.MkdirAll("/etc/swanctl/conf.d", 0755)
  110. var sb strings.Builder
  111. sb.WriteString("# Generated by Proxmox RouterOS - strongSwan IPSec Configuration\n\n")
  112. sb.WriteString("connections {\n")
  113. for _, c := range m.config.Connections {
  114. sb.WriteString(fmt.Sprintf(" %s {\n", c.Name))
  115. sb.WriteString(fmt.Sprintf(" local_addrs = %s\n", c.LocalAddrs))
  116. sb.WriteString(fmt.Sprintf(" remote_addrs = %s\n", c.RemoteAddrs))
  117. sb.WriteString(" local {\n")
  118. sb.WriteString(" auth = psk\n")
  119. sb.WriteString(" }\n")
  120. sb.WriteString(" remote {\n")
  121. sb.WriteString(" auth = psk\n")
  122. sb.WriteString(" }\n")
  123. sb.WriteString(" children {\n")
  124. sb.WriteString(fmt.Sprintf(" %s {\n", c.Name))
  125. if c.LocalSubnet != "" {
  126. sb.WriteString(fmt.Sprintf(" local_ts = %s\n", c.LocalSubnet))
  127. }
  128. if c.RemoteSubnet != "" {
  129. sb.WriteString(fmt.Sprintf(" remote_ts = %s\n", c.RemoteSubnet))
  130. }
  131. sb.WriteString(fmt.Sprintf(" esp_proposals = %s\n", c.ESPProposals))
  132. if c.AutoStart {
  133. sb.WriteString(" start_action = start\n")
  134. }
  135. sb.WriteString(" }\n")
  136. sb.WriteString(" }\n")
  137. sb.WriteString(fmt.Sprintf(" proposals = %s\n", c.IKEProposals))
  138. sb.WriteString(" }\n\n")
  139. }
  140. sb.WriteString("}\n\n")
  141. // Secrets
  142. sb.WriteString("secrets {\n")
  143. for _, c := range m.config.Connections {
  144. if c.PresharedKey != "" {
  145. sb.WriteString(fmt.Sprintf(" ike-%s {\n", c.Name))
  146. sb.WriteString(fmt.Sprintf(" id = %s\n", c.RemoteAddrs))
  147. sb.WriteString(fmt.Sprintf(" secret = %q\n", c.PresharedKey))
  148. sb.WriteString(" }\n")
  149. }
  150. }
  151. sb.WriteString("}\n")
  152. return os.WriteFile(SwanctlConfPath, []byte(sb.String()), 0600)
  153. }
  154. func (m *IPSecManager) StartTunnel(name string) error {
  155. m.mu.Lock()
  156. defer m.mu.Unlock()
  157. _ = exec.Command("systemctl", "start", "strongswan").Run()
  158. _ = exec.Command("swanctl", "--load-all").Run()
  159. cmd := exec.Command("swanctl", "--initiate", "--child", name)
  160. if out, err := cmd.CombinedOutput(); err != nil {
  161. return fmt.Errorf("failed to initiate IPSec tunnel %s: %s: %w", name, string(out), err)
  162. }
  163. return nil
  164. }
  165. func (m *IPSecManager) SetEnabled(name string, enabled bool) error {
  166. m.mu.Lock()
  167. defer m.mu.Unlock()
  168. for i, c := range m.config.Connections {
  169. if c.Name == name {
  170. m.config.Connections[i].Disabled = !enabled
  171. _ = m.save()
  172. if enabled {
  173. go m.StartTunnel(name)
  174. } else {
  175. go m.StopTunnel(name)
  176. }
  177. return nil
  178. }
  179. }
  180. return fmt.Errorf("connection %q not found", name)
  181. }
  182. func (m *IPSecManager) List() []IPSecConnection {
  183. st := m.GetStatus()
  184. return st.Connections
  185. }
  186. func (m *IPSecManager) StopTunnel(name string) error {
  187. m.mu.Lock()
  188. defer m.mu.Unlock()
  189. cmd := exec.Command("swanctl", "--terminate", "--child", name)
  190. out, err := cmd.CombinedOutput()
  191. if err != nil {
  192. return fmt.Errorf("failed to terminate tunnel %s: %s: %w", name, string(out), err)
  193. }
  194. return nil
  195. }
  196. func (m *IPSecManager) GetStatus() IPSecStatus {
  197. m.mu.Lock()
  198. defer m.mu.Unlock()
  199. out, err := exec.Command("swanctl", "--list-sas").CombinedOutput()
  200. rawOutput := string(out)
  201. running := err == nil
  202. activeSAs := 0
  203. conns := make([]IPSecConnection, len(m.config.Connections))
  204. copy(conns, m.config.Connections)
  205. for i := range conns {
  206. if strings.Contains(rawOutput, conns[i].Name) && strings.Contains(rawOutput, "INSTALLED") {
  207. conns[i].Status = "ESTABLISHED"
  208. conns[i].Running = true
  209. activeSAs++
  210. } else {
  211. conns[i].Status = "DISCONNECTED"
  212. conns[i].Running = false
  213. }
  214. }
  215. return IPSecStatus{
  216. Running: running,
  217. ActiveSAs: activeSAs,
  218. Connections: conns,
  219. RawStatus: rawOutput,
  220. }
  221. }