Skip to content

Commit

Permalink
Groups gatherer (#264)
Browse files Browse the repository at this point in the history
* Add Groups gatherer

* Add groups gatherer to the available gatherers

* Addressing review feedbacks
  • Loading branch information
CDimonaco authored Sep 28, 2023
1 parent 72c8765 commit 44cfe3f
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 3 deletions.
7 changes: 4 additions & 3 deletions internal/factsengine/gatherers/gatherer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ func StandardGatherers() map[string]FactGatherer {
CibAdminGathererName: NewDefaultCibAdminGatherer(),
CorosyncCmapCtlGathererName: NewDefaultCorosyncCmapctlGatherer(),
CorosyncConfGathererName: NewDefaultCorosyncConfGatherer(),
GroupsGathererName: NewDefaultGroupsGatherer(),
HostsFileGathererName: NewDefaultHostsFileGatherer(),
SystemDGathererName: NewDefaultSystemDGatherer(),
PackageVersionGathererName: NewDefaultPackageVersionGatherer(),
PasswdGathererName: NewDefaultPasswdGatherer(),
SapHostCtrlGathererName: NewDefaultSapHostCtrlGatherer(),
SaptuneGathererName: NewDefaultSaptuneGatherer(),
SBDConfigGathererName: NewDefaultSBDGatherer(),
SBDDumpGathererName: NewDefaultSBDDumpGatherer(),
SapHostCtrlGathererName: NewDefaultSapHostCtrlGatherer(),
SystemDGathererName: NewDefaultSystemDGatherer(),
VerifyPasswordGathererName: NewDefaultPasswordGatherer(),
SaptuneGathererName: NewDefaultSaptuneGatherer(),
}
}
136 changes: 136 additions & 0 deletions internal/factsengine/gatherers/groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package gatherers

import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"

log "github.com/sirupsen/logrus"
"github.com/trento-project/agent/pkg/factsengine/entities"
)

const (
GroupsGathererName = "groups"
GroupsFilePath = "/etc/group"
)

// nolint:gochecknoglobals
var (
GroupsFileError = entities.FactGatheringError{
Type: "groups-file-error",
Message: "error reading /etc/group file",
}

GroupsFileDecodingError = entities.FactGatheringError{
Type: "groups-decoding-error",
Message: "error deconding groups file",
}
)

type GroupsEntry struct {
Name string `json:"name"`
GID uint64 `json:"gid"`
Users []string `json:"users"`
}

type GroupsGatherer struct {
groupsFilePath string
}

func NewDefaultGroupsGatherer() *GroupsGatherer {
return NewGroupsGatherer(GroupsFilePath)
}

func NewGroupsGatherer(groupsFilePath string) *GroupsGatherer {
return &GroupsGatherer{groupsFilePath: groupsFilePath}
}

func (g *GroupsGatherer) Gather(factsRequests []entities.FactRequest) ([]entities.Fact, error) {
log.Infof("Starting %s facts gathering process", GroupsGathererName)
facts := []entities.Fact{}

groupsFile, err := os.Open(g.groupsFilePath)
if err != nil {
return nil, GroupsFileError.Wrap(err.Error())
}

defer func() {
err := groupsFile.Close()
if err != nil {
log.Errorf("could not close groups file %s, error: %s", g.groupsFilePath, err)
}
}()

entries, err := parseGroupsFile(groupsFile)
if err != nil {
return nil, GroupsFileDecodingError.Wrap(err.Error())
}

factValues, err := mapGroupsEntriesToFactValue(entries)
if err != nil {
return nil, GroupsFileDecodingError.Wrap(err.Error())
}

for _, requestedFact := range factsRequests {
facts = append(facts, entities.NewFactGatheredWithRequest(requestedFact, factValues))
}

log.Infof("Requested %s facts gathered", GroupsGathererName)

return facts, nil
}

func parseGroupsFile(fileContent io.Reader) ([]GroupsEntry, error) {
lineScanner := bufio.NewScanner(fileContent)
lineScanner.Split(bufio.ScanLines)

var entries []GroupsEntry

for lineScanner.Scan() {
groupsLine := lineScanner.Text()

values := strings.Split(groupsLine, ":")

if len(values) != 4 {
return nil, fmt.Errorf("could not decode groups file line %s, entry are less then 4", groupsLine)
}

groupID, err := strconv.Atoi(values[2])
if err != nil {
return nil, fmt.Errorf("could not convert group id %s to integer", values[2])
}

groupUsers := strings.Split(values[3], ",")
if len(groupUsers) == 1 && groupUsers[0] == "" {
// no groups found, set the slice to empty to avoid one item with empty string as user
groupUsers = []string{}
}

entries = append(entries, GroupsEntry{
Name: values[0],
GID: uint64(groupID),
Users: groupUsers,
})
}

return entries, nil
}

