diff --git a/README.md b/README.md index 001d64f..7c3192e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ different consumers for four different use cases: it also watches for user home directory to detect ssh key injection. - Access consumer, just watch __/access.conf__ - Generic consumer, watches for any existing or new files/directories for any given parent directory -- Sudoers consumer, watches __/sudoers__ and all files under __/etc/sudoers.d__ directory. +- Generic diff consumer, same as generic consumer, but provides diff of content instead of hashes All consumers hold their own states to keep track of changes and diffing. If a difference is spotted, the diff is logged to our stdout in json format. diff --git a/cfg/agent.toml b/cfg/agent.toml index 27345ef..412e3e5 100644 --- a/cfg/agent.toml +++ b/cfg/agent.toml @@ -6,8 +6,9 @@ bcc = "pkg/ebpf/vfs.o" [consumers] root = "/" access = "/access.conf" +genericDiff = ["/sudoers", "/etc/sudoers.d"] generic = ["/etc"] -sudoers = ["/sudoers", "/etc/sudoers.d"] +excludes = ["/etc/bookings/pool_roster"] [consumers.users] root = "/" diff --git a/cmd/main.go b/cmd/main.go index fe36112..6ac5b30 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -52,17 +52,17 @@ type ( HostRoleToken string } Consumers struct { - Root string - Access string - Sudoers []string - Users struct { + Root string + Access string + GenericDiff []string + Users struct { Shadow, Passwd string } Generic []string Excludes []string } } - // filesToMonitor is the struct for watching files, used for generic and sudoers consumers + // filesToMonitor is the struct for watching files, used for generic and generic diff consumers FileInfo struct { File string IsDir bool @@ -148,18 +148,18 @@ func (c Configuration) consumers(db *pkg.AgentDB) (consumers pkg.BaseConsumers) existingConsumersFiles[c.Consumers.Users.Passwd] = true } } - if len(c.Consumers.Sudoers) > 0 { + if len(c.Consumers.GenericDiff) > 0 { //get list of files to watch - sudoersFiles := c.getListOfFiles(fs, c.Consumers.Sudoers) - for _, sudoersFile := range sudoersFiles { - if !c.isFileToBeExcluded(sudoersFile.File, existingConsumersFiles) { - state := &pkg.SudoersState{ - SudoersListener: pkg.NewSudoersListener( - pkg.SudoersFileOpt(fs, sudoersFile.File, c.logger()), + genericDiffFiles := c.getListOfFiles(fs, c.Consumers.GenericDiff) + for _, genericDiffFile := range genericDiffFiles { + if !c.isFileToBeExcluded(genericDiffFile.File, existingConsumersFiles) { + state := &pkg.GenericDiffState{ + GenericDiffListener: pkg.NewGenericDiffListener( + pkg.GenericDiffFileOpt(fs, genericDiffFile.File, c.logger()), ), } consumers = append(consumers, &pkg.BaseConsumer{AgentDB: db, ParserLoader: state}) - existingConsumersFiles[sudoersFile.File] = true + existingConsumersFiles[genericDiffFile.File] = true } } } @@ -373,7 +373,7 @@ func (c Configuration) watcher() (*pkg.Watcher, error) { } } return pkg.NewWatcher(func(w *pkg.Watcher) { - w.Logger, w.Consumers, w.FIM, w.Database, w.Key, w.Excludes, w.Sudoers = logger, consumers.Consumers(), fim, database, c.key, c.Consumers.Excludes, c.Consumers.Sudoers + w.Logger, w.Consumers, w.FIM, w.Database, w.Key, w.Excludes, w.GenericDiff = logger, consumers.Consumers(), fim, database, c.key, c.Consumers.Excludes, c.Consumers.GenericDiff }), nil } diff --git a/docs/README.md b/docs/README.md index 119c5f4..4581cb1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ of state for dedicated structures. For the moment there's only __4 types of stru - users - access - generic -- sudoers +- genericDiff For each of those the internal structure is bit different, but the way it is logged is the same. Basically, when detecting a change bpfink will logs __3 different information__: @@ -24,7 +24,7 @@ is the same. Basically, when detecting a change bpfink will logs __3 different i - what has been added, under the `add` JSON key - what has been deleted, under the `del` JSON key - what is the current state, which as a different key depending on the consumer: -`users`, `generic`, `access`, `sudoers`. +`users`, `generic`, `access`, `genericDiff`. In order to avoid complex logging logic, if an internal part of a structure has changed, this structure is logged both as `add` and `del`, the difference can @@ -103,16 +103,18 @@ In this example the file dynamicPathFile was created. { "level": "warn", "add": { - "Sudoers": ["root ALL = (ALL:ALL) ALL"] + "Content": ["options timeout:2"] }, "del": { - "Sudoers": [] + "Content": [] }, - "file": "bpfink.sudoers", - "processName": "-bash ", + "file": "/etc/resolv.conf", + "processName": "bash", "user": "root", - "message": "Sudoers file modified" + "version": "0.6.51", + "message": "Critical Generic file modified" } ``` -In the above example the file bpfink.sudoers was modified. +In the above example the file /etc/resolv.conf was modified by adding an option. Instead of the hash as seen in generic consumer, +the diff of the content is logged. diff --git a/e2etests/basic_test.go b/e2etests/basic_test.go index 960d87c..636a726 100644 --- a/e2etests/basic_test.go +++ b/e2etests/basic_test.go @@ -12,7 +12,7 @@ func TestBPfink(t *testing.T) { world := SetUp(t) defer world.TearDown() t.Run("generic file create/modify/delete", world.SubTest(testCreateGenericFile)) - t.Run("sudoers file create", world.SubTest(testCreateSudoersDir)) + t.Run("generic file with diff create/modify/delete", world.SubTest(testCreateGenericDiffFile)) t.Run("generic file in newly created dir", world.SubTest(testCreateDirectory)) } @@ -37,23 +37,23 @@ func testCreateGenericFile(t *testing.T, w *World) { }) } -func testCreateSudoersDir(t *testing.T, w *World) { - sudoersFile := path.Join(w.FS.SudoersDir, "testSudoers") - f := w.FS.MustCreateFile(t, sudoersFile) +func testCreateGenericDiffFile(t *testing.T, w *World) { + genericDiffFile := path.Join(w.FS.GenericMonitoringDir, "test-generic", "test-generic-diff.txt") + f := w.FS.MustCreateFile(t, genericDiffFile) w.BPFink.ExpectEvent(t, Event{ - File: sudoersFile, - Message: "Sudoers file created", + File: genericDiffFile, + Message: "Critical Generic file created", }) f.WriteString("root ALL=(ALL) ALL") w.BPFink.ExpectEvent(t, Event{ - File: sudoersFile, - Message: "Sudoers file modified", + File: genericDiffFile, + Message: "Critical Generic file modified", }) - w.FS.MustRemoveFile(t, sudoersFile) + w.FS.MustRemoveFile(t, genericDiffFile) w.BPFink.ExpectEvent(t, Event{ - File: sudoersFile, - Message: "Sudoers file deleted", + File: genericDiffFile, + Message: "Critical Generic file deleted", }) } diff --git a/e2etests/bpfink.go b/e2etests/bpfink.go index 9304340..c039c90 100644 --- a/e2etests/bpfink.go +++ b/e2etests/bpfink.go @@ -26,7 +26,6 @@ type BPFinkRunParameters struct { BPFinkEbpfProgramm string TestRootDir string GenericMonitoringDir string - SudoersDir string } type baseFileLogRecord struct { @@ -45,13 +44,13 @@ type genericFileLogRecord struct { // nolint: deadcode, unused } } -type sudoersFileLogRecord struct { // nolint: deadcode, unused +type genericDiffFileLogRecord struct { // nolint: deadcode, unused baseFileLogRecord Add struct { - Sudoers []string + Content []string } Del struct { - Sudoers []string + Content []string } } @@ -69,7 +68,7 @@ const ( HEALTHY ) -func generateConfig(t *testing.T, testRootDir, genericDir string, ebpfProgrammPath string, sudoersDir string) string { +func generateConfig(t *testing.T, testRootDir, genericDir string, ebpfProgrammPath string) string { tmplt := strings.TrimSpace(` level = "info" database = "{{.TestRootDir}}/bpfink.db" @@ -79,7 +78,7 @@ bcc = "{{.EBPfProgrammPath}}" [consumers] root = "/" generic = ["{{.GenericMonitoringDir}}"] -sudoers = ["{{.SudoersDir}}"] +genericDiff = ["{{.GenericMonitoringDir}}/test-generic"] `) outConfigPath := path.Join(testRootDir, "agent.toml") @@ -93,8 +92,7 @@ sudoers = ["{{.SudoersDir}}"] TestRootDir string GenericMonitoringDir string EBPfProgrammPath string - SudoersDir string - }{testRootDir, genericDir, ebpfProgrammPath, sudoersDir}) + }{testRootDir, genericDir, ebpfProgrammPath}) if err != nil { t.Fatalf("failed to generate config file %s: %s", outConfigPath, err) @@ -130,8 +128,8 @@ func BPFinkRun(t *testing.T, params BPFinkRunParameters) *BPFinkInstance { t.Fatalf("unable to create dir for generic monitoring: %s", err) } - if err = os.Mkdir(params.SudoersDir, 0666); err != nil { - t.Fatalf("unable to create dir for sudoers monitoring: %s", err) + if err = os.Mkdir(path.Join(params.GenericMonitoringDir, "test-generic"), 0666); err != nil { + t.Fatalf("unable to create dir for generic file diff monitoring: %s", err) } //create broken symlink @@ -139,7 +137,7 @@ func BPFinkRun(t *testing.T, params BPFinkRunParameters) *BPFinkInstance { t.Fatalf("unable to create symlink for file: %s", err) } - configPath := generateConfig(t, params.TestRootDir, params.GenericMonitoringDir, params.BPFinkEbpfProgramm, params.SudoersDir) + configPath := generateConfig(t, params.TestRootDir, params.GenericMonitoringDir, params.BPFinkEbpfProgramm) instance.cmd = exec.Command( //nolint:gosec params.BPFinkBinPath, diff --git a/e2etests/fs.go b/e2etests/fs.go index afbfe96..ad9c5c4 100644 --- a/e2etests/fs.go +++ b/e2etests/fs.go @@ -7,7 +7,6 @@ import ( type FS struct { GenericMonitoringDir string - SudoersDir string } func (fs *FS) MustCreateFile(t *testing.T, filePath string) *os.File { diff --git a/e2etests/world.go b/e2etests/world.go index 9d86017..ce31f50 100644 --- a/e2etests/world.go +++ b/e2etests/world.go @@ -41,7 +41,6 @@ func SetUp(t *testing.T) *World { BPFinkEbpfProgramm: *executionParams.ebpfObjPath, TestRootDir: filepath.Join(testRootDir), GenericMonitoringDir: filepath.Join(testRootDir, "root"), - SudoersDir: filepath.Join(testRootDir, "sudoers"), } bpfink := BPFinkRun(t, bpfinkParams) @@ -57,7 +56,6 @@ func SetUp(t *testing.T) *World { BPFink: bpfink, FS: &FS{ GenericMonitoringDir: bpfinkParams.GenericMonitoringDir, - SudoersDir: bpfinkParams.SudoersDir, }, } } diff --git a/examples/watcher/setup.sh b/examples/watcher/setup.sh index 6df1a3c..9b45e92 100755 --- a/examples/watcher/setup.sh +++ b/examples/watcher/setup.sh @@ -44,12 +44,6 @@ _shadow() { EOF } -_sudoers () { - cat > bpfink.sudoers <<- EOF - root ALL = (ALL:ALL) ALL - EOF -} - _config () { echo "bcc = \"${PROJECT}/pkg/ebpf/vfs.o\"" >> bpfink.toml echo "keyfile = \"\"" >> bpfink.toml @@ -61,8 +55,8 @@ _config () { echo "root = \"/\"" >>bpfink.toml echo "access = \"${PROJECT}/examples/watcher/test-dir/bpfink.access\"" >> bpfink.toml + echo "genericDiff = [\"${PROJECT}/examples/watcher/test-dir/dynamic-watcher/test-generic-diff-file\", \"/etc/resolv.conf\"]" >> bpfink.toml echo "generic = [\"${PROJECT}/examples/watcher/test-dir/dynamic-watcher\", \"/etc\"]" >> bpfink.toml - echo "sudoers = [\"${PROJECT}/examples/watcher/test-dir/bpfink.sudoers\", \"/etc/sudoers.d\"]" >> bpfink.toml echo "excludes = [\"${PROJECT}/examples/watcher/test-dir/dynamic-watcher/exclude-dir\", \"${PROJECT}/examples/watcher/test-dir/excludeFile\"]" >>bpfink.toml cat >>bpfink.toml <<-'EOF' @@ -90,6 +84,7 @@ init() { mkdir -p dynamic-watcher/dir-test touch dynamic-watcher/testFile touch dynamic-watcher/dir-test/example.txt + touch dynamic-watcher/test-generic-diff-file cd dynamic-watcher || exit ln -s dir-test/example.txt linked_text mkdir exclude-dir @@ -99,7 +94,6 @@ init() { _passwd _shadow _access - _sudoers _config make -r -C "${PROJECT}/pkg/ebpf" || exit } @@ -132,13 +126,13 @@ run_test() { sed -i '$ d' bpfink.shadow sleep 2 - ##Sudoers - printf "\n\nadding 'ncircle ALL=(ALL) NOPASSWD:NCIRCLE_CMDS' and 'Cmnd_Alias NCIRCLE_CMDS=ALL, !/bin/netstat' to bpfink.sudoers\n\n" - echo "ncircle ALL=(ALL) NOPASSWD:NCIRCLE_CMDS" >>bpfink.sudoers - echo "Cmnd_Alias NCIRCLE_CMDS=ALL, !/bin/netstat" >>bpfink.sudoers + ##Generic File Diff + printf "\n\ntesting generic file diff\n\n" + echo "Test file" >>dynamic-watcher/test-generic-diff-file + echo "Generic file" >>dynamic-watcher/test-generic-diff-file sleep 2 printf "\n\nremove last addition\n" - sed -i '$ d' bpfink.sudoers + sed -i '$ d' dynamic-watcher/test-generic-diff-file sleep 2 printf "\n\ncreate a new file in dynamic-watcher\n\n" diff --git a/pkg/consumers.go b/pkg/consumers.go index 52a1bb1..3363bc2 100644 --- a/pkg/consumers.go +++ b/pkg/consumers.go @@ -334,97 +334,97 @@ func (gs *GenericState) Load(db *AgentDB) error { return err } -/* --------------------------------- SUDOERS --------------------------------- */ +/* --------------------------------- GENERIC FILE DIFF --------------------------------- */ type ( - //SudoersState struct keeps track of state changes based on SudoersListener struct and methods - SudoersState struct { - *SudoersListener - current, next Sudoers + //GenericDiffState struct keeps track of state changes based on GenericDiffListener struct and methods + GenericDiffState struct { + *GenericDiffListener + current, next GenericDiff } ) -//Parse calls parse(), and update new SudoersState -func (ss *SudoersState) Parse() (State, error) { - switch sudoers, err := ss.parse(); { +//Parse calls parse(), and update new GenericDiffState +func (gds *GenericDiffState) Parse() (State, error) { + switch genericDiff, err := gds.parse(); { case err == nil: - ss.next = sudoers - return ss, nil + gds.next = genericDiff + return gds, nil case IsNotExist(err): // file deleted - return ss, nil + return gds, nil default: return nil, err } } -//Changed checks if the new SudoersState instance is different from old SudoersState instance -func (ss *SudoersState) Changed() bool { - if ss.next.IsEmpty() && !ss.current.IsEmpty() { +//Changed checks if the new GenericDiffState instance is different from old GenericDiffState instance +func (gds *GenericDiffState) Changed() bool { + if gds.next.IsEmpty() && !gds.current.IsEmpty() { return true } - add, del := sudoersDiff(ss.current, ss.next) + add, del := findGenericDiff(gds.current, gds.next) return !add.IsEmpty() || !del.IsEmpty() } -//Created checks if the current SudoersState has been created -func (ss *SudoersState) Created() bool { return ss.current.IsEmpty() } +//Created checks if the current GenericDiffState has been created +func (gds *GenericDiffState) Created() bool { return gds.current.IsEmpty() } //Notify is the method to notify of a change in state -func (ss *SudoersState) Notify(cmd string, user string) { - add, del := sudoersDiff(ss.current, ss.next) - if ss.current.IsEmpty() { - ss.Warn(). - Object("add", LogSudoers(add)). - Object("del", LogSudoers(del)). - Str("file", ss.sudoers). +func (gds *GenericDiffState) Notify(cmd string, user string) { + add, del := findGenericDiff(gds.current, gds.next) + if gds.current.IsEmpty() { + gds.Warn(). + Object("add", LogGenericDiff(add)). + Object("del", LogGenericDiff(del)). + Str("file", gds.genericDiff). Str("processName", cmd). Str("user", user). - Msg("Sudoers file created") + Msg("Critical Generic file created") return } - if ss.next.IsEmpty() { - ss.Warn(). - Object("add", LogSudoers(add)). - Object("del", LogSudoers(del)). - Str("file", ss.sudoers). + if gds.next.IsEmpty() { + gds.Warn(). + Object("add", LogGenericDiff(add)). + Object("del", LogGenericDiff(del)). + Str("file", gds.genericDiff). Str("processName", cmd). Str("user", user). - Msg("Sudoers file deleted") + Msg("Critical Generic file deleted") return } - ss.Warn(). - Object("add", LogSudoers(add)). - Object("del", LogSudoers(del)). - Str("file", ss.sudoers). + gds.Warn(). + Object("add", LogGenericDiff(add)). + Object("del", LogGenericDiff(del)). + Str("file", gds.genericDiff). Str("processName", cmd). Str("user", user). - Msg("Sudoers file modified") + Msg("Critical Generic file modified") } //Teardown is the reset method when a change has been detected. Set new state to old state, and reload. -func (ss *SudoersState) Teardown() error { - ss.current = ss.next - ss.next = Sudoers{} +func (gds *GenericDiffState) Teardown() error { + gds.current = gds.next + gds.next = GenericDiff{} return nil } //Register returns a list of files to watch for changes -func (ss *SudoersState) Register() []string { - return ss.SudoersListener.Register() +func (gds *GenericDiffState) Register() []string { + return gds.GenericDiffListener.Register() } //Save commits a state to the local DB instance. -func (ss *SudoersState) Save(db *AgentDB) error { - ss.Debug().Object("sudoers", LogSudoers(ss.next)).Msg("Save sudoers file") - return db.SaveSudoers(ss.next) +func (gds *GenericDiffState) Save(db *AgentDB) error { + gds.Debug().Object("generic diff", LogGenericDiff(gds.next)).Msg("Save critical generic file") + return db.SaveGenericDiff(gds.next) } //Load reads in current state from local db instance -func (ss *SudoersState) Load(db *AgentDB) (err error) { - sudoers, err := db.LoadSudoers() +func (gds *GenericDiffState) Load(db *AgentDB) (err error) { + genericDiff, err := db.LoadGenericDiff() if err != nil { return err } - ss.current = sudoers + gds.current = genericDiff return err } diff --git a/pkg/db.go b/pkg/db.go index 4bc5bf8..a7ce2b8 100644 --- a/pkg/db.go +++ b/pkg/db.go @@ -14,11 +14,11 @@ type ( ) const ( - bpfinkDB = "bpfink" - usersKey = "users" - accessKey = "access" - genericKey = "generic" - sudoersKey = "sudoers" + bpfinkDB = "bpfink" + usersKey = "users" + accessKey = "access" + genericKey = "generic" + genericDiffKey = "genericDiff" ) func (a *AgentDB) save(k string, v interface{}) error { @@ -53,8 +53,6 @@ func (a *AgentDB) load(k string, v interface{}) error { }) } -// SaveSudoers method to save a sudoer - // SaveUsers method to save Users func (a *AgentDB) SaveUsers(users Users) error { return a.save(usersKey, users) } @@ -64,8 +62,10 @@ func (a *AgentDB) SaveAccess(access Access) error { return a.save(accessKey, acc // SaveGeneric method to save generic files func (a *AgentDB) SaveGeneric(generic Generic) error { return a.save(genericKey, generic) } -//SaveSudoers method to save sudoers -func (a *AgentDB) SaveSudoers(sudoers Sudoers) error { return a.save(sudoersKey, sudoers) } +//SaveGenericDiff method to save generic files that require a diff +func (a *AgentDB) SaveGenericDiff(genericDiff GenericDiff) error { + return a.save(genericDiffKey, genericDiff) +} //LoadUsers method to load users func (a *AgentDB) LoadUsers() (Users, error) { @@ -79,14 +79,14 @@ func (a *AgentDB) LoadAccess() (Access, error) { return access, a.load(accessKey, &access) } -// LoadGeneric method to load access +// LoadGeneric method to load generic files func (a *AgentDB) LoadGeneric() (Generic, error) { generic := Generic{} return generic, a.load(genericKey, &generic) } -//LoadUsers method to load users -func (a *AgentDB) LoadSudoers() (Sudoers, error) { - sudoers := Sudoers{} - return sudoers, a.load(sudoersKey, &sudoers) +//LoadGenericDiff method to load generic files that require a diff +func (a *AgentDB) LoadGenericDiff() (GenericDiff, error) { + genericDiff := GenericDiff{} + return genericDiff, a.load(genericDiffKey, &genericDiff) } diff --git a/pkg/generic_diff.go b/pkg/generic_diff.go new file mode 100644 index 0000000..9bd8189 --- /dev/null +++ b/pkg/generic_diff.go @@ -0,0 +1,85 @@ +package pkg + +import ( + "github.com/bookingcom/bpfink/pkg/lang/genericdiff" + "github.com/rs/zerolog" + "github.com/spf13/afero" +) + +type ( + //GenericDiff struct used to store changes to the generic file with diff + GenericDiff struct { + Rule []string + } + + //GenericDiffListener struct used for filestream events. + GenericDiffListener struct { + zerolog.Logger + afero.Fs + genericDiff string + } + + genericDiffListener struct { + GenericDiff + zerolog.Logger + } +) + +func findGenericDiff(old, new GenericDiff) (add, del GenericDiff) { + add, del = GenericDiff{}, GenericDiff{} + add.Rule, del.Rule = ArrayDiff(old.Rule, new.Rule) + return +} + +//IsEmpty method to check if diff is empty +func (gd GenericDiff) IsEmpty() bool { return len(gd.Rule) == 0 } + +//GenericDiffFileOpt function used to return metadata on a file +func GenericDiffFileOpt(fs afero.Fs, path string, logger zerolog.Logger) func(*GenericDiffListener) { + return func(listener *GenericDiffListener) { + listener.genericDiff = path + listener.Logger = logger + listener.Fs = fs + } +} + +//NewGenericDiffListener function to create a new file event listener +func NewGenericDiffListener(options ...func(*GenericDiffListener)) *GenericDiffListener { + gdl := &GenericDiffListener{Logger: zerolog.Nop()} + for _, option := range options { + option(gdl) + } + return gdl +} + +func (gdl *GenericDiffListener) parse() (GenericDiff, error) { + listener := &genericDiffListener{Logger: gdl.Logger} + gdl.Debug().Msgf("parsing critical generic file: %v", gdl.genericDiff) + + err := listener.genericDiffParse(gdl.genericDiff) + if err != nil { + return GenericDiff{}, err + } + return listener.GenericDiff, nil +} + +func (gdl *genericDiffListener) genericDiffParse(fileName string) error { + genericDiffData := genericdiff.Parser{FileName: fileName, Logger: gdl.Logger} + err := genericDiffData.Parse() + if err != nil { + return err + } + for _, genericDiffdata := range genericDiffData.GenericDiff { + gdl.GenericDiff.Rule = append(gdl.GenericDiff.Rule, genericDiffdata.Rule) + } + return nil +} + +//Register method returns list of paths to files to be watched +func (gdl *GenericDiffListener) Register() []string { + if base, ok := gdl.Fs.(*afero.BasePathFs); ok { + path, _ := base.RealPath(gdl.genericDiff) + return []string{path} + } + return []string{gdl.genericDiff} +} diff --git a/pkg/lang/sudoers/parser.go b/pkg/lang/genericdiff/parser.go similarity index 68% rename from pkg/lang/sudoers/parser.go rename to pkg/lang/genericdiff/parser.go index e36a0d9..4eb6e7c 100644 --- a/pkg/lang/sudoers/parser.go +++ b/pkg/lang/genericdiff/parser.go @@ -1,4 +1,4 @@ -package sudoers +package genericdiff import ( "bufio" @@ -7,16 +7,16 @@ import ( "github.com/rs/zerolog" ) -//Sudoer struct that represents permission in the suoders file -type Sudoer struct { +//Diff struct that represents permission in the suoders file +type Diff struct { Rule string } -//Parser struct to handle parsing of sudoers file +//Parser struct to handle parsing of generic file with diff type Parser struct { zerolog.Logger - FileName string - Sudoers []Sudoer + FileName string + GenericDiff []Diff } //Parse func that parses a passwd file to collect users @@ -38,7 +38,7 @@ func (p *Parser) Parse() error { } if stat.Size() == 0 { - p.Sudoers = append(p.Sudoers, Sudoer{ + p.GenericDiff = append(p.GenericDiff, Diff{ Rule: " ", }) } @@ -48,8 +48,7 @@ func (p *Parser) Parse() error { if len(line) == 0 || string(line[0]) == "#" { continue } - p.Logger.Debug().Msgf("Sudoers entries are %v", line) - p.Sudoers = append(p.Sudoers, Sudoer{ + p.GenericDiff = append(p.GenericDiff, Diff{ Rule: line, }) } diff --git a/pkg/logs.go b/pkg/logs.go index 35bfce7..2ac0c8f 100644 --- a/pkg/logs.go +++ b/pkg/logs.go @@ -19,8 +19,8 @@ type ( LogAccess Access // LogGeneric type wrapper LogGeneric GenericState - //LogSudoers type wrapper - LogSudoers Sudoers + //LogGenericDiff type wrapper + LogGenericDiff GenericDiff ) // MarshalZerologObject method to wrap a logger @@ -73,7 +73,7 @@ func (lg LogGeneric) MarshalZerologObject(e *zerolog.Event) { e.Hex("next", lg.next.Contents) // update to aes-gcm } -//MarshalZerologObject method to marshal sudoers object -func (ls LogSudoers) MarshalZerologObject(e *zerolog.Event) { - e.Strs("Sudoers", ls.Rule) +//MarshalZerologObject method to marshal generic diff object +func (lgd LogGenericDiff) MarshalZerologObject(e *zerolog.Event) { + e.Strs("Content", lgd.Rule) } diff --git a/pkg/sudoers.go b/pkg/sudoers.go deleted file mode 100644 index ce208ff..0000000 --- a/pkg/sudoers.go +++ /dev/null @@ -1,86 +0,0 @@ -package pkg - -import ( - "github.com/bookingcom/bpfink/pkg/lang/sudoers" - "github.com/rs/zerolog" - "github.com/spf13/afero" -) - -type ( - //Sudoers struct used to store changes to the sudoers file - Sudoers struct { - Rule []string - } - - //SudoersListener struct used for filestream events. - SudoersListener struct { - zerolog.Logger - afero.Fs - sudoers string - } - - sudoersListener struct { - Sudoers - zerolog.Logger - } -) - -func sudoersDiff(old, new Sudoers) (add, del Sudoers) { - add, del = Sudoers{}, Sudoers{} - add.Rule, del.Rule = ArrayDiff(old.Rule, new.Rule) - return -} - -//IsEmpty method to check if diff is empty -func (s Sudoers) IsEmpty() bool { return len(s.Rule) == 0 } - -//SudoersFileOpt function used to return metadata on a file -func SudoersFileOpt(fs afero.Fs, path string, logger zerolog.Logger) func(*SudoersListener) { - return func(listener *SudoersListener) { - listener.sudoers = path - listener.Logger = logger - listener.Fs = fs - } -} - -//NewSudoersListener function to create a new file event listener -func NewSudoersListener(options ...func(*SudoersListener)) *SudoersListener { - sl := &SudoersListener{Logger: zerolog.Nop()} - for _, option := range options { - option(sl) - } - return sl -} - -func (sl *SudoersListener) parse() (Sudoers, error) { - listener := &sudoersListener{Logger: sl.Logger} - sl.Debug().Msgf("parsing sudoers: %v", sl.sudoers) - - err := listener.sudoersParse(sl.sudoers) - if err != nil { - return Sudoers{}, err - } - return listener.Sudoers, nil -} - -func (sl *sudoersListener) sudoersParse(fileName string) error { - sudoersData := sudoers.Parser{FileName: fileName, Logger: sl.Logger} - sl.Debug().Msg("parsing sudoers file") - err := sudoersData.Parse() - if err != nil { - return err - } - for _, sudoersdata := range sudoersData.Sudoers { - sl.Sudoers.Rule = append(sl.Sudoers.Rule, sudoersdata.Rule) - } - return nil -} - -//Register method returns list of paths to files to be watched -func (sl *SudoersListener) Register() []string { - if base, ok := sl.Fs.(*afero.BasePathFs); ok { - path, _ := base.RealPath(sl.sudoers) - return []string{path} - } - return []string{sl.sudoers} -} diff --git a/pkg/watcher.go b/pkg/watcher.go index eb43884..6f8e4a8 100644 --- a/pkg/watcher.go +++ b/pkg/watcher.go @@ -21,7 +21,7 @@ type ( consumers Consumers CloseChannels chan struct{} Excludes []string - Sudoers []string + GenericDiff []string Metrics *Metrics } // Register defines register interface for a watcher @@ -137,16 +137,16 @@ func (w *Watcher) addInode(event *Event, isdir bool) { return } } - var isSudoerFile bool - for _, sudoersFile := range w.Sudoers { - if strings.HasPrefix(event.Path, sudoersFile) { - isSudoerFile = true + var isGenericDiffFile bool + for _, genericDiffFile := range w.GenericDiff { + if strings.HasPrefix(event.Path, genericDiffFile) { + isGenericDiffFile = true } } - if isSudoerFile { - state := &SudoersState{ - SudoersListener: NewSudoersListener(func(s *SudoersListener) { - s.sudoers = event.Path + if isGenericDiffFile { + state := &GenericDiffState{ + GenericDiffListener: NewGenericDiffListener(func(s *GenericDiffListener) { + s.genericDiff = event.Path s.Logger = w.Logger }), }