feat: add pre-commit to check we don't have real passwords in yml files (#990)
* feat: use existing pre-commit framework * feat(ci): add github action for password_check * feat: add some simple tests to password_check.py * fix: set `backend_password_secret` in default values.yaml to an allowed password
This commit is contained in:
parent
c21153255a
commit
b5a9c42df1
40
.github/workflows/password-check.yaml
vendored
Normal file
40
.github/workflows/password-check.yaml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Password Check
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.yaml'
|
||||
- '*.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- '*.yaml'
|
||||
- '*.yml'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd backend/
|
||||
python -m pip install --upgrade pip
|
||||
pip install pyyaml
|
||||
|
||||
- name: Password Check
|
||||
run: |
|
||||
CHANGED_FILES=$(git diff --name-only HEAD^..HEAD)
|
||||
echo $CHANGED_FILES
|
||||
YML_FILES=$(echo "$CHANGED_FILES" | { grep ".yml$\|.yaml$" || true; })
|
||||
if [[ -n "$YML_FILES" ]]; then
|
||||
python3 scripts/check_passwords.py $YML_FILES
|
||||
fi
|
@ -19,3 +19,10 @@ repos:
|
||||
language: system
|
||||
entry: frontend/.husky/pre-commit
|
||||
pass_filenames: false
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: password-check
|
||||
name: password-check
|
||||
language: system
|
||||
types: [yaml]
|
||||
entry: python3 scripts/check_passwords.py
|
||||
|
@ -91,7 +91,7 @@ default_org: "My Organization"
|
||||
backend_image: "docker.io/webrecorder/browsertrix-backend:latest"
|
||||
backend_pull_policy: "Always"
|
||||
|
||||
backend_password_secret: "c9085f33ecce4347aa1d69339e16c499"
|
||||
backend_password_secret: "PASSWORD!"
|
||||
|
||||
# number of backend pods
|
||||
backend_num_replicas: 1
|
||||
|
47
scripts/check_passwords.py
Normal file
47
scripts/check_passwords.py
Normal file
@ -0,0 +1,47 @@
|
||||
"A small dirty script to check that none of the password config options have been set to real passwords"
|
||||
from collections.abc import Generator
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
|
||||
ALLOWED_PASSWORDS = ["PassW0rd!", "password", "PASSWORD@", "PASSW0RD!", "PASSWORD!"]
|
||||
|
||||
def key_finder(d: dict, key: str = "password", top_level = None) -> Generator:
|
||||
"""This recursive function yields all the keys in {d} that _contains_ the string {key}
|
||||
|
||||
:param dict d: The dictionary to dive through
|
||||
:param str key: The phrase we are going to match keys against
|
||||
:return: A generator that creates tuples containing Optional[top_level_key], key, value
|
||||
:rtype Union[tuple[str, str], tuple[str, str, str]]
|
||||
"""
|
||||
if d is None:
|
||||
return {}
|
||||
for k, v in d.items():
|
||||
if isinstance(v, dict):
|
||||
if top_level is None:
|
||||
yield from key_finder(v, key, k) # Pass the top level name into the recursive descent
|
||||
else:
|
||||
yield from key_finder(v, key, top_level) # name isn't the top level key
|
||||
if key in str(k): # Sometimes yaml gets parsed with key True
|
||||
if top_level is None:
|
||||
yield k, v # Key is already top level
|
||||
else:
|
||||
yield top_level, k, v # Use the top level name
|
||||
|
||||
WE_DUN_GOOFED: bool = False
|
||||
|
||||
changed_files = sys.argv[1:] # Ignore filename of this script
|
||||
for file in changed_files:
|
||||
with open(file, 'r') as f:
|
||||
yml = yaml.safe_load(f)
|
||||
gen = key_finder(yml)
|
||||
for password_keys in gen:
|
||||
if password_keys[-1] not in ALLOWED_PASSWORDS:
|
||||
if len(password_keys) == 2:
|
||||
print(f"top level key '{password_keys[0]}' in {file} contains a real password!")
|
||||
else:
|
||||
print(f"top level key '{password_keys[0]}' with subkey '{password_keys[1]}' in {file} contains a real password!")
|
||||
WE_DUN_GOOFED = True
|
||||
|
||||
if WE_DUN_GOOFED:
|
||||
exit(1)
|
79
scripts/test/test_check_passwords.py
Normal file
79
scripts/test/test_check_passwords.py
Normal file
@ -0,0 +1,79 @@
|
||||
import pytest
|
||||
import yaml
|
||||
from os import listdir
|
||||
# Import hacking for script
|
||||
import sys
|
||||
sys.path.insert(0, '.')
|
||||
import check_passwords
|
||||
|
||||
@pytest.fixture
|
||||
def yaml_files(tmp_path):
|
||||
with_password = """
|
||||
nested:
|
||||
deep:
|
||||
in_the_land:
|
||||
is_a_password: thisislegit!
|
||||
not_nested_password: uh_oh_i_commited_creds
|
||||
"""
|
||||
with_allowed_password = """
|
||||
nested:
|
||||
deep:
|
||||
in_the_land:
|
||||
is_a_password: PassW0rd!
|
||||
not_nested_password: password
|
||||
"""
|
||||
example_yaml = """
|
||||
doe: "a deer, a female deer"
|
||||
ray: "a drop of golden sun"
|
||||
pi: 3.14159
|
||||
xmas: true
|
||||
french-hens: 3
|
||||
calling-birds:
|
||||
- huey
|
||||
- dewey
|
||||
- louie
|
||||
- fred
|
||||
xmas-fifth-day:
|
||||
calling-birds: four
|
||||
french-hens: 3
|
||||
golden-rings: 5
|
||||
partridges:
|
||||
count: 1
|
||||
location: "a pear tree"
|
||||
turtle-doves: two
|
||||
"""
|
||||
with open(tmp_path / "with_password.yaml", 'w') as fobj:
|
||||
fobj.write(with_password)
|
||||
|
||||
with open(tmp_path / "with_allowed_password.yaml", 'w') as fobj:
|
||||
fobj.write(with_allowed_password)
|
||||
|
||||
with open(tmp_path / "example.yaml", 'w') as fobj:
|
||||
fobj.write(example_yaml)
|
||||
return tmp_path
|
||||
|
||||
class TestCheckPasswords:
|
||||
def test_find_passwords(self, yaml_files):
|
||||
with open(yaml_files / "with_password.yaml", 'r') as fobj:
|
||||
yml = yaml.safe_load(fobj)
|
||||
gen = check_passwords.key_finder(yml)
|
||||
assert ('nested', 'is_a_password', "thisislegit!") == next(gen)
|
||||
assert ('not_nested_password', 'uh_oh_i_commited_creds') == next(gen)
|
||||
|
||||
def test_dont_find_passwords(self, yaml_files):
|
||||
with open(yaml_files / "with_allowed_password.yaml", 'r') as fobj:
|
||||
yml = yaml.safe_load(fobj)
|
||||
gen = check_passwords.key_finder(yml)
|
||||
(_, _, password) = next(gen)
|
||||
assert password in ["PassW0rd!", "password"]
|
||||
(_, password) = next(gen)
|
||||
assert password in ["PassW0rd!", "password"]
|
||||
with pytest.raises(StopIteration):
|
||||
next(gen)
|
||||
|
||||
def test_parsing_yaml(self, yaml_files):
|
||||
with open(yaml_files / "example.yaml", 'r') as fobj:
|
||||
yml = yaml.safe_load(fobj)
|
||||
gen = check_passwords.key_finder(yml)
|
||||
with pytest.raises(StopIteration):
|
||||
next(gen)
|
Loading…
Reference in New Issue
Block a user