info_api_test.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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 info API is not served on windows")
  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. }
  61. // A connection's RemoteAddr, port and all, resolves to the peer.
  62. status, body = test.HTTPGet(t, path, "/v1/identity?address="+netip.AddrPortFrom(theirVpnIpNet[0].Addr(), 51234).String())
  63. require.Equal(t, http.StatusOK, status, body)
  64. var them identity
  65. require.NoError(t, json.Unmarshal([]byte(body), &them))
  66. assert.Equal(t, "them", them.Name)
  67. assert.Equal(t, []netip.Addr{theirVpnIpNet[0].Addr()}, them.VpnAddrs)
  68. assert.Equal(t, theirVpnIpNet, them.Networks)
  69. assert.Equal(t, []string{}, them.Groups)
  70. caFP, err := ca.Fingerprint()
  71. require.NoError(t, err)
  72. assert.Equal(t, caFP, them.Issuer)
  73. // No address is a caller that forgot the parameter, and is refused rather than handed
  74. // this node's identity to authorize against; whoami is the command for that.
  75. status, body = test.HTTPGet(t, path, "/v1/identity")
  76. assert.Equal(t, http.StatusBadRequest, status, body)
  77. assert.Contains(t, body, "whoami")
  78. status, body = test.HTTPGet(t, path, "/v1/whoami")
  79. require.Equal(t, http.StatusOK, status, body)
  80. var me identity
  81. require.NoError(t, json.Unmarshal([]byte(body), &me))
  82. assert.Equal(t, "me", me.Name)
  83. assert.Equal(t, []netip.Addr{myVpnIpNet[0].Addr()}, me.VpnAddrs)
  84. // An empty address is a caller's bug, and is told so rather than handed our identity.
  85. status, body = test.HTTPGet(t, path, "/v1/identity?address=")
  86. assert.Equal(t, http.StatusBadRequest, status)
  87. assert.JSONEq(t, `{"error":"The provided vpn addr could not be parsed: "}`, body)
  88. // Nothing else nebula serves is reachable here.
  89. status, body = test.HTTPGet(t, path, "/v1/list-hostmap")
  90. assert.Equal(t, http.StatusForbidden, status)
  91. assert.Contains(t, body, "identity lookups only")
  92. r.RenderHostmaps("Final hostmaps", myControl, theirControl)
  93. }
  94. // infoSocket returns a short socket path in a private directory, one level below it
  95. // so that listen has a directory of its own to create.
  96. func infoSocket(t *testing.T) string {
  97. t.Helper()
  98. return filepath.Join(test.ShortTempDir(t), "n", "info.sock")
  99. }