From 56cebaeec285c74ef531fa7b8436febe85547608 Mon Sep 17 00:00:00 2001 From: rajendra-dendukuri <47423477+rajendra-dendukuri@users.noreply.github.com> Date: Tue, 13 Feb 2024 19:49:42 -0500 Subject: [PATCH] Enhancements to allow custom umask usage while executing plugins (#42) - Use default umask value of 022 to execute ztp plugins - User can provide a custom umask octal value string as part of plugin data if a specific umask is required. { "plugin" : { "url" : "http://10.1.1.1/plugins/my-plugin", "umask" : "177" } } - Firmware plugin has been modified to always use 022 umask when installing sonic image - Added unit test cases to test user specified umask value --- .../lib/python3/dist-packages/ztp/ZTPLib.py | 6 ++--- .../lib/python3/dist-packages/ztp/defaults.py | 1 + src/usr/lib/ztp/plugins/firmware | 2 +- src/usr/lib/ztp/ztp-engine.py | 9 +++++++- tests/test_ZTPLib.py | 8 +++++++ tests/test_ztp_engine.py | 23 +++++++++++++++++++ 6 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/usr/lib/python3/dist-packages/ztp/ZTPLib.py b/src/usr/lib/python3/dist-packages/ztp/ZTPLib.py index f2354ce..bea60b0 100644 --- a/src/usr/lib/python3/dist-packages/ztp/ZTPLib.py +++ b/src/usr/lib/python3/dist-packages/ztp/ZTPLib.py @@ -58,7 +58,7 @@ def getTimestamp(): ## Global variable to keep track of the pid or process created by runCommand() runcmd_pids = [] -def runCommand(cmd, capture_stdout=True, use_shell=False): +def runCommand(cmd, capture_stdout=True, use_shell=False, umask=-1): '''! Execute a given command @@ -95,7 +95,7 @@ def runCommand(cmd, capture_stdout=True, use_shell=False): else: shcmd = cmd if capture_stdout is True: - proc = subprocess.Popen(shcmd, shell=use_shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) + proc = subprocess.Popen(shcmd, shell=use_shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, umask=umask) pid = proc.pid runcmd_pids.append(pid) output_stdout, output_stderr = proc.communicate() @@ -109,7 +109,7 @@ def runCommand(cmd, capture_stdout=True, use_shell=False): list_stderr.append(str(l.decode())) return (proc.returncode, list_stdout, list_stderr) else: - proc = subprocess.Popen(shcmd, shell=use_shell) + proc = subprocess.Popen(shcmd, shell=use_shell, umask=umask) pid = proc.pid runcmd_pids.append(pid) proc.communicate() diff --git a/src/usr/lib/python3/dist-packages/ztp/defaults.py b/src/usr/lib/python3/dist-packages/ztp/defaults.py index 34464d3..d3f8bd9 100644 --- a/src/usr/lib/python3/dist-packages/ztp/defaults.py +++ b/src/usr/lib/python3/dist-packages/ztp/defaults.py @@ -59,6 +59,7 @@ "section-input-file" : "input.json", \ "sighandler-wait-interval" : 60, \ "test-mode" : False, \ + "umask" : "022", \ "ztp-activity" : '/var/run/ztp/activity', \ "ztp-cfg-dir" : "/host/ztp", \ "ztp-json" : "/host/ztp/ztp_data.json", \ diff --git a/src/usr/lib/ztp/plugins/firmware b/src/usr/lib/ztp/plugins/firmware index c1fca46..416f684 100755 --- a/src/usr/lib/ztp/plugins/firmware +++ b/src/usr/lib/ztp/plugins/firmware @@ -379,7 +379,7 @@ class Firmware: # Execute sonic-installer command logger.info('firmware: Installing firmware image located at \'%s\'.' % self.__dest_file) updateActivity('firmware: Installing firmware image located at \'%s\'.' % self.__dest_file) - rc = runCommand(cmd, capture_stdout=False) + rc = runCommand(cmd, capture_stdout=False, umask=0o022) if rc != 0: self.__bailout('Error (%d) encountered while processing command : %s' % (cmd)) diff --git a/src/usr/lib/ztp/ztp-engine.py b/src/usr/lib/ztp/ztp-engine.py index 1d6ad89..3edd5e1 100755 --- a/src/usr/lib/ztp/ztp-engine.py +++ b/src/usr/lib/ztp/ztp-engine.py @@ -498,6 +498,13 @@ def __processConfigSections(self): # Determine if shell has to be used to execute plugin _shell = getField(plugin_data, 'shell', bool, False) + # Determine if user specified umask has to be used to execute plugin + try: + _umask = int(getField(plugin_data, 'umask', str, getCfg("umask")), 8) + except Exception as e: + logger.error('Exception[%s] encountered while reading umask to execute the plugin for %s. Using default value -1.' % (str(e), sec)) + _umask = -1 + # Construct the full plugin command string along with arguments plugin_cmd = plugin if plugin_args is not None: @@ -506,7 +513,7 @@ def __processConfigSections(self): # A plugin has been resolved and its input configuration section data as well logger.debug('Executing plugin %s.' % (plugin_cmd)) # Execute identified plugin - rc = runCommand(plugin_cmd, capture_stdout=False, use_shell=_shell) + rc = runCommand(plugin_cmd, capture_stdout=False, use_shell=_shell, umask=_umask) logger.debug('Plugin %s exit code = %d.' % (plugin_cmd, rc)) # Compare plugin exit code diff --git a/tests/test_ZTPLib.py b/tests/test_ZTPLib.py index aa3c57a..d8a23ae 100644 --- a/tests/test_ZTPLib.py +++ b/tests/test_ZTPLib.py @@ -41,6 +41,14 @@ def test_cmd(self, tmpdir): assert((cmd_stdout1 == cmd_stdout2) and (cmd_stdout2 == cmd_stdout3) and (cmd_stdout3 == cmd_stdout4)) assert((cmd_stderr1 == cmd_stderr2) and (cmd_stderr2 == cmd_stderr3) and (cmd_stderr3 == cmd_stderr4)) + (rc5, cmd_stdout5, cmd_stderr5) = runCommand(['touch', '/tmp/test_file_644'], use_shell=True, umask=0o022) + st = os.stat('/tmp/test_file_644') + assert(0o644, oct(st.st_mode)) + (rc6, cmd_stdout6, cmd_stderr6) = runCommand(['touch', '/tmp/test_file_600'], use_shell=True, umask=0o177) + st = os.stat('/tmp/test_file_600') + assert(0o600, oct(st.st_mode)) + runCommand("rm -f /tmp/test_file_644 /tmp/test_file_600") + (rc1, cmd_stdout1, cmd_stderr1) = runCommand('ps hjk') (rc2, cmd_stdout2, cmd_stderr2) = runCommand('ps hjk', use_shell=True) (rc3, cmd_stdout3, cmd_stderr3) = runCommand(['ps', 'hjk']) diff --git a/tests/test_ztp_engine.py b/tests/test_ztp_engine.py index 7d73d9c..d7b00d7 100644 --- a/tests/test_ztp_engine.py +++ b/tests/test_ztp_engine.py @@ -181,6 +181,22 @@ def test_ztp_json(self): "message-file" : "/etc/ztp.results", "fail" : false, "status" : "DISABLED" + }, + "0005-test-plugin": { + "plugin" : { + "name" : "test-plugin", + "umask" : "177" + }, + "message" : "umask-check", + "message-file" : "/tmp/test_plugin_umask_600" + }, + "0006-test-plugin": { + "plugin" : { + "name" : "test-plugin", + "umask" : "177" + }, + "message" : "umask-check", + "message-file" : "/tmp/test_plugin_umask_644" } } }""" @@ -219,6 +235,13 @@ def test_ztp_json(self): assert(jsonDict.get('ztp').get('0004-test-plugin').get('timestamp') != None) assert(jsonDict.get('ztp').get('0004-test-plugin').get('exit-code') == 0) + st = os.stat("/tmp/test_plugin_umask_600") + assert(0o600, oct(st.st_mode)) + + st = os.stat("/tmp/test_plugin_umask_644") + assert(0o644, oct(st.st_mode)) + runCommand("rm -f /tmp/test_plugin_umask_600 /tmp/test_plugin_umask_644") + old_timestamp = jsonDict.get('ztp').get('timestamp') runCommand(COVERAGE + ZTP_ENGINE_CMD) runCommand(COVERAGE + ZTP_CMD + ' status -v')