pki_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package nebula
  2. import (
  3. "crypto/fips140"
  4. "fmt"
  5. "net/netip"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/slackhq/nebula/cert"
  10. "github.com/slackhq/nebula/cert_test"
  11. "github.com/slackhq/nebula/config"
  12. "github.com/slackhq/nebula/test"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. func TestNewCipherSuite(t *testing.T) {
  17. tests := []struct {
  18. name string
  19. curve cert.Curve
  20. cipher string
  21. fips140Enforced bool
  22. wantErr string
  23. // wantName is the full expected CipherSuite name (<DH>_<Cipher>_<Hash>),
  24. // only checked when wantErr is empty. Asserting the whole name makes both
  25. // the curve and cipher selection load-bearing.
  26. wantName string
  27. }{
  28. {
  29. name: "curve25519 aesgcm, not enforced",
  30. curve: cert.Curve_CURVE25519,
  31. cipher: "aesgcm",
  32. wantName: "25519_AESGCM_SHA256",
  33. },
  34. {
  35. name: "curve25519 chachapoly, not enforced",
  36. curve: cert.Curve_CURVE25519,
  37. cipher: "chachapoly",
  38. wantName: "25519_ChaChaPoly_SHA256",
  39. },
  40. {
  41. name: "p256 aesgcm, not enforced",
  42. curve: cert.Curve_P256,
  43. cipher: "aesgcm",
  44. wantName: "P256_AESGCM_SHA256",
  45. },
  46. {
  47. name: "p256 aesgcm, enforced is allowed",
  48. curve: cert.Curve_P256,
  49. cipher: "aesgcm",
  50. fips140Enforced: true,
  51. wantName: "P256_AESGCM_SHA256",
  52. },
  53. {
  54. name: "curve25519 rejected when enforced",
  55. curve: cert.Curve_CURVE25519,
  56. cipher: "aesgcm",
  57. fips140Enforced: true,
  58. wantErr: "pki: use of Curve25519 is not allowed in FIPS 140-only mode",
  59. },
  60. {
  61. name: "chachapoly rejected when enforced",
  62. curve: cert.Curve_P256,
  63. cipher: "chachapoly",
  64. fips140Enforced: true,
  65. wantErr: "pki: use of ChaChaPoly is not allowed in FIPS 140-only mode",
  66. },
  67. {
  68. // Curve is checked before cipher, so a Curve25519+ChaChaPoly
  69. // request reports the Curve25519 rejection.
  70. name: "curve25519 chachapoly rejected on curve when enforced",
  71. curve: cert.Curve_CURVE25519,
  72. cipher: "chachapoly",
  73. fips140Enforced: true,
  74. wantErr: "pki: use of Curve25519 is not allowed in FIPS 140-only mode",
  75. },
  76. }
  77. for _, tt := range tests {
  78. t.Run(tt.name, func(t *testing.T) {
  79. cs, err := newCipherSuite(tt.curve, false, tt.cipher, tt.fips140Enforced)
  80. if tt.wantErr != "" {
  81. require.EqualError(t, err, tt.wantErr)
  82. assert.Nil(t, cs)
  83. return
  84. }
  85. require.NoError(t, err)
  86. require.NotNil(t, cs)
  87. assert.Equal(t, tt.wantName, string(cs.Name()))
  88. })
  89. }
  90. }
  91. func TestNewCipherSuiteUnsupportedCurve(t *testing.T) {
  92. cs, err := newCipherSuite(cert.Curve(99), false, "aesgcm", false)
  93. require.Error(t, err)
  94. assert.True(t, strings.HasPrefix(err.Error(), "unsupported curve:"), "got: %v", err)
  95. assert.Nil(t, cs)
  96. }
  97. func TestPKIReloadRestartRequired(t *testing.T) {
  98. ca, _, caKey, caPEM := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  99. pkiConfig := func(cipher, network string) string {
  100. _, _, keyPEM, certPEM := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca, caKey, "host", time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix(network)}, nil, nil)
  101. return fmt.Sprintf("cipher: %s\npki:\n ca: %q\n cert: %q\n key: %q\n", cipher, caPEM, certPEM, keyPEM)
  102. }
  103. c := config.NewC(test.NewLogger())
  104. require.NoError(t, c.LoadString(pkiConfig("aes", "10.1.0.2/24")))
  105. p, err := NewPKIFromConfig(test.NewLogger(), c)
  106. require.NoError(t, err)
  107. // A cert that changes our networks is refused and needs a restart, it is not a failure
  108. res := c.ReloadConfigString(pkiConfig("aes", "10.1.0.3/24"))
  109. require.NoError(t, res.Err)
  110. assert.True(t, res.RestartRequired)
  111. assert.False(t, res.Failed)
  112. assert.Equal(t, []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")}, p.getCertState().myVpnNetworks)
  113. // The cipher only applies at startup
  114. c2 := config.NewC(test.NewLogger())
  115. require.NoError(t, c2.LoadString(pkiConfig("aes", "10.1.0.2/24")))
  116. p, err = NewPKIFromConfig(test.NewLogger(), c2)
  117. require.NoError(t, err)
  118. res = c2.ReloadConfigString(pkiConfig("chachapoly", "10.1.0.2/24"))
  119. require.NoError(t, res.Err)
  120. assert.True(t, res.RestartRequired)
  121. assert.Equal(t, "aes", p.getCertState().cipher)
  122. // A cipher we can't run would fail the restart, so it fails the reload instead of asking for one
  123. c4 := config.NewC(test.NewLogger())
  124. require.NoError(t, c4.LoadString(pkiConfig("aes", "10.1.0.2/24")))
  125. _, err = NewPKIFromConfig(test.NewLogger(), c4)
  126. require.NoError(t, err)
  127. for range 2 {
  128. res = c4.ReloadConfigString(pkiConfig("nope", "10.1.0.2/24"))
  129. require.NoError(t, res.Err)
  130. assert.True(t, res.Failed, "a bad cipher that didn't change since the last reload is still bad")
  131. assert.False(t, res.RestartRequired)
  132. }
  133. // Fixing it back to the running cipher needs nothing
  134. res = c4.ReloadConfigString(pkiConfig("aes", "10.1.0.2/24"))
  135. require.NoError(t, res.Err)
  136. assert.False(t, res.Failed)
  137. assert.False(t, res.RestartRequired)
  138. // A cert that keeps our networks applies without a restart
  139. c3 := config.NewC(test.NewLogger())
  140. require.NoError(t, c3.LoadString(pkiConfig("aes", "10.1.0.2/24")))
  141. _, err = NewPKIFromConfig(test.NewLogger(), c3)
  142. require.NoError(t, err)
  143. res = c3.ReloadConfigString(pkiConfig("aes", "10.1.0.2/24"))
  144. require.NoError(t, res.Err)
  145. assert.False(t, res.RestartRequired)
  146. // A cert that can't be read is a failure, and a restart wouldn't help
  147. res = c3.ReloadConfigString(strings.Replace(pkiConfig("aes", "10.1.0.2/24"), "cert: \"", "cert: \"garbage", 1))
  148. require.NoError(t, res.Err)
  149. assert.True(t, res.Failed)
  150. assert.False(t, res.RestartRequired)
  151. }
  152. // A cert for a version we weren't running is accepted without comparing networks, so switching to one with different
  153. // networks needs a restart. One with the same networks doesn't
  154. func TestPKIReloadNewVersionWithOtherNetworks(t *testing.T) {
  155. ca1, _, ca1Key, ca1PEM := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  156. ca2, _, ca2Key, ca2PEM := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  157. caPEM := string(ca1PEM) + string(ca2PEM)
  158. pkiConfig := func(v cert.Version, network string) string {
  159. ca, caKey := ca1, ca1Key
  160. if v == cert.Version2 {
  161. ca, caKey = ca2, ca2Key
  162. }
  163. _, _, keyPEM, certPEM := cert_test.NewTestCert(v, cert.Curve_CURVE25519, ca, caKey, "host", time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix(network)}, nil, nil)
  164. return fmt.Sprintf("pki:\n ca: %q\n cert: %q\n key: %q\n", caPEM, certPEM, keyPEM)
  165. }
  166. for _, tc := range []struct {
  167. network string
  168. restart bool
  169. }{
  170. {"10.9.0.7/24", true},
  171. {"10.1.0.2/24", false},
  172. } {
  173. c := config.NewC(test.NewLogger())
  174. require.NoError(t, c.LoadString(pkiConfig(cert.Version1, "10.1.0.2/24")))
  175. p, err := NewPKIFromConfig(test.NewLogger(), c)
  176. require.NoError(t, err)
  177. res := c.ReloadConfigString(pkiConfig(cert.Version2, tc.network))
  178. require.NoError(t, res.Err)
  179. assert.False(t, res.Failed, tc.network)
  180. assert.Equal(t, tc.restart, res.RestartRequired, tc.network)
  181. assert.Equal(t, []netip.Prefix{netip.MustParsePrefix(tc.network)}, p.getCertState().myVpnNetworks, "accepted either way, as before")
  182. }
  183. }
  184. // FIPS 140-only mode refuses chachapoly at startup, so reloading to it fails rather than asks for a restart
  185. func TestPKIReloadChachapolyInFIPSOnlyFails(t *testing.T) {
  186. fipsOnly = func() bool { return true }
  187. t.Cleanup(func() { fipsOnly = fips140.Enforced })
  188. ca, _, caKey, caPEM := cert_test.NewTestCaCert(cert.Version2, cert.Curve_P256, time.Time{}, time.Time{}, nil, nil, nil)
  189. _, _, keyPEM, certPEM := cert_test.NewTestCert(cert.Version2, cert.Curve_P256, ca, caKey, "host", time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix("10.1.0.2/24")}, nil, nil)
  190. pkiConfig := func(cipher string) string {
  191. return fmt.Sprintf("cipher: %s\npki:\n ca: %q\n cert: %q\n key: %q\n", cipher, caPEM, certPEM, keyPEM)
  192. }
  193. c := config.NewC(test.NewLogger())
  194. require.NoError(t, c.LoadString(pkiConfig("aes")))
  195. _, err := NewPKIFromConfig(test.NewLogger(), c)
  196. require.NoError(t, err)
  197. res := c.ReloadConfigString(pkiConfig("chachapoly"))
  198. require.NoError(t, res.Err)
  199. assert.True(t, res.Failed)
  200. assert.False(t, res.RestartRequired)
  201. }