Skip to content

Commit

Permalink
Create a shadow ZTP data json file accessible to non-root user (#13)
Browse files Browse the repository at this point in the history
To allow non-root user to view status information, a shadow file for the current
ztp_data.json is created. The shadow file (ztp_data_shadow.json) contains only
status information which does not provide knowledge of url's of the
configuration scripts used for ztp. The ztp_data_shadow.json file is updated
everytume ztp_data.json is updated. The ztp_data_shadow.json is accessible to
non-root user as well while ztp_data.json is accessible only to root user and
has more information about the url's where configuration scripts can be downloaded
from.

Signed-off-by: Rajendra Dendukuri <rajendra.dendukuri@broadcom.com>
  • Loading branch information
rajendra-dendukuri authored Apr 29, 2020
1 parent 374c9e8 commit 1018662
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 9 deletions.
27 changes: 19 additions & 8 deletions src/usr/bin/ztp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def getActivityString():
print ('ZTP Service is not running\n')
return

if os.geteuid() != 0:
print ('ZTP Service is active\n')
return

activity_str = None
f = getCfg('ztp-activity')
if os.path.isfile(f):
Expand Down Expand Up @@ -160,6 +164,8 @@ def ztp_erase(yesFlag):
# Destroy current provisioning data
if os.path.isfile(getCfg('ztp-json', ztp_cfg=ztp_cfg)):
os.remove(getCfg('ztp-json', ztp_cfg=ztp_cfg))
if os.path.isfile(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg)):
os.remove(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg))

