From cd37185d2adea0030e2188221f758cd47abdcb3e Mon Sep 17 00:00:00 2001 From: Q Date: Sun, 22 Jan 2023 13:31:46 +0200 Subject: [PATCH] bean spiller --- py-packages/Makefile | 5 +- .../SimpleWebPage/simplewebpage/__init__.py | 3 +- .../ffmpegparser/ffprobeparser.py | 4 +- py-packages/spiller/setup.py | 25 ++ py-packages/spiller/spiller/__init__.py | 2 + py-packages/spiller/spiller/spiller.py | 220 ++++++++++++++++++ 6 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 py-packages/spiller/setup.py create mode 100644 py-packages/spiller/spiller/__init__.py create mode 100755 py-packages/spiller/spiller/spiller.py diff --git a/py-packages/Makefile b/py-packages/Makefile index 8a4e511..5dfa7bf 100644 --- a/py-packages/Makefile +++ b/py-packages/Makefile @@ -22,10 +22,7 @@ pipx: ## Install all packages with pipx for module in *; do if [ -f $$module/setup.py ]; then pipx install $$module; fi; done format: ## Reformat packages with black - black SimpleWebPage/ - black TSVFilter/ - black markslider/ - black ffmpeg-parser/ + for module in *; do if [ -f $$module/setup.py ]; then black $$module/; fi; done tar-SimpleWebPage: clean ## Create package for SimpleWebPage tar czf SimpleWebPage.tgz SimpleWebPage/ diff --git a/py-packages/SimpleWebPage/simplewebpage/__init__.py b/py-packages/SimpleWebPage/simplewebpage/__init__.py index 79d0435..8e1ceb0 100755 --- a/py-packages/SimpleWebPage/simplewebpage/__init__.py +++ b/py-packages/SimpleWebPage/simplewebpage/__init__.py @@ -1056,8 +1056,7 @@ def get_footer(readme, show_wget=True): return """{README} {WGET_STR} """.format( - README=readme, - WGET_STR=wget_str + README=readme, WGET_STR=wget_str ) diff --git a/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py b/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py index 1994d70..d6841e0 100755 --- a/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py +++ b/py-packages/ffmpeg-parser/ffmpegparser/ffprobeparser.py @@ -156,7 +156,7 @@ Sample Rate: {sample_rate} Hz def _mbstr(self, b): try: - return "{:.1f}".format((float(b) / (1024 ** 2))) + return "{:.1f}".format((float(b) / (1024**2))) except Exception: return b @@ -164,7 +164,7 @@ Sample Rate: {sample_rate} Hz return self._sizefmt(float(b), suffix="bit/s") try: - return "{:.1f}".format((float(b) / (1024 ** 2))) + return "{:.1f}".format((float(b) / (1024**2))) except Exception: return b diff --git a/py-packages/spiller/setup.py b/py-packages/spiller/setup.py new file mode 100644 index 0000000..0ad80c8 --- /dev/null +++ b/py-packages/spiller/setup.py @@ -0,0 +1,25 @@ +from distutils.core import setup +import os + + +def version_reader(path): + for line in open(path, "rt").read(1024).split("\n"): + if line.startswith("__version__"): + return line.split("=")[1].strip().replace('"', "") + + +version = version_reader(os.path.join("spiller", "__init__.py")) + +setup( + name="spiller", + packages=["spiller"], + version=version, + description="Very simple password storage, that encrypts with GPG cmdline tool.", + author="Ville Rantanen", + author_email="ville.q.rantanen@gmail.com", + entry_points={ + "console_scripts": [ + "spill = spiller.spiller:main", + ] + }, +) diff --git a/py-packages/spiller/spiller/__init__.py b/py-packages/spiller/spiller/__init__.py new file mode 100644 index 0000000..52d028d --- /dev/null +++ b/py-packages/spiller/spiller/__init__.py @@ -0,0 +1,2 @@ +__version__ = "0.1" +from spiller.spiller import retrieve, store, list_storage diff --git a/py-packages/spiller/spiller/spiller.py b/py-packages/spiller/spiller/spiller.py new file mode 100755 index 0000000..3cecd48 --- /dev/null +++ b/py-packages/spiller/spiller/spiller.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 + +import json +import sys +import argparse +import os +import subprocess +import random +import stat +import string + +JSON = os.getenv( + "SPILLER_STORAGE", os.path.expanduser("~/.config/spiller/storage.json") +) + + +def get_opts(): + + parser = argparse.ArgumentParser( + prog="spill", + description="""Key/value storage that uses JSON and GPG as backend. + Values are encrypted symmetrically with the key provided, or a random string is generated. Storage file can be changed with + SPILLER_STORAGE env variable. Encryption key can be passed from a variable SPILLER_KEY instead of a switch.""", + ) + subparsers = parser.add_subparsers(dest="command", help="Command") + set_parser = subparsers.add_parser("set") + get_parser = subparsers.add_parser("get") + del_parser = subparsers.add_parser("list") + del_parser = subparsers.add_parser("del") + + set_parser.add_argument( + "name", + action="store", + help="Name of secret", + ) + set_parser.add_argument( + "data", + action="store", + help="Data to store", + ) + set_parser.add_argument( + "--plain", action="store_true", default=False, help="Do not encrypt" + ) + set_parser.add_argument( + "--key", + action="store", + default=os.getenv("SPILLER_KEY", None), + help="Encryption key", + ) + get_parser.add_argument( + "name", + action="store", + help="Name of secret", + ) + get_parser.add_argument( + "--key", + action="store", + default=os.getenv("SPILLER_KEY", None), + help="Decryption key", + ) + del_parser.add_argument( + "name", + action="store", + help="Name of secret to delete", + ) + return parser.parse_args() + + +def list_storage(): + """ + Get list of keys in the secret storage + + Args: + + Returns: + List[List]: List of name and encryption method + """ + + storage = load_storage() + names = [] + for name in sorted(storage.keys()): + names.append([name, storage[name]["encryption"]]) + names.sort(key=lambda x: x[1]) + return names + + +def load_storage(): + + try: + with open(JSON, "rt") as fp: + return json.load(fp) + except FileNotFoundError: + return {} + + +def save_storage(storage): + + if not os.path.exists(JSON): + os.makedirs(os.path.dirname(JSON), exist_ok=True) + with open(JSON, "wt") as fp: + json.dump(storage, fp, indent=2) + try: + os.chmod(JSON, stat.S_IWUSR | stat.S_IREAD) + except Exception: + pass + + +def del_storage(name): + + storage = load_storage() + del storage[name] + save_storage(storage) + print("Deleted " + name) + + +def store(name, data, key, plain): + """ + Store key to secrets storage. + + Args: + name (str): Name of the secret. + data (str): Data to encrypt. + key (str): Encryption key. If None, randomly generate the key + plain (bool): If set, stores the data as is, without encryption + Returns: + str: Key used to encrypt, useful if it was generated. + """ + + entry = {"encryption": "gpg", "data": data} + storage = load_storage() + if plain: + entry["encryption"] = "none" + else: + if key == None: + key = get_random_key() + print("Random key: " + key) + entry["data"] = encrypt(data, key) + + storage[name] = entry + save_storage(storage) + return key + + +def retrieve(name, key=None): + """ + Retrieve a secret from storage + + Args: + name (str): Name of the secret. + key (str): Encryption key, if any required. + + Returns: + str: Decrypted secret + """ + + storage = load_storage() + entry = storage[name] + if entry["encryption"] == "none": + return entry["data"] + return decrypt(entry["data"], key) + + +def encrypt(data, key): + + p = subprocess.run( + ["gpg", "-a", "--symmetric", "--batch", "--passphrase-fd", "0"], + input=f"{key}\n{data}".encode(), + capture_output=True, + ) + encrypted = p.stdout.decode() + if encrypted == "": + print("Encrypt failed!", file=sys.stderr) + sys.exit(1) + return encrypted + + +def decrypt(encrypted, key): + + if key == None: + print("Requires --key!", file=sys.stderr) + sys.exit(1) + + p = subprocess.run( + ["gpg", "-d", "--batch", "--passphrase-fd", "0"], + input=f"{key}\n{encrypted}".encode(), + capture_output=True, + ) + data = p.stdout.decode() + if data == "": + print("Decrypt failed!", file=sys.stderr) + sys.exit(1) + return data + + +def get_random_key(): + + return "-".join( + [ + "".join( + [random.choice(string.ascii_letters + string.digits) for x in range(8)] + ) + for x in range(5) + ] + ) + + +def main(): + opts = get_opts() + if opts.command == "set": + store(opts.name, opts.data, opts.key, opts.plain) + if opts.command == "get": + print(retrieve(opts.name, opts.key)) + if opts.command == "list": + names = list_storage() + names.insert(0, ["Name", "Encryption"]) + padlen = max([len(x[0]) for x in names]) + for row in names: + print(("{:" + str(padlen) + "} {}").format(*row)) + if opts.command == "del": + del_storage(opts.name)