info_api_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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/"+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, is the resource's path and resolves to the
  63. // peer.
  64. theirAddrPort := netip.AddrPortFrom(theirVpnIpNet[0].Addr(), 51234).String()
  65. status, body = test.HTTPGet(t, path, "/v1/identity/"+theirAddrPort)
  66. require.Equal(t, http.StatusOK, status, body)
  67. var them identity
  68. require.NoError(t, json.Unmarshal([]byte(body), &them))
  69. assert.Equal(t, "them", them.Name)
  70. assert.Equal(t, []netip.Addr{theirVpnIpNet[0].Addr()}, them.VpnAddrs)
  71. assert.Equal(t, theirVpnIpNet, them.Networks)
  72. assert.Equal(t, []string{}, them.Groups)
  73. caFP, err := ca.Fingerprint()
  74. require.NoError(t, err)
  75. assert.Equal(t, caFP, them.Issuer)
  76. assert.NotContains(t, body, `"pem"`, "the certificate is its own resource")
  77. // The certificate itself is the identity's pem sub-resource, for a program that
  78. // verifies it against its own CAs: the one nebula verified when the tunnel came up.
  79. status, body = test.HTTPGet(t, path, "/v1/identity/"+theirAddrPort+"/pem")
  80. require.Equal(t, http.StatusOK, status, body)
  81. var raw struct {
  82. PEM string `json:"pem"`
  83. }
  84. require.NoError(t, json.Unmarshal([]byte(body), &raw))
  85. theirs, _, err := cert.UnmarshalCertificateFromPEM([]byte(raw.PEM))
  86. require.NoError(t, err)
  87. assert.Equal(t, "them", theirs.Name())
  88. fp, err := theirs.Fingerprint()
  89. require.NoError(t, err)
  90. assert.Equal(t, them.Fingerprint, fp, "the same certificate the identity was read from")
  91. // The resource itself is this node, and so is its pem.
  92. status, body = test.HTTPGet(t, path, "/v1/identity")
  93. require.Equal(t, http.StatusOK, status, body)
  94. var me identity
  95. require.NoError(t, json.Unmarshal([]byte(body), &me))
  96. assert.Equal(t, "me", me.Name)
  97. assert.Equal(t, []netip.Addr{myVpnIpNet[0].Addr()}, me.VpnAddrs)
  98. status, body = test.HTTPGet(t, path, "/v1/identity/pem")
  99. require.Equal(t, http.StatusOK, status, body)
  100. require.NoError(t, json.Unmarshal([]byte(body), &raw))
  101. mine, _, err := cert.UnmarshalCertificateFromPEM([]byte(raw.PEM))
  102. require.NoError(t, err)
  103. assert.Equal(t, "me", mine.Name())
  104. // An empty address is a caller's bug: a program that built the path from an address
  105. // it failed to fill in gets no route, never this node's identity to authorize against.
  106. status, body = test.HTTPGet(t, path, "/v1/identity/")
  107. assert.Equal(t, http.StatusNotFound, status)
  108. assert.Contains(t, body, "no such path")
  109. status, body = test.HTTPGet(t, path, "/v1/identity/not-an-address")
  110. assert.Equal(t, http.StatusBadRequest, status)
  111. assert.JSONEq(t, `{"error":"The provided vpn addr could not be parsed: not-an-address"}`, body)
  112. // Nothing else nebula serves is reachable here, with or without an operand.
  113. status, body = test.HTTPGet(t, path, "/v1/list-hostmap")
  114. assert.Equal(t, http.StatusForbidden, status)
  115. assert.Contains(t, body, "identity lookups only")
  116. status, _ = test.HTTPGet(t, path, "/v1/print-tunnel/"+theirAddrPort)
  117. assert.Equal(t, http.StatusForbidden, status)
  118. r.RenderHostmaps("Final hostmaps", myControl, theirControl)
  119. }
  120. // infoSocket returns a short socket path in a private directory, one level below it
  121. // so that listen has a directory of its own to create.
  122. func infoSocket(t *testing.T) string {
  123. t.Helper()
  124. return filepath.Join(test.ShortTempDir(t), "n", "info.sock")
  125. }