Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote script execution for snaps #209

Merged
merged 9 commits into from
Feb 20, 2024
32 changes: 22 additions & 10 deletions landscape/client/manager/scriptexecution.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from twisted.python.compat import unicode

from landscape import VERSION
from landscape.client import IS_SNAP
from landscape.client.manager.plugin import FAILED
from landscape.client.manager.plugin import ManagerPlugin
from landscape.client.manager.plugin import SUCCEEDED
Expand Down Expand Up @@ -96,9 +97,11 @@ def write_script_file(self, script_file, filename, shell, code, uid, gid):
# It would be nice to use fchown(2) and fchmod(2), but they're not
# available in python and using it with ctypes is pretty tedious, not
# to mention we can't get errno.
# Don't attempt to change file owner if the client is a snap
os.chmod(filename, 0o700)
if uid is not None:
os.chown(filename, uid, gid)
if not IS_SNAP:
if uid is not None:
mcw-work marked this conversation as resolved.
Show resolved Hide resolved
os.chown(filename, uid, gid)

script = build_script(shell, code)
script = script.encode("utf-8")
Expand Down Expand Up @@ -172,7 +175,8 @@ def _respond(self, status, data, opid, result_code=None):
def _handle_execute_script(self, message):
opid = message["operation-id"]
try:
user = message["username"]
user = message["username"] if not IS_SNAP else "root"

if not self.is_user_allowed(user):
return self._respond(
FAILED,
Expand Down Expand Up @@ -245,12 +249,14 @@ def _save_attachments(self, attachments, uid, gid, computer_id, env):
full_filename = os.path.join(attachment_dir, filename)
with open(full_filename, "wb") as attachment:
os.chmod(full_filename, 0o600)
if uid is not None:
os.chown(full_filename, uid, gid)
if not IS_SNAP:
if uid is not None:
os.chown(full_filename, uid, gid)
attachment.write(data)
os.chmod(attachment_dir, 0o700)
if uid is not None:
os.chown(attachment_dir, uid, gid)
if not IS_SNAP:
if uid is not None:
os.chown(attachment_dir, uid, gid)
returnValue(attachment_dir)

def run_script(
Expand Down Expand Up @@ -296,9 +302,15 @@ def run_script(
"USER": user or "",
"HOME": path or "",
}
for locale_var in ("LANG", "LC_ALL", "LC_CTYPE"):
if locale_var in os.environ:
env[locale_var] = os.environ[locale_var]
for env_var in (
"LANG",
"LC_ALL",
"LC_CTYPE",
"LD_LIBRARY_PATH",
"PYTHONPATH",
):
mcw-work marked this conversation as resolved.
Show resolved Hide resolved
if env_var in os.environ:
env[env_var] = os.environ[env_var]
if server_supplied_env:
env.update(server_supplied_env)
old_umask = os.umask(0o022)
Expand Down
45 changes: 38 additions & 7 deletions landscape/client/manager/tests/test_scriptexecution.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ def get_default_environment():
"USER": username,
"HOME": home,
}
for var in {"LANG", "LC_ALL", "LC_CTYPE"}:
for var in {
"LANG",
"LC_ALL",
"LC_CTYPE",
"LD_LIBRARY_PATH",
"PYTHONPATH",
}:
if var in os.environ:
env[var] = os.environ[var]
return env
Expand Down Expand Up @@ -415,9 +421,14 @@ def check(result):
result.addCallback(check)
return result

def _run_script(self, username, uid, gid, path):
expected_uid = uid if uid != os.getuid() else None
expected_gid = gid if gid != os.getgid() else None
def _run_script(self, username, uid, gid, path, from_snap=False):

if from_snap:
expected_gid = None
expected_uid = None
else:
expected_uid = uid if uid != os.getuid() else None
expected_gid = gid if gid != os.getgid() else None

factory = StubProcessFactory()
self.plugin.process_factory = factory
Expand All @@ -426,6 +437,10 @@ def _run_script(self, username, uid, gid, path):
patch_chown = mock.patch("os.chown")
mock_chown = patch_chown.start()

patch_issnap = mock.patch("landscape.client.IS_SNAP")
patch_issnap.return_value = from_snap
patch_issnap.start()
mcw-work marked this conversation as resolved.
Show resolved Hide resolved

result = self.plugin.run_script("/bin/sh", "echo hi", user=username)

self.assertEqual(len(factory.spawns), 1)
Expand All @@ -441,14 +456,15 @@ def _run_script(self, username, uid, gid, path):
protocol.processEnded(Failure(ProcessDone(0)))

def check(result):
mock_chown.assert_called_with()
mock_chown.assert_called()
self.assertEqual(result, "foobar")

def cleanup(result):
patch_chown.stop()
patch_issnap.stop()
return result

return result.addErrback(check).addBoth(cleanup)
return result.addCallback(check).addBoth(cleanup)

def test_user(self):
"""
Expand All @@ -463,7 +479,22 @@ def test_user(self):
gid = info.pw_gid
path = info.pw_dir

return self._run_script(username, uid, gid, path)
return self._run_script(username, uid, gid, path, from_snap=False)

def test_user_from_snap(self):
"""
Running a script as a particular user calls
C{IReactorProcess.spawnProcess} with an appropriate C{uid} argument,
with the user's primary group as the C{gid} argument and with the user
home as C{path} argument.
"""
uid = os.getuid()
info = pwd.getpwuid(uid)
username = info.pw_name
gid = info.pw_gid
path = info.pw_dir

return self._run_script(username, uid, gid, path, from_snap=True)

def test_user_no_home(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion snap/hooks/default-configure
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ url = ${_url}/message-system
ping_url = ${_url}/ping
log_level = info
script_users = ALL
manager_plugins = ProcessKiller,UserManager,ShutdownManager,HardwareInfo,KeystoneToken,SnapManager
manager_plugins = ProcessKiller,UserManager,ShutdownManager,HardwareInfo,KeystoneToken,SnapManager,ScriptExecution
monitor_plugins = ActiveProcessInfo,ComputerInfo,LoadAverage,MemoryInfo,MountInfo,ProcessorInfo,Temperature,UserMonitor,RebootRequired,NetworkActivity,NetworkDevice,CPUUsage,SwiftUsage,CephUsage,ComputerTags,SnapMonitor
EOF

Expand Down
Loading