Skip to content

Commit d01008b

Browse files
Merge pull request #7702 from freedomofpress/test-apparmor
Add test to verify apparmor config's completeness
2 parents d2a804a + eac6d32 commit d01008b

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Tests for AppArmor configuration completeness."""
2+
3+
import os
4+
import re
5+
from pathlib import Path
6+
7+
8+
def test_apparmor_config():
9+
"""
10+
Verify that the set of Python files in securedrop/ exactly matches the set
11+
of Python files listed in the AppArmor configuration file. This ensures:
12+
1. All application code has proper AppArmor permissions when running under Apache
13+
2. The AppArmor config doesn't have stale entries for deleted files
14+
"""
15+
# Files/directories that don't run under Apache and should be exempted
16+
EXEMPTIONS = {
17+
# Database migrations run via management commands, not Apache
18+
"alembic/",
19+
# Management commands run separately, not via Apache
20+
"management/",
21+
# Developer/test scripts that don't run under Apache
22+
"loaddata.py",
23+
"loadfixeddata.py",
24+
"manage.py",
25+
"setup.py",
26+
"specialstrings.py",
27+
"upload-screenshots.py",
28+
}
29+
30+
# Find all Python files in securedrop/ directory (excluding tests and debian)
31+
securedrop_dir = Path(__file__).parent.parent
32+
expected_python_files = set()
33+
34+
for root, dirs, files in os.walk(securedrop_dir):
35+
# Skip hidden directories, __pycache__, and debian directory
36+
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ("__pycache__", "debian")]
37+
38+
# Also skip the tests directory itself - tests don't run under Apache
39+
relative_root = Path(root).relative_to(securedrop_dir)
40+
if relative_root.parts and relative_root.parts[0] == "tests":
41+
continue
42+
43+
for file in files:
44+
if file.endswith(".py"):
45+
# Get the path relative to securedrop/
46+
full_path = Path(root) / file
47+
rel_path = full_path.relative_to(securedrop_dir)
48+
rel_path_str = str(rel_path)
49+
50+
# Check if this file matches any exemption
51+
is_exempted = False
52+
for exemption in EXEMPTIONS:
53+
if exemption.endswith("/"):
54+
# Directory exemption
55+
if rel_path_str.startswith(exemption):
56+
is_exempted = True
57+
break
58+
# File exemption
59+
elif rel_path_str == exemption:
60+
is_exempted = True
61+
break
62+
63+
if not is_exempted:
64+
expected_python_files.add(rel_path_str)
65+
66+
# Extract Python file paths from AppArmor config
67+
# Look for lines like: /var/www/securedrop/path/to/file.py r
68+
actual_python_files = set()
69+
python_file_pattern = re.compile(r"^\s*/var/www/securedrop/(.*\.py)\s+r\s*,?\s*$")
70+
71+
apparmor_config = (
72+
Path(__file__).parent.parent / "debian/app-code/etc/apparmor.d/usr.sbin.apache2"
73+
).read_text()
74+
75+
for line in apparmor_config.splitlines():
76+
match = python_file_pattern.match(line)
77+
if match:
78+
py_file = match.group(1)
79+
actual_python_files.add(py_file)
80+
81+
assert expected_python_files == actual_python_files

0 commit comments

Comments
 (0)