blob: 8ff5e2a75fec27325d2e44a4001e8a2f2967596e [file] [log] [blame]
Serge Bazanskia5be0d82018-12-23 01:35:07 +01001#!/usr/bin/env python3
2
3# A little tool to encrypt/decrypt git secrets. Kinda like password-store, but more purpose specific and portable.
4
Sergiusz Bazanski73cef112019-04-07 00:06:23 +02005import logging
6import os
Serge Bazanskia5be0d82018-12-23 01:35:07 +01007import sys
8import subprocess
9
10keys = [
Sergiusz Bazanski711c4a92019-01-13 00:02:10 +010011 "63DFE737F078657CC8A51C00C29ADD73B3563D82", # q3k
12 "482FF104C29294AD1CAF827BA43890A3DE74ECC7", # inf
Gitead600ebb2020-05-31 17:39:31 +020013 "F07205946C07EEB2041A72FBC60C64879534F768", # cz2
Sergiusz Bazanski29afb4c2019-05-19 03:10:25 +020014 "0879F9FCA1C836677BB808C870FD60197E195C26", # implr
Serge Bazanskia5be0d82018-12-23 01:35:07 +010015]
16
Sergiusz Bazanski73cef112019-04-07 00:06:23 +020017
18logger = logging.getLogger(__name__)
19
20
Sergiusz Bazanskide061802019-01-13 21:14:02 +010021def encrypt(src, dst):
22 cmd = ['gpg' , '--encrypt', '--armor', '--batch', '--yes', '--output', dst]
23 for k in keys:
24 cmd.append('--recipient')
25 cmd.append(k)
26 cmd.append(src)
27 subprocess.check_call(cmd)
28
29def decrypt(src, dst):
Sergiusz Bazanskia9bb1d52019-04-28 17:13:12 +020030 cmd = ['gpg', '--decrypt', '--batch', '--yes', '--output', dst, src]
Sergiusz Bazanskide061802019-01-13 21:14:02 +010031 subprocess.check_call(cmd)
32
Sergiusz Bazanski73cef112019-04-07 00:06:23 +020033
34class SecretStoreMissing(Exception):
35 pass
36
37
38class SecretStore(object):
39 def __init__(self, plain_root, cipher_root):
40 self.proot = plain_root
41 self.croot = cipher_root
42
43 def exists(self, suffix):
44 p = os.path.join(self.proot, suffix)
45 c = os.path.join(self.croot, suffix)
46 return os.path.exists(c) or os.path.exists(p)
47
48 def plaintext(self, suffix):
Piotr Dobrowolskic10f00b2019-04-09 13:29:21 +020049 p = os.path.join(self.proot, suffix)
50 c = os.path.join(self.croot, suffix)
51
Serge Bazanskid493ab62019-10-31 17:07:19 +010052 has_p = os.path.exists(p)
53 has_c = os.path.exists(c)
54
55 if has_c and has_p and os.path.getctime(p) < os.path.getctime(c):
Piotr Dobrowolskic10f00b2019-04-09 13:29:21 +020056 logger.info("Decrypting {} ({})...".format(suffix, c))
57 decrypt(c, p)
58
59 return p
Sergiusz Bazanski73cef112019-04-07 00:06:23 +020060
61 def open(self, suffix, mode, *a, **kw):
62 p = os.path.join(self.proot, suffix)
63 c = os.path.join(self.croot, suffix)
64 if 'w' in mode:
Piotr Dobrowolskic10f00b2019-04-09 13:29:21 +020065 return open(p, mode, *a, **kw)
Sergiusz Bazanski73cef112019-04-07 00:06:23 +020066
67 if not self.exists(suffix):
68 raise SecretStoreMissing("Secret {} does not exist".format(suffix))
69
70 if not os.path.exists(p) or os.path.getctime(p) < os.path.getctime(c):
71 logger.info("Decrypting {} ({})...".format(suffix, c))
72 decrypt(c, p)
73
74 return open(p, mode, *a, **kw)
75
76
Serge Bazanskia5be0d82018-12-23 01:35:07 +010077def main():
78 if len(sys.argv) < 3 or sys.argv[1] not in ('encrypt', 'decrypt'):
Sergiusz Bazanskif2a812b2019-01-13 17:51:34 +010079 sys.stderr.write("Usage: {} encrypt/decrypt file\n".format(sys.argv[0]))
80 sys.stderr.flush()
81 return 1
Serge Bazanskia5be0d82018-12-23 01:35:07 +010082
83 action = sys.argv[1]
84 src = sys.argv[2]
85
86 if action == 'encrypt':
Sergiusz Bazanskide061802019-01-13 21:14:02 +010087 encrypt(src, '-')
Serge Bazanskia5be0d82018-12-23 01:35:07 +010088 else:
Sergiusz Bazanskide061802019-01-13 21:14:02 +010089 decrypt(src, '-')
Serge Bazanskia5be0d82018-12-23 01:35:07 +010090
91if __name__ == '__main__':
92 sys.exit(main() or 0)