tls_client.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. package runtime
  2. import (
  3. "crypto/sha256"
  4. "crypto/subtle"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "fmt"
  9. "net/http"
  10. "strings"
  11. "sync"
  12. "sync/atomic"
  13. "time"
  14. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  15. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  16. "github.com/mhsanaei/3x-ui/v3/internal/util/netproxy"
  17. "github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
  18. )
  19. // MasterClientCertProvider supplies the master client certificate this panel
  20. // presents to nodes in mtls mode. It is injected by the web layer so the
  21. // runtime package need not import service.
  22. type MasterClientCertProvider func() (tls.Certificate, error)
  23. var (
  24. masterClientCertMu sync.RWMutex
  25. masterClientCert MasterClientCertProvider
  26. masterCertEpoch atomic.Uint64
  27. )
  28. // SetMasterClientCertProvider installs the provider used to obtain the master
  29. // client certificate for mtls nodes. Passing nil disables it.
  30. func SetMasterClientCertProvider(p MasterClientCertProvider) {
  31. masterClientCertMu.Lock()
  32. defer masterClientCertMu.Unlock()
  33. masterClientCert = p
  34. }
  35. func getMasterClientCert() (tls.Certificate, error) {
  36. masterClientCertMu.RLock()
  37. p := masterClientCert
  38. masterClientCertMu.RUnlock()
  39. if p == nil {
  40. return tls.Certificate{}, common.NewError("mtls: master client certificate provider not configured")
  41. }
  42. return p()
  43. }
  44. // InvalidateMasterClientConnections advances the client-credential generation.
  45. // Every cached mTLS transport observes the generation before its next request,
  46. // replaces its TLS transport, and closes the old idle pool. Requests already
  47. // in flight are not interrupted; no request that starts after invalidation can
  48. // reuse a connection authenticated with the previous leaf.
  49. func InvalidateMasterClientConnections() {
  50. masterCertEpoch.Add(1)
  51. }
  52. // ReloadMasterClientConnections validates that the currently configured
  53. // provider can load the master credential, then invalidates every cached mTLS
  54. // transport. Operators that rotate the credential outside the process (for
  55. // example by restoring settings) can call this without restarting the panel.
  56. func ReloadMasterClientConnections() error {
  57. if _, err := getMasterClientCert(); err != nil {
  58. return err
  59. }
  60. InvalidateMasterClientConnections()
  61. return nil
  62. }
  63. type idleClosingRoundTripper interface {
  64. http.RoundTripper
  65. CloseIdleConnections()
  66. }
  67. type credentialRotatingTransport struct {
  68. mu sync.Mutex
  69. generation uint64
  70. current idleClosingRoundTripper
  71. build func() (idleClosingRoundTripper, error)
  72. }
  73. func buildStableCredentialTransport(build func() (idleClosingRoundTripper, error)) (idleClosingRoundTripper, uint64, error) {
  74. for {
  75. before := masterCertEpoch.Load()
  76. current, err := build()
  77. if err != nil {
  78. return nil, 0, err
  79. }
  80. after := masterCertEpoch.Load()
  81. if before == after {
  82. return current, after, nil
  83. }
  84. current.CloseIdleConnections()
  85. }
  86. }
  87. func newCredentialRotatingTransport(build func() (idleClosingRoundTripper, error)) (*credentialRotatingTransport, error) {
  88. current, generation, err := buildStableCredentialTransport(build)
  89. if err != nil {
  90. return nil, err
  91. }
  92. return &credentialRotatingTransport{
  93. generation: generation,
  94. current: current,
  95. build: build,
  96. }, nil
  97. }
  98. func (t *credentialRotatingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  99. t.mu.Lock()
  100. if masterCertEpoch.Load() != t.generation {
  101. next, generation, err := buildStableCredentialTransport(t.build)
  102. if err != nil {
  103. t.mu.Unlock()
  104. return nil, err
  105. }
  106. previous := t.current
  107. t.current = next
  108. t.generation = generation
  109. previous.CloseIdleConnections()
  110. }
  111. current := t.current
  112. t.mu.Unlock()
  113. return current.RoundTrip(req)
  114. }
  115. func (t *credentialRotatingTransport) CloseIdleConnections() {
  116. t.mu.Lock()
  117. current := t.current
  118. t.mu.Unlock()
  119. current.CloseIdleConnections()
  120. }
  121. // The global cap must exceed the fleet size: below it Go closes a node's
  122. // connection before its next heartbeat, costing a handshake every tick.
  123. const (
  124. maxIdleNodeConns = 512
  125. maxIdleNodeConnsPerHost = 8
  126. )
  127. func newNodeTransport(tlsCfg *tls.Config) *http.Transport {
  128. return &http.Transport{
  129. MaxIdleConns: maxIdleNodeConns,
  130. MaxIdleConnsPerHost: maxIdleNodeConnsPerHost,
  131. IdleConnTimeout: 60 * time.Second,
  132. DialContext: netsafe.SSRFGuardedDialContext,
  133. TLSClientConfig: tlsCfg,
  134. }
  135. }
  136. // defaultNodeHTTPClient reaches nodes trusting the system CA store ("verify"
  137. // mode or plain http); shared so connections pool across nodes.
  138. var defaultNodeHTTPClient = &http.Client{Transport: newNodeTransport(nil)}
  139. // nodeClients caches one client per node: heartbeat and traffic sync reach it
  140. // every few seconds, and a rebuilt client would open its own empty pool.
  141. type nodeClientEntry struct {
  142. nodeID int
  143. client *http.Client
  144. }
  145. var (
  146. nodeClientsMu sync.Mutex
  147. nodeClientsCache = map[string]nodeClientEntry{}
  148. )
  149. // nodeClientIdentity covers everything that decides how the node is trusted; the
  150. // proxy URL is a variant of it, so it stays out of the identity itself.
  151. func nodeClientIdentity(n *model.Node, mode string) string {
  152. return fmt.Sprintf("%d|%s|%s|%s|%d|%s", n.Id, mode, n.Scheme, n.Address, n.Port, n.PinnedCertSha256)
  153. }
  154. // dropNodeClients discards every cached client of one node except keep, so a
  155. // node never holds more than the variant it is using now. Callers hold the lock.
  156. func dropNodeClients(nodeID int, keep string) {
  157. for key, entry := range nodeClientsCache {
  158. if entry.nodeID != nodeID || key == keep {
  159. continue
  160. }
  161. entry.client.CloseIdleConnections()
  162. delete(nodeClientsCache, key)
  163. }
  164. }
  165. // HTTPClientForNode returns the pooled client for n, building it on first use
  166. // and whenever the node's identity or TLS material changes.
  167. func HTTPClientForNode(n *model.Node, proxyURL string) (*http.Client, error) {
  168. mode := n.TlsVerifyMode
  169. if mode == "" {
  170. mode = "verify"
  171. }
  172. if mode == "verify" || n.Scheme == "http" {
  173. // Shared across nodes and not node-specific: nothing to key on.
  174. if proxyURL == "" {
  175. nodeClientsMu.Lock()
  176. dropNodeClients(n.Id, "")
  177. nodeClientsMu.Unlock()
  178. return defaultNodeHTTPClient, nil
  179. }
  180. }
  181. identity := nodeClientIdentity(n, mode)
  182. key := identity + "|" + proxyURL
  183. nodeClientsMu.Lock()
  184. if entry, ok := nodeClientsCache[key]; ok {
  185. nodeClientsMu.Unlock()
  186. return entry.client, nil
  187. }
  188. nodeClientsMu.Unlock()
  189. client, err := buildNodeHTTPClient(n, mode, proxyURL)
  190. if err != nil {
  191. return nil, err
  192. }
  193. nodeClientsMu.Lock()
  194. if entry, ok := nodeClientsCache[key]; ok {
  195. // A concurrent caller won the race; keep its client and drop ours.
  196. nodeClientsMu.Unlock()
  197. client.CloseIdleConnections()
  198. return entry.client, nil
  199. }
  200. // Any other variant is dead weight: a stale identity's pool fits no trust
  201. // decision now, and an ephemeral proxy URL is never asked for twice.
  202. dropNodeClients(n.Id, key)
  203. nodeClientsCache[key] = nodeClientEntry{nodeID: n.Id, client: client}
  204. nodeClientsMu.Unlock()
  205. return client, nil
  206. }
  207. func buildNodeHTTPClient(n *model.Node, mode, proxyURL string) (*http.Client, error) {
  208. if proxyURL != "" {
  209. if mode == "mtls" && n.Scheme != "http" {
  210. timeout := remoteHTTPTimeout
  211. build := func() (idleClosingRoundTripper, error) {
  212. client, err := netproxy.NewHTTPClient(proxyURL, remoteHTTPTimeout)
  213. if err != nil {
  214. return nil, err
  215. }
  216. transport, ok := client.Transport.(*http.Transport)
  217. if !ok {
  218. return nil, common.NewError("mtls proxy client transport does not support credential rotation")
  219. }
  220. tlsCfg, err := tlsConfigForNode(n)
  221. if err != nil {
  222. return nil, err
  223. }
  224. transport.TLSClientConfig = tlsCfg
  225. return transport, nil
  226. }
  227. transport, err := newCredentialRotatingTransport(build)
  228. if err != nil {
  229. return nil, err
  230. }
  231. return &http.Client{Transport: transport, Timeout: timeout}, nil
  232. }
  233. client, err := netproxy.NewHTTPClient(proxyURL, remoteHTTPTimeout)
  234. if err != nil {
  235. return nil, err
  236. }
  237. if mode == "verify" || n.Scheme == "http" {
  238. return client, nil
  239. }
  240. transport, ok := client.Transport.(*http.Transport)
  241. if !ok {
  242. return client, nil
  243. }
  244. tlsCfg, err := tlsConfigForNode(n)
  245. if err != nil {
  246. return nil, err
  247. }
  248. transport.TLSClientConfig = tlsCfg
  249. return client, nil
  250. }
  251. if mode == "mtls" {
  252. build := func() (idleClosingRoundTripper, error) {
  253. tlsCfg, err := tlsConfigForNode(n)
  254. if err != nil {
  255. return nil, err
  256. }
  257. return newNodeTransport(tlsCfg), nil
  258. }
  259. transport, err := newCredentialRotatingTransport(build)
  260. if err != nil {
  261. return nil, err
  262. }
  263. return &http.Client{Transport: transport}, nil
  264. }
  265. tlsCfg, err := tlsConfigForNode(n)
  266. if err != nil {
  267. return nil, err
  268. }
  269. return &http.Client{Transport: newNodeTransport(tlsCfg)}, nil
  270. }
  271. func tlsConfigForNode(n *model.Node) (*tls.Config, error) {
  272. if n.TlsVerifyMode == "mtls" {
  273. // Present the master client cert; verify the node's server cert against
  274. // the system roots (no InsecureSkipVerify). mtls authenticates the
  275. // caller — it does not change how the node's server identity is checked.
  276. cert, err := getMasterClientCert()
  277. if err != nil {
  278. return nil, err
  279. }
  280. return &tls.Config{
  281. Certificates: []tls.Certificate{cert},
  282. MinVersion: tls.VersionTLS12,
  283. }, nil
  284. }
  285. tlsCfg := &tls.Config{InsecureSkipVerify: true} // lgtm[go/disabled-certificate-check]
  286. if n.TlsVerifyMode == "pin" {
  287. want, err := DecodeCertPin(n.PinnedCertSha256)
  288. if err != nil {
  289. return nil, err
  290. }
  291. tlsCfg.VerifyConnection = func(cs tls.ConnectionState) error {
  292. if len(cs.PeerCertificates) == 0 {
  293. return common.NewError("node presented no certificate")
  294. }
  295. sum := sha256.Sum256(cs.PeerCertificates[0].Raw)
  296. if subtle.ConstantTimeCompare(sum[:], want) != 1 {
  297. return common.NewError("node certificate does not match pinned SHA-256")
  298. }
  299. return nil
  300. }
  301. }
  302. return tlsCfg, nil
  303. }
  304. // DecodeCertPin decodes a SHA-256 cert pin given as base64 (Xray's
  305. // pinnedPeerCertSha256 form) or hex with optional colons into 32 raw bytes.
  306. func DecodeCertPin(s string) ([]byte, error) {
  307. s = strings.TrimSpace(s)
  308. if s == "" {
  309. return nil, common.NewError("certificate pin is empty")
  310. }
  311. if b, err := hex.DecodeString(strings.ReplaceAll(s, ":", "")); err == nil && len(b) == sha256.Size {
  312. return b, nil
  313. }
  314. for _, enc := range []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding} {
  315. if b, err := enc.DecodeString(s); err == nil && len(b) == sha256.Size {
  316. return b, nil
  317. }
  318. }
  319. return nil, common.NewError("certificate pin must be a SHA-256 hash (base64 or hex)")
  320. }