internal/rules
Rule engine for traffic filtering with pattern matching.
Overview
The rules package provides a thread-safe rule engine that evaluates commands and domains against configurable rules. Rules are defined in YAML and support glob-style patterns for commands and wildcard patterns for domains.
Types
Rule
A single filtering rule.
type Rule struct {
ID string `yaml:"id"`
Pattern string `yaml:"pattern,omitempty"` // Command pattern (glob)
Domain string `yaml:"domain,omitempty"` // Domain pattern
Action string `yaml:"action"` // "block" or "allow"
Description string `yaml:"description,omitempty"`
Enabled bool `yaml:"enabled"`
}RuleSet
Collection of rules, typically loaded from YAML.
type RuleSet struct {
Rules []Rule `yaml:"rules"`
}Engine
Thread-safe rule evaluation engine.
type Engine struct {
rules *RuleSet
compiled []*CompiledRule
rulesPath string
defaultAction string // "allow" or "block"
}CompiledRule
Pre-compiled rule with regex matchers for efficient evaluation.
type CompiledRule struct {
Rule *Rule
CommandMatcher *regexp.Regexp
DomainMatcher *regexp.Regexp
}Engine Functions
NewEngine
Creates a new rule engine with optional configuration.
func NewEngine(opts ...EngineOption) *Engine
// Options
func WithDefaultAction(action string) EngineOptionDefault action is "allow" when no rules match.
Loading Rules
// From file
func (e *Engine) LoadRules(path string) error
// From bytes (for testing or embedded configs)
func (e *Engine) LoadRulesFromBytes(data []byte) error
// Reload from previously loaded path
func (e *Engine) Reload() errorChecking Traffic
// Check a command
func (e *Engine) CheckCommand(command string) (allowed bool, matchedRule *Rule, reason string)
// Check a domain
func (e *Engine) CheckDomain(domain string) (allowed bool, matchedRule *Rule, reason string)Utility Methods
func (e *Engine) RulesPath() string // Current rules file path
func (e *Engine) RuleCount() int // Number of loaded rulesLoader Functions
// Load from file
func LoadFromFile(path string) (*RuleSet, error)
// Parse from bytes
func LoadFromBytes(data []byte) (*RuleSet, error)
// Save to file
func SaveToFile(rs *RuleSet, path string) errorMatcher Functions
CompileRule
Compiles a rule’s patterns into regex matchers.
func CompileRule(r *Rule) (*CompiledRule, error)CompiledRule Methods
func (cr *CompiledRule) MatchCommand(cmd string) bool
func (cr *CompiledRule) MatchDomain(domain string) boolPattern Syntax
Command Patterns (Glob)
Use * as a wildcard that matches any sequence of characters.
| Pattern | Matches | Doesn’t Match |
|---|---|---|
rm -rf * | rm -rf /, rm -rf /tmp | rm file.txt |
| `curl * | *sh` | `curl url |
*ssh* | ssh host, openssh-client | scp file |
Matching is case-insensitive.
Domain Patterns
Three pattern types:
Exact match:
example.com- Matches only
example.com - Does NOT match
www.example.com
- Matches only
Wildcard subdomain:
*.example.com- Matches
sub.example.com,a.b.example.com - Also matches base
example.com
- Matches
Contains wildcard:
*xmr*- Matches any domain containing "xmr"
- Examples:
xmrpool.net,pool.xmr.io
Exact match: example.com
YAML Rule Format
rules:
# Block dangerous commands
- id: block-rm-rf
pattern: "rm -rf *"
action: block
description: "Block recursive delete"
enabled: true
# Block curl pipe to shell
- id: block-curl-pipe
pattern: "curl * | *sh"
action: block
description: "Block curl pipe to shell"
enabled: true
# Block specific domain
- id: block-pastebin
domain: "pastebin.com"
action: block
description: "Block pastebin"
enabled: true
# Block all subdomains
- id: block-temp-hosts
domain: "*.temp.sh"
action: block
description: "Block temporary file hosts"
enabled: true
# Block domains containing pattern
- id: block-xmr
domain: "*xmr*"
action: block
description: "Block crypto mining domains"
enabled: true
# Allow rule (takes precedence if matched first)
- id: allow-ls
pattern: "ls *"
action: allow
description: "Explicitly allow ls"
enabled: trueUsage Examples
Basic Usage
engine := rules.NewEngine()
// Load rules from file
if err := engine.LoadRules("rules.yaml"); err != nil {
log.Fatal(err)
}
// Check a command
allowed, rule, reason := engine.CheckCommand("rm -rf /tmp")
if !allowed {
fmt.Printf("Blocked: %s (rule: %s)n", reason, rule.ID)
}
// Check a domain
allowed, rule, reason = engine.CheckDomain("pastebin.com")
if !allowed {
fmt.Printf("Blocked: %s (rule: %s)n", reason, rule.ID)
}Default Block Policy
// Block everything not explicitly allowed
engine := rules.NewEngine(rules.WithDefaultAction("block"))
err := engine.LoadRulesFromBytes([]byte(`
rules:
- id: allow-google
domain: "*.google.com"
action: allow
enabled: true
`))
// google.com is allowed
allowed, _, _ := engine.CheckDomain("api.google.com")
// allowed == true
// Everything else is blocked by default
allowed, _, reason := engine.CheckDomain("unknown.com")
// allowed == false, reason == "blocked by default policy"Hot Reload
engine := rules.NewEngine()
engine.LoadRules("/etc/plasma-shield/rules.yaml")
// Later, reload rules without restart
if err := engine.Reload(); err != nil {
log.Printf("Failed to reload rules: %v", err)
}Testing Examples
Wildcard subdomain: *.example.com
func TestCheckCommand(t *testing.T) {
e := NewEngine()
yaml := `
rules:
- id: block-rm-rf
pattern: "rm -rf *"
action: block
description: "Block recursive delete"
enabled: true
`
if err := e.LoadRulesFromBytes([]byte(yaml)); err != nil {
t.Fatalf("LoadRulesFromBytes: %v", err)
}
tests := []struct {
cmd string
allowed bool
ruleID string
}{
{"rm -rf /", false, "block-rm-rf"},
{"rm file.txt", true, ""},
{"ls -la", true, ""},
}
for _, tt := range tests {
allowed, rule, _ := e.CheckCommand(tt.cmd)
if allowed != tt.allowed {
t.Errorf("CheckCommand(%q) = %v, want %v", tt.cmd, allowed, tt.allowed)
}
}
}Thread Safety
Contains wildcard: *xmr*
- Matches only
example.com - Does NOT match
www.example.com
Rule Evaluation Order
- Rules are evaluated in definition order
- First matching rule determines the outcome
- If no rule matches, default action is used (allow or block)
Package Dependencies
rules
├── regexp - Pattern compilation
├── sync - Thread safety
├── gopkg.in/yaml.v3 - YAML parsing
└── os - File I/O