setting_mtls_test.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package service
  2. import (
  3. "bytes"
  4. "crypto/x509"
  5. "encoding/pem"
  6. "path/filepath"
  7. "testing"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database"
  9. )
  10. func setupSettingMtlsDB(t *testing.T) *SettingService {
  11. t.Helper()
  12. dbDir := t.TempDir()
  13. t.Setenv("XUI_DB_FOLDER", dbDir)
  14. if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
  15. t.Fatalf("InitDB: %v", err)
  16. }
  17. t.Cleanup(func() { _ = database.CloseDB() })
  18. return &SettingService{}
  19. }
  20. func TestEnsureNodeMtlsCA_Idempotent(t *testing.T) {
  21. s := setupSettingMtlsDB(t)
  22. first, err := s.EnsureNodeMtlsCA()
  23. if err != nil {
  24. t.Fatalf("EnsureNodeMtlsCA (first): %v", err)
  25. }
  26. block, _ := pem.Decode(first.CertPEM)
  27. if block == nil {
  28. t.Fatal("CA cert is not valid PEM")
  29. }
  30. caCert, err := x509.ParseCertificate(block.Bytes)
  31. if err != nil {
  32. t.Fatalf("parse CA cert: %v", err)
  33. }
  34. if !caCert.IsCA {
  35. t.Fatal("stored CA must have IsCA=true")
  36. }
  37. second, err := s.EnsureNodeMtlsCA()
  38. if err != nil {
  39. t.Fatalf("EnsureNodeMtlsCA (second): %v", err)
  40. }
  41. if !bytes.Equal(first.CertPEM, second.CertPEM) || !bytes.Equal(first.KeyPEM, second.KeyPEM) {
  42. t.Fatal("EnsureNodeMtlsCA must be idempotent: second call returned different PEMs")
  43. }
  44. }
  45. func TestEnsureMasterClientCert_VerifiesAndIdempotent(t *testing.T) {
  46. s := setupSettingMtlsDB(t)
  47. ca, err := s.EnsureNodeMtlsCA()
  48. if err != nil {
  49. t.Fatalf("EnsureNodeMtlsCA: %v", err)
  50. }
  51. client, err := s.EnsureMasterClientCert()
  52. if err != nil {
  53. t.Fatalf("EnsureMasterClientCert: %v", err)
  54. }
  55. cblock, _ := pem.Decode(client.CertPEM)
  56. if cblock == nil {
  57. t.Fatal("client cert is not valid PEM")
  58. }
  59. leaf, err := x509.ParseCertificate(cblock.Bytes)
  60. if err != nil {
  61. t.Fatalf("parse client cert: %v", err)
  62. }
  63. caBlock, _ := pem.Decode(ca.CertPEM)
  64. roots := x509.NewCertPool()
  65. roots.AddCert(mustParse(t, caBlock.Bytes))
  66. if _, err := leaf.Verify(x509.VerifyOptions{
  67. Roots: roots,
  68. KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
  69. }); err != nil {
  70. t.Fatalf("master client cert must verify against the node CA for client auth: %v", err)
  71. }
  72. again, err := s.EnsureMasterClientCert()
  73. if err != nil {
  74. t.Fatalf("EnsureMasterClientCert (second): %v", err)
  75. }
  76. if !bytes.Equal(client.CertPEM, again.CertPEM) || !bytes.Equal(client.KeyPEM, again.KeyPEM) {
  77. t.Fatal("EnsureMasterClientCert must be idempotent")
  78. }
  79. }
  80. func TestNodeMtlsClientCAPool(t *testing.T) {
  81. s := setupSettingMtlsDB(t)
  82. pool, err := s.NodeMtlsClientCAPool()
  83. if err != nil {
  84. t.Fatalf("NodeMtlsClientCAPool (unset): %v", err)
  85. }
  86. if pool != nil {
  87. t.Fatal("with no trust CA configured, the pool must be nil (mTLS off; listener unchanged)")
  88. }
  89. ca, err := s.EnsureNodeMtlsCA()
  90. if err != nil {
  91. t.Fatalf("EnsureNodeMtlsCA: %v", err)
  92. }
  93. if err := s.setString("nodeMtlsClientCAPem", string(ca.CertPEM)); err != nil {
  94. t.Fatalf("set trust CA: %v", err)
  95. }
  96. pool, err = s.NodeMtlsClientCAPool()
  97. if err != nil {
  98. t.Fatalf("NodeMtlsClientCAPool (set): %v", err)
  99. }
  100. if pool == nil {
  101. t.Fatal("with a trust CA configured, the pool must be non-nil")
  102. }
  103. }
  104. func mustParse(t *testing.T, der []byte) *x509.Certificate {
  105. t.Helper()
  106. c, err := x509.ParseCertificate(der)
  107. if err != nil {
  108. t.Fatalf("ParseCertificate: %v", err)
  109. }
  110. return c
  111. }