Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions cmd/fibratus/app/rules/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2021-2022 by Nedim Sabic Sabic
* https://www.fibratus.io
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package rules

import (
"fmt"
"github.com/enescakir/emoji"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/rabbitstack/fibratus/internal/bootstrap"
"os"
"strings"
)

func listRules() error {
if err := bootstrap.InitConfigAndLogger(cfg); err != nil {
return err
}
if err := cfg.Filters.LoadGroups(); err != nil {
return fmt.Errorf("%v %v", emoji.DisappointedFace, err)
}
groups := cfg.GetRuleGroups()
if len(groups) == 0 {
return fmt.Errorf("%v no rules found in %s", emoji.DisappointedFace, strings.Join(cfg.Filters.Rules.FromPaths, ","))
}

t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleLight)

// render summary
if summarized {
t.AppendHeader(table.Row{"Tactic", "# Rules"})
t.SetColumnConfigs([]table.ColumnConfig{
{Name: "Tactic", WidthMin: 50, WidthMax: 50},
{Name: "#", WidthMin: 50, WidthMax: 50},
})
tactics := make(map[string]int)
techniques := make(map[string]int)
for _, group := range groups {
tactics[group.Labels["tactic.name"]] += len(group.Rules)
techniques[group.Labels["technique.name"]] += len(group.Rules)
}
tot := 0
for tac, n := range tactics {
t.AppendRow(table.Row{tac, n})
tot += n
}

t.AppendSeparator()
t.AppendRow(table.Row{"TECHNIQUE", "# RULES"})
t.AppendSeparator()

for tec, n := range techniques {
t.AppendRow(table.Row{tec, n})
}

t.AppendFooter(table.Row{"TOTAL", tot})
} else {
// show all rules
t.AppendHeader(table.Row{"#", "Rule", "Technique", "Tactic"})
t.SetColumnConfigs([]table.ColumnConfig{
{Name: "#", WidthMax: 5},
{Name: "Rule"},
{Name: "Technique"},
{Name: "Tactic", WidthMax: 50},
})

n := 0
tactics := make(map[string]int)
techniques := make(map[string]int)

for _, group := range groups {
tac := group.Labels["tactic.name"]
tec := group.Labels["technique.name"]
if _, ok := tactics[tac]; !ok {
tactics[tac] = 1
}
if _, ok := tactics[tec]; !ok {
techniques[tec] = 1
}
for _, rule := range group.Rules {
t.AppendRow(table.Row{n + 1, rule.Name, group.Name, group.Labels["tactic.name"]})
n++
}
}

var (
totTat int
totTec int
)

for _, n := range tactics {
totTat += n
}
for _, n := range techniques {
totTec += n
}

t.AppendFooter(table.Row{"TOTAL", n, totTec, totTat})
}

t.Render()

return nil
}
67 changes: 67 additions & 0 deletions cmd/fibratus/app/rules/rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2021-2022 by Nedim Sabic Sabic
* https://www.fibratus.io
* All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package rules

import (
"fmt"
"github.com/rabbitstack/fibratus/pkg/config"
"github.com/spf13/cobra"
)

var Command = &cobra.Command{
Use: "rules",
Short: "Validate, list, or search detection rules",
}

var validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate rules for structural and syntactic correctness",
RunE: validate,
}

var listCmd = &cobra.Command{
Use: "list",
Short: "List rules",
RunE: list,
}

var cfg = config.NewWithOpts(config.WithValidate(), config.WithList())

var (
summarized bool
)

func init() {
cfg.MustViperize(Command)

Command.AddCommand(validateCmd)

listCmd.PersistentFlags().BoolVarP(&summarized, "summary", "s", false, "Show rules summary by MITRE tactics and techniques")
Command.AddCommand(listCmd)
}

func validate(cmd *cobra.Command, args []string) error {
return validateRules()
}

func list(cmd *cobra.Command, args []string) error {
return listRules()
}

func emo(s string, args ...any) { fmt.Printf(s, args...) }
56 changes: 20 additions & 36 deletions cmd/fibratus/app/rules/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,21 @@ import (
"fmt"
"github.com/enescakir/emoji"
"github.com/rabbitstack/fibratus/internal/bootstrap"
"github.com/rabbitstack/fibratus/pkg/config"
"github.com/rabbitstack/fibratus/pkg/filter"
"github.com/rabbitstack/fibratus/pkg/filter/fields"
"github.com/spf13/cobra"
"path/filepath"
"strings"
)

var Command = &cobra.Command{
Use: "rules",
Short: "Validate, list, or search detection rules",
}

var validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate rules for structural and syntactic correctness",
RunE: validate,
}

var cfg = config.NewWithOpts(config.WithValidate())

func init() {
cfg.MustViperize(Command)
Command.AddCommand(validateCmd)
}

func validate(cmd *cobra.Command, args []string) error {
func validateRules() error {
if err := bootstrap.InitConfigAndLogger(cfg); err != nil {
return err
}

isValidExt := func(path string) bool {
return filepath.Ext(path) == ".yml" || filepath.Ext(path) == ".yaml"
}

// load macros and rules
for _, m := range cfg.Filters.Macros.FromPaths {
paths, err := filepath.Glob(m)
if err != nil {
Expand All @@ -65,7 +46,7 @@ func validate(cmd *cobra.Command, args []string) error {
if !isValidExt(path) {
continue
}
emo("%v Loading macros from %s\n", emoji.Magnet, path)
emo("%v Loading macros from %s\n", emoji.Hook, path)
}
}
if err := cfg.Filters.LoadMacros(); err != nil {
Expand All @@ -87,31 +68,34 @@ func validate(cmd *cobra.Command, args []string) error {
if err := cfg.Filters.LoadGroups(); err != nil {
return fmt.Errorf("%v %v", emoji.DisappointedFace, err)
}
if len(cfg.GetRuleGroups()) == 0 {
return fmt.Errorf("%v no rules found in %s", emoji.DisappointedFace, strings.Join(cfg.Filters.Rules.FromPaths, ","))
}

warnings := make([]string, 0)
// validate rule for every group
for _, group := range cfg.GetRuleGroups() {
for _, rule := range group.Rules {
f := filter.New(rule.Condition, cfg)
err := f.Compile()
if err != nil {
return fmt.Errorf("%v %v", emoji.DisappointedFace, filter.ErrInvalidFilter(rule.Name, group.Name, err))
}
for _, field := range f.GetFields() {
deprecated, d := fields.IsDeprecated(field)
if deprecated {
emo("%v Deprecation: %s rule uses "+
"the [%s] field which was deprecated starting "+
"from version %s. "+
"Please consider migrating to %s field(s) "+
"because [%s] will be removed in future versions\n",
emoji.Warning, rule.Name, field, d.Since, d.Fields, field)
for _, fld := range f.GetFields() {
if isDeprecated, dep := fields.IsDeprecated(fld); isDeprecated {
warnings = append(warnings,
fmt.Sprintf("%s field deprecated in favor of %v in rule %s", fld.String(), dep.Fields, rule.Name))
}
}
}
}
if len(warnings) > 0 {
for _, warn := range warnings {
emo("%v %s\n", emoji.Warning, warn)
}
fmt.Printf("%d warning(s)\n", len(warnings))
}

emo("%v Detection rules OK. Ready to go!", emoji.Rocket)

emo("%v Validation successful. Ready to go!", emoji.Rocket)
return nil
}

func emo(s string, args ...any) { fmt.Printf(s, args...) }