func mapGroupsEntriesToFactValue(entries []GroupsEntry) (entities.FactValue, error) {
marshalled, err := json.Marshal(&entries)
if err != nil {
return nil, err
}

var unmarshalled []interface{}
err = json.Unmarshal(marshalled, &unmarshalled)
if err != nil {
return nil, err
}

return entities.NewFactValue(unmarshalled)
}
112 changes: 112 additions & 0 deletions internal/factsengine/gatherers/groups_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package gatherers_test

import (
"testing"

"github.com/stretchr/testify/suite"
"github.com/trento-project/agent/internal/factsengine/gatherers"
"github.com/trento-project/agent/pkg/factsengine/entities"
"github.com/trento-project/agent/test/helpers"
)

type GroupsGathererSuite struct {
suite.Suite
}

func TestGroupsGatherer(t *testing.T) {
suite.Run(t, new(GroupsGathererSuite))
}

func (s *GroupsGathererSuite) TestGroupsParsingSuccess() {
gatherer := gatherers.NewGroupsGatherer(helpers.GetFixturePath("gatherers/groups.valid"))

fr := []entities.FactRequest{{
Name: "groups",
Gatherer: "groups",
CheckID: "checkone",
}}

expectedFacts := []entities.Fact{
{
Name: "groups",
CheckID: "checkone",
Value: &entities.FactValueList{
Value: []entities.FactValue{
&entities.FactValueMap{
Value: map[string]entities.FactValue{
"name": &entities.FactValueString{
Value: "root",
},
"gid": &entities.FactValueInt{
Value: 0,
},
"users": &entities.FactValueList{
Value: []entities.FactValue{},
},
},
},
&entities.FactValueMap{
Value: map[string]entities.FactValue{
"name": &entities.FactValueString{
Value: "daemon",
},
"gid": &entities.FactValueInt{
Value: 1,
},
"users": &entities.FactValueList{
Value: []entities.FactValue{},
},
},
},
&entities.FactValueMap{
Value: map[string]entities.FactValue{
"name": &entities.FactValueString{
Value: "adm",
},
"gid": &entities.FactValueInt{
Value: 4,
},
"users": &entities.FactValueList{
Value: []entities.FactValue{
&entities.FactValueString{Value: "syslog"},
&entities.FactValueString{Value: "trento"},
},
},
},
},
},
},
},
}
results, err := gatherer.Gather(fr)
s.NoError(err)
s.EqualValues(expectedFacts, results)
}

func (s *GroupsGathererSuite) TestGroupsParsingDecodeErrorInvalidGID() {
gatherer := gatherers.NewGroupsGatherer(helpers.GetFixturePath("gatherers/groups.invalidgid"))

fr := []entities.FactRequest{{
Name: "groups",
Gatherer: "groups",
CheckID: "checkone",
}}

result, err := gatherer.Gather(fr)
s.Nil(result)
s.EqualError(err, "fact gathering error: groups-decoding-error - error deconding groups file: could not convert group id to integer")
}

func (s *GroupsGathererSuite) TestGroupsParsingDecodeErrorInvalidFormat() {
gatherer := gatherers.NewGroupsGatherer(helpers.GetFixturePath("gatherers/groups.invalidformat"))

fr := []entities.FactRequest{{
Name: "groups",
Gatherer: "groups",
CheckID: "checkone",
}}

result, err := gatherer.Gather(fr)
s.Nil(result)
s.EqualError(err, "fact gathering error: groups-decoding-error - error deconding groups file: could not decode groups file line daemon:x:1, entry are less then 4")
}
3 changes: 3 additions & 0 deletions test/fixtures/gatherers/groups.invalidformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:x:0:
daemon:x:1
adm:x:4:syslog,trento
3 changes: 3 additions & 0 deletions test/fixtures/gatherers/groups.invalidgid
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:x:0:
daemon:x:1:
adm:x::syslog,trento
3 changes: 3 additions & 0 deletions test/fixtures/gatherers/groups.valid
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
root:x:0:
daemon:x:1:
adm:x:4:syslog,trento

0 comments on commit 44cfe3f

Please sign in to comment.