Skip to content

Commit fb20f1d

Browse files
committed
fix: excludes
1 parent b7cde79 commit fb20f1d

File tree

6 files changed

+309
-8
lines changed

6 files changed

+309
-8
lines changed

nix/packages/cis-audit/scanner/internal/config/defaults.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ var DefaultExclusions = Config{
2525
// Package manager caches
2626
"/var/lib/apt/lists/*",
2727
"/var/cache/apt/*",
28+
29+
// Kernel version-specific files (change with kernel updates)
30+
"/boot/System.map-*",
31+
"/boot/config-*",
32+
"/boot/initrd.img-*",
33+
"/boot/vmlinuz-*",
2834
},
2935

3036
ShallowDirs: []string{
@@ -41,12 +47,19 @@ var DefaultExclusions = Config{
4147
"fs.file-nr", // File handle statistics (dynamic)
4248
"fs.inode-nr", // Inode statistics (dynamic)
4349
"fs.inode-state", // Inode state (dynamic)
50+
"fs.aio-nr", // Current async I/O operations (dynamic)
4451
"kernel.random.uuid", // Random UUID (changes every read)
4552
"kernel.random.boot_id", // Boot ID (changes per boot but not security-relevant)
53+
"kernel.random.entropy_avail", // Available entropy (changes constantly)
4654
"kernel.ns_last_pid", // Last PID allocated (dynamic)
4755
"kernel.pty.nr", // Current number of PTYs (dynamic)
4856
"net.netfilter.*_conntrack_count", // Connection tracking counts (dynamic)
4957
"net.netfilter.*_conntrack_max", // Connection tracking max (dynamic)
58+
59+
// RAM-dependent parameters (auto-tuned based on system memory)
60+
"fs.epoll.max_user_watches", // Computed from RAM
61+
"net.netfilter.nf_conntrack_buckets", // Auto-tuned based on RAM
62+
"net.netfilter.nf_conntrack_expect_max", // Derived from buckets
5063
},
5164

5265
DisabledScanners: []string{

nix/packages/cis-audit/scanner/internal/config/loader.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,25 @@ func Load(configPath string, opts CLIOptions) (*Config, error) {
8181

8282
// Apply CLI overrides (highest precedence)
8383
if opts.IncludeDynamic {
84-
// Remove all dynamic kernel params from the default list
84+
// Remove all dynamic/RAM-dependent kernel params from the exclusion list
8585
dynamicParams := []string{
86+
// Dynamic counters/statistics
8687
"fs.dentry-state",
8788
"fs.file-nr",
8889
"fs.inode-nr",
8990
"fs.inode-state",
91+
"fs.aio-nr",
9092
"kernel.random.uuid",
9193
"kernel.random.boot_id",
94+
"kernel.random.entropy_avail",
9295
"kernel.ns_last_pid",
9396
"kernel.pty.nr",
97+
"net.netfilter.*_conntrack_count",
98+
"net.netfilter.*_conntrack_max",
99+
// RAM-dependent parameters
100+
"fs.epoll.max_user_watches",
101+
"net.netfilter.nf_conntrack_buckets",
102+
"net.netfilter.nf_conntrack_expect_max",
94103
}
95104
cfg.KernelParams = removeItems(cfg.KernelParams, dynamicParams)
96105
}

nix/packages/cis-audit/scanner/internal/config/loader_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,67 @@ kernelParams:
156156
}
157157
}
158158

159+
func TestLoad_ShallowDepthZero(t *testing.T) {
160+
// Test that ShallowDepthSet allows explicit 0 to be set
161+
cfg, err := Load("", CLIOptions{
162+
ShallowDepth: 0,
163+
ShallowDepthSet: true,
164+
})
165+
if err != nil {
166+
t.Fatalf("Load() error = %v", err)
167+
}
168+
169+
if cfg.ShallowDepth != 0 {
170+
t.Errorf("Expected ShallowDepth 0 when explicitly set, got %d", cfg.ShallowDepth)
171+
}
172+
}
173+
174+
func TestLoad_ShallowDepthDefault(t *testing.T) {
175+
// Test that ShallowDepth defaults to 1 when not set
176+
cfg, err := Load("", CLIOptions{
177+
ShallowDepthSet: false,
178+
})
179+
if err != nil {
180+
t.Fatalf("Load() error = %v", err)
181+
}
182+
183+
if cfg.ShallowDepth != 1 {
184+
t.Errorf("Expected ShallowDepth 1 as default, got %d", cfg.ShallowDepth)
185+
}
186+
}
187+
188+
func TestLoad_ShallowDepthFromCLI(t *testing.T) {
189+
// Test that CLI shallow depth overrides defaults
190+
cfg, err := Load("", CLIOptions{
191+
ShallowDepth: 3,
192+
ShallowDepthSet: true,
193+
})
194+
if err != nil {
195+
t.Fatalf("Load() error = %v", err)
196+
}
197+
198+
if cfg.ShallowDepth != 3 {
199+
t.Errorf("Expected ShallowDepth 3 from CLI, got %d", cfg.ShallowDepth)
200+
}
201+
}
202+
203+
func TestLoad_ShallowDirs(t *testing.T) {
204+
// Test that CLI shallow dirs are added
205+
cfg, err := Load("", CLIOptions{
206+
ShallowDirs: []string{"/custom/shallow", "/another/shallow"},
207+
})
208+
if err != nil {
209+
t.Fatalf("Load() error = %v", err)
210+
}
211+
212+
if !contains(cfg.ShallowDirs, "/custom/shallow") {
213+
t.Errorf("Expected /custom/shallow in ShallowDirs")
214+
}
215+
if !contains(cfg.ShallowDirs, "/another/shallow") {
216+
t.Errorf("Expected /another/shallow in ShallowDirs")
217+
}
218+
}
219+
159220
// Helper function to check if a slice contains a string
160221
func contains(slice []string, item string) bool {
161222
for _, s := range slice {

nix/packages/cis-audit/scanner/internal/scanners/files_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,114 @@ func TestFileScanner_Exclusions(t *testing.T) {
8383
t.Errorf("Expected 1 file (excluded proc), got %d", len(results))
8484
}
8585
}
86+
87+
func TestFileScanner_ShallowDirsDepthZero(t *testing.T) {
88+
tmpDir := t.TempDir()
89+
90+
// Create a shallow dir with files inside
91+
shallowDir := filepath.Join(tmpDir, "nix-store")
92+
os.Mkdir(shallowDir, 0755)
93+
os.WriteFile(filepath.Join(shallowDir, "file1.txt"), []byte("data"), 0644)
94+
os.WriteFile(filepath.Join(shallowDir, "file2.txt"), []byte("data"), 0644)
95+
96+
// Create a subdir inside shallow dir
97+
subdir := filepath.Join(shallowDir, "subdir")
98+
os.Mkdir(subdir, 0755)
99+
os.WriteFile(filepath.Join(subdir, "nested.txt"), []byte("data"), 0644)
100+
101+
// Create a normal dir that should be scanned
102+
normalDir := filepath.Join(tmpDir, "etc")
103+
os.Mkdir(normalDir, 0755)
104+
os.WriteFile(filepath.Join(normalDir, "passwd"), []byte("data"), 0644)
105+
106+
scanner := &FileScanner{rootPath: tmpDir}
107+
writer := spec.NewTestWriter()
108+
109+
opts := ScanOptions{
110+
Writer: writer,
111+
Config: &config.Config{
112+
ShallowDirs: []string{shallowDir},
113+
ShallowDepth: 0, // Don't scan ANY files inside shallow dirs
114+
},
115+
Logger: testLogger(),
116+
}
117+
118+
scanner.Scan(context.Background(), opts)
119+
120+
results := writer.GetFileResults()
121+
122+
// With depth 0, we should only get:
123+
// - /etc/passwd (normal dir)
124+
// - the shallow dir itself (as directory entry)
125+
// Files inside shallow dir should be skipped
126+
127+
hasPasswd := false
128+
hasShallowDirFiles := false
129+
for _, r := range results {
130+
if filepath.Base(r.Path) == "passwd" {
131+
hasPasswd = true
132+
}
133+
if filepath.Base(r.Path) == "file1.txt" || filepath.Base(r.Path) == "file2.txt" || filepath.Base(r.Path) == "nested.txt" {
134+
hasShallowDirFiles = true
135+
}
136+
}
137+
138+
if !hasPasswd {
139+
t.Errorf("Expected /etc/passwd to be scanned")
140+
}
141+
if hasShallowDirFiles {
142+
t.Errorf("Files inside shallow dir should be skipped with depth 0")
143+
}
144+
}
145+
146+
func TestFileScanner_ShallowDirsDepthOne(t *testing.T) {
147+
tmpDir := t.TempDir()
148+
149+
// Create a shallow dir with files inside
150+
shallowDir := filepath.Join(tmpDir, "nix-store")
151+
os.Mkdir(shallowDir, 0755)
152+
os.WriteFile(filepath.Join(shallowDir, "file1.txt"), []byte("data"), 0644)
153+
154+
// Create a subdir inside shallow dir
155+
subdir := filepath.Join(shallowDir, "subdir")
156+
os.Mkdir(subdir, 0755)
157+
os.WriteFile(filepath.Join(subdir, "nested.txt"), []byte("data"), 0644)
158+
159+
scanner := &FileScanner{rootPath: tmpDir}
160+
writer := spec.NewTestWriter()
161+
162+
opts := ScanOptions{
163+
Writer: writer,
164+
Config: &config.Config{
165+
ShallowDirs: []string{shallowDir},
166+
ShallowDepth: 1, // Scan top-level files only
167+
},
168+
Logger: testLogger(),
169+
}
170+
171+
scanner.Scan(context.Background(), opts)
172+
173+
results := writer.GetFileResults()
174+
175+
// With depth 1, we should get:
176+
// - file1.txt (direct child of shallow dir)
177+
// But NOT nested.txt (inside subdir)
178+
179+
hasFile1 := false
180+
hasNested := false
181+
for _, r := range results {
182+
if filepath.Base(r.Path) == "file1.txt" {
183+
hasFile1 = true
184+
}
185+
if filepath.Base(r.Path) == "nested.txt" {
186+
hasNested = true
187+
}
188+
}
189+
190+
if !hasFile1 {
191+
t.Errorf("Expected file1.txt (direct child) to be scanned with depth 1")
192+
}
193+
if hasNested {
194+
t.Errorf("nested.txt should be skipped with depth 1")
195+
}
196+
}

nix/packages/cis-audit/scanner/internal/scanners/mounts.go

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,33 @@ func (s *MountScanner) getMounts(opts ScanOptions) (map[string]spec.MountSpec, e
9595
fstype := fields[2]
9696
optionsStr := fields[3]
9797

98-
// Parse options (comma-separated)
99-
var opts []string
98+
// Parse options (comma-separated), filtering out instance-specific values
99+
var filteredOpts []string
100100
if optionsStr != "" {
101-
opts = strings.Split(optionsStr, ",")
101+
for _, opt := range strings.Split(optionsStr, ",") {
102+
// Skip instance-specific options that vary by RAM/instance type
103+
if strings.HasPrefix(opt, "size=") ||
104+
strings.HasPrefix(opt, "nr_inodes=") ||
105+
strings.HasPrefix(opt, "nr_blocks=") {
106+
continue
107+
}
108+
filteredOpts = append(filteredOpts, opt)
109+
}
110+
}
111+
112+
// Determine if source should be included
113+
// Skip source for virtual filesystems where device names are meaningless or instance-specific
114+
source := device
115+
if isVirtualOrInstanceSpecificSource(device, fstype) {
116+
source = ""
102117
}
103118

104119
mounts[mountpoint] = spec.MountSpec{
105120
Path: mountpoint,
106121
Exists: true,
107122
Filesystem: fstype,
108-
Opts: opts,
109-
Source: device,
123+
Opts: filteredOpts,
124+
Source: source,
110125
}
111126
}
112127

@@ -116,3 +131,42 @@ func (s *MountScanner) getMounts(opts ScanOptions) (map[string]spec.MountSpec, e
116131

117132
return mounts, nil
118133
}
134+
135+
// isVirtualOrInstanceSpecificSource returns true if the device/source is virtual
136+
// or instance-specific (e.g., /dev/nvme* device names that vary by instance)
137+
func isVirtualOrInstanceSpecificSource(device, fstype string) bool {
138+
// Virtual filesystems where source is just a label
139+
virtualFsTypes := map[string]bool{
140+
"tmpfs": true,
141+
"devtmpfs": true,
142+
"sysfs": true,
143+
"proc": true,
144+
"devpts": true,
145+
"cgroup": true,
146+
"cgroup2": true,
147+
"securityfs": true,
148+
"debugfs": true,
149+
"hugetlbfs": true,
150+
"mqueue": true,
151+
"binfmt_misc": true,
152+
"configfs": true,
153+
"fusectl": true,
154+
"tracefs": true,
155+
"pstore": true,
156+
"efivarfs": true,
157+
"bpf": true,
158+
}
159+
160+
if virtualFsTypes[fstype] {
161+
return true
162+
}
163+
164+
// Instance-specific block devices (NVMe devices vary by instance)
165+
if strings.HasPrefix(device, "/dev/nvme") ||
166+
strings.HasPrefix(device, "/dev/xvd") ||
167+
strings.HasPrefix(device, "/dev/sd") {
168+
return true
169+
}
170+
171+
return false
172+
}

nix/packages/cis-audit/scanner/internal/scanners/mounts_test.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ tmpfs /tmp tmpfs rw,nosuid,nodev 0 0
5252
if root.Filesystem != "ext4" {
5353
t.Errorf("Expected filesystem 'ext4', got '%s'", root.Filesystem)
5454
}
55-
if root.Source != "/dev/sda1" {
56-
t.Errorf("Expected source '/dev/sda1', got '%s'", root.Source)
55+
// Source is now filtered out for /dev/* devices (instance-specific)
56+
if root.Source != "" {
57+
t.Errorf("Expected source to be empty for /dev/* device, got '%s'", root.Source)
5758
}
5859
if len(root.Opts) == 0 {
5960
t.Errorf("Expected mount options to be parsed")
@@ -93,6 +94,58 @@ func TestMountScanner_Properties(t *testing.T) {
9394
}
9495
}
9596

97+
func TestMountScanner_FiltersInstanceSpecificOptions(t *testing.T) {
98+
tmpDir := t.TempDir()
99+
mountsFile := filepath.Join(tmpDir, "mounts")
100+
101+
// Create mounts file with instance-specific options that should be filtered
102+
mountsContent := `tmpfs /dev/shm tmpfs rw,nosuid,nodev,size=194656k,nr_inodes=48664,inode64 0 0
103+
/dev/nvme1n1p2 / ext4 rw,relatime,discard 0 0
104+
`
105+
if err := os.WriteFile(mountsFile, []byte(mountsContent), 0644); err != nil {
106+
t.Fatalf("Failed to create test mounts file: %v", err)
107+
}
108+
109+
scanner := &MountScanner{mountsPath: mountsFile}
110+
writer := spec.NewTestWriter()
111+
112+
opts := ScanOptions{
113+
Writer: writer,
114+
Logger: testLogger(),
115+
}
116+
117+
_, err := scanner.Scan(context.Background(), opts)
118+
if err != nil {
119+
t.Fatalf("Scan failed: %v", err)
120+
}
121+
122+
results := writer.GetMountResults()
123+
124+
// Check /dev/shm - should have size= and nr_inodes= filtered out
125+
shm, ok := results["/dev/shm"]
126+
if !ok {
127+
t.Fatalf("/dev/shm mount not found")
128+
}
129+
for _, opt := range shm.Opts {
130+
if opt == "size=194656k" || opt == "nr_inodes=48664" {
131+
t.Errorf("Instance-specific option '%s' should have been filtered out", opt)
132+
}
133+
}
134+
// Source should be empty for tmpfs (virtual filesystem)
135+
if shm.Source != "" {
136+
t.Errorf("Expected empty source for tmpfs, got '%s'", shm.Source)
137+
}
138+
139+
// Check root - source should be empty for /dev/nvme* devices
140+
root, ok := results["/"]
141+
if !ok {
142+
t.Fatalf("root mount not found")
143+
}
144+
if root.Source != "" {
145+
t.Errorf("Expected empty source for /dev/nvme* device, got '%s'", root.Source)
146+
}
147+
}
148+
96149
func TestMountScanner_MalformedLines(t *testing.T) {
97150
tmpDir := t.TempDir()
98151
mountsFile := filepath.Join(tmpDir, "mounts")

0 commit comments

Comments
 (0)