## Administratively disable ZTP.
# It also stops ztp service if it found active, before modifying the configuration.
Expand Down Expand Up @@ -219,8 +225,8 @@ def ztp_run(yesFlag):
def ztp_status_code():
if getCfg('admin-mode', ztp_cfg=ztp_cfg) is False:
print ('0:DISABLED')
elif os.path.isfile(getCfg('ztp-json', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json', ztp_cfg=ztp_cfg), indent=4)
elif os.path.isfile(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg), indent=4)
ztpDict = jsonDict.get('ztp')
if ztpDict.get('status') == 'BOOT':
print ('3:NOT-STARTED')
Expand All @@ -241,8 +247,8 @@ def ztp_status_code():
def ztp_status_terse():
# Print overall ZTP status
print ('ZTP Admin Mode : %r' % getCfg('admin-mode', ztp_cfg=ztp_cfg))
if os.path.isfile(getCfg('ztp-json', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json', ztp_cfg=ztp_cfg), indent=4)
if os.path.isfile(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg), indent=4)
ztpDict = jsonDict.get('ztp')
if ztp_active() != 0:
print ('ZTP Service : Inactive')
Expand Down Expand Up @@ -284,8 +290,8 @@ def ztp_status():
print('%s' % 'ZTP')
print('========================================')
print ('ZTP Admin Mode : %r' % getCfg('admin-mode', ztp_cfg=ztp_cfg))
if os.path.isfile(getCfg('ztp-json', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json', ztp_cfg=ztp_cfg), indent=4)
if os.path.isfile(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg)):
objJson, jsonDict = JsonReader(getCfg('ztp-json-shadow', ztp_cfg=ztp_cfg), indent=4)
ztpDict = jsonDict.get('ztp')
if ztp_active() != 0:
print ('ZTP Service : Inactive')
Expand Down Expand Up @@ -343,9 +349,11 @@ def ztp_status():

def main():

# Only privileged users can execute this command
# Check the user's root privileges
if os.geteuid() != 0:
sys.exit("Root privileges required for this operation")
root_user = False
else:
root_user = True

# Add allowed arguments
parser = argparse.ArgumentParser(description="Zero Touch Provisioning configuration and status monitoring tool",
Expand Down Expand Up @@ -393,6 +401,9 @@ run Restart ZTP\nstatus Display current state of ZTP and last known resul
print('Exiting ZTP service.')
sys.exit(1)

if root_user == False and cmd in ['enable', 'disable', 'run', 'erase']:
sys.exit("Root privileges required for this operation")

# Execute the command
try:
if cmd == 'enable' :
Expand Down
37 changes: 37 additions & 0 deletions src/usr/lib/python3/dist-packages/ztp/ZTPSections.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,39 @@ class ZTPJson(ConfigSection):
'''!
\brief This class is use to store and access ZTP JSON data downloaded by ZTP service.
'''

def updateStatus(self, obj, status):
super().updateStatus(obj, status)
# Update the shadow ZTP JSON file with new information
self.__writeShadowJSON()

def __writeShadowJSON(self):
'''!
Save contents of ZTP JSON in a shadow file which includes data that provides
just provisioning status information and filters out all other sensitive information.
'''
allowed_keys = ['ignore-result', 'reboot-on-success', \
'reboot-on-failure', 'halt-on-failure', \
'description', 'timestamp', 'status', \
'start-timestamp', 'error']
section_names = list()
shadowObj, shadowJsonDict = JsonReader(self.json_dst_file, getCfg('ztp-json-shadow'), indent=getCfg('json-indent'))
shadowDict = shadowJsonDict['ztp']
for k, v in shadowDict.items():
# Identify configuration sections
if isinstance(v, dict):
section_names.append(k)

for section in section_names:
section_elements = list(shadowDict[section].keys())
for sub_k in section_elements:
# Remove sensitive data
if sub_k not in allowed_keys:
del shadowDict[section][sub_k]

shadowObj.writeJson()
os.chmod(getCfg('ztp-json-shadow'), 0o644)

def __getitem__(self, key):
'''!
Get value of specified key in the top level ztp section read from ZTP JSON file.
Expand Down Expand Up @@ -175,6 +208,8 @@ def __setitem__(self, key, val):
self.updateStatus(self.ztpDict, val)
else:
self.ztpDict[key] = val
# Update the shadow ZTP JSON file with new information
self.__writeShadowJSON()

def pluginArgs(self, section_name):
'''!
Expand Down Expand Up @@ -420,3 +455,5 @@ def __init__(self, json_src_file=None, json_dst_file=None):

# Write ZTP JSON data to file
self.objJson.writeJson()
# Update the shadow ZTP JSON file with new information
self.__writeShadowJSON()
1 change: 1 addition & 0 deletions src/usr/lib/python3/dist-packages/ztp/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"ztp-activity" : '/var/run/ztp/activity', \
"ztp-cfg-dir" : "/host/ztp", \
"ztp-json" : "/host/ztp/ztp_data.json", \
"ztp-json-shadow" : "/host/ztp/ztp_data_shadow.json", \
"ztp-json-local" : "/host/ztp/ztp_data_local.json", \
"ztp-json-opt59" : "/var/run/ztp/ztp_data_opt59.json", \
"ztp-json-opt67" : "/var/run/ztp/ztp_data_opt67.json", \
Expand Down
6 changes: 6 additions & 0 deletions src/usr/lib/ztp/ztp-engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ def __processZTPJson(self):
logger.error('ZTP JSON file %s processing failed.' % (self.json_src))
try:
os.remove(getCfg('ztp-json'))
if os.path.isfile(getCfg('ztp-json-shadow')):
os.remove(getCfg('ztp-json-shadow'))
except OSError as v:
if v.errno != errno.ENOENT:
logger.warning('Exception [%s] encountered while deleting ZTP JSON file %s.' % (str(v), getCfg('ztp-json')))
Expand Down Expand Up @@ -507,6 +509,8 @@ def __processZTPJson(self):
# Discover new ZTP data after deleting historic ZTP data
logger.info("ZTP restart requested. Deleting previous ZTP session JSON data.")
os.remove(getCfg('ztp-json'))
if os.path.isfile(getCfg('ztp-json-shadow')):
os.remove(getCfg('ztp-json-shadow'))
self.objztpJson = None
return ("retry", "ZTP restart requested")
else:
Expand Down Expand Up @@ -539,6 +543,8 @@ def __processZTPJson(self):
# Mark ZTP for restart
if _restart_ztp_missing_config or _restart_ztp_on_failure:
os.remove(getCfg('ztp-json'))
if os.path.isfile(getCfg('ztp-json-shadow')):
os.remove(getCfg('ztp-json-shadow'))
self.objztpJson = None
# Remove startup-config file to obtain a new one through ZTP
if getCfg('monitor-startup-config') is True and os.path.isfile(getCfg('config-db-json')):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ztp_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def __init_ztp_data(self):
runCommand("systemctl stop ztp")
# Destroy current provisioning data
file_list = ["ztp-json-local", "ztp-json-opt67", "ztp-json", "provisioning-script", "opt67-url", "opt59-v6-url", \
"opt239-url", "opt239-v6-url", "ztp-restart-flag", "opt66-tftp-server", "acl-url", "graph-url"]
"opt239-url", "opt239-v6-url", "ztp-restart-flag", "opt66-tftp-server", "acl-url", "graph-url", "ztp-json-shadow"]

for filename in file_list:
if os.path.isfile(self.cfgGet(filename)):
Expand Down

0 comments on commit 1018662

Please sign in to comment.