info_api_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. //go:build e2e_testing
  2. package e2e
  3. import (
  4. "context"
  5. "encoding/json"
  6. "net/http"
  7. "net/netip"
  8. "path/filepath"
  9. "runtime"
  10. "testing"
  11. "time"
  12. "github.com/slackhq/nebula/cert"
  13. "github.com/slackhq/nebula/cert_test"
  14. "github.com/slackhq/nebula/e2e/router"
  15. "github.com/slackhq/nebula/localapi"
  16. "github.com/slackhq/nebula/test"
  17. "github.com/stretchr/testify/assert"
  18. "github.com/stretchr/testify/require"
  19. )
  20. // TestIdentityOverTheInfoAPI is the info API's reason to exist, end to end: a program on
  21. // this host has a connection arriving over the overlay and asks who is behind its
  22. // source address, and the answer is the identity in the certificate the tunnel was
  23. // established with.
  24. func TestIdentityOverTheInfoAPI(t *testing.T) {
  25. if runtime.GOOS == "windows" {
  26. t.Skip("the test's HTTP helpers dial unix sockets; on windows the info API is a named pipe")
  27. }
  28. ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
  29. myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.1/24", nil)
  30. theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "them", "10.128.0.2/24", nil)
  31. myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
  32. r := router.NewR(t, myControl, theirControl)
  33. defer r.RenderFlow()
  34. myControl.Start()
  35. theirControl.Start()
  36. t.Cleanup(func() {
  37. myControl.Stop()
  38. theirControl.Stop()
  39. })
  40. // Before the tunnel is up their address is unknown to us.
  41. path := infoSocket(t)
  42. info := localapi.NewInfoServer(NewTestLogger(), myControl.Commands())
  43. require.NoError(t, info.Rebind(context.Background(), path, localapi.Access{Mode: 0600}))
  44. t.Cleanup(info.Stop)
  45. status, body := test.HTTPGet(t, path, "/v1/identity?address="+theirVpnIpNet[0].Addr().String())
  46. assert.Equal(t, http.StatusNotFound, status)
  47. assert.JSONEq(t, `{"error":"No active tunnel for vpn addr: 10.128.0.2"}`, body)
  48. // Only we know how to reach them, so the first packet has to be ours; once the tunnel
  49. // is up both directions work and assertTunnel can check it.
  50. myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me")))
  51. p := r.RouteForAllUntilTxTun(theirControl)
  52. assertUdpPacket(t, []byte("Hi from me"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)
  53. assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
  54. type identity struct {
  55. Name string `json:"name"`
  56. VpnAddrs []netip.Addr `json:"vpnAddrs"`
  57. Networks []netip.Prefix `json:"networks"`
  58. Groups []string `json:"groups"`
  59. Issuer string `json:"issuer"`
  60. Fingerprint string `json:"fingerprint"`
  61. }
  62. // A connection's RemoteAddr, port and all, resolves to the peer.
  63. status, body = test.HTTPGet(t, path, "/v1/identity?address="+netip.AddrPortFrom(theirVpnIpNet[0].Addr(), 51234).String())
  64. require.Equal(t, http.StatusOK, status, body)
  65. var them identity
  66. require.NoError(t, json.Unmarshal([]byte(body), &them))
  67. assert.Equal(t, "them", them.Name)
  68. assert.Equal(t, []netip.Addr{theirVpnIpNet[0].Addr()}, them.VpnAddrs)
  69. assert.Equal(t, theirVpnIpNet, them.Networks)
  70. assert.Equal(t, []string{}, them.Groups)
  71. caFP, err := ca.Fingerprint()
  72. require.NoError(t, err)
  73. assert.Equal(t, caFP, them.Issuer)
  74. // No address is a caller that forgot the parameter, and is refused rather than handed
  75. // this node's identity to authorize against; whoami is the command for that.
  76. status, body = test.HTTPGet(t, path, "/v1/identity")
  77. assert.Equal(t, http.StatusBadRequest, status, body)
  78. assert.Contains(t, body, "whoami")
  79. status, body = test.HTTPGet(t, path, "/v1/whoami")
  80. require.Equal(t, http.StatusOK, status, body)
  81. var me identity
  82. require.NoError(t, json.Unmarshal([]byte(body), &me))
  83. assert.Equal(t, "me", me.Name)
  84. assert.Equal(t, []netip.Addr{myVpnIpNet[0].Addr()}, me.VpnAddrs)
  85. // An empty address is a caller's bug, and is told so rather than handed our identity.
  86. status, body = test.HTTPGet(t, path, "/v1/identity?address=")
  87. assert.Equal(t, http.StatusBadRequest, status)
  88. assert.JSONEq(t, `{"error":"The provided vpn addr could not be parsed: "}`, body)
  89. // The certificate itself, for a program that verifies it against its own CAs: the one
  90. // nebula verified when the tunnel came up, as PEM, by the address a connection carries.
  91. status, body = test.HTTPGet(t, path, "/v1/print-cert?address="+netip.AddrPortFrom(theirVpnIpNet[0].Addr(), 51234).String()+"&raw=true")
  92. require.Equal(t, http.StatusOK, status, body)
  93. var raw struct {
  94. PEM string `json:"pem"`
  95. }
  96. require.NoError(t, json.Unmarshal([]byte(body), &raw))
  97. theirs, _, err := cert.UnmarshalCertificateFromPEM([]byte(raw.PEM))
  98. require.NoError(t, err)
  99. assert.Equal(t, "them", theirs.Name())
  100. fp, err := theirs.Fingerprint()
  101. require.NoError(t, err)
  102. assert.Equal(t, them.Fingerprint, fp, "the same certificate identity reports")
  103. // Without raw it is the certificate's fields; without an address, our own.
  104. status, body = test.HTTPGet(t, path, "/v1/print-cert")
  105. require.Equal(t, http.StatusOK, status, body)
  106. assert.Contains(t, body, `"name":"me"`)
  107. status, body = test.HTTPGet(t, path, "/v1/print-cert?address=10.128.0.9")
  108. assert.Equal(t, http.StatusNotFound, status)
  109. assert.JSONEq(t, `{"error":"Could not find tunnel for vpn addr: 10.128.0.9"}`, body)
  110. // Nothing else nebula serves is reachable here.
  111. status, body = test.HTTPGet(t, path, "/v1/list-hostmap")
  112. assert.Equal(t, http.StatusForbidden, status)
  113. assert.Contains(t, body, "identity lookups only")
  114. r.RenderHostmaps("Final hostmaps", myControl, theirControl)
  115. }
  116. // infoSocket returns a short socket path in a private directory, one level below it
  117. // so that listen has a directory of its own to create.
  118. func infoSocket(t *testing.T) string {
  119. t.Helper()
  120. return filepath.Join(test.ShortTempDir(t), "n", "info.sock")
  121. }