| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- //go:build !windows
- package localapi
- import (
- "context"
- "os"
- "os/user"
- "strconv"
- "syscall"
- "testing"
- "github.com/slackhq/nebula/config"
- "github.com/slackhq/nebula/test"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // ownGroup returns a group this process may hand a socket to: its own effective group,
- // by name and by gid, which is the one chown accepts from an unprivileged user.
- func ownGroup(t *testing.T) (name, gid string) {
- t.Helper()
- gid = strconv.Itoa(os.Getegid())
- g, err := user.LookupGroupId(gid)
- require.NoError(t, err)
- return g.Name, gid
- }
- func socketGroup(t *testing.T, path string) uint32 {
- t.Helper()
- fi, err := os.Lstat(path)
- require.NoError(t, err)
- st, ok := fi.Sys().(*syscall.Stat_t)
- require.True(t, ok)
- return uint32(st.Gid)
- }
- // TestLookupGroupID: a group is named as the host names it or by its gid; a gid the
- // host does not list is accepted as chown would accept it, and a name it does not list
- // is a mistake to report.
- func TestLookupGroupID(t *testing.T) {
- name, gid := ownGroup(t)
- got, err := lookupGroupID(name)
- require.NoError(t, err)
- assert.Equal(t, gid, got)
- got, err = lookupGroupID(gid)
- require.NoError(t, err)
- assert.Equal(t, gid, got)
- got, err = lookupGroupID("4000000000")
- require.NoError(t, err)
- assert.Equal(t, "4000000000", got)
- _, err = lookupGroupID("no-such-group-nebula-test")
- require.ErrorContains(t, err, "does not exist")
- }
- // TestControlAccessFromConfig: no group is the owner-only socket; a group is 0660 with
- // that group; a group the host does not know is a config error naming the key.
- func TestControlAccessFromConfig(t *testing.T) {
- name, gid := ownGroup(t)
- load := func(body string) (Access, error) {
- c := config.NewC(test.NewLogger())
- require.NoError(t, c.LoadString(body))
- return controlAccessFrom(c)
- }
- acc, err := load("local_api: {}\n")
- require.NoError(t, err)
- assert.Equal(t, ownerOnly(), acc)
- assert.Nil(t, acc.Group)
- acc, err = load("local_api:\n control:\n group: " + name + "\n")
- require.NoError(t, err)
- assert.Equal(t, os.FileMode(0660), acc.Mode)
- require.NotNil(t, acc.Group)
- assert.Equal(t, name, acc.Group.Name)
- assert.Equal(t, gid, acc.Group.ID)
- _, err = load("local_api:\n control:\n group: no-such-group-nebula-test\n")
- require.ErrorContains(t, err, CtlGroupKey)
- assert.ErrorContains(t, err, "does not exist")
- }
- // TestControlSocketIsHandedToTheGroup: the socket is created with the group's gid and
- // 0660, and a reload that takes the group away tightens the socket in place, without
- // moving it.
- func TestControlSocketIsHandedToTheGroup(t *testing.T) {
- name, gid := ownGroup(t)
- want, err := strconv.ParseUint(gid, 10, 32)
- require.NoError(t, err)
- s := NewServer(test.NewLogger(), basicRegistry())
- path := testSocket(t)
- require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
- defer s.Stop()
- fi, err := os.Lstat(path)
- require.NoError(t, err)
- assert.Equal(t, os.FileMode(0660), fi.Mode().Perm())
- assert.Equal(t, uint32(want), socketGroup(t, path))
- require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
- fi, err = os.Lstat(path)
- require.NoError(t, err)
- assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
- assert.Equal(t, path, s.Path())
- _, err = (&Client{Path: path}).Describe()
- require.NoError(t, err, "the same listener is still serving")
- }
- // TestControlSocketWithAGroupTrustsTheKernel: with a group sharing the socket, the
- // kernel's check on connect is the access control and the daemon's peer check stands
- // aside, so a connection the peer check alone would refuse is served. Without a group
- // the same connection is refused; see TestPeerCheckRefusesAnotherUser.
- func TestControlSocketWithAGroupTrustsTheKernel(t *testing.T) {
- if !peerCheckSupported {
- t.Skip("no peer credentials on this platform")
- }
- name, gid := ownGroup(t)
- me := currentUID()
- asUID(t, me+1)
- s := NewServer(test.NewLogger(), basicRegistry())
- asUID(t, me)
- path := testSocket(t)
- require.NoError(t, s.Rebind(context.Background(), path, Access{Mode: 0660, Group: &Group{Name: name, ID: gid}}))
- defer s.Stop()
- _, err := (&Client{Path: path}).Describe()
- require.NoError(t, err, "the socket's group admitted us; the peer check must not refuse")
- // Taking the group away on a reload puts the peer check back.
- require.NoError(t, s.Rebind(context.Background(), path, ownerOnly()))
- _, err = (&Client{Path: path}).Describe()
- require.ErrorContains(t, err, "not permitted")
- }
|