cloudinfo_test.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package cloudinfo
  4. import (
  5. "context"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/netip"
  9. "slices"
  10. "testing"
  11. "tailscale.com/util/cloudenv"
  12. )
  13. func TestCloudInfo_AWS(t *testing.T) {
  14. const (
  15. mac1 = "06:1d:00:00:00:00"
  16. mac2 = "06:1d:00:00:00:01"
  17. publicV4 = "1.2.3.4"
  18. otherV4_1 = "5.6.7.8"
  19. otherV4_2 = "11.12.13.14"
  20. v6addr = "2001:db8::1"
  21. macsPrefix = "/latest/meta-data/network/interfaces/macs/"
  22. )
  23. // Launch a fake AWS IMDS server
  24. fake := &fakeIMDS{
  25. tb: t,
  26. paths: map[string]string{
  27. macsPrefix: mac1 + "\n" + mac2,
  28. // This is the "main" public IP address for the instance
  29. macsPrefix + mac1 + "/public-ipv4s": publicV4,
  30. // There's another interface with two public IPs
  31. // attached to it and an IPv6 address, all of which we
  32. // should discover.
  33. macsPrefix + mac2 + "/public-ipv4s": otherV4_1 + "\n" + otherV4_2,
  34. macsPrefix + mac2 + "/ipv6s": v6addr,
  35. },
  36. }
  37. srv := httptest.NewServer(fake)
  38. defer srv.Close()
  39. ci := New(t.Logf)
  40. ci.cloud = cloudenv.AWS
  41. ci.endpoint = srv.URL
  42. ips, err := ci.GetPublicIPs(context.Background())
  43. if err != nil {
  44. t.Fatalf("unexpected error: %v", err)
  45. }
  46. wantIPs := []netip.Addr{
  47. netip.MustParseAddr(publicV4),
  48. netip.MustParseAddr(otherV4_1),
  49. netip.MustParseAddr(otherV4_2),
  50. netip.MustParseAddr(v6addr),
  51. }
  52. if !slices.Equal(ips, wantIPs) {
  53. t.Fatalf("got %v, want %v", ips, wantIPs)
  54. }
  55. }
  56. func TestCloudInfo_AWSNotPublic(t *testing.T) {
  57. returns404 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  58. if r.Method == "PUT" && r.URL.Path == "/latest/api/token" {
  59. w.Header().Set("Server", "EC2ws")
  60. w.Write([]byte("fake-imds-token"))
  61. return
  62. }
  63. http.NotFound(w, r)
  64. })
  65. srv := httptest.NewServer(returns404)
  66. defer srv.Close()
  67. ci := New(t.Logf)
  68. ci.cloud = cloudenv.AWS
  69. ci.endpoint = srv.URL
  70. // If the IMDS server doesn't return any public IPs, it's not an error
  71. // and we should just get an empty list.
  72. ips, err := ci.GetPublicIPs(context.Background())
  73. if err != nil {
  74. t.Fatalf("unexpected error: %v", err)
  75. }
  76. if len(ips) != 0 {
  77. t.Fatalf("got %v, want none", ips)
  78. }
  79. }
  80. type fakeIMDS struct {
  81. tb testing.TB
  82. paths map[string]string
  83. }
  84. func (f *fakeIMDS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  85. f.tb.Logf("%s %s", r.Method, r.URL.Path)
  86. path := r.URL.Path
  87. // Handle the /latest/api/token case
  88. const token = "fake-imds-token"
  89. if r.Method == "PUT" && path == "/latest/api/token" {
  90. w.Header().Set("Server", "EC2ws")
  91. w.Write([]byte(token))
  92. return
  93. }
  94. // Otherwise, require the IMDSv2 token to be set
  95. if r.Header.Get("X-aws-ec2-metadata-token") != token {
  96. f.tb.Errorf("missing or invalid IMDSv2 token")
  97. http.Error(w, "missing or invalid IMDSv2 token", http.StatusForbidden)
  98. return
  99. }
  100. if v, ok := f.paths[path]; ok {
  101. w.Write([]byte(v))
  102. return
  103. }
  104. http.NotFound(w, r)
  105. }