Skip to content

Commit 46d84a9

Browse files
committed
Remove container dependence; do xattr syscall manually
1 parent e629048 commit 46d84a9

1 file changed

Lines changed: 136 additions & 142 deletions

File tree

tests/test_acl.py

Lines changed: 136 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
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

37
from __future__ import annotations
48

59
import os
10+
import struct
611

7-
import docker.models.containers
812
import pytest
913

1014
from event import (
@@ -19,6 +23,26 @@
1923
)
2024
from 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

2347
def _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

4872
def 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

104119
def 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

141152
def 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

209215
def 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(
258257
def 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

Comments
 (0)