Created
March 11, 2021 14:26
-
-
Save coder-chenzhi/3e348c0c9de4cd6f968a6390dab1011c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import ast | |
PROJECTS_HOME = r"E:\OSS\ansible_collections" | |
SENSITIVE_KEYS = "passw" | |
def get_key_from_dict(tree, key): | |
""" | |
:param tree: ast tree to traverse, usually is the value of a dict | |
:param key: key to search, usually is 'type' or 'no_log' | |
:return: if key exist in tree, return the node, if not exist, return None | |
""" | |
for value in ast.walk(tree): | |
if isinstance(value, ast.Dict): | |
for i, key_name in enumerate(value.keys): | |
if isinstance(key_name, ast.Str): | |
if key in key_name.s: | |
return value.values[i] | |
if isinstance(key_name, ast.Name): | |
if key in key_name.id: | |
return value.values[i] | |
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "dict": | |
for keyword in value.keywords: | |
if key in keyword.arg: | |
return keyword.value | |
return None | |
def traverse_dict(tree): | |
def _check(name, value): | |
type = get_key_from_dict(value, "type") | |
default = get_key_from_dict(value, "default") | |
choices = get_key_from_dict(value, "choices") | |
if type is not None and "str" in ast.dump(type) and default is None and choices is None: | |
print("Found sensitive key:", name) | |
no_log = get_key_from_dict(value, "no_log") | |
if no_log is None: | |
print("Missing of no_log for sensitive key at line {line}: {name}".format( | |
line=value.lineno, name=name)) | |
for value in ast.walk(tree): | |
real_name = None | |
if isinstance(value, ast.Dict): | |
for i, key_name in enumerate(value.keys): | |
if isinstance(key_name, ast.Str): | |
if SENSITIVE_KEYS in key_name.s: | |
real_name = key_name.s | |
real_value = value.values[i] | |
if isinstance(key_name, ast.Name): | |
if SENSITIVE_KEYS in key_name.id: | |
real_name = key_name.id | |
real_value = value.values[i] | |
if real_name is not None: | |
_check(real_name, real_value) | |
if isinstance(value, ast.Call) and isinstance(value.func, ast.Name) and value.func.id == "dict": | |
for keyword in value.keywords: | |
if SENSITIVE_KEYS in keyword.arg: | |
real_name = keyword.arg | |
real_value = keyword.value | |
_check(real_name, real_value) | |
if __name__ == '__main__': | |
projects = os.listdir(PROJECTS_HOME) | |
for project in projects: | |
for root, dirs, files in os.walk(os.path.join(PROJECTS_HOME, project), topdown=False): | |
for name in files: | |
if not name.endswith(".py"): | |
continue | |
file_path = os.path.join(root, name) | |
with open(file_path, "r", encoding="UTF-8", errors="ignore") as file_handler: | |
code = file_handler.read() | |
print(file_path) | |
tree = ast.parse(code) | |
for node in ast.walk(tree): | |
if isinstance(node, ast.Assign): | |
for name in node.targets: | |
if isinstance(name, ast.Name) and "argument_spec" in name.id: | |
traverse_dict(node.value) | |
if isinstance(node, ast.Call): | |
func = node.func | |
if isinstance(func, ast.Attribute): | |
if "argument_spec" in ast.dump(func.value) and "update" == func.attr: | |
for arg in node.args: | |
traverse_dict(arg) | |
for keyword in node.keywords: | |
traverse_dict(keyword) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment