allow notification plugins to work on freebsd and non-root functional tests (#1253)

* random uuid for all platforms
* check group writable and setgid; don't check group ownership
* allow user to run plugins without changing desired user/group (set them to "")
This commit is contained in:
mmetc 2022-03-09 12:09:50 +01:00 committed by GitHub
parent 5a15f9b39b
commit 10ce45c054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 25 deletions

1
go.sum
View file

@ -311,7 +311,6 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=

View file

@ -22,6 +22,7 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/google/uuid"
plugin "github.com/hashicorp/go-plugin"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@ -253,11 +254,13 @@ func (pb *PluginBroker) loadNotificationPlugin(name string, binaryPath string) (
return nil, err
}
cmd := exec.Command(binaryPath)
cmd.SysProcAttr, err = getProcessAtr(pb.pluginProcConfig.User, pb.pluginProcConfig.Group)
if err != nil {
return nil, errors.Wrap(err, "while getting process attributes")
if pb.pluginProcConfig.User != "" || pb.pluginProcConfig.Group != "" {
cmd.SysProcAttr, err = getProcessAttr(pb.pluginProcConfig.User, pb.pluginProcConfig.Group)
if err != nil {
return nil, errors.Wrap(err, "while getting process attributes")
}
cmd.SysProcAttr.Credential.NoSetGroups = true
}
cmd.SysProcAttr.Credential.NoSetGroups = true
pb.pluginMap[name] = &NotifierPlugin{}
l := log.New()
err = types.ConfigureLogger(l)
@ -288,6 +291,7 @@ func (pb *PluginBroker) loadNotificationPlugin(name string, binaryPath string) (
}
func (pb *PluginBroker) pushNotificationsToPlugin(pluginName string, alerts []*models.Alert) error {
log.WithField("plugin", pluginName).Debug("pushing alerts to plugin")
if len(alerts) == 0 {
return nil
}
@ -372,18 +376,27 @@ func pluginIsValid(path string) error {
if err != nil {
return errors.Wrap(err, "while getting current user")
}
procAttr, err := getProcessAtr(currentUser.Username, currentUser.Username)
currentUID, err := getUID(currentUser.Username)
if err != nil {
return errors.Wrap(err, "while getting process attributes")
return errors.Wrap(err, "while looking up the current uid")
}
stat := details.Sys().(*syscall.Stat_t)
if stat.Uid != procAttr.Credential.Uid || stat.Gid != procAttr.Credential.Gid {
return fmt.Errorf("plugin at %s is not owned by %s user and group", path, currentUser.Username)
if stat.Uid != currentUID {
return fmt.Errorf("plugin at %s is not owned by user '%s'", path, currentUser.Username)
}
if (int(details.Mode()) & 2) != 0 {
mode := details.Mode()
perm := uint32(mode)
if (perm & 00002) != 0 {
return fmt.Errorf("plugin at %s is world writable, world writable plugins are invalid", path)
}
if (perm & 00020) != 0 {
return fmt.Errorf("plugin at %s is group writable, group writable plugins are invalid", path)
}
if (mode & os.ModeSetgid) != 0 {
return fmt.Errorf("plugin at %s has setgid permission, which is not allowed", path)
}
return nil
}
@ -412,43 +425,59 @@ func getPluginTypeAndSubtypeFromPath(path string) (string, string, error) {
return strings.Join(parts[:len(parts)-1], "-"), parts[len(parts)-1], nil
}
func getProcessAtr(username string, groupname string) (*syscall.SysProcAttr, error) {
func getUID(username string) (uint32, error) {
u, err := user.Lookup(username)
if err != nil {
return nil, err
}
g, err := user.LookupGroup(groupname)
if err != nil {
return nil, err
return 0, err
}
uid, err := strconv.ParseInt(u.Uid, 10, 32)
if err != nil {
return nil, err
return 0, err
}
if uid < 0 || uid > math.MaxInt32 {
return nil, fmt.Errorf("out of bound uid")
return 0, fmt.Errorf("out of bound uid")
}
return uint32(uid), nil
}
func getGID(groupname string) (uint32, error) {
g, err := user.LookupGroup(groupname)
if err != nil {
return 0, err
}
gid, err := strconv.ParseInt(g.Gid, 10, 32)
if err != nil {
return nil, err
return 0, err
}
if gid < 0 || gid > math.MaxInt32 {
return nil, fmt.Errorf("out of bound gid")
return 0, fmt.Errorf("out of bound gid")
}
return uint32(gid), nil
}
func getProcessAttr(username string, groupname string) (*syscall.SysProcAttr, error) {
uid, err := getUID(username)
if err != nil {
return nil, err
}
gid, err := getGID(groupname)
if err != nil {
return nil, err
}
return &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
Uid: uid,
Gid: gid,
},
}, nil
}
func getUUID() (string, error) {
if d, err := os.ReadFile("/proc/sys/kernel/random/uuid"); err != nil {
uuidv4, err := uuid.NewRandom()
if err != nil {
return "", err
} else {
return string(d), nil
}
return uuidv4.String(), nil
}
func getHandshake() (plugin.HandshakeConfig, error) {