1
0

access_unix_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //go:build !windows
  2. package localapi
  3. import (
  4. "context"
  5. "net/http"
  6. "os"
  7. "os/user"
  8. "strconv"
  9. "syscall"
  10. "testing"
  11. "github.com/slackhq/nebula/config"
  12. "github.com/slackhq/nebula/test"
  13. "github.com/stretchr/testify/assert"
  14. "github.com/stretchr/testify/require"
  15. )
  16. // ownGroup returns a group this process may hand a socket to: its own effective group,
  17. // by name and by gid, which is the one chown accepts from an unprivileged user.
  18. func ownGroup(t *testing.T) (name, gid string) {
  19. t.Helper()
  20. gid = strconv.Itoa(os.Getegid())
  21. g, err := user.LookupGroupId(gid)
  22. require.NoError(t, err)
  23. return g.Name, gid
  24. }
  25. func socketGroup(t *testing.T, path string) uint32 {
  26. t.Helper()
  27. fi, err := os.Lstat(path)
  28. require.NoError(t, err)
  29. st, ok := fi.Sys().(*syscall.Stat_t)
  30. require.True(t, ok)
  31. return uint32(st.Gid)
  32. }
  33. // TestLookupGroupID: a group is named as the host names it or by its gid; a gid the
  34. // host does not list is accepted as chown would accept it, and a name it does not list
  35. // is a mistake to report.
  36. func TestLookupGroupID(t *testing.T) {
  37. name, gid := ownGroup(t)
  38. got, err := lookupGroupID(name)
  39. require.NoError(t, err)
  40. assert.Equal(t, gid, got)
  41. got, err = lookupGroupID(gid)
  42. require.NoError(t, err)
  43. assert.Equal(t, gid, got)
  44. got, err = lookupGroupID("4000000000")
  45. require.NoError(t, err)
  46. assert.Equal(t, "4000000000", got)
  47. _, err = lookupGroupID("no-such-group-nebula-test")
  48. require.ErrorContains(t, err, "does not exist")
  49. }
  50. // TestControlAccessFromConfig: no group is the owner-only socket; a group is 0660 with
  51. // that group; a group the host does not know is a config error naming the key.
  52. func TestControlAccessFromConfig(t *testing.T) {
  53. name, gid := ownGroup(t)
  54. load := func(body string) (Access, error) {
  55. c := config.NewC(test.NewLogger())
  56. require.NoError(t, c.LoadString(body))
  57. return controlAccessFrom(c)
  58. }
  59. acc, err := load("local_api: {}\n")
  60. require.NoError(t, err)
  61. assert.Equal(t, ownerOnly(), acc)
  62. assert.Nil(t, acc.Group)
  63. acc, err = load("local_api:\n control:\n group: " + name + "\n")
  64. require.NoError(t, err)
  65. assert.Equal(t, os.FileMode(0660), acc.Mode)
  66. require.NotNil(t, acc.Group)
  67. assert.Equal(t, name, acc.Group.Name)
  68. assert.Equal(t, gid, acc.Group.ID)
  69. _, err = load("local_api:\n control:\n group: no-such-group-nebula-test\n")
  70. require.ErrorContains(t, err, CtlGroupKey)
  71. assert.ErrorContains(t, err, "does not exist")
  72. }
  73. // TestControlSocketIsHandedToTheGroup: the socket is created with the group's gid and
  74. // 0660, and a reload that takes the group away tightens the socket in place, without
  75. // moving it.
  76. func TestControlSocketIsHandedToTheGroup(t *testing.T) {
  77. name, gid := ownGroup(t)
  78. want, err := strconv.ParseUint(gid, 10, 32)
  79. require.NoError(t, err)
  80. s := NewServer(test.NewLogger(), basicRegistry())
  81. path := testSocket(t)
  82. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
  83. defer s.Stop()
  84. fi, err := os.Lstat(path)
  85. require.NoError(t, err)
  86. assert.Equal(t, os.FileMode(0660), fi.Mode().Perm())
  87. assert.Equal(t, uint32(want), socketGroup(t, path))
  88. require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
  89. fi, err = os.Lstat(path)
  90. require.NoError(t, err)
  91. assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
  92. assert.Equal(t, path, s.Path())
  93. _, err = (&Client{Path: path}).Describe()
  94. require.NoError(t, err, "the same listener is still serving")
  95. }
  96. // TestControlSocketWithAGroupTrustsTheKernel: with a group sharing the socket, the
  97. // kernel's check on connect is the access control and the daemon's peer check stands
  98. // aside, so a connection the peer check alone would refuse is served. Without a group
  99. // the same connection is refused; see TestPeerCheckRefusesAnotherUser.
  100. func TestControlSocketWithAGroupTrustsTheKernel(t *testing.T) {
  101. if !peerCheckSupported {
  102. t.Skip("no peer credentials on this platform")
  103. }
  104. name, gid := ownGroup(t)
  105. me := currentUID()
  106. asUID(t, me+1)
  107. s := NewServer(test.NewLogger(), basicRegistry())
  108. asUID(t, me)
  109. path := testSocket(t)
  110. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
  111. defer s.Stop()
  112. _, err := (&Client{Path: path}).Describe()
  113. require.NoError(t, err, "the socket's group admitted us; the peer check must not refuse")
  114. // Taking the group away on a reload puts the peer check back.
  115. require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
  116. _, err = (&Client{Path: path}).Describe()
  117. require.ErrorContains(t, err, "not permitted")
  118. }
  119. // TestInfoAccessFromConfig: a group with no mode is 0660; a mode that gives the group
  120. // nothing is refused, with the mode that would; an explicit mode with group bits is
  121. // kept as given.
  122. func TestInfoAccessFromConfig(t *testing.T) {
  123. name, gid := ownGroup(t)
  124. load := func(body string) (infoConfig, error) {
  125. c := config.NewC(test.NewLogger())
  126. require.NoError(t, c.LoadString(body))
  127. return infoConfigFrom(c)
  128. }
  129. section := func(extra string) string {
  130. return "local_api:\n info:\n enabled: true\n socket: /run/nebula/info.sock\n" + extra
  131. }
  132. cfg, err := load(section(" group: " + name + "\n"))
  133. require.NoError(t, err)
  134. assert.Equal(t, os.FileMode(0660), cfg.Access.Mode)
  135. require.NotNil(t, cfg.Access.Group)
  136. assert.Equal(t, gid, cfg.Access.Group.ID)
  137. cfg, err = load(section(" group: " + name + "\n socket_mode: \"0664\"\n"))
  138. require.NoError(t, err)
  139. assert.Equal(t, os.FileMode(0664), cfg.Access.Mode)
  140. _, err = load(section(" group: " + name + "\n socket_mode: \"0600\"\n"))
  141. require.ErrorContains(t, err, "gives group "+name+" no access")
  142. require.ErrorContains(t, err, `"0660"`)
  143. cfg, err = load(section(""))
  144. require.NoError(t, err)
  145. assert.Equal(t, os.FileMode(0600), cfg.Access.Mode)
  146. assert.Nil(t, cfg.Access.Group)
  147. }
  148. // TestSocketIsHandedToTheGroup: the socket is created with the group's gid and the
  149. // mode, and a changed group or mode on a reload is applied to the socket in place.
  150. func TestSocketIsHandedToTheGroup(t *testing.T) {
  151. name, gid := ownGroup(t)
  152. group := &Group{Name: name, ID: gid}
  153. want, err := strconv.ParseUint(gid, 10, 32)
  154. require.NoError(t, err)
  155. s, path := serveInfo(t, infoRegistry(), 0600)
  156. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: group}))
  157. fi, err := os.Lstat(path)
  158. require.NoError(t, err)
  159. assert.Equal(t, os.FileMode(0660), fi.Mode().Perm())
  160. assert.Equal(t, uint32(want), socketGroup(t, path))
  161. status, _ := test.HTTPGet(t, path, "/v1/whoami")
  162. assert.Equal(t, http.StatusOK, status, "the same listener is still serving")
  163. // The mode is the operator's to widen further.
  164. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0664, Group: group}))
  165. fi, err = os.Lstat(path)
  166. require.NoError(t, err)
  167. assert.Equal(t, os.FileMode(0664), fi.Mode().Perm())
  168. assert.Equal(t, path, s.Path())
  169. }