access_unix_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. //go:build !windows
  2. package localapi
  3. import (
  4. "context"
  5. "os"
  6. "os/user"
  7. "strconv"
  8. "syscall"
  9. "testing"
  10. "github.com/slackhq/nebula/config"
  11. "github.com/slackhq/nebula/test"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. // ownGroup returns a group this process may hand a socket to: its own effective group,
  16. // by name and by gid, which is the one chown accepts from an unprivileged user.
  17. func ownGroup(t *testing.T) (name, gid string) {
  18. t.Helper()
  19. gid = strconv.Itoa(os.Getegid())
  20. g, err := user.LookupGroupId(gid)
  21. require.NoError(t, err)
  22. return g.Name, gid
  23. }
  24. func socketGroup(t *testing.T, path string) uint32 {
  25. t.Helper()
  26. fi, err := os.Lstat(path)
  27. require.NoError(t, err)
  28. st, ok := fi.Sys().(*syscall.Stat_t)
  29. require.True(t, ok)
  30. return uint32(st.Gid)
  31. }
  32. // TestLookupGroupID: a group is named as the host names it or by its gid; a gid the
  33. // host does not list is accepted as chown would accept it, and a name it does not list
  34. // is a mistake to report.
  35. func TestLookupGroupID(t *testing.T) {
  36. name, gid := ownGroup(t)
  37. got, err := lookupGroupID(name)
  38. require.NoError(t, err)
  39. assert.Equal(t, gid, got)
  40. got, err = lookupGroupID(gid)
  41. require.NoError(t, err)
  42. assert.Equal(t, gid, got)
  43. got, err = lookupGroupID("4000000000")
  44. require.NoError(t, err)
  45. assert.Equal(t, "4000000000", got)
  46. _, err = lookupGroupID("no-such-group-nebula-test")
  47. require.ErrorContains(t, err, "does not exist")
  48. }
  49. // TestControlAccessFromConfig: no group is the owner-only socket; a group is 0660 with
  50. // that group; a group the host does not know is a config error naming the key.
  51. func TestControlAccessFromConfig(t *testing.T) {
  52. name, gid := ownGroup(t)
  53. load := func(body string) (Access, error) {
  54. c := config.NewC(test.NewLogger())
  55. require.NoError(t, c.LoadString(body))
  56. return controlAccessFrom(c)
  57. }
  58. acc, err := load("local_api: {}\n")
  59. require.NoError(t, err)
  60. assert.Equal(t, ownerOnly(), acc)
  61. assert.Nil(t, acc.Group)
  62. acc, err = load("local_api:\n control:\n group: " + name + "\n")
  63. require.NoError(t, err)
  64. assert.Equal(t, os.FileMode(0660), acc.Mode)
  65. require.NotNil(t, acc.Group)
  66. assert.Equal(t, name, acc.Group.Name)
  67. assert.Equal(t, gid, acc.Group.ID)
  68. _, err = load("local_api:\n control:\n group: no-such-group-nebula-test\n")
  69. require.ErrorContains(t, err, CtlGroupKey)
  70. assert.ErrorContains(t, err, "does not exist")
  71. }
  72. // TestControlSocketIsHandedToTheGroup: the socket is created with the group's gid and
  73. // 0660, and a reload that takes the group away tightens the socket in place, without
  74. // moving it.
  75. func TestControlSocketIsHandedToTheGroup(t *testing.T) {
  76. name, gid := ownGroup(t)
  77. want, err := strconv.ParseUint(gid, 10, 32)
  78. require.NoError(t, err)
  79. s := NewServer(test.NewLogger(), basicRegistry())
  80. path := testSocket(t)
  81. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
  82. defer s.Stop()
  83. fi, err := os.Lstat(path)
  84. require.NoError(t, err)
  85. assert.Equal(t, os.FileMode(0660), fi.Mode().Perm())
  86. assert.Equal(t, uint32(want), socketGroup(t, path))
  87. require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
  88. fi, err = os.Lstat(path)
  89. require.NoError(t, err)
  90. assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
  91. assert.Equal(t, path, s.Path())
  92. _, err = (&Client{Path: path}).Describe()
  93. require.NoError(t, err, "the same listener is still serving")
  94. }
  95. // TestControlSocketWithAGroupTrustsTheKernel: with a group sharing the socket, the
  96. // kernel's check on connect is the access control and the daemon's peer check stands
  97. // aside, so a connection the peer check alone would refuse is served. Without a group
  98. // the same connection is refused; see TestPeerCheckRefusesAnotherUser.
  99. func TestControlSocketWithAGroupTrustsTheKernel(t *testing.T) {
  100. if !peerCheckSupported {
  101. t.Skip("no peer credentials on this platform")
  102. }
  103. name, gid := ownGroup(t)
  104. me := currentUID()
  105. asUID(t, me+1)
  106. s := NewServer(test.NewLogger(), basicRegistry())
  107. asUID(t, me)
  108. path := testSocket(t)
  109. require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
  110. defer s.Stop()
  111. _, err := (&Client{Path: path}).Describe()
  112. require.NoError(t, err, "the socket's group admitted us; the peer check must not refuse")
  113. // Taking the group away on a reload puts the peer check back.
  114. require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
  115. _, err = (&Client{Path: path}).Describe()
  116. require.ErrorContains(t, err, "not permitted")
  117. }