Initial commit
This commit is contained in:
8
.pre-commit-hooks.yaml
Normal file
8
.pre-commit-hooks.yaml
Normal 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.*
|
||||||
25
README.md
25
README.md
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
0
pre_commit_hook_ensure_sops/__init__.py
Normal file
0
pre_commit_hook_ensure_sops/__init__.py
Normal file
90
pre_commit_hook_ensure_sops/__main__.py
Normal file
90
pre_commit_hook_ensure_sops/__main__.py
Normal 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
19
setup.py
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user