@@ -7,13 +7,17 @@ package resources
77import (
88 "errors"
99 "fmt"
10+ "regexp"
1011 "slices"
1112 "strings"
1213 "sync"
14+ "unicode"
1315
16+ "go.mondoo.com/cnquery/v11/checksums"
1417 "go.mondoo.com/cnquery/v11/llx"
1518 "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
1619 "go.mondoo.com/cnquery/v11/providers/os/resources/parsers"
20+ "go.mondoo.com/cnquery/v11/types"
1721 "go.mondoo.com/cnquery/v11/utils/multierr"
1822)
1923
@@ -135,3 +139,280 @@ var auditdDowncaseKeywords = []string{
135139 "enable_krb5" ,
136140 "overflow_action" ,
137141}
142+
143+ type mqlAuditdRulesInternal struct {
144+ lock sync.Mutex
145+ loaded bool
146+ loadError error
147+ }
148+
149+ const defaultAuditdRules = "/etc/audit/rules.d"
150+
151+ func (s * mqlAuditdRules ) id () (string , error ) {
152+ return s .Path .Data , nil
153+ }
154+
155+ func (s * mqlAuditdRules ) path () (string , error ) {
156+ return defaultAuditdRules , nil
157+ }
158+
159+ func (s * mqlAuditdRules ) load (path string ) error {
160+ s .lock .Lock ()
161+ defer s .lock .Unlock ()
162+ if s .loaded {
163+ return s .loadError
164+ }
165+
166+ if path == "" {
167+ return errors .New ("the path must be non-empty to parse auditd rules" )
168+ }
169+
170+ files , err := getSortedPathFiles (s .MqlRuntime , path )
171+ if err != nil {
172+ s .Controls = plugin.TValue [[]any ]{State : plugin .StateIsSet , Error : err }
173+ s .Files = plugin.TValue [[]any ]{State : plugin .StateIsSet , Error : err }
174+ s .Syscalls = plugin.TValue [[]any ]{State : plugin .StateIsSet , Error : err }
175+ return err
176+ }
177+
178+ var errors multierr.Errors
179+ for i := range files {
180+ file := files [i ].(* mqlFile )
181+
182+ bn := file .GetBasename ()
183+ if ! strings .HasSuffix (bn .Data , ".rules" ) {
184+ continue
185+ }
186+
187+ content := file .GetContent ()
188+ if content .Error != nil {
189+ return content .Error
190+ }
191+
192+ s .parse (content .Data , & errors )
193+ }
194+
195+ s .loadError = errors .Deduplicate ()
196+ s .loaded = true
197+ return s .loadError
198+ }
199+
200+ func parseKeyVal (line string ) (string , string , int ) {
201+ runes := []rune (line )
202+ i := 0
203+
204+ // invalid prefix
205+ if line [i ] != '-' {
206+ for ; i < len (runes ); i ++ {
207+ if unicode .IsSpace (runes [i ]) {
208+ break
209+ }
210+ }
211+ for ; i < len (runes ); i ++ {
212+ if ! unicode .IsSpace (runes [i ]) {
213+ break
214+ }
215+ }
216+ return "" , "" , i
217+ }
218+
219+ if len (line ) < 2 {
220+ return "" , "" , len (line )
221+ }
222+ if line [1 ] == '-' {
223+ i = 2
224+ } else {
225+ i = 1
226+ }
227+
228+ for ; i < len (runes ); i ++ {
229+ if unicode .IsSpace (runes [i ]) {
230+ break
231+ }
232+ }
233+ if i == len (runes ) {
234+ return line , "" , i
235+ }
236+ keyend := i
237+
238+ for ; i < len (runes ); i ++ {
239+ if ! unicode .IsSpace (runes [i ]) {
240+ break
241+ }
242+ }
243+ valstart := i
244+ for ; i < len (runes ); i ++ {
245+ if unicode .IsSpace (runes [i ]) {
246+ break
247+ }
248+ }
249+ valend := i
250+
251+ for ; i < len (runes ); i ++ {
252+ if ! unicode .IsSpace (runes [i ]) {
253+ break
254+ }
255+ }
256+
257+ return line [:keyend ], line [valstart :valend ], i
258+ }
259+
260+ var reOperator = regexp .MustCompile (`(=|!=|<|<=|>|>=)` )
261+
262+ func (s * mqlAuditdRules ) parse (content string , errors * multierr.Errors ) {
263+ s .Syscalls .State = plugin .StateIsSet
264+ s .Files .State = plugin .StateIsSet
265+ s .Controls .State = plugin .StateIsSet
266+
267+ lines := strings .Split (content , "\n " )
268+ for _ , rawline := range lines {
269+ line := strings .TrimSpace (rawline )
270+ if line == "" || line [0 ] == '#' {
271+ continue
272+ }
273+
274+ resourceName := "auditd.rule.control"
275+ args := map [string ]* llx.RawData {}
276+ rawFields := []string {}
277+ syscalls := []any {}
278+ other := [][2 ]string {}
279+
280+ for line != "" {
281+ k , v , idx := parseKeyVal (line )
282+ line = line [idx :]
283+
284+ switch k {
285+ case "-a" :
286+ resourceName = "auditd.rule.syscall"
287+ arr := strings .SplitN (v , "," , 2 )
288+ args ["action" ] = llx .StringData (arr [0 ])
289+ args ["list" ] = llx .StringData (arr [1 ])
290+
291+ case "-F" :
292+ rawFields = append (rawFields , v )
293+
294+ case "-w" :
295+ resourceName = "auditd.rule.file"
296+ args ["path" ] = llx .StringData (v )
297+
298+ case "-k" :
299+ args ["keyname" ] = llx .StringData (v )
300+
301+ case "-p" :
302+ args ["permissions" ] = llx .StringData (v )
303+
304+ case "-S" :
305+ syscalls = append (syscalls , v )
306+
307+ default :
308+ other = append (other , [2 ]string {k , v })
309+ }
310+ }
311+
312+ switch resourceName {
313+ case "auditd.rule.file" :
314+ if _ , ok := args ["keyname" ]; ! ok {
315+ args ["keyname" ] = llx .StringData ("" )
316+ }
317+
318+ r , err := CreateResource (s .MqlRuntime , resourceName , args )
319+ if err != nil {
320+ errors .Add (err )
321+ continue
322+ }
323+ s .Files .Data = append (s .Files .Data , r )
324+
325+ case "auditd.rule.syscall" :
326+ args ["syscalls" ] = llx .ArrayData (syscalls , types .String )
327+
328+ fields := make ([]any , len (rawFields ))
329+ for i , raw := range rawFields {
330+ op := reOperator .FindString (raw )
331+ if op == "" {
332+ fields [i ] = map [string ]any {"key" : raw }
333+ continue
334+ }
335+ // it must exist according to the preceding statement
336+ idx := strings .Index (raw , op )
337+ fields [i ] = map [string ]any {
338+ "key" : raw [0 :idx ],
339+ "op" : raw [idx : idx + len (op )],
340+ "value" : raw [idx + len (op ):],
341+ }
342+ }
343+ args ["fields" ] = llx .ArrayData (fields , types .Dict )
344+
345+ if _ , ok := args ["keyname" ]; ! ok {
346+ args ["keyname" ] = llx .StringData ("" )
347+ }
348+
349+ r , err := CreateResource (s .MqlRuntime , resourceName , args )
350+ if err != nil {
351+ errors .Add (err )
352+ continue
353+ }
354+ s .Syscalls .Data = append (s .Syscalls .Data , r )
355+
356+ default :
357+ for io := range other {
358+ r , err := CreateResource (s .MqlRuntime , resourceName , map [string ]* llx.RawData {
359+ "flag" : llx .StringData (other [io ][0 ]),
360+ "value" : llx .StringData (other [io ][1 ]),
361+ })
362+ if err != nil {
363+ errors .Add (err )
364+ continue
365+ }
366+ s .Controls .Data = append (s .Controls .Data , r )
367+ }
368+ }
369+ }
370+ }
371+
372+ func (s * mqlAuditdRules ) controls (path string ) ([]any , error ) {
373+ return nil , s .load (path )
374+ }
375+
376+ func (s * mqlAuditdRules ) files (path string ) ([]any , error ) {
377+ return nil , s .load (path )
378+ }
379+
380+ func (s * mqlAuditdRules ) syscalls (path string ) ([]any , error ) {
381+ return nil , s .load (path )
382+ }
383+
384+ func (s * mqlAuditdRuleFile ) id () (string , error ) {
385+ var f checksums.Fast
386+ return f .
387+ Add (s .Path .Data ).
388+ Add (s .Permissions .Data ).
389+ Add (s .Keyname .Data ).
390+ String (), nil
391+ }
392+
393+ func (s * mqlAuditdRuleControl ) id () (string , error ) {
394+ var f checksums.Fast
395+ return f .
396+ Add (s .Flag .Data ).
397+ Add (s .Value .Data ).
398+ String (), nil
399+ }
400+
401+ func (s * mqlAuditdRuleSyscall ) id () (string , error ) {
402+ var f checksums.Fast
403+ f = f .
404+ Add (s .Action .Data ).
405+ Add (s .List .Data ).
406+ Add (s .Keyname .Data )
407+ for i := range s .Syscalls .Data {
408+ f = f .Add (s .Syscalls .Data [i ].(string ))
409+ }
410+ for i := range s .Fields .Data {
411+ c := s .Fields .Data [i ].(map [string ]any )
412+ for k , v := range c {
413+ f = f .Add (k ).Add (v .(string ))
414+ }
415+ }
416+
417+ return f .String (), nil
418+ }
0 commit comments