From 8324bbdd2a12d1edf5e9d452047204ae5999f7b7 Mon Sep 17 00:00:00 2001 From: Nelson Kopliku Date: Wed, 17 Aug 2022 18:42:03 +0200 Subject: [PATCH 1/3] Adds osutil dependency --- go.mod | 3 ++- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3c44ef7d..03e73a76 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 + github.com/tklauser/go-sysconf v0.3.10 // indirect + github.com/tredoe/osutil v1.0.6 github.com/vektra/mockery/v2 v2.12.3 github.com/wagslane/go-rabbitmq v0.10.0 golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 @@ -49,7 +51,6 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.4.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect - github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/numcpus v0.4.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect diff --git a/go.sum b/go.sum index 30806a05..6131ac5d 100644 --- a/go.sum +++ b/go.sum @@ -403,6 +403,10 @@ github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03O github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tredoe/fileutil v1.0.5/go.mod h1:HFzzpvg+3Q8LgmZgo1mVF5epHc/CVkWKEb3hja+/1Zo= +github.com/tredoe/goutil v1.0.0/go.mod h1:Qhf75QLcNEChimbl4wb8nROzw9PCFCPYTEUmTnoszXY= +github.com/tredoe/osutil v1.0.6 h1:KJvG9AFmUPLe3hsNKyPMIjNx77CkAJtMKVS4ugAT7vM= +github.com/tredoe/osutil v1.0.6/go.mod h1:zNq93p2DLHJWkHi2/+zi3xOjZl8xxiv3tiI2A6zcB3w= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/vektra/mockery/v2 v2.12.3 h1:74h0R+p75tdr3QNwiNz3MXeCwSP/I5bYUbZY6oT4t20= github.com/vektra/mockery/v2 v2.12.3/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U= From f5ff6cdd0c727aca139afcdfdd5d55fb740e7e75 Mon Sep 17 00:00:00 2001 From: Nelson Kopliku Date: Wed, 17 Aug 2022 18:42:36 +0200 Subject: [PATCH 2/3] Implement the hacluster password verify gatherer --- .../factsengine/gatherers/verifypassword.go | 77 +++++++++++++ .../gatherers/verifypassword_test.go | 107 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 internal/factsengine/gatherers/verifypassword.go create mode 100644 internal/factsengine/gatherers/verifypassword_test.go diff --git a/internal/factsengine/gatherers/verifypassword.go b/internal/factsengine/gatherers/verifypassword.go new file mode 100644 index 00000000..c2783286 --- /dev/null +++ b/internal/factsengine/gatherers/verifypassword.go @@ -0,0 +1,77 @@ +package gatherers + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + crypt "github.com/tredoe/osutil/user/crypt" + sha512crypt "github.com/tredoe/osutil/user/crypt/sha512_crypt" +) + +const ( + VerifyPasswordGathererName = "verify_password" +) + +type VerifyPasswordGatherer struct { + executor CommandExecutor +} + +func NewDefaultPasswordGatherer() *VerifyPasswordGatherer { + return NewVerifyPasswordGatherer(Executor{}) +} + +func NewVerifyPasswordGatherer(executor CommandExecutor) *VerifyPasswordGatherer { + return &VerifyPasswordGatherer{ + executor, + } +} + +/* +This gatherer expects the next format for the argument: "username:password" +Where: +- username: The user which the password is verified +- password: The password to verify +*/ + +func (g *VerifyPasswordGatherer) Gather(factsRequests []FactRequest) ([]Fact, error) { + facts := []Fact{} + log.Infof("Starting password verifying facts gathering process") + + for _, factReq := range factsRequests { + arguments := strings.Split(factReq.Argument, ":") + if len(arguments) != 2 { + log.Error("the provider argument should follow the \"username:password\" format") + continue + } + username := arguments[0] + password := []byte(arguments[1]) + + salt, hash, err := g.getSalt(username) + if err != nil { + log.Error(err) + } + log.Debugf("Obtained salt using user %s and password %s: %s", username, password, salt) + + crypter := sha512crypt.New() + match := crypter.Verify(hash, password) + + fact := NewFactWithRequest(factReq, !errors.Is(match, crypt.ErrKeyMismatch)) + facts = append(facts, fact) + } + + log.Infof("Requested password verifying facts gathered") + return facts, nil +} + +func (g *VerifyPasswordGatherer) getSalt(user string) ([]byte, string, error) { + shadow, err := g.executor.Exec("getent", "shadow", user) + if err != nil { + return nil, "", errors.Wrap(err, "Error getting salt") + } + salt := strings.Split(string(shadow), "$")[2] + hash := strings.Split(string(shadow), ":")[1] + + return []byte(fmt.Sprintf("$6$%s", salt)), hash, nil +} diff --git a/internal/factsengine/gatherers/verifypassword_test.go b/internal/factsengine/gatherers/verifypassword_test.go new file mode 100644 index 00000000..e9df25d2 --- /dev/null +++ b/internal/factsengine/gatherers/verifypassword_test.go @@ -0,0 +1,107 @@ +package gatherers + +import ( + "testing" + + "github.com/stretchr/testify/suite" + mocks "github.com/trento-project/agent/internal/factsengine/gatherers/mocks" +) + +type PasswordTestSuite struct { + suite.Suite + mockExecutor *mocks.CommandExecutor +} + +func TestPasswordTestSuite(t *testing.T) { + suite.Run(t, new(PasswordTestSuite)) +} + +func (suite *PasswordTestSuite) SetupTest() { + suite.mockExecutor = new(mocks.CommandExecutor) +} + +func (suite *PasswordTestSuite) TestPasswordGatherEqual() { + shadow := []byte("hacluster:$6$WFEgPAefduOyvLCN$MprO90En7b/" + + "cP8uJJpHzJ7ufTPjYuWoVF4s.3MUdOR9iwcO.6E3uCHX1waqypjey458NKGE9O7lnWpV/" + + "qd2tg1:19029::::::") + + suite.mockExecutor.On("Exec", "getent", "shadow", "hacluster").Return( + shadow, nil) + + verifyPasswordGatherer := NewVerifyPasswordGatherer(suite.mockExecutor) + + factRequests := []FactRequest{ + { + Name: "hacluster", + Gatherer: "password", + Argument: "hacluster:linux", + CheckID: "check1", + }, + } + + factResults, err := verifyPasswordGatherer.Gather(factRequests) + + expectedResults := []Fact{ + { + Name: "hacluster", + Value: true, + CheckID: "check1", + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *PasswordTestSuite) TestPasswordGatherNotEqual() { + shadow := []byte("hacluster:$6$WFEgSAefduOyvLCN$MprO90En7b/" + + "cP8uJJpHzJ7ufTPjYuWoVF4s.3MUdOR9iwcO.6E3uCHX1waqypjey458NKGE9O7lnWpV" + + "/qd2tg1:19029::::::") + + suite.mockExecutor.On("Exec", "getent", "shadow", "hacluster").Return( + shadow, nil) + + verifyPasswordGatherer := NewVerifyPasswordGatherer(suite.mockExecutor) + + factRequests := []FactRequest{ + { + Name: "hacluster", + Gatherer: "password", + Argument: "hacluster:linux", + CheckID: "check1", + }, + } + + factResults, err := verifyPasswordGatherer.Gather(factRequests) + + expectedResults := []Fact{ + { + Name: "hacluster", + Value: false, + CheckID: "check1", + }, + } + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} + +func (suite *PasswordTestSuite) TestPasswordGatherWrongArguments() { + verifyPasswordGatherer := &VerifyPasswordGatherer{} // nolint + + factRequests := []FactRequest{ + { + Name: "hacluster", + Gatherer: "password", + Argument: "linux", + CheckID: "check1", + }, + } + + factResults, err := verifyPasswordGatherer.Gather(factRequests) + + expectedResults := []Fact{} + + suite.NoError(err) + suite.ElementsMatch(expectedResults, factResults) +} From d6611675927489be8d967ff4bd833c3b172aabdf Mon Sep 17 00:00:00 2001 From: Nelson Kopliku Date: Wed, 17 Aug 2022 18:42:59 +0200 Subject: [PATCH 3/3] Register the password verify gatherer in the facts engine --- internal/factsengine/factsengine.go | 1 + internal/factsengine/factsengine_test.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/factsengine/factsengine.go b/internal/factsengine/factsengine.go index 5302a154..9f66df0d 100644 --- a/internal/factsengine/factsengine.go +++ b/internal/factsengine/factsengine.go @@ -33,6 +33,7 @@ func NewFactsEngine(agentID, factsEngineService string) *FactsEngine { gatherers.CibAdminGathererName: gatherers.NewCibAdminGatherer(), gatherers.SystemDGathererName: gatherers.NewSystemDGatherer(), gatherers.SBDConfigGathererName: gatherers.NewSBDGathererWithDefaultConfig(), + gatherers.VerifyPasswordGathererName: gatherers.NewDefaultPasswordGatherer(), }, pluginLoaders: NewPluginLoaders(), } diff --git a/internal/factsengine/factsengine_test.go b/internal/factsengine/factsengine_test.go index a290d515..d0370f14 100644 --- a/internal/factsengine/factsengine_test.go +++ b/internal/factsengine/factsengine_test.go @@ -359,7 +359,16 @@ func (suite *FactsEngineTestSuite) TestFactsEngineGetGatherersListNative() { gatherers := engine.GetGatherersList() - expectedGatherers := []string{"corosync.conf", "corosync-cmapctl", "package_version", "crm_mon", "cibadmin", "systemd", "sbd_config"} + expectedGatherers := []string{ + "corosync.conf", + "corosync-cmapctl", + "package_version", + "crm_mon", + "cibadmin", + "systemd", + "sbd_config", + "verify_password", + } suite.ElementsMatch(expectedGatherers, gatherers) }