bean spiller
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -1056,8 +1056,7 @@ def get_footer(readme, show_wget=True):
|
||||
return """</tbody></table>{README}
|
||||
</div>{WGET_STR}
|
||||
</body></html>""".format(
|
||||
README=readme,
|
||||
WGET_STR=wget_str
|
||||
README=readme, WGET_STR=wget_str
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
25
py-packages/spiller/setup.py
Normal file
25
py-packages/spiller/setup.py
Normal file
@@ -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",
|
||||
]
|
||||
},
|
||||
)
|
||||
2
py-packages/spiller/spiller/__init__.py
Normal file
2
py-packages/spiller/spiller/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__version__ = "0.1"
|
||||
from spiller.spiller import retrieve, store, list_storage
|
||||
220
py-packages/spiller/spiller/spiller.py
Executable file
220
py-packages/spiller/spiller/spiller.py
Executable file
@@ -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)
|
||||
Reference in New Issue
Block a user