Skip to content

Commit 09d7e60

Browse files
committed
maybe simplify the YAML processing for multi-OS keys
1 parent 51ae37f commit 09d7e60

File tree

5 files changed

+262
-220
lines changed

5 files changed

+262
-220
lines changed

cmd/agent/checks.yaml

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ checks:
2626
- Enable full disk encryption using LUKS
2727
- Run 'sudo cryptsetup luksFormat /dev/sdX' for each unencrypted partition
2828
- Update /etc/crypttab and /etc/fstab accordingly
29+
# Check for ZFS root encryption (zroot is common on Linux with ZFS)
30+
- output: sudo zfs get -H encryption zroot 2>/dev/null || doas zfs get -H encryption zroot 2>/dev/null || zfs get -H encryption zroot 2>/dev/null
31+
includes: "zroot.*encryption.*off"
32+
remediation:
33+
- ZFS root pool is not encrypted
34+
- Create an encrypted ZFS dataset or migrate to an encrypted pool
35+
- "For new pools: zpool create -O encryption=on -O keyformat=passphrase zroot ..."
36+
- Note that existing pools cannot be encrypted in-place
2937
darwin:
3038
- output: fdesetup status
3139
includes: "FileVault is Off|Encryption Not Enabled"
@@ -40,6 +48,14 @@ checks:
4048
- Enable GELI disk encryption
4149
- See FreeBSD Handbook chapter on disk encryption
4250
- Use 'geli init' to initialize encrypted providers
51+
# Check for ZFS root encryption (zroot is common on FreeBSD)
52+
- output: sudo zfs get -H encryption zroot 2>/dev/null || doas zfs get -H encryption zroot 2>/dev/null || zfs get -H encryption zroot 2>/dev/null
53+
includes: "zroot.*encryption.*off"
54+
remediation:
55+
- ZFS root pool is not encrypted
56+
- Create an encrypted ZFS dataset or migrate to an encrypted pool
57+
- "For new pools: zpool create -O encryption=on -O keyformat=passphrase zroot ..."
58+
- Note that existing pools cannot be encrypted in-place
4359
openbsd:
4460
- output: doas bioctl softraid0
4561
includes: "No such device|not configured"
@@ -54,19 +70,19 @@ checks:
5470
- Enable CGD (CryptoGraphic Disk) encryption
5571
- Configure /etc/cgd/cgd.conf
5672
- See NetBSD guide on CGD configuration
57-
linux, freebsd:
58-
# Check for ZFS root encryption (zroot is common on FreeBSD and Linux)
59-
- output: (sudo zfs get -H encryption zroot 2>/dev/null || doas zfs get -H encryption zroot 2>/dev/null || zfs get -H encryption zroot 2>/dev/null) | awk '{print $3}'
60-
includes: "^off$"
73+
solaris:
74+
# Check for ZFS root encryption (rpool is standard on Solaris)
75+
- output: sudo zfs get -H encryption rpool 2>/dev/null || doas zfs get -H encryption rpool 2>/dev/null || zfs get -H encryption rpool 2>/dev/null
76+
includes: "rpool.*encryption.*off"
6177
remediation:
6278
- ZFS root pool is not encrypted
6379
- Create an encrypted ZFS dataset or migrate to an encrypted pool
64-
- "For new pools: zpool create -O encryption=on -O keyformat=passphrase zroot ..."
80+
- "For new pools: zpool create -O encryption=on -O keyformat=passphrase rpool ..."
6581
- Note that existing pools cannot be encrypted in-place
66-
solaris, illumos:
67-
# Check for ZFS root encryption (rpool is standard on Solaris/illumos)
68-
- output: (sudo zfs get -H encryption rpool 2>/dev/null || doas zfs get -H encryption rpool 2>/dev/null || zfs get -H encryption rpool 2>/dev/null) | awk '{print $3}'
69-
includes: "^off$"
82+
illumos:
83+
# Check for ZFS root encryption (rpool is standard on illumos)
84+
- output: sudo zfs get -H encryption rpool 2>/dev/null || doas zfs get -H encryption rpool 2>/dev/null || zfs get -H encryption rpool 2>/dev/null
85+
includes: "rpool.*encryption.*off"
7086
remediation:
7187
- ZFS root pool is not encrypted
7288
- Create an encrypted ZFS dataset or migrate to an encrypted pool
@@ -133,30 +149,30 @@ checks:
133149
- Configure iptables rules for basic protection
134150
freebsd:
135151
# Try sudo first, then doas, then direct access
136-
- output: (sudo pfctl -s info 2>/dev/null || doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null) | grep -i status
137-
includes: "Disabled"
152+
- output: sudo pfctl -s info 2>/dev/null || doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null
153+
includes: "Status:.*Disabled"
138154
remediation:
139155
- Enable PF firewall with 'sudo pfctl -e' or 'doas pfctl -e'
140156
- Configure /etc/pf.conf with appropriate rules
141157
- Add pf_enable="YES" to /etc/rc.conf
142158
- Start with 'service pf start'
143159
# Also check if PF is even loaded in the kernel
144-
- output: kldstat | grep -q pf.ko && echo "loaded" || echo "not loaded"
145-
includes: "not loaded"
160+
- output: kldstat
161+
excludes: "pf\\.ko"
146162
remediation:
147163
- Load PF kernel module with 'sudo kldload pf'
148164
- Add pf_load="YES" to /boot/loader.conf for persistence
149165
# Check if ipfw is being used instead of pf
150-
- output: (sudo ipfw list 2>/dev/null || doas ipfw list 2>/dev/null || ipfw list 2>/dev/null) | head -1
166+
- output: sudo ipfw list 2>/dev/null || doas ipfw list 2>/dev/null || ipfw list 2>/dev/null
151167
excludes: "00001|65535"
152168
remediation:
153169
- IPFW firewall appears to be inactive or not configured
154170
- Add firewall rules with 'ipfw add' commands
155171
- Configure /etc/rc.conf with firewall_enable="YES"
156172
openbsd:
157173
# OpenBSD has PF enabled by default, check if it's actually filtering
158-
- output: (doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null) | grep -i status
159-
includes: "Disabled"
174+
- output: doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null
175+
includes: "Status:.*Disabled"
160176
remediation:
161177
- Enable PF firewall with 'doas pfctl -e' or 'pfctl -e'
162178
- Configure /etc/pf.conf with appropriate rules
@@ -169,16 +185,16 @@ checks:
169185
- Configure firewall rules in /etc/pf.conf
170186
- Load rules with 'doas pfctl -f /etc/pf.conf'
171187
netbsd:
172-
- output: (sudo npfctl show 2>/dev/null || doas npfctl show 2>/dev/null || npfctl show 2>/dev/null) | grep -i config
188+
- output: sudo npfctl show 2>/dev/null || doas npfctl show 2>/dev/null || npfctl show 2>/dev/null
173189
includes: "inactive|empty"
174190
remediation:
175191
- Enable NPF firewall
176192
- Configure /etc/npf.conf with firewall rules
177193
- Add npf=YES to /etc/rc.conf
178194
- Start with 'service npf start'
179195
dragonfly:
180-
- output: (sudo pfctl -s info 2>/dev/null || doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null) | grep -i status
181-
includes: "Disabled"
196+
- output: sudo pfctl -s info 2>/dev/null || doas pfctl -s info 2>/dev/null || pfctl -s info 2>/dev/null
197+
includes: "Status:.*Disabled"
182198
remediation:
183199
- Enable PF firewall with 'sudo pfctl -e' or 'doas pfctl -e'
184200
- Configure /etc/pf.conf with appropriate rules
@@ -597,7 +613,7 @@ checks:
597613
antivirus:
598614
description: "Detect antivirus/anti-malware software"
599615
darwin:
600-
- output: system_profiler SPInstallHistoryDataType | grep -i "xprotect\|malware"
616+
- output: system_profiler SPInstallHistoryDataType
601617
excludes: "XProtect|MRT"
602618
remediation:
603619
- macOS includes built-in XProtect antivirus

