setting_mtls.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package service
  2. import (
  3. "crypto/x509"
  4. "github.com/mhsanaei/3x-ui/v3/internal/util/common"
  5. "github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
  6. )
  7. const (
  8. settingNodeMtlsCaCert = "nodeMtlsCaCertPem"
  9. settingNodeMtlsCaKey = "nodeMtlsCaKeyPem"
  10. settingNodeMtlsClientCert = "nodeMtlsClientCertPem"
  11. settingNodeMtlsClientKey = "nodeMtlsClientKeyPem"
  12. settingNodeMtlsClientCA = "nodeMtlsClientCAPem"
  13. )
  14. // EnsureNodeMtlsCA returns this panel's node-auth CA, minting and persisting it
  15. // on first use and reusing the stored pair thereafter. The CA private key never
  16. // leaves the panel.
  17. func (s *SettingService) EnsureNodeMtlsCA() (crypto.CertKeyPEM, error) {
  18. certPem, err := s.getString(settingNodeMtlsCaCert)
  19. if err != nil {
  20. return crypto.CertKeyPEM{}, err
  21. }
  22. keyPem, err := s.getString(settingNodeMtlsCaKey)
  23. if err != nil {
  24. return crypto.CertKeyPEM{}, err
  25. }
  26. if certPem != "" && keyPem != "" {
  27. return crypto.CertKeyPEM{CertPEM: []byte(certPem), KeyPEM: []byte(keyPem)}, nil
  28. }
  29. // Fail closed on a half-present pair: regenerating here would silently rotate
  30. // the CA and break trust on nodes that already hold the old cert. Only mint
  31. // when neither half exists (first use).
  32. if certPem != "" || keyPem != "" {
  33. return crypto.CertKeyPEM{}, common.NewError("node mTLS CA is incomplete: one of cert/key is missing; refusing to regenerate")
  34. }
  35. ca, err := crypto.GenerateNodeCA("3x-ui node mTLS CA")
  36. if err != nil {
  37. return crypto.CertKeyPEM{}, err
  38. }
  39. if err := s.saveSetting(settingNodeMtlsCaCert, string(ca.CertPEM)); err != nil {
  40. return crypto.CertKeyPEM{}, err
  41. }
  42. if err := s.saveSetting(settingNodeMtlsCaKey, string(ca.KeyPEM)); err != nil {
  43. return crypto.CertKeyPEM{}, err
  44. }
  45. return ca, nil
  46. }
  47. // EnsureMasterClientCert returns the client certificate this panel presents when
  48. // calling its nodes over mTLS, issuing it from the node CA on first use and
  49. // reusing the stored pair thereafter.
  50. func (s *SettingService) EnsureMasterClientCert() (crypto.CertKeyPEM, error) {
  51. certPem, err := s.getString(settingNodeMtlsClientCert)
  52. if err != nil {
  53. return crypto.CertKeyPEM{}, err
  54. }
  55. keyPem, err := s.getString(settingNodeMtlsClientKey)
  56. if err != nil {
  57. return crypto.CertKeyPEM{}, err
  58. }
  59. if certPem != "" && keyPem != "" {
  60. return crypto.CertKeyPEM{CertPEM: []byte(certPem), KeyPEM: []byte(keyPem)}, nil
  61. }
  62. // Half a stored pair signals corrupted settings; reissuing would rotate the
  63. // master client credential (and indirectly the CA). Only mint on first use.
  64. if certPem != "" || keyPem != "" {
  65. return crypto.CertKeyPEM{}, common.NewError("master client cert is incomplete: one of cert/key is missing; refusing to reissue")
  66. }
  67. ca, err := s.EnsureNodeMtlsCA()
  68. if err != nil {
  69. return crypto.CertKeyPEM{}, err
  70. }
  71. client, err := crypto.IssueClientCert(ca, "3x-ui master")
  72. if err != nil {
  73. return crypto.CertKeyPEM{}, err
  74. }
  75. if err := s.saveSetting(settingNodeMtlsClientCert, string(client.CertPEM)); err != nil {
  76. return crypto.CertKeyPEM{}, err
  77. }
  78. if err := s.saveSetting(settingNodeMtlsClientKey, string(client.KeyPEM)); err != nil {
  79. return crypto.CertKeyPEM{}, err
  80. }
  81. return client, nil
  82. }
  83. // NodeMtlsClientCAPool builds the trust pool used as the panel listener's
  84. // ClientCAs for incoming node-API client certificates. It returns (nil, nil)
  85. // when no trust CA is configured, so mTLS stays off and the listener behaves
  86. // exactly as before.
  87. func (s *SettingService) NodeMtlsClientCAPool() (*x509.CertPool, error) {
  88. caPem, err := s.getString(settingNodeMtlsClientCA)
  89. if err != nil {
  90. return nil, err
  91. }
  92. if caPem == "" {
  93. return nil, nil
  94. }
  95. pool := x509.NewCertPool()
  96. if !pool.AppendCertsFromPEM([]byte(caPem)) {
  97. return nil, common.NewError("nodeMtlsClientCAPem is not a valid certificate")
  98. }
  99. return pool, nil
  100. }