aim-web2.1.0rc9
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
"""Linux local socket/UID integration. Synthetic core inventory, no remote hosts."""
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import grp
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import pytest
|
||||
from aim_webgui.config import Settings
|
||||
from aim_webgui.core.client import CoreClient
|
||||
from aim_webgui.credentials import wire
|
||||
|
||||
|
||||
def test_nonroot_executor_public_core_and_peer_rejection(core_root):
|
||||
if os.geteuid()!=0 or not hasattr(socket,'SO_PEERCRED'):
|
||||
pytest.skip('Needs Linux root in the isolated test container to exercise different local UIDs.')
|
||||
account=pwd.getpwnam('nobody')
|
||||
base=Path(tempfile.mkdtemp(prefix='aim-exec-'));base.chmod(0o755)
|
||||
process=None
|
||||
try:
|
||||
core=base/'core';shutil.copytree(core_root,core)
|
||||
for p in [core,*core.rglob('*')]:
|
||||
if p.is_dir():p.chmod(0o755)
|
||||
else:p.chmod(0o644)
|
||||
config=core/'scripts/aim.yml'
|
||||
config.write_text(f'root_dir: {core}\nservice_user: synthetic\nrequired_group: {grp.getgrgid(account.pw_gid).gr_name}\n')
|
||||
runtime=base/'socket';runtime.mkdir(mode=0o711);os.chown(runtime,account.pw_uid,account.pw_gid)
|
||||
home=base/'home';home.mkdir(mode=0o700);os.chown(home,account.pw_uid,account.pw_gid)
|
||||
cfg=base/'web.toml'
|
||||
cfg.write_text('[core]\ntransport="unix"\n'+f'command=[{json.dumps(sys.executable)},{json.dumps(str(core/"scripts/aimctl.py"))}]\n'+
|
||||
f'config={json.dumps(str(config))}\nhome={json.dumps(str(home))}\nsocket={json.dumps(str(runtime/"core.sock"))}\nexecutor_user="nobody"\nclient_user="root"\n')
|
||||
cfg.chmod(0o644)
|
||||
settings=Settings.load(cfg)
|
||||
source=Path(__file__).resolve().parents[1]/'src'
|
||||
def drop():os.setgroups([0,account.pw_gid]);os.setgid(account.pw_gid);os.setuid(account.pw_uid)
|
||||
process=subprocess.Popen([sys.executable,'-B','-m','aim_webgui','--config',str(cfg),'executor'],
|
||||
env={**os.environ,'PYTHONPATH':str(source)},stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,preexec_fn=drop)
|
||||
for _ in range(200):
|
||||
if settings.core_socket.exists():break
|
||||
if process.poll()is not None:pytest.fail('Executor fixture exited before opening socket')
|
||||
time.sleep(.05)
|
||||
assert settings.core_socket.exists()
|
||||
assert settings.core_socket.stat().st_uid==account.pw_uid
|
||||
assert runtime.stat().st_gid==account.pw_gid
|
||||
assert settings.core_socket.stat().st_gid==0
|
||||
assert settings.core_socket.stat().st_mode&0o777==0o660
|
||||
client=CoreClient(settings)
|
||||
assert client.request('capabilities')['core_version']=='3.3.0rc8'
|
||||
assert client.request('list_customers')==['example']
|
||||
# A direct local client cannot smuggle an execution executable/path field.
|
||||
with socket.socket(socket.AF_UNIX)as conn:
|
||||
conn.connect(str(settings.core_socket));wire.send(conn,{'request':{'api_version':'1.0','operation':'list_customers','command':'id'},'credential_frame':False,'start_seconds':None})
|
||||
assert wire.receive(conn)['type']=='transport_error'
|
||||
# Make only this synthetic endpoint reachable to a different UID, so the
|
||||
# SO_PEERCRED check, rather than DAC alone, is exercised.
|
||||
runtime.chmod(0o755);settings.core_socket.chmod(0o666)
|
||||
script='''import socket,sys
|
||||
s=socket.socket(socket.AF_UNIX);s.connect(sys.argv[1]);s.settimeout(3)
|
||||
assert s.recv(4)==b''
|
||||
'''
|
||||
other=pwd.getpwnam('daemon')
|
||||
def other_drop():os.setgroups([]);os.setgid(other.pw_gid);os.setuid(other.pw_uid)
|
||||
result=subprocess.run([sys.executable,'-c',script,str(settings.core_socket)],preexec_fn=other_drop,capture_output=True,timeout=5)
|
||||
assert result.returncode==0
|
||||
finally:
|
||||
if process and process.poll()is None:process.send_signal(signal.SIGTERM);process.wait(timeout=20)
|
||||
shutil.rmtree(base)
|
||||
Reference in New Issue
Block a user