diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2023-01-30 12:20:23 +0100 |
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2023-01-30 12:20:23 +0100 |
| commit | cfc871cce6aefaa0ff64619780a807cba761c6b2 (patch) | |
| tree | 965e8276ed36f11698b6c7d6eadab9f88d5f97c5 /tools/sops-inventory | |
| parent | aa54fe89b98d354d21141c589332ce7950ef2e59 (diff) | |
| download | nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.gz nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.bz2 nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.tar.xz nixos-cfc871cce6aefaa0ff64619780a807cba761c6b2.zip | |
...
Diffstat (limited to 'tools/sops-inventory')
| -rw-r--r-- | tools/sops-inventory/default.nix | 19 | ||||
| -rw-r--r-- | tools/sops-inventory/setup.py | 11 | ||||
| -rw-r--r-- | tools/sops-inventory/sops_inventory/__init__.py | 0 | ||||
| -rw-r--r-- | tools/sops-inventory/sops_inventory/__main__.py | 85 |
4 files changed, 115 insertions, 0 deletions
diff --git a/tools/sops-inventory/default.nix b/tools/sops-inventory/default.nix new file mode 100644 index 00000000..94c455e5 --- /dev/null +++ b/tools/sops-inventory/default.nix | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | { system, self, mach-nix, ... }: | ||
| 2 | let | ||
| 3 | pkgs = self.legacyPackages.${system}; | ||
| 4 | in mach-nix.lib.${system}.buildPythonPackage { | ||
| 5 | pname = "sops-inventory"; | ||
| 6 | version = "0.0.0"; | ||
| 7 | |||
| 8 | src = pkgs.lib.sourceByRegex ./. ["^setup\.py$" "^sops_inventory(/[^/]+.*)?$"]; | ||
| 9 | |||
| 10 | ignoreDataOutdated = true; | ||
| 11 | requirements = '' | ||
| 12 | pyyaml | ||
| 13 | ''; | ||
| 14 | |||
| 15 | postInstall = '' | ||
| 16 | wrapProgram $out/bin/sops-inventory \ | ||
| 17 | --set-default SOPS_INVENTORY_BASE ${self} | ||
| 18 | ''; | ||
| 19 | } | ||
diff --git a/tools/sops-inventory/setup.py b/tools/sops-inventory/setup.py new file mode 100644 index 00000000..3ea2a5d1 --- /dev/null +++ b/tools/sops-inventory/setup.py | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | from setuptools import setup | ||
| 2 | |||
| 3 | setup( | ||
| 4 | name='sops-inventory', | ||
| 5 | packages=['sops_inventory'], | ||
| 6 | entry_points={ | ||
| 7 | 'console_scripts': [ | ||
| 8 | 'sops-inventory=sops_inventory.__main__:main' | ||
| 9 | ], | ||
| 10 | }, | ||
| 11 | ) | ||
diff --git a/tools/sops-inventory/sops_inventory/__init__.py b/tools/sops-inventory/sops_inventory/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tools/sops-inventory/sops_inventory/__init__.py | |||
diff --git a/tools/sops-inventory/sops_inventory/__main__.py b/tools/sops-inventory/sops_inventory/__main__.py new file mode 100644 index 00000000..68f72b60 --- /dev/null +++ b/tools/sops-inventory/sops_inventory/__main__.py | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | import os,sys | ||
| 2 | |||
| 3 | from pathlib import Path | ||
| 4 | from collections import deque, defaultdict | ||
| 5 | |||
| 6 | import argparse | ||
| 7 | |||
| 8 | from yaml import load, YAMLError | ||
| 9 | try: | ||
| 10 | from yaml import CLoader as Loader | ||
| 11 | except ImportError: | ||
| 12 | from yaml import Loader | ||
| 13 | |||
| 14 | |||
| 15 | SOPS_TYPES = frozenset({'kms', 'gcp_kms', 'azure_kv', 'hc_vault', 'age', 'pgp'}) | ||
| 16 | |||
| 17 | |||
| 18 | class BooleanAction(argparse.Action): | ||
| 19 | def __init__(self, option_strings, dest, nargs=None, **kwargs): | ||
| 20 | super(BooleanAction, self).__init__(option_strings, dest, nargs=0, **kwargs) | ||
| 21 | |||
| 22 | def __call__(self, parser, namespace, values, option_string=None): | ||
| 23 | setattr(namespace, self.dest, False if option_string.startswith('--no') else True) | ||
| 24 | |||
| 25 | |||
| 26 | def main(): | ||
| 27 | default_base = os.getenv('SOPS_INVENTORY_BASE', default=[]) | ||
| 28 | if default_base: | ||
| 29 | default_base = Path(default_base) | ||
| 30 | |||
| 31 | parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| 32 | parser.add_argument('--list-files', '--no-list-files', action=BooleanAction, default=False, help='Only list sops files') | ||
| 33 | parser.add_argument('path', metavar='PATH', nargs='?' if default_base else None, type=Path, default=default_base, help='Base directory to take inventory of') | ||
| 34 | args = parser.parse_args() | ||
| 35 | |||
| 36 | inventory = defaultdict(set) | ||
| 37 | |||
| 38 | queue = deque([args.path]) | ||
| 39 | while queue: | ||
| 40 | baseDir = queue.popleft() | ||
| 41 | for child in baseDir.iterdir(): | ||
| 42 | if child.is_dir(): | ||
| 43 | queue.append(child) | ||
| 44 | else: | ||
| 45 | try: | ||
| 46 | with child.open(mode='r') as fh: | ||
| 47 | yaml = load(fh, Loader=Loader) | ||
| 48 | if not yaml: | ||
| 49 | raise ValueError('Could not parse YAML') | ||
| 50 | if not isinstance(yaml, dict) or not 'sops' in yaml: | ||
| 51 | raise ValueError('Did not find "sops" key') | ||
| 52 | sops = yaml['sops'] | ||
| 53 | |||
| 54 | key_info = set() | ||
| 55 | for k in SOPS_TYPES: | ||
| 56 | if k in sops: | ||
| 57 | v = sops[k] | ||
| 58 | if not v: | ||
| 59 | continue | ||
| 60 | |||
| 61 | match k: | ||
| 62 | case 'pgp': | ||
| 63 | for r in v: | ||
| 64 | key_info.add(r['fp']) | ||
| 65 | case 'age': | ||
| 66 | for r in v: | ||
| 67 | key_info.add(r['recipient']) | ||
| 68 | case _: | ||
| 69 | raise NotImplementedError | ||
| 70 | inventory[frozenset(key_info)].add(child.relative_to(args.path)) | ||
| 71 | except (YAMLError, ValueError) as e: | ||
| 72 | pass | ||
| 73 | |||
| 74 | if not args.list_files: | ||
| 75 | for keys, files in inventory.items(): | ||
| 76 | print(','.join(keys) + ':') | ||
| 77 | for file in files: | ||
| 78 | print(' - ' + str(file)) | ||
| 79 | else: | ||
| 80 | for _, files in inventory.items(): | ||
| 81 | for file in files: | ||
| 82 | print(file) | ||
| 83 | |||
| 84 | if __name__ == '__main__': | ||
| 85 | os.exit(main()) | ||
