1- """Tests for POSIX ACL change events."""
1+ """Tests for POSIX ACL change events.
2+
3+ Uses os.setxattr to set ACLs directly via the POSIX ACL xattr wire
4+ format, avoiding a dependency on the setfacl tool.
5+ """
26
37from __future__ import annotations
48
59import os
10+ import struct
611
7- import docker .models .containers
812import pytest
913
1014from event import (
1923)
2024from server import FileActivityService
2125
26+ # POSIX ACL xattr wire format constants
27+ _ACL_VERSION = 2
28+ _ACL_UNDEFINED_ID = 0xFFFFFFFF
29+
30+ # Kernel ACL tag values (from include/uapi/linux/posix_acl.h)
31+ _ACL_USER_OBJ = 0x01
32+ _ACL_USER = 0x02
33+ _ACL_GROUP_OBJ = 0x04
34+ _ACL_GROUP = 0x08
35+ _ACL_MASK = 0x10
36+ _ACL_OTHER = 0x20
37+
38+
39+ def _make_acl_xattr (entries : list [tuple [int , int , int ]]) -> bytes :
40+ """Build a POSIX ACL xattr value from a list of (tag, perm, id) tuples."""
41+ data = struct .pack ('<I' , _ACL_VERSION )
42+ for tag , perm , uid in entries :
43+ data += struct .pack ('<HHI' , tag , perm , uid )
44+ return data
45+
2246
2347def _kernel_supports_acl_hook () -> bool :
2448 """Check whether the kernel has the inode_set_acl LSM hook by
@@ -46,54 +70,45 @@ def _kernel_supports_acl_hook() -> bool:
4670
4771
4872def test_set_access_acl (
49- test_container : docker . models . containers . Container ,
73+ monitored_dir : str ,
5074 server : FileActivityService ,
5175):
52- """
53- Test setting an access ACL on a file inside a container.
54-
55- Args:
56- test_container: A container for running commands in.
57- server: The server instance to communicate with.
58- """
59- assert test_container .id is not None
60- fut = '/container-dir/acl_test.txt'
61-
62- test_container .exec_run (f'touch { fut } ' )
63- test_container .exec_run (f'setfacl -m u:1000:rw { fut } ' )
64-
65- touch = Process .in_container (
66- exe_path = '/usr/bin/touch' ,
67- args = f'touch { fut } ' ,
68- name = 'touch' ,
69- container_id = test_container .id [:12 ],
70- )
71- setfacl = Process .in_container (
72- exe_path = '/usr/bin/setfacl' ,
73- args = f'setfacl -m u:1000:rw { fut } ' ,
74- name = 'setfacl' ,
75- container_id = test_container .id [:12 ],
76+ """Test setting an access ACL on a monitored file."""
77+ fut = os .path .join (monitored_dir , 'acl_test.txt' )
78+ with open (fut , 'w' ) as f :
79+ f .write ('test' )
80+
81+ acl = _make_acl_xattr (
82+ [
83+ (_ACL_USER_OBJ , 6 , _ACL_UNDEFINED_ID ),
84+ (_ACL_USER , 6 , 1000 ),
85+ (_ACL_GROUP_OBJ , 4 , _ACL_UNDEFINED_ID ),
86+ (_ACL_MASK , 6 , _ACL_UNDEFINED_ID ),
87+ (_ACL_OTHER , 4 , _ACL_UNDEFINED_ID ),
88+ ]
7689 )
90+ os .setxattr (fut , 'system.posix_acl_access' , acl )
7791
92+ process = Process .from_proc ()
7893 events = [
7994 Event (
80- process = touch ,
95+ process = process ,
8196 event_type = EventType .CREATION ,
8297 file = fut ,
83- host_path = '' ,
98+ host_path = fut ,
8499 ),
85100 Event (
86- process = setfacl ,
101+ process = process ,
87102 event_type = EventType .ACL ,
88103 file = fut ,
89- host_path = '' ,
104+ host_path = fut ,
90105 acl_type = 'access' ,
91106 acl_entries = [
92- {'tag' : ACL_TAG_USER_OBJ , 'perm' : 6 , 'id' : 0xFFFFFFFF },
107+ {'tag' : ACL_TAG_USER_OBJ , 'perm' : 6 , 'id' : _ACL_UNDEFINED_ID },
93108 {'tag' : ACL_TAG_USER , 'perm' : 6 , 'id' : 1000 },
94- {'tag' : ACL_TAG_GROUP_OBJ , 'perm' : 4 , 'id' : 0xFFFFFFFF },
95- {'tag' : ACL_TAG_MASK , 'perm' : 6 , 'id' : 0xFFFFFFFF },
96- {'tag' : ACL_TAG_OTHER , 'perm' : 4 , 'id' : 0xFFFFFFFF },
109+ {'tag' : ACL_TAG_GROUP_OBJ , 'perm' : 4 , 'id' : _ACL_UNDEFINED_ID },
110+ {'tag' : ACL_TAG_MASK , 'perm' : 6 , 'id' : _ACL_UNDEFINED_ID },
111+ {'tag' : ACL_TAG_OTHER , 'perm' : 4 , 'id' : _ACL_UNDEFINED_ID },
97112 ],
98113 ),
99114 ]
@@ -102,35 +117,31 @@ def test_set_access_acl(
102117
103118
104119def test_set_default_acl (
105- test_container : docker . models . containers . Container ,
120+ monitored_dir : str ,
106121 server : FileActivityService ,
107122):
108- """
109- Test setting a default ACL on a directory inside a container.
110-
111- Args:
112- test_container: A container for running commands in.
113- server: The server instance to communicate with.
114- """
115- assert test_container .id is not None
116- fut = '/container-dir/subdir'
117-
118- test_container .exec_run (f'mkdir -p { fut } ' )
119- test_container .exec_run (f'setfacl -d -m g:1000:rx { fut } ' )
120-
121- setfacl = Process .in_container (
122- exe_path = '/usr/bin/setfacl' ,
123- args = f'setfacl -d -m g:1000:rx { fut } ' ,
124- name = 'setfacl' ,
125- container_id = test_container .id [:12 ],
123+ """Test setting a default ACL on a monitored directory."""
124+ fut = os .path .join (monitored_dir , 'acl_subdir' )
125+ os .makedirs (fut , exist_ok = True )
126+
127+ acl = _make_acl_xattr (
128+ [
129+ (_ACL_USER_OBJ , 7 , _ACL_UNDEFINED_ID ),
130+ (_ACL_GROUP_OBJ , 5 , _ACL_UNDEFINED_ID ),
131+ (_ACL_GROUP , 5 , 1000 ),
132+ (_ACL_MASK , 5 , _ACL_UNDEFINED_ID ),
133+ (_ACL_OTHER , 5 , _ACL_UNDEFINED_ID ),
134+ ]
126135 )
136+ os .setxattr (fut , 'system.posix_acl_default' , acl )
127137
138+ process = Process .from_proc ()
128139 events = [
129140 Event (
130- process = setfacl ,
141+ process = process ,
131142 event_type = EventType .ACL ,
132143 file = fut ,
133- host_path = '' ,
144+ host_path = fut ,
134145 acl_type = 'default' ,
135146 ),
136147 ]
@@ -139,66 +150,61 @@ def test_set_default_acl(
139150
140151
141152def test_remove_acl (
142- test_container : docker . models . containers . Container ,
153+ monitored_dir : str ,
143154 server : FileActivityService ,
144155):
145- """
146- Test removing ACLs from a file inside a container.
147-
148- Args:
149- test_container: A container for running commands in.
150- server: The server instance to communicate with.
151- """
152- assert test_container .id is not None
153- fut = '/container-dir/acl_remove.txt'
154-
155- test_container .exec_run (f'touch { fut } ' )
156- test_container .exec_run (f'setfacl -m u:1000:rw { fut } ' )
157- test_container .exec_run (f'setfacl -b { fut } ' )
158-
159- touch = Process .in_container (
160- exe_path = '/usr/bin/touch' ,
161- args = f'touch { fut } ' ,
162- name = 'touch' ,
163- container_id = test_container .id [:12 ],
164- )
165- setfacl_set = Process .in_container (
166- exe_path = '/usr/bin/setfacl' ,
167- args = f'setfacl -m u:1000:rw { fut } ' ,
168- name = 'setfacl' ,
169- container_id = test_container .id [:12 ],
156+ """Test removing ACLs from a monitored file."""
157+ fut = os .path .join (monitored_dir , 'acl_remove.txt' )
158+ with open (fut , 'w' ) as f :
159+ f .write ('test' )
160+
161+ # Set an ACL with an extra user entry
162+ acl_with_user = _make_acl_xattr (
163+ [
164+ (_ACL_USER_OBJ , 6 , _ACL_UNDEFINED_ID ),
165+ (_ACL_USER , 6 , 1000 ),
166+ (_ACL_GROUP_OBJ , 4 , _ACL_UNDEFINED_ID ),
167+ (_ACL_MASK , 6 , _ACL_UNDEFINED_ID ),
168+ (_ACL_OTHER , 4 , _ACL_UNDEFINED_ID ),
169+ ]
170170 )
171- setfacl_remove = Process .in_container (
172- exe_path = '/usr/bin/setfacl' ,
173- args = f'setfacl -b { fut } ' ,
174- name = 'setfacl' ,
175- container_id = test_container .id [:12 ],
171+ os .setxattr (fut , 'system.posix_acl_access' , acl_with_user )
172+
173+ # Remove extended ACL entries by setting a minimal ACL
174+ acl_minimal = _make_acl_xattr (
175+ [
176+ (_ACL_USER_OBJ , 6 , _ACL_UNDEFINED_ID ),
177+ (_ACL_GROUP_OBJ , 4 , _ACL_UNDEFINED_ID ),
178+ (_ACL_OTHER , 4 , _ACL_UNDEFINED_ID ),
179+ ]
176180 )
181+ os .setxattr (fut , 'system.posix_acl_access' , acl_minimal )
177182
183+ process = Process .from_proc ()
178184 events = [
179185 Event (
180- process = touch ,
186+ process = process ,
181187 event_type = EventType .CREATION ,
182188 file = fut ,
183- host_path = '' ,
189+ host_path = fut ,
184190 ),
185191 Event (
186- process = setfacl_set ,
192+ process = process ,
187193 event_type = EventType .ACL ,
188194 file = fut ,
189- host_path = '' ,
195+ host_path = fut ,
190196 acl_type = 'access' ,
191197 ),
192198 Event (
193- process = setfacl_remove ,
199+ process = process ,
194200 event_type = EventType .ACL ,
195201 file = fut ,
196- host_path = '' ,
202+ host_path = fut ,
197203 acl_type = 'access' ,
198204 acl_entries = [
199- {'tag' : ACL_TAG_USER_OBJ , 'perm' : 6 , 'id' : 0xFFFFFFFF },
200- {'tag' : ACL_TAG_GROUP_OBJ , 'perm' : 4 , 'id' : 0xFFFFFFFF },
201- {'tag' : ACL_TAG_OTHER , 'perm' : 4 , 'id' : 0xFFFFFFFF },
205+ {'tag' : ACL_TAG_USER_OBJ , 'perm' : 6 , 'id' : _ACL_UNDEFINED_ID },
206+ {'tag' : ACL_TAG_GROUP_OBJ , 'perm' : 4 , 'id' : _ACL_UNDEFINED_ID },
207+ {'tag' : ACL_TAG_OTHER , 'perm' : 4 , 'id' : _ACL_UNDEFINED_ID },
202208 ],
203209 ),
204210 ]
@@ -207,47 +213,40 @@ def test_remove_acl(
207213
208214
209215def test_multiple_entries (
210- test_container : docker . models . containers . Container ,
216+ monitored_dir : str ,
211217 server : FileActivityService ,
212218):
213- """
214- Test setting multiple ACL entries on a single file.
215-
216- Args:
217- test_container: A container for running commands in.
218- server: The server instance to communicate with.
219- """
220- assert test_container .id is not None
221- fut = '/container-dir/acl_multi.txt'
222-
223- test_container .exec_run (f'touch { fut } ' )
224- test_container .exec_run (f'setfacl -m u:1000:rwx,u:1001:r,g:2000:rw { fut } ' )
225-
226- touch = Process .in_container (
227- exe_path = '/usr/bin/touch' ,
228- args = f'touch { fut } ' ,
229- name = 'touch' ,
230- container_id = test_container .id [:12 ],
231- )
232- setfacl = Process .in_container (
233- exe_path = '/usr/bin/setfacl' ,
234- args = f"setfacl -m 'u:1000:rwx,u:1001:r,g:2000:rw' { fut } " ,
235- name = 'setfacl' ,
236- container_id = test_container .id [:12 ],
219+ """Test setting multiple ACL entries on a single file."""
220+ fut = os .path .join (monitored_dir , 'acl_multi.txt' )
221+ with open (fut , 'w' ) as f :
222+ f .write ('test' )
223+
224+ acl = _make_acl_xattr (
225+ [
226+ (_ACL_USER_OBJ , 6 , _ACL_UNDEFINED_ID ),
227+ (_ACL_USER , 7 , 1000 ),
228+ (_ACL_USER , 4 , 1001 ),
229+ (_ACL_GROUP_OBJ , 4 , _ACL_UNDEFINED_ID ),
230+ (_ACL_GROUP , 6 , 2000 ),
231+ (_ACL_MASK , 7 , _ACL_UNDEFINED_ID ),
232+ (_ACL_OTHER , 4 , _ACL_UNDEFINED_ID ),
233+ ]
237234 )
235+ os .setxattr (fut , 'system.posix_acl_access' , acl )
238236
237+ process = Process .from_proc ()
239238 events = [
240239 Event (
241- process = touch ,
240+ process = process ,
242241 event_type = EventType .CREATION ,
243242 file = fut ,
244- host_path = '' ,
243+ host_path = fut ,
245244 ),
246245 Event (
247- process = setfacl ,
246+ process = process ,
248247 event_type = EventType .ACL ,
249248 file = fut ,
250- host_path = '' ,
249+ host_path = fut ,
251250 acl_type = 'access' ,
252251 ),
253252 ]
@@ -258,30 +257,25 @@ def test_multiple_entries(
258257def test_ignored_path (
259258 test_file : str ,
260259 ignored_dir : str ,
261- test_container : docker .models .containers .Container ,
262260 server : FileActivityService ,
263261):
264- """
265- Test that ACL changes on ignored paths are not captured.
266-
267- Args:
268- test_file: File monitored on the host.
269- ignored_dir: Temporary directory that is not monitored.
270- test_container: A container for running commands in.
271- server: The server instance to communicate with.
272- """
273- assert test_container .id is not None
274-
275- # Set ACL on an ignored file -- should not produce an event
262+ """Test that ACL changes on ignored paths are not captured."""
276263 ignored_file = os .path .join (ignored_dir , 'ignored_acl.txt' )
277264 with open (ignored_file , 'w' ) as f :
278265 f .write ('ignored' )
279266
280- # This runs on the host but the file is in an unmonitored directory.
281- # Since we only match by inode and this file was just created in an
282- # ignored dir, it won't be in the inode_map and won't trigger.
267+ acl = _make_acl_xattr (
268+ [
269+ (_ACL_USER_OBJ , 6 , _ACL_UNDEFINED_ID ),
270+ (_ACL_USER , 6 , 1000 ),
271+ (_ACL_GROUP_OBJ , 4 , _ACL_UNDEFINED_ID ),
272+ (_ACL_MASK , 6 , _ACL_UNDEFINED_ID ),
273+ (_ACL_OTHER , 4 , _ACL_UNDEFINED_ID ),
274+ ]
275+ )
276+ os .setxattr (ignored_file , 'system.posix_acl_access' , acl )
283277
284- # Now do a chmod on the monitored file to verify the server is working
278+ # Verify the server is working by doing a chmod on a monitored file
285279 process = Process .from_proc ()
286280 mode = 0o644
287281 os .chmod (test_file , mode )
0 commit comments