identity_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package nebula
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "net/netip"
  6. "testing"
  7. "time"
  8. "github.com/slackhq/nebula/cert"
  9. "github.com/slackhq/nebula/cert_test"
  10. "github.com/slackhq/nebula/commands"
  11. "github.com/slackhq/nebula/test"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. // identityFixture is an Interface with our own certificate loaded and one peer behind an
  16. // established tunnel, which is all identity reads.
  17. func identityFixture(t *testing.T) (*Interface, cert.Certificate) {
  18. t.Helper()
  19. l := test.NewLogger()
  20. me := netip.MustParseAddr("10.0.0.1")
  21. peer := netip.MustParsePrefix("10.0.0.2/24")
  22. ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  23. peerCert, _, _, _ := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca, caKey, "peer", time.Time{}, time.Time{},
  24. []netip.Prefix{peer}, []netip.Prefix{netip.MustParsePrefix("192.168.1.0/24")}, []string{"web", "db"})
  25. hm := newHostMap(l)
  26. hm.Hosts[peer.Addr()] = &HostInfo{
  27. vpnAddrs: []netip.Addr{peer.Addr()},
  28. ConnectionState: &ConnectionState{peerCert: &cert.CachedCertificate{Certificate: peerCert}},
  29. }
  30. // A tunnel still handshaking has no certificate yet, and must not be reported.
  31. hm.Hosts[netip.MustParseAddr("10.0.0.3")] = &HostInfo{
  32. vpnAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.3")},
  33. ConnectionState: &ConnectionState{},
  34. }
  35. return &Interface{pki: newTestPKI(t, "me", []netip.Addr{me}), hostMap: hm}, peerCert
  36. }
  37. // identity runs the command with -address given as address.
  38. func identity(t *testing.T, f *Interface, address string, operands ...string) commands.Result {
  39. t.Helper()
  40. fs := &identityFlags{}
  41. require.NoError(t, fs.setAddress(address))
  42. r, err := runIdentity(f, fs, operands)
  43. require.NoError(t, err)
  44. return r
  45. }
  46. // whoami runs the whoami command.
  47. func whoami(t *testing.T, f *Interface) commands.Result {
  48. t.Helper()
  49. r, err := runWhoami(f)
  50. require.NoError(t, err)
  51. return r
  52. }
  53. func TestWhoamiReportsThisNode(t *testing.T) {
  54. f, _ := identityFixture(t)
  55. r := whoami(t, f)
  56. require.True(t, r.OK)
  57. id, ok := r.Value.(hostIdentity)
  58. require.True(t, ok, "got %T", r.Value)
  59. assert.Equal(t, "me", id.Name)
  60. assert.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.1")}, id.VpnAddrs)
  61. assert.Equal(t, 2, id.CertVersion)
  62. assert.NotEmpty(t, id.Fingerprint)
  63. assert.NotEmpty(t, id.Issuer)
  64. // Our own address answers the same way: the hostmap never holds ourselves.
  65. self := identity(t, f, "10.0.0.1")
  66. assert.Equal(t, id, self.Value)
  67. }
  68. func TestIdentityReportsAPeerFromItsCertificate(t *testing.T) {
  69. f, peerCert := identityFixture(t)
  70. for _, addr := range []string{
  71. "10.0.0.2",
  72. "10.0.0.2:54321", // a connection's RemoteAddr, as a server has it
  73. "::ffff:10.0.0.2", // the same peer seen through a dual stack listener
  74. "[::ffff:10.0.0.2]:8", // and with a port
  75. } {
  76. r := identity(t, f, addr)
  77. require.True(t, r.OK, "%s: %s", addr, r.Reason)
  78. id := r.Value.(hostIdentity)
  79. assert.Equal(t, "peer", id.Name, addr)
  80. assert.Equal(t, []netip.Addr{netip.MustParseAddr("10.0.0.2")}, id.VpnAddrs, addr)
  81. assert.Equal(t, []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, id.Networks, addr)
  82. assert.Equal(t, []netip.Prefix{netip.MustParsePrefix("192.168.1.0/24")}, id.UnsafeNetworks, addr)
  83. assert.Equal(t, []string{"web", "db"}, id.Groups, addr)
  84. assert.Equal(t, peerCert.Issuer(), id.Issuer, addr)
  85. assert.Equal(t, peerCert.NotAfter(), id.NotAfter, addr)
  86. }
  87. }
  88. // TestIdentityRefusals: the ways a lookup fails are told apart, because a program
  89. // deciding whether to let a connection in treats them differently.
  90. func TestIdentityRefusals(t *testing.T) {
  91. f, _ := identityFixture(t)
  92. unknown := identity(t, f, "10.0.0.9")
  93. assert.False(t, unknown.OK)
  94. assert.True(t, unknown.NotFound, "no tunnel is a not found")
  95. assert.Equal(t, "No active tunnel for vpn addr: 10.0.0.9", unknown.Reason)
  96. pending := identity(t, f, "10.0.0.3:1")
  97. assert.True(t, pending.NotFound, "a tunnel without a certificate yet is not active")
  98. assert.Equal(t, "No active tunnel for vpn addr: 10.0.0.3", pending.Reason, "the reason names the address, not the port")
  99. garbage := identity(t, f, "not-an-address")
  100. assert.False(t, garbage.OK)
  101. assert.False(t, garbage.NotFound, "an address that does not parse is the caller's mistake")
  102. assert.Equal(t, "The provided vpn addr could not be parsed: not-an-address", garbage.Reason)
  103. // An address that was given and is empty is a caller that failed to fill it in, and
  104. // must not be answered with our own identity.
  105. empty := identity(t, f, "")
  106. assert.False(t, empty.OK)
  107. assert.False(t, empty.NotFound)
  108. assert.Equal(t, "The provided vpn addr could not be parsed: ", empty.Reason)
  109. operand := identity(t, f, "", "10.0.0.2")
  110. assert.False(t, operand.OK)
  111. assert.Contains(t, operand.Reason, "identity -address 10.0.0.2")
  112. // No address at all is a caller that forgot the parameter, and is pointed at whoami
  113. // rather than handed this node's identity.
  114. omitted, err := runIdentity(f, &identityFlags{}, nil)
  115. require.NoError(t, err)
  116. assert.False(t, omitted.OK)
  117. assert.False(t, omitted.NotFound)
  118. assert.Contains(t, omitted.Reason, "-address")
  119. assert.Contains(t, omitted.Reason, "whoami")
  120. }
  121. // TestOwnCertificatePrefersV2: with both versions loaded, the v2 certificate is the one
  122. // myVpnAddrsTable is built from and may carry addresses the v1 does not, so it is the one
  123. // whoami and an identity lookup of our own address report, whichever version handshakes
  124. // are initiated with.
  125. func TestOwnCertificatePrefersV2(t *testing.T) {
  126. v4 := netip.MustParsePrefix("10.0.0.1/24")
  127. ca1, _, caKey1, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  128. v1, _, _, _ := cert_test.NewTestCert(cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "me", time.Time{}, time.Time{}, []netip.Prefix{v4}, nil, nil)
  129. ca2, _, caKey2, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil)
  130. v2, _, _, _ := cert_test.NewTestCert(cert.Version2, cert.Curve_CURVE25519, ca2, caKey2, "me", time.Time{}, time.Time{},
  131. []netip.Prefix{v4, netip.MustParsePrefix("fd00::1/64")}, nil, nil)
  132. assert.Nil(t, ownCertificate(nil))
  133. assert.Equal(t, v1, ownCertificate(&CertState{v1Cert: v1, initiatingVersion: cert.Version1}))
  134. assert.Equal(t, v2, ownCertificate(&CertState{v2Cert: v2, initiatingVersion: cert.Version2}))
  135. assert.Equal(t, v2, ownCertificate(&CertState{v1Cert: v1, v2Cert: v2, initiatingVersion: cert.Version1}),
  136. "the initiating version is v1, the reported certificate is still the v2")
  137. }
  138. // TestIdentityShapes pins the JSON a program reads and the text a person reads. The
  139. // field names are an API served to programs not built against nebula.
  140. func TestIdentityShapes(t *testing.T) {
  141. f, _ := identityFixture(t)
  142. r := identity(t, f, "10.0.0.2")
  143. b, err := json.Marshal(r.Payload())
  144. require.NoError(t, err)
  145. var got map[string]any
  146. require.NoError(t, json.Unmarshal(b, &got))
  147. var keys []string
  148. for k := range got {
  149. keys = append(keys, k)
  150. }
  151. assert.ElementsMatch(t, []string{
  152. "name", "vpnAddrs", "networks", "unsafeNetworks", "groups",
  153. "fingerprint", "issuer", "notBefore", "notAfter", "certVersion",
  154. }, keys)
  155. // Empty lists marshal as [], never null.
  156. self := whoami(t, f)
  157. b, err = json.Marshal(self.Payload())
  158. require.NoError(t, err)
  159. assert.Contains(t, string(b), `"groups":[]`)
  160. assert.Contains(t, string(b), `"unsafeNetworks":[]`)
  161. out := &bytes.Buffer{}
  162. require.NoError(t, commands.Render(&commands.Command{Name: commands.Identity, Text: commands.Typed(textIdentity)}, r, commands.FormatText, out))
  163. assert.Contains(t, out.String(), "name: peer\n")
  164. assert.Contains(t, out.String(), "vpn addrs: 10.0.0.2\n")
  165. assert.Contains(t, out.String(), "groups: web, db\n")
  166. assert.Contains(t, out.String(), "cert version: 2\n")
  167. }
  168. func TestParseVpnAddrWithOptionalPort(t *testing.T) {
  169. for in, want := range map[string]string{
  170. "10.0.0.2": "10.0.0.2",
  171. "10.0.0.2:443": "10.0.0.2",
  172. "fd00::1": "fd00::1",
  173. "[fd00::1]:443": "fd00::1",
  174. "::ffff:10.0.0.2": "10.0.0.2",
  175. "[::ffff:10.0.0.2]:1": "10.0.0.2",
  176. "fe80::1%eth0": "fe80::1",
  177. } {
  178. got, err := parseVpnAddrWithOptionalPort(in)
  179. require.NoError(t, err, in)
  180. assert.Equal(t, netip.MustParseAddr(want), got, in)
  181. }
  182. for _, in := range []string{"", "10.0.0.2:", ":443", "10.0.0", "example.com:80"} {
  183. _, err := parseVpnAddrWithOptionalPort(in)
  184. assert.Error(t, err, in)
  185. }
  186. }