Config: Support refuse_mode_mask

refuse_mode_mask can be passed to the constructor and determines
which permission bits need to be absent from config file modes if
they smell like they contain secrets.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2025-01-30 09:54:03 +01:00
commit e688cd2364

View file

@ -13,7 +13,7 @@ from .log import *
class Config(): # export class Config(): # export
def __load(self, search_dirs, glob_paths): def __load(self, search_dirs, glob_paths, refuse_mode_mask):
def __is_abs(path): def __is_abs(path):
if path is None: if path is None:
@ -45,26 +45,27 @@ class Config(): # export
paths_buf = [] paths_buf = []
tree = stree.read(f, paths_buf=paths_buf) tree = stree.read(f, paths_buf=paths_buf)
assert(len(paths_buf)) assert(len(paths_buf))
for p in paths_buf: if refuse_mode_mask is not None:
st = os.stat(p) for p in paths_buf:
if st.st_mode & 0o0077: st = os.stat(p)
for item in tree.child_list(): if st.st_mode & refuse_mode_mask:
if item.content is None: for item in tree.child_list():
continue if item.content is None:
if not re.search('password|secret', cast(str, item.content), flags=re.IGNORECASE): continue
continue if not re.search('password|secret', cast(str, item.content), flags=re.IGNORECASE):
msg = "Config files define secret, but at least one has file permissions open for group or world" continue
slog(ERR, f'{msg}:') msg = "Config files define secret, but at least one has file permissions open for world"
for pp in paths_buf: slog(ERR, f'{msg}:')
slog(ERR, f' {((os.stat(p).st_mode) & 0o7777):o} {pp}') for pp in paths_buf:
raise Exception(msg) slog(ERR, f' {((os.stat(pp).st_mode) & 0o7777):o} {pp}')
raise Exception(msg)
tree.dump(DEBUG, f) tree.dump(DEBUG, f)
ret.add("", tree) ret.add("", tree)
return ret return ret
def __init__(self, search_dirs: Optional[list[str]]=None, glob_paths: Optional[list[str]]=None, def __init__(self, search_dirs: Optional[list[str]]=None, glob_paths: Optional[list[str]]=None,
defaults: Optional[Dict[str, str]]=None, tree: Optional[StringTree]=None, parent=None, defaults: Optional[Dict[str, str]]=None, tree: Optional[StringTree]=None, parent=None,
root_section=None) -> None: root_section=None, refuse_mode_mask=0o0027) -> None:
self.__parent = parent self.__parent = parent
@ -74,7 +75,8 @@ class Config(): # export
self.__conf = tree self.__conf = tree
else: else:
assert(tree is None) assert(tree is None)
self.__conf = self.__load(search_dirs=search_dirs, glob_paths=glob_paths) self.__conf = self.__load(search_dirs=search_dirs, glob_paths=glob_paths,
refuse_mode_mask=refuse_mode_mask)
if root_section is not None: if root_section is not None:
tmp = self.__conf.get(root_section) tmp = self.__conf.get(root_section)