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

Create a shadow ZTP data json file accessible to non-root user #13

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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