#!/usr/bin/python3 # Validate basic syntax of shell script and yaml. import os import re import stat import subprocess import yaml INITRD_SERVICES_WITHOUT_BEFORE = { # Depended on by other services 'coreos-livepxe-rootfs.service', } validated=0 def openat(dirfd, name, mode='r'): def opener(path, flags): return os.open(path, flags, dir_fd=dirfd) return open(name, mode, opener=opener) def validate_initrd_service(rootfd, name): with openat(rootfd, name) as fh: if ([l for l in fh.readlines() if l.startswith('Before=')] or name in INITRD_SERVICES_WITHOUT_BEFORE): global validated validated += 1 else: raise Exception( f'{name} has no Before= and may race with switch-root' ) BASH_UNBRACKETED_IF = re.compile(r'\sif\s+"?\$') def validate_shell(rootfd, name): subprocess.check_call(['bash', '-n', name], preexec_fn=lambda: os.fchdir(rootfd)) with openat(rootfd, name) as fh: if BASH_UNBRACKETED_IF.search(fh.read()): raise Exception(f'Possible unbracketed conditional in {name}') global validated validated +=1 for root, dirs, files, rootfd in os.fwalk('.'): # Skip .git if '.git' in dirs: dirs.remove('.git') for name in files: print(f"[+] Looking at {name}") if name.endswith(('.yaml', '.yml')): with open(os.open(name, dir_fd=rootfd, flags=os.O_RDONLY)) as f: yaml.safe_load(f) validated +=1 continue elif name.endswith('.sh'): validate_shell(rootfd, name) continue elif 'dracut/modules.d' in root and name.endswith('.service'): validate_initrd_service(rootfd, name) stbuf = os.lstat(name, dir_fd=rootfd) if not stat.S_ISREG(stbuf.st_mode): continue if not stbuf.st_mode & stat.S_IXUSR: continue mimetype = subprocess.check_output(['file', '-b', '--mime-type', name], encoding='UTF-8', preexec_fn=lambda: os.fchdir(rootfd)).strip() if mimetype == 'text/x-shellscript': validate_shell(rootfd, name) print(f"Validated {validated} files")