Initial commit

This commit is contained in:
YuviPanda
2022-03-14 18:21:38 -07:00
parent 4c3ce16e11
commit 5e68018338
5 changed files with 141 additions and 1 deletions

8
.pre-commit-hooks.yaml Normal file
View File

@@ -0,0 +1,8 @@
- id: sops-encryption
language: python
entry: python3 -m pre_commit_hook_ensure_sops
name: Ensure secrets are encrypted with sops
# Be aggressive - ensure anything with the word secret in the filename
# or file path is encryped. Users of individual repos can exclude things
# with `exclude` if necessary.
files: .*secret.*

View File

@@ -1,2 +1,25 @@
# pre-commit-hook-ensure-sops # pre-commit-hook-ensure-sops
pre-commit hook to ensure that files that should be encrypted with sops are
A [pre-commit](https://pre-commit.com/) hook to ensure that users don't
accidentally check-in unencrypted files into a repository that uses
[sops](https://github.com/mozilla/sops) to safely store encrypted secrets.
By default, any file with the word `secret` in its path is required to
be encrypted with `sops`. This means any files under a directory
named `secret` are also required to be encrypted. If you want to exempt
specific files or directories from this requirement in your repository,
use the `exclude` option in your `.pre-commit-config.yaml`. When pushing
secrets to a repo, better safe than sorry :)
## Installation
Add this to your `.pre-commit-config.yaml`:
```yaml
- repo: https://github.com/yuvipanda/pre-commit-hook-ensure-sops
rev: v1.0
hooks:
- id: sops-encryption
# Uncomment to exclude all markdown files from encryption
# exclude: *.\.md
```

View File

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Validate if given list of files are encrypted with sops.
"""
from argparse import ArgumentParser
from ruamel.yaml import YAML
from ruamel.yaml.parser import ParserError
import sys
yaml = YAML(typ='safe')
def validate_enc(item):
"""
Validate given item is encrypted.
All leaf values in a sops encrypted file must be strings that
start with ENC[. We iterate through lists and dicts, checking
only for leaf strings. Presence of any other data type (like
bool, number, etc) also makes the file invalid.
"""
if isinstance(item, str):
return item.startswith('ENC[')
elif isinstance(item, list):
return all(validate_enc(i) for i in item)
elif isinstance(item, dict):
return all(validate_enc(i) for i in item.values())
else:
return False
def check_file(filename):
"""
Check if a file has been encrypted properly with sops.
Returns a boolean indicating wether given file is valid or not, as well as
a string with a human readable success / failure message.
"""
# sops doesn't have a --verify (https://github.com/mozilla/sops/issues/437)
# so we implement some heuristics, primarily to guard against unencrypted
# files being checked in.
with open(filename) as f:
try:
# Use the YAML parser to load files. All JSON is valid YAML, so this
# properly deals with JSON files too
doc = yaml.load(f)
except ParserError:
# All sops encrypted files are valid JSON or YAML
return False, f"{filename}: Not valid JSON or YAML, is not properly encrypted"
if 'sops' not in doc:
# sops puts a `sops` key in the encrypted output. If it is not
# present, very likely the file is not encrypted.
return False, f"{filename}: sops metadata key not found in file, is not properly encrypted"
invalid_keys = []
for k in doc:
if k != 'sops':
# Values under the `sops` key are not encrypted.
if not validate_enc(doc[k]):
# Collect all invalid keys so we can provide useful error message
invalid_keys.append(k)
if invalid_keys:
return False, f"{filename}: Unencrypted values found nested under keys: {','.join(invalid_keys)}"
return True, f"{filename}: Valid encryption"
def main():
argparser = ArgumentParser()
argparser.add_argument('filenames', nargs='+')
args = argparser.parse_args()
failed_messages = []
for f in args.filenames:
is_valid, message = check_file(f)
if not is_valid:
failed_messages.append(message)
if failed_messages:
print('\n'.join(failed_messages))
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

19
setup.py Normal file
View File

@@ -0,0 +1,19 @@
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="pre-commit-hook-ensure-sops",
version="0.1",
author="Yuvi Panda",
author_email="yuvipanda@gmail.com",
description="pre-commit hook to ensure that files that should be encrypted with sops are in fact encrypted",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yuvipanda/pre-commit-hook-ensure-sops",
packages=setuptools.find_packages(),
install_requires=[
"ruamel.yaml",
],
)