cmd/agent/executeCheck.go

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -84,51 +84,55 @@ func (a *Agent) executeCommand(ctx context.Context, checkName string, rule confi
8484
start := time.Now()
8585
command := rule.Output
8686

87-
// Extract the primary command (first word) to check if it exists
88-
commandParts := strings.Fields(command)
89-
if len(commandParts) > 0 {
90-
primaryCmd := commandParts[0]
91-
92-
// Check if this is a shell builtin or special command
93-
shellBuiltins := map[string]bool{
94-
"echo": true, "test": true, "[": true, "[[": true, "if": true,
95-
"then": true, "else": true, "fi": true, "for": true, "while": true,
96-
"do": true, "done": true, "case": true, "esac": true, "function": true,
97-
"return": true, "break": true, "continue": true, "exit": true,
98-
"source": true, ".": true, "eval": true, "exec": true, "export": true,
99-
"unset": true, "shift": true, "cd": true, "pwd": true, "read": true,
100-
"readonly": true, "declare": true, "typeset": true, "local": true,
101-
"true": true, "false": true, "type": true, "command": true,
102-
// Include sudo and doas since they're commonly used
103-
"sudo": true, "doas": true,
104-
}
105-
106-
// If it's not a shell builtin and not a path, check if the command exists
107-
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
108-
// Temporarily set PATH for LookPath
109-
oldPath := os.Getenv("PATH")
110-
if err := os.Setenv("PATH", securePath()); err != nil {
111-
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
112-
}
113-
_, lookupErr := exec.LookPath(primaryCmd)
114-
if err := os.Setenv("PATH", oldPath); err != nil {
115-
log.Printf("[WARN] Failed to restore PATH: %v", err)
87+
// Skip PATH checking if the command contains shell operators
88+
// These commands need shell interpretation and can't be validated simply
89+
if !containsShellOperators(command) {
90+
// Extract the primary command (first word) to check if it exists
91+
commandParts := strings.Fields(command)
92+
if len(commandParts) > 0 {
93+
primaryCmd := commandParts[0]
94+
95+
// Check if this is a shell builtin or special command
96+
shellBuiltins := map[string]bool{
97+
"echo": true, "test": true, "[": true, "[[": true, "if": true,
98+
"then": true, "else": true, "fi": true, "for": true, "while": true,
99+
"do": true, "done": true, "case": true, "esac": true, "function": true,
100+
"return": true, "break": true, "continue": true, "exit": true,
101+
"source": true, ".": true, "eval": true, "exec": true, "export": true,
102+
"unset": true, "shift": true, "cd": true, "pwd": true, "read": true,
103+
"readonly": true, "declare": true, "typeset": true, "local": true,
104+
"true": true, "false": true, "type": true, "command": true,
105+
// Include sudo and doas since they're commonly used
106+
"sudo": true, "doas": true,
116107
}
117108

118-
if lookupErr != nil {
119-
if *debug {
120-
log.Printf("[DEBUG] Command '%s' not found in PATH for check '%s', skipping", primaryCmd, checkName)
109+
// If it's not a shell builtin and not a path, check if the command exists
110+
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
111+
// Temporarily set PATH for LookPath
112+
oldPath := os.Getenv("PATH")
113+
if err := os.Setenv("PATH", securePath()); err != nil {
114+
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
121115
}
122-
123-
output := gitmdm.CommandOutput{
124-
Command: command,
125-
Skipped: true,
126-
FileMissing: true, // Treat missing command like missing file
127-
Stderr: fmt.Sprintf("Skipped: %s not found", primaryCmd),
116+
_, lookupErr := exec.LookPath(primaryCmd)
117+
if err := os.Setenv("PATH", oldPath); err != nil {
118+
log.Printf("[WARN] Failed to restore PATH: %v", err)
128119
}
129120

130-
// Don't analyze skipped commands
131-
return output
121+
if lookupErr != nil {
122+
if *debug {
123+
log.Printf("[DEBUG] Command '%s' not found in PATH for check '%s', skipping", primaryCmd, checkName)
124+
}
125+
126+
output := gitmdm.CommandOutput{
127+
Command: command,
128+
Skipped: true,
129+
FileMissing: true, // Treat missing command like missing file
130+
Stderr: fmt.Sprintf("Skipped: %s not found", primaryCmd),
131+
}
132+
133+
// Don't analyze skipped commands
134+
return output
135+
}
132136
}
133137
}
134138
}

cmd/agent/executeCommand.go

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import (
1414
"time"
1515
)
1616

17+
// containsShellOperators checks if a command contains shell operators that indicate
18+
// it needs shell interpretation (pipes, redirects, command chaining, subshells, etc).
19+
func containsShellOperators(command string) bool {
20+
shellOperators := []string{"|", "&", ";", ">", "<", "(", ")", "{", "}", "$", "`", "||", "&&"}
21+
for _, op := range shellOperators {
22+
if strings.Contains(command, op) {
23+
return true
24+
}
25+
}
26+
return false
27+
}
28+
1729
// securePath returns a secure PATH based on the OS.
1830
func securePath() string {
1931
switch runtime.GOOS {
@@ -43,46 +55,50 @@ func securePath() string {
4355
func (*Agent) executeCommandWithPipes(ctx context.Context, checkName, command string) gitmdm.CommandOutput {
4456
start := time.Now()
4557

46-
// Extract the primary command (first word) to check if it exists
47-
commandParts := strings.Fields(command)
48-
if len(commandParts) > 0 {
49-
primaryCmd := commandParts[0]
50-
51-
// Check if this is a shell builtin or special command
52-
shellBuiltins := map[string]bool{
53-
"echo": true, "test": true, "[": true, "[[": true, "if": true,
54-
"then": true, "else": true, "fi": true, "for": true, "while": true,
55-
"do": true, "done": true, "case": true, "esac": true, "function": true,
56-
"return": true, "break": true, "continue": true, "exit": true,
57-
"source": true, ".": true, "eval": true, "exec": true, "export": true,
58-
"unset": true, "shift": true, "cd": true, "pwd": true, "read": true,
59-
"readonly": true, "declare": true, "typeset": true, "local": true,
60-
"true": true, "false": true, "type": true, "command": true,
61-
// Include sudo and doas since they're commonly used
62-
"sudo": true, "doas": true,
63-
}
64-
65-
// If it's not a shell builtin and not a path, check if the command exists
66-
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
67-
// Temporarily set PATH for LookPath
68-
oldPath := os.Getenv("PATH")
69-
if err := os.Setenv("PATH", securePath()); err != nil {
70-
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
71-
}
72-
_, lookupErr := exec.LookPath(primaryCmd)
73-
if err := os.Setenv("PATH", oldPath); err != nil {
74-
log.Printf("[WARN] Failed to restore PATH: %v", err)
58+
// Skip PATH checking if the command contains shell operators
59+
// These commands need shell interpretation and can't be validated simply
60+
if !containsShellOperators(command) {
61+
// Extract the primary command (first word) to check if it exists
62+
commandParts := strings.Fields(command)
63+
if len(commandParts) > 0 {
64+
primaryCmd := commandParts[0]
65+
66+
// Check if this is a shell builtin or special command
67+
shellBuiltins := map[string]bool{
68+
"echo": true, "test": true, "[": true, "[[": true, "if": true,
69+
"then": true, "else": true, "fi": true, "for": true, "while": true,
70+
"do": true, "done": true, "case": true, "esac": true, "function": true,
71+
"return": true, "break": true, "continue": true, "exit": true,
72+
"source": true, ".": true, "eval": true, "exec": true, "export": true,
73+
"unset": true, "shift": true, "cd": true, "pwd": true, "read": true,
74+
"readonly": true, "declare": true, "typeset": true, "local": true,
75+
"true": true, "false": true, "type": true, "command": true,
76+
// Include sudo and doas since they're commonly used
77+
"sudo": true, "doas": true,
7578
}
7679

77-
if lookupErr != nil {
78-
if *debug {
79-
log.Printf("[DEBUG] Command '%s' not found in PATH for check '%s', skipping", primaryCmd, checkName)
80+
// If it's not a shell builtin and not a path, check if the command exists
81+
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
82+
// Temporarily set PATH for LookPath
83+
oldPath := os.Getenv("PATH")
84+
if err := os.Setenv("PATH", securePath()); err != nil {
85+
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
8086
}
81-
return gitmdm.CommandOutput{
82-
Command: command,
83-
Stdout: "",
84-
Stderr: fmt.Sprintf("Skipped: %s not found", primaryCmd),
85-
ExitCode: -2, // Special exit code for skipped
87+
_, lookupErr := exec.LookPath(primaryCmd)
88+
if err := os.Setenv("PATH", oldPath); err != nil {
89+
log.Printf("[WARN] Failed to restore PATH: %v", err)
90+
}
91+
92+
if lookupErr != nil {
93+
if *debug {
94+
log.Printf("[DEBUG] Command '%s' not found in PATH for check '%s', skipping", primaryCmd, checkName)
95+
}
96+
return gitmdm.CommandOutput{
97+
Command: command,
98+
Stdout: "",
99+
Stderr: fmt.Sprintf("Skipped: %s not found", primaryCmd),
100+
ExitCode: -2, // Special exit code for skipped
101+
}
86102
}
87103
}
88104
}

cmd/agent/main.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"time"
2929

3030
"github.com/codeGROOVE-dev/retry"
31-
3231
"gopkg.in/yaml.v3"
3332
)
3433

@@ -964,7 +963,7 @@ func (a *Agent) listAvailableChecks() {
964963
// Display checks with descriptions from YAML
965964
for _, checkName := range availableChecks {
966965
checkDef := a.config.Checks[checkName]
967-
description := checkDef.Description
966+
description := checkDef.Description()
968967
if description == "" {
969968
description = "Compliance check"
970969
}

0 commit comments

Comments
 (0)