1
0

tls_client.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package runtime
  2. import (
  3. "crypto/sha256"
  4. "crypto/subtle"
  5. "crypto/tls"
  6. "encoding/base64"
  7. "encoding/hex"
  8. "net/http"
  9. "strings"
  10. "sync"
  11. "sync/atomic"
  12. "time"
  13. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  14. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  15. "github.com/mhsanaei/3x-ui/v3/internal/util/netproxy"
  16. "github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
  17. )
  18. // MasterClientCertProvider supplies the master client certificate this panel
  19. // presents to nodes in mtls mode. It is injected by the web layer so the
  20. // runtime package need not import service.
  21. type MasterClientCertProvider func() (tls.Certificate, error)
  22. var (
  23. masterClientCertMu sync.RWMutex
  24. masterClientCert MasterClientCertProvider
  25. masterCertEpoch atomic.Uint64
  26. )
  27. // SetMasterClientCertProvider installs the provider used to obtain the master
  28. // client certificate for mtls nodes. Passing nil disables it.
  29. func SetMasterClientCertProvider(p MasterClientCertProvider) {
  30. masterClientCertMu.Lock()
  31. defer masterClientCertMu.Unlock()
  32. masterClientCert = p
  33. }
  34. func getMasterClientCert() (tls.Certificate, error) {
  35. masterClientCertMu.RLock()
  36. p := masterClientCert
  37. masterClientCertMu.RUnlock()
  38. if p == nil {
  39. return tls.Certificate{}, common.NewError("mtls: master client certificate provider not configured")
  40. }
  41. return p()
  42. }
  43. // InvalidateMasterClientConnections advances the client-credential generation.
  44. // Every cached mTLS transport observes the generation before its next request,
  45. // replaces its TLS transport, and closes the old idle pool. Requests already
  46. // in flight are not interrupted; no request that starts after invalidation can
  47. // reuse a connection authenticated with the previous leaf.
  48. func InvalidateMasterClientConnections() {
  49. masterCertEpoch.Add(1)
  50. }
  51. // ReloadMasterClientConnections validates that the currently configured
  52. // provider can load the master credential, then invalidates every cached mTLS
  53. // transport. Operators that rotate the credential outside the process (for
  54. // example by restoring settings) can call this without restarting the panel.
  55. func ReloadMasterClientConnections() error {
  56. if _, err := getMasterClientCert(); err != nil {
  57. return err
  58. }
  59. InvalidateMasterClientConnections()
  60. return nil
  61. }
  62. type idleClosingRoundTripper interface {
  63. http.RoundTripper
  64. CloseIdleConnections()
  65. }
  66. type credentialRotatingTransport struct {
  67. mu sync.Mutex
  68. generation uint64
  69. current idleClosingRoundTripper
  70. build func() (idleClosingRoundTripper, error)
  71. }
  72. func buildStableCredentialTransport(build func() (idleClosingRoundTripper, error)) (idleClosingRoundTripper, uint64, error) {
  73. for {
  74. before := masterCertEpoch.Load()
  75. current, err := build()
  76. if err != nil {
  77. return nil, 0, err
  78. }
  79. after := masterCertEpoch.Load()
  80. if before == after {
  81. return current, after, nil
  82. }
  83. current.CloseIdleConnections()
  84. }
  85. }
  86. func newCredentialRotatingTransport(build func() (idleClosingRoundTripper, error)) (*credentialRotatingTransport, error) {
  87. current, generation, err := buildStableCredentialTransport(build)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return &credentialRotatingTransport{
  92. generation: generation,
  93. current: current,
  94. build: build,
  95. }, nil
  96. }
  97. func (t *credentialRotatingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  98. t.mu.Lock()
  99. if masterCertEpoch.Load() != t.generation {
  100. next, generation, err := buildStableCredentialTransport(t.build)
  101. if err != nil {
  102. t.mu.Unlock()
  103. return nil, err
  104. }
  105. previous := t.current
  106. t.current = next
  107. t.generation = generation
  108. previous.CloseIdleConnections()
  109. }
  110. current := t.current
  111. t.mu.Unlock()
  112. return current.RoundTrip(req)
  113. }
  114. func (t *credentialRotatingTransport) CloseIdleConnections() {
  115. t.mu.Lock()
  116. current := t.current
  117. t.mu.Unlock()
  118. current.CloseIdleConnections()
  119. }
  120. // defaultNodeHTTPClient reaches nodes trusting the system CA store ("verify"
  121. // mode or plain http); shared so connections pool across nodes.
  122. var defaultNodeHTTPClient = &http.Client{
  123. Transport: &http.Transport{
  124. MaxIdleConns: 64,
  125. MaxIdleConnsPerHost: 4,
  126. IdleConnTimeout: 60 * time.Second,
  127. DialContext: netsafe.SSRFGuardedDialContext,
  128. },
  129. }
  130. func HTTPClientForNode(n *model.Node, proxyURL string) (*http.Client, error) {
  131. mode := n.TlsVerifyMode
  132. if mode == "" {
  133. mode = "verify"
  134. }
  135. if proxyURL != "" {
  136. if mode == "mtls" && n.Scheme != "http" {
  137. timeout := remoteHTTPTimeout
  138. build := func() (idleClosingRoundTripper, error) {
  139. client, err := netproxy.NewHTTPClient(proxyURL, remoteHTTPTimeout)
  140. if err != nil {
  141. return nil, err
  142. }
  143. transport, ok := client.Transport.(*http.Transport)
  144. if !ok {
  145. return nil, common.NewError("mtls proxy client transport does not support credential rotation")
  146. }
  147. tlsCfg, err := tlsConfigForNode(n)
  148. if err != nil {
  149. return nil, err
  150. }
  151. transport.TLSClientConfig = tlsCfg
  152. return transport, nil
  153. }
  154. transport, err := newCredentialRotatingTransport(build)
  155. if err != nil {
  156. return nil, err
  157. }
  158. return &http.Client{Transport: transport, Timeout: timeout}, nil
  159. }
  160. client, err := netproxy.NewHTTPClient(proxyURL, remoteHTTPTimeout)
  161. if err != nil {
  162. return nil, err
  163. }
  164. if mode == "verify" || n.Scheme == "http" {
  165. return client, nil
  166. }
  167. transport, ok := client.Transport.(*http.Transport)
  168. if !ok {
  169. return client, nil
  170. }
  171. tlsCfg, err := tlsConfigForNode(n)
  172. if err != nil {
  173. return nil, err
  174. }
  175. transport.TLSClientConfig = tlsCfg
  176. return client, nil
  177. }
  178. if mode == "verify" || n.Scheme == "http" {
  179. return defaultNodeHTTPClient, nil
  180. }
  181. if mode == "mtls" {
  182. build := func() (idleClosingRoundTripper, error) {
  183. tlsCfg, err := tlsConfigForNode(n)
  184. if err != nil {
  185. return nil, err
  186. }
  187. return &http.Transport{
  188. MaxIdleConns: 64,
  189. MaxIdleConnsPerHost: 4,
  190. IdleConnTimeout: 60 * time.Second,
  191. DialContext: netsafe.SSRFGuardedDialContext,
  192. TLSClientConfig: tlsCfg,
  193. }, nil
  194. }
  195. transport, err := newCredentialRotatingTransport(build)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return &http.Client{Transport: transport}, nil
  200. }
  201. tlsCfg, err := tlsConfigForNode(n)
  202. if err != nil {
  203. return nil, err
  204. }
  205. return &http.Client{
  206. Transport: &http.Transport{
  207. MaxIdleConns: 64,
  208. MaxIdleConnsPerHost: 4,
  209. IdleConnTimeout: 60 * time.Second,
  210. DialContext: netsafe.SSRFGuardedDialContext,
  211. TLSClientConfig: tlsCfg,
  212. },
  213. }, nil
  214. }
  215. func tlsConfigForNode(n *model.Node) (*tls.Config, error) {
  216. if n.TlsVerifyMode == "mtls" {
  217. // Present the master client cert; verify the node's server cert against
  218. // the system roots (no InsecureSkipVerify). mtls authenticates the
  219. // caller — it does not change how the node's server identity is checked.
  220. cert, err := getMasterClientCert()
  221. if err != nil {
  222. return nil, err
  223. }
  224. return &tls.Config{
  225. Certificates: []tls.Certificate{cert},
  226. MinVersion: tls.VersionTLS12,
  227. }, nil
  228. }
  229. tlsCfg := &tls.Config{InsecureSkipVerify: true} // lgtm[go/disabled-certificate-check]
  230. if n.TlsVerifyMode == "pin" {
  231. want, err := DecodeCertPin(n.PinnedCertSha256)
  232. if err != nil {
  233. return nil, err
  234. }
  235. tlsCfg.VerifyConnection = func(cs tls.ConnectionState) error {
  236. if len(cs.PeerCertificates) == 0 {
  237. return common.NewError("node presented no certificate")
  238. }
  239. sum := sha256.Sum256(cs.PeerCertificates[0].Raw)
  240. if subtle.ConstantTimeCompare(sum[:], want) != 1 {
  241. return common.NewError("node certificate does not match pinned SHA-256")
  242. }
  243. return nil
  244. }
  245. }
  246. return tlsCfg, nil
  247. }
  248. // DecodeCertPin decodes a SHA-256 cert pin given as base64 (Xray's
  249. // pinnedPeerCertSha256 form) or hex with optional colons into 32 raw bytes.
  250. func DecodeCertPin(s string) ([]byte, error) {
  251. s = strings.TrimSpace(s)
  252. if s == "" {
  253. return nil, common.NewError("certificate pin is empty")
  254. }
  255. if b, err := hex.DecodeString(strings.ReplaceAll(s, ":", "")); err == nil && len(b) == sha256.Size {
  256. return b, nil
  257. }
  258. for _, enc := range []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding} {
  259. if b, err := enc.DecodeString(s); err == nil && len(b) == sha256.Size {
  260. return b, nil
  261. }
  262. }
  263. return nil, common.NewError("certificate pin must be a SHA-256 hash (base64 or hex)")
  264. }