2018-12-23 00:35:07 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# A little tool to encrypt/decrypt git secrets. Kinda like password-store, but more purpose specific and portable.
|
|
|
|
|
2019-04-06 22:06:23 +00:00
|
|
|
import logging
|
|
|
|
import os
|
2018-12-23 00:35:07 +00:00
|
|
|
import sys
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
keys = [
|
2019-01-12 23:02:10 +00:00
|
|
|
"63DFE737F078657CC8A51C00C29ADD73B3563D82", # q3k
|
|
|
|
"482FF104C29294AD1CAF827BA43890A3DE74ECC7", # inf
|
2020-02-18 22:01:07 +00:00
|
|
|
# "F07205946C07EEB2041A72FBC60C64879534F768", # cz2 (expired 2020-01-17)
|
2019-05-19 01:10:25 +00:00
|
|
|
"0879F9FCA1C836677BB808C870FD60197E195C26", # implr
|
2018-12-23 00:35:07 +00:00
|
|
|
]
|
|
|
|
|
2019-04-06 22:06:23 +00:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-01-13 20:14:02 +00:00
|
|
|
def encrypt(src, dst):
|
|
|
|
cmd = ['gpg' , '--encrypt', '--armor', '--batch', '--yes', '--output', dst]
|
|
|
|
for k in keys:
|
|
|
|
cmd.append('--recipient')
|
|
|
|
cmd.append(k)
|
|
|
|
cmd.append(src)
|
|
|
|
subprocess.check_call(cmd)
|
|
|
|
|
|
|
|
def decrypt(src, dst):
|
2019-04-28 15:13:12 +00:00
|
|
|
cmd = ['gpg', '--decrypt', '--batch', '--yes', '--output', dst, src]
|
2019-01-13 20:14:02 +00:00
|
|
|
subprocess.check_call(cmd)
|
|
|
|
|
2019-04-06 22:06:23 +00:00
|
|
|
|
|
|
|
class SecretStoreMissing(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class SecretStore(object):
|
|
|
|
def __init__(self, plain_root, cipher_root):
|
|
|
|
self.proot = plain_root
|
|
|
|
self.croot = cipher_root
|
|
|
|
|
|
|
|
def exists(self, suffix):
|
|
|
|
p = os.path.join(self.proot, suffix)
|
|
|
|
c = os.path.join(self.croot, suffix)
|
|
|
|
return os.path.exists(c) or os.path.exists(p)
|
|
|
|
|
|
|
|
def plaintext(self, suffix):
|
2019-04-09 11:29:21 +00:00
|
|
|
p = os.path.join(self.proot, suffix)
|
|
|
|
c = os.path.join(self.croot, suffix)
|
|
|
|
|
2019-10-31 16:07:19 +00:00
|
|
|
has_p = os.path.exists(p)
|
|
|
|
has_c = os.path.exists(c)
|
|
|
|
|
|
|
|
if has_c and has_p and os.path.getctime(p) < os.path.getctime(c):
|
2019-04-09 11:29:21 +00:00
|
|
|
logger.info("Decrypting {} ({})...".format(suffix, c))
|
|
|
|
decrypt(c, p)
|
|
|
|
|
|
|
|
return p
|
2019-04-06 22:06:23 +00:00
|
|
|
|
|
|
|
def open(self, suffix, mode, *a, **kw):
|
|
|
|
p = os.path.join(self.proot, suffix)
|
|
|
|
c = os.path.join(self.croot, suffix)
|
|
|
|
if 'w' in mode:
|
2019-04-09 11:29:21 +00:00
|
|
|
return open(p, mode, *a, **kw)
|
2019-04-06 22:06:23 +00:00
|
|
|
|
|
|
|
if not self.exists(suffix):
|
|
|
|
raise SecretStoreMissing("Secret {} does not exist".format(suffix))
|
|
|
|
|
|
|
|
if not os.path.exists(p) or os.path.getctime(p) < os.path.getctime(c):
|
|
|
|
logger.info("Decrypting {} ({})...".format(suffix, c))
|
|
|
|
decrypt(c, p)
|
|
|
|
|
|
|
|
return open(p, mode, *a, **kw)
|
|
|
|
|
|
|
|
|
2018-12-23 00:35:07 +00:00
|
|
|
def main():
|
|
|
|
if len(sys.argv) < 3 or sys.argv[1] not in ('encrypt', 'decrypt'):
|
2019-01-13 16:51:34 +00:00
|
|
|
sys.stderr.write("Usage: {} encrypt/decrypt file\n".format(sys.argv[0]))
|
|
|
|
sys.stderr.flush()
|
|
|
|
return 1
|
2018-12-23 00:35:07 +00:00
|
|
|
|
|
|
|
action = sys.argv[1]
|
|
|
|
src = sys.argv[2]
|
|
|
|
|
|
|
|
if action == 'encrypt':
|
2019-01-13 20:14:02 +00:00
|
|
|
encrypt(src, '-')
|
2018-12-23 00:35:07 +00:00
|
|
|
else:
|
2019-01-13 20:14:02 +00:00
|
|
|
decrypt(src, '-')
|
2018-12-23 00:35:07 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit(main() or 0)
|