harden sysrescue-autorun sysrescue-initialize.py against missing config values

Until now sysrescue-configuration.lua always ensured the default values were
in the effective JSON config because there was no way to remove them. Now the
lua script is improved to allow full config merging, including a delete function.
This could lead to the user accidently removing a value. The scripts did not
expect this and accessed non existing keys, leading to an exception.

This is fixed with this commit.
This commit is contained in:
Gerd v. Egidy 2022-04-24 01:08:27 +02:00
parent 24529bbb58
commit 8cb9d2de6b
2 changed files with 72 additions and 42 deletions

View file

@ -124,7 +124,17 @@ def search_autoruns(dirname, suffixes, copyfilefct):
found+=1
return found
def read_cfg_value(name, defaultval=None):
if name in config:
val = config[name]
else:
val = defaultval
print(f"config['{name}']={val}")
return val
def main():
global config
errcnt=0 # in case no autorun executed
logging.basicConfig(filename='/var/log/sysrescue-autorun.log', format='%(asctime)s %(message)s', level=logging.DEBUG)
writemsg('Initializing autorun ...')
@ -135,39 +145,47 @@ def main():
sys.exit(1)
with open(effectivecfg) as file:
fullcfg = json.load(file)
config = fullcfg['autorun']
#print(json.dumps(config, indent=4))
if 'autorun' in fullcfg:
config = fullcfg['autorun']
else:
config = { }
# ---- parse legacy options passed on the boot command line
for curopt in open("/proc/cmdline","r").read().split():
if re.match('^autoruns=', curopt): # "autoruns" is the legacy name for 'ar_suffixes'
config['ar_suffixes'] = curopt.replace('autoruns=','')
# ---- show the effective configuration
# ---- load the config into local variables, allow the keys not to exist
# ---- also print the effective configuration
logging.info(f"Showing the effective autorun configuration ...")
for key, val in config.items():
logging.info(f"config['{key}']={val}")
ar_suffixes = read_cfg_value('ar_suffixes', "")
ar_disable = read_cfg_value('ar_disable', False)
ar_attempts = read_cfg_value('ar_attempts', 1)
ar_source = read_cfg_value('ar_source', "")
ar_ignorefail = read_cfg_value('ar_ignorefail', False)
ar_nodel = read_cfg_value('ar_nodel', False)
ar_nowait = read_cfg_value('ar_nowait', False)
# ---- determine the effective script files suffixes
if config['ar_suffixes'] in (None, 'no', ''):
if ar_suffixes in (None, 'no', ''):
suffixes=['']
else:
suffixes=[''] + str(config['ar_suffixes']).split(',')
suffixes=[''] + str(ar_suffixes).split(',')
logging.info(f"suffixes={suffixes}")
# ---- exit here is there is nothing to do
if config['ar_disable'] == True:
if ar_disable == True:
writemsg(f"Autorun has been disabled using ar_disable, exiting now")
sys.exit(0)
# ---- parse autorun sources ----
if re.match('^https?://', config['ar_source']):
while config['ar_attempts'] > 0 and not autorunfiles:
if re.match('^https?://', ar_source):
while ar_attempts > 0 and not autorunfiles:
time.sleep(1)
config['ar_attempts'] -= 1
search_autoruns(config['ar_source'], suffixes, copyfilefct_http)
elif re.match('^/dev/', config['ar_source']): # mount a partition/device
mnt1=('mount',config['ar_source'],autorunmnt)
ar_attempts -= 1
search_autoruns(ar_source, suffixes, copyfilefct_http)
elif re.match('^/dev/', ar_source): # mount a partition/device
mnt1=('mount',ar_source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
p.wait()
@ -176,8 +194,8 @@ def main():
sys.exit(1)
search_autoruns(autorunmnt, suffixes, copyfilefct_basic)
subprocess.Popen(mnt2)
elif re.match('^nfs://', config['ar_source']): # mount an nfs share
source=config['ar_source'].replace('nfs://','')
elif re.match('^nfs://', ar_source): # mount an nfs share
source=ar_source.replace('nfs://','')
mnt1=('mount','-t','nfs','-o','nolock',source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
@ -187,8 +205,8 @@ def main():
sys.exit(1)
search_autoruns(autorunmnt, suffixes, copyfilefct_basic)
subprocess.Popen(mnt2)
elif re.match('^smb://', config['ar_source']): # mount a samba share
source=config['ar_source'].replace('smb://','')
elif re.match('^smb://', ar_source): # mount a samba share
source=ar_source.replace('smb://','')
mnt1=('mount','-t','cifs','//%s'%source,autorunmnt)
mnt2=('umount',autorunmnt)
p = subprocess.Popen(mnt1)
@ -260,20 +278,20 @@ def main():
writemsg (f'Execution of {filebase} returned {returncode}')
if returncode != 0:
errcnt += 1
if config['ar_ignorefail'] == False:
if ar_ignorefail == False:
writemsg (f'Now aborting autorun as {filebase} has failed')
break; # Stop on the first script failure
# ---- delete the copies of the scripts ----
if config['ar_nodel'] == False:
if ar_nodel == False:
for curfile in autorunfiles:
writemsg (f'removing {curfile}')
os.unlink(curfile)
# ---- wait a keypress feature -----
if os.path.isfile('/etc/ar_nowait'):
config['ar_nowait'] = True
if (config['ar_nowait'] == False) and (len(autorunfiles) > 0):
ar_nowait = True
if (ar_nowait != True) and (len(autorunfiles) > 0):
writemsg(f'Autorun scripts completed with {errcnt} errors, press <Enter> to continue')
sys.stdin.read(1)

View file

@ -28,17 +28,35 @@ with open(effectivecfg) as file:
config = json.load(file)
# ==============================================================================
# Show the effective configuration
# Sanitize config, initialize variables
# Make sysrescue-initialize work safely without them being defined
# Also show the effective configuration
# ==============================================================================
print(f"====> Showing the effective global configuration (except clear passwords) ...")
print(f"config['global']['setkmap']='{config['global']['setkmap']}'")
print(f"config['global']['rootshell']='{config['global']['rootshell']}'")
print(f"config['global']['rootcryptpass']='{config['global']['rootcryptpass']}'")
print(f"config['global']['nofirewall']={config['global']['nofirewall']}")
print(f"config['global']['dostartx']={config['global']['dostartx']}")
print(f"config['global']['noautologin']={config['global']['noautologin']}")
print(f"config['global']['dovnc']={config['global']['dovnc']}")
print(f"config['global']['late_load_srm']={config['global']['late_load_srm']}")
def read_cfg_value(scope, name, printval):
if not scope in config:
val = None
elif name in config[scope]:
val = config[scope][name]
else:
val = None
if printval:
print(f"config['{scope}']['{name}']={val}")
return val
setkmap = read_cfg_value('global','setkmap', True)
rootshell = read_cfg_value('global','rootshell', True)
rootpass = read_cfg_value('global','rootpass', False)
rootcryptpass = read_cfg_value('global','rootcryptpass', False)
nofirewall = read_cfg_value('global','nofirewall', True)
noautologin = read_cfg_value('global','noautologin', True)
dostartx = read_cfg_value('global','dostartx', True)
dovnc = read_cfg_value('global','dovnc', True)
vncpass = read_cfg_value('global','vncpass', False)
late_load_srm = read_cfg_value('global','late_load_srm', True)
# ==============================================================================
# Apply the effective configuration
@ -46,7 +64,6 @@ print(f"config['global']['late_load_srm']={config['global']['late_load_srm']}")
print(f"====> Applying configuration ...")
# Configure keyboard layout if requested in the configuration
setkmap = config['global']['setkmap']
if (setkmap != None) and (setkmap != ""):
p = subprocess.run(["localectl", "set-keymap", setkmap], text=True)
if p.returncode == 0:
@ -56,7 +73,6 @@ if (setkmap != None) and (setkmap != ""):
errcnt+=1
# Configure root login shell if requested in the configuration
rootshell = config['global']['rootshell']
if (rootshell != None) and (rootshell != ""):
p = subprocess.run(["chsh", "--shell", rootshell, "root"], text=True)
if p.returncode == 0:
@ -66,7 +82,6 @@ if (rootshell != None) and (rootshell != ""):
errcnt+=1
# Set the system root password from a clear password
rootpass = config['global']['rootpass']
if (rootpass != None) and (rootpass != ""):
p = subprocess.run(["chpasswd", "--crypt-method", "SHA512"], text=True, input=f"root:{rootpass}")
if p.returncode == 0:
@ -78,7 +93,6 @@ if (rootpass != None) and (rootpass != ""):
# Set the system root password from an encrypted password
# A password can be encrypted using a one-line python3 command such as:
# python3 -c 'import crypt; print(crypt.crypt("MyPassWord123", crypt.mksalt(crypt.METHOD_SHA512)))'
rootcryptpass = config['global']['rootcryptpass']
if (rootcryptpass != None) and (rootcryptpass != ""):
p = subprocess.run(["chpasswd", "--encrypted"], text=True, input=f"root:{rootcryptpass}")
if p.returncode == 0:
@ -88,7 +102,7 @@ if (rootcryptpass != None) and (rootcryptpass != ""):
errcnt+=1
# Disable the firewall
if config['global']['nofirewall'] == True:
if nofirewall == True:
# The firewall service(s) must be in the Before-section of sysrescue-initialize.service
p = subprocess.run(["systemctl", "disable", "--now", "iptables.service", "ip6tables.service"], text=True)
if p.returncode == 0:
@ -98,7 +112,7 @@ if config['global']['nofirewall'] == True:
errcnt+=1
# Auto-start the graphical environment (tty1 only)
if config['global']['dostartx'] == True:
if dostartx == True:
str = '[[ ! $DISPLAY ]] && [[ ! $SSH_TTY ]] && [[ $XDG_VTNR == 1 ]] && startx'
if (os.path.exists("/root/.bash_profile") == False) or (open("/root/.bash_profile", 'r').read().find(str) == -1):
file1 = open("/root/.bash_profile", "a")
@ -109,7 +123,7 @@ if config['global']['dostartx'] == True:
file2.close()
# Require authenticated console access
if config['global']['noautologin'] == True:
if noautologin == True:
p = subprocess.run(["systemctl", "revert", "getty@.service", "serial-getty@.service"], text=True)
if p.returncode == 0:
print (f"Have enabled authenticated console access successfully")
@ -118,7 +132,6 @@ if config['global']['noautologin'] == True:
errcnt+=1
# Set the VNC password from a clear password
vncpass = config['global']['vncpass']
if (vncpass != None) and (vncpass != ""):
os.makedirs("/root/.vnc", exist_ok = True)
p = subprocess.run(["x11vnc", "-storepasswd", vncpass, "/root/.vnc/passwd"], text=True)
@ -129,7 +142,7 @@ if (vncpass != None) and (vncpass != ""):
errcnt+=1
# Auto-start x11vnc with the graphical environment
if config['global']['dovnc'] == True:
if dovnc == True:
print (f"Enabling VNC Server in /root/.xprofile ...")
file = open("/root/.xprofile", "w")
file.write("""[ -f ~/.vnc/passwd ] && pwopt="-usepw" || pwopt="-nopw"\n""")
@ -141,7 +154,7 @@ if config['global']['dovnc'] == True:
# ==============================================================================
ca_anchor_path = "/etc/ca-certificates/trust-source/anchors/"
if config['sysconfig']['ca-trust']:
if 'sysconfig' in config and 'ca-trust' in config['sysconfig'] and config['sysconfig']['ca-trust']:
print(f"====> Adding trusted CA certificates ...")
for name, cert in sorted(config['sysconfig']['ca-trust'].items()):
@ -156,7 +169,6 @@ if config['sysconfig']['ca-trust']:
# late-load a SystemRescueModule (SRM)
# ==============================================================================
late_load_srm = config['global']['late_load_srm']
if (late_load_srm != None) and (late_load_srm != ""):
print(f"====> Late-loading SystemRescueModule (SRM) ...")
p = subprocess.run(["/usr/share/sysrescue/bin/load-srm", late_load_srm], text=True)