1#!/usr/bin/env python3
2
3<<<hidden due to potential security issue>>>
4
5<<<hidden due to potential security issue>>>
6<<<hidden due to potential security issue>>>
7<<<hidden due to potential security issue>>>
8<<<hidden due to potential security issue>>>
9 user_input='A prompt')
10
11<<<hidden due to potential security issue>>>
12<<<hidden due to potential security issue>>>
13
14- string :: A fixed string, stored in the settings file
15<<<hidden due to potential security issue>>>
16<<<hidden due to potential security issue>>>
17<<<hidden due to potential security issue>>>
18<<<hidden due to potential security issue>>>
19
20A non encrypted keyring file has the form:
21
22# a comment
23<<<hidden due to potential security issue>>>
24
25it is located by settings.KEYRING_FILE
26
27An encrpyted keyring file is the same but encrpyted using:
28
29<<<hidden due to potential security issue>>>
30
31Verify and view with:
32
33<<<hidden due to potential security issue>>>
34
35"""
36
37import re
38import sys
39import getpass
40import subprocess
41from io import StringIO
42import logging
43
44logger = logging.getLogger()
45
46# do not import anything from chart* here because we are imported
47# from settings
48
49<<<hidden due to potential security issue>>>
50 pass
51
52
53<<<hidden due to potential security issue>>>
54<<<hidden due to potential security issue>>>
55<<<hidden due to potential security issue>>>
56 """
57
58 def __init__(self,
59 string=None,
60 # environ=None,
61 keyring=None,
62 gpg_keyring=None,
63 user_input=None):
64 # assert keyring != 'SECRET_KEY' # make sure keyring SECRET_KEY is set for web auth
65 self.string = string
66 # self.environ = environ
67 self.keyring = keyring
68 self.gpg_keyring = gpg_keyring
69 self.user_input = user_input
70
71 def replace(self, a, b):
72 """Pretend to be a string so we can be passed to Django, which happens to call replace()."""
73 return self
74
75 def __call__(self, keyring_path=None):
76<<<hidden due to potential security issue>>>
77 if self.string is not None:
78 return self.string
79
80<<<hidden due to potential security issue>>>
81 if self.keyring is not None:
82 res = self.from_keyring(keyring_path)
83 if res is not None:
84 return res
85
86<<<hidden due to potential security issue>>>
87 if self.gpg_keyring is not None:
88 res = self.from_gpg_keyring()
89 if res is not None:
90 return res
91
92 # prompt for user input
93 if self.user_input is not None:
94 res = self.from_user_input()
95 if res is not None:
96 return res
97
98 else:
99 missing = []
100 if self.keyring is not None:
101 missing.append('keyring: {k}'.format(k=self.keyring))
102
103 if self.gpg_keyring is not None:
104 missing.append('gpg_keyring: {k}'.format(k=self.gpg_keyring))
105
106<<<hidden due to potential security issue>>>
107 missing=', '.join(missing)))
108
109 def __str__(self):
110<<<hidden due to potential security issue>>>
111 return self()
112
113 def describe(self):
114 """Text description of ourselves for the 'db --info' tool."""
115 descs = []
116 if self.string is not None:
117 descs.append('"{str}"'.format(str=self.string))
118
119 # if self.environ is not None:
120 # descs.append('env ${env}'.format(env=self.environ))
121
122 if self.keyring is not None:
123 descs.append('keyring:{key}'.format(key=self.keyring))
124
125 if self.user_input is not None:
126 descs.append('user input')
127
128 return ' then '.join(descs)
129
130 def from_user_input(self):
131<<<hidden due to potential security issue>>>
132 Raises an ValueError is there is no terminal."""
133
134 if not getattr(sys.stdout, 'isatty', False) or not sys.stdout.isatty():
135 raise ValueError('Waiting for keyboard input in autonomous backend process')
136
137 try:
138 res = getpass.getpass('{prompt}:'.format(prompt=self.user_input))
139 except EOFError:
140 res = ''
141
142 if res == '':
143<<<hidden due to potential security issue>>>
144
145 else:
146 return res
147
148 @staticmethod
149 def read_keyring(path=None):
150 """Return the entire keyring file as a file handle."""
151 if path is None:
152 from chart.project import settings
153 if not settings.KEYRING_FILE.is_file():
154 # users don't need a keyring file
155 return None
156
157 else:
158 return settings.KEYRING_FILE.open()
159
160 else:
161 if path.exists():
162 return path.open()
163
164 else:
165<<<hidden due to potential security issue>>>
166
167 def from_keyring(self, path):
168<<<hidden due to potential security issue>>>
169<<<hidden due to potential security issue>>>
170 if handle is None:
171 return None
172
173 else:
174 return self.parse_keyring(handle, self.keyring)
175
176 @staticmethod
177 def read_gpg_keyring():
178 """Return the GPG protected keyring as a string."""
179<<<hidden due to potential security issue>>>
180 from chart.project import settings
181
182 if not settings.GPG_KEYRING_FILE.exists():
183 print('Secrets file {path} does not exist'.format(
184 path=settings.GPG_KEYRING_FILE))
185 return None
186
187 # extract content of settings.SECRET_FILE to temp
188 # temp_dir = tempfile.mkdtemp(prefix='chart-')
189 # temp_file = os.path.join(temp_dir, 'pw')
190 command = ('gpg', '-quiet', '-d', str(settings.GPG_KEYRING_FILE))
191 proc = subprocess.Popen(command,
192 stderr=subprocess.PIPE,
193 stdout=subprocess.PIPE)
194
195 err, _ = proc.communicate()
196
197 if proc.returncode != 0:
198 raise ValueError('Did not run gpg program successfully ({cmd})'.format(
199 cmd=' '.join(command)))
200
201 return err
202
203 def from_gpg_keyring(self):
204<<<hidden due to potential security issue>>>
205 gpg = self.read_gpg_keyring()
206 if gpg is None:
207 return None
208
209 else:
210 return self.parse_keyring(StringIO(gpg), self.gpg_keyring)
211
212 def parse_keyring(self, gen, seek):
213 """Look through keyring file generator `gen` for key `seek`.
214 File should look like 'KEY=value'.
215 """
216 uncomment = re.compile('#.*')
217 for cc, line in enumerate(gen, 1):
218
219 # remove any comments
220 line = uncomment.sub('', line).strip()
221 if len(line) == 0:
222 continue
223
224 # the remainder of the lines should look like KEY=value
225 if '=' not in line:
226 print('Cannot process line {cc} in keyring'.format(cc=cc))
227
228 parts = line.partition('=')
229
230 # empty keys make the file invalid
231 key = parts[0].strip()
232 if len(key) == 0:
233 raise ValueError('No key in {line}'.format(line=line))
234
235 # skip lines that don't contain the key we are looking for
236 if key != seek:
237 continue
238
239<<<hidden due to potential security issue>>>
240 value = parts[2].strip()
241 if len(value) == 0:
242 return ''
243
244 # remove enclosing quotes
245 if (value[0] == '"' and value[-1] == '"') or (value[0] == "'" and value[-1] == "'"):
246 value = value[1:-1]
247
248 return value
249
250 return None
251
252
253def show_keyring():
254<<<hidden due to potential security issue>>>
255 from chart.project import settings
256<<<hidden due to potential security issue>>>
257 if handle is None:
258 print('Cannot find keyring file {path}'.format(path=settings.KEYRING_FILE))
259
260 else:
261 print(handle.read())
262
263
264def show_gpg_keyring():
265<<<hidden due to potential security issue>>>
266<<<hidden due to potential security issue>>>
267
268
269def show_keyring_item(item):
270 """Show contents of a single (non-encrpyted) keyring item."""
271 from chart.project import settings
272 try:
273<<<hidden due to potential security issue>>>
274 print(res)
275
276 except ValueError:
277<<<hidden due to potential security issue>>>
278 if handle is None:
279 print('Keyring file {path} not found'.format(path=settings.KEYRING_FILE))
280
281 else:
282 if item != item.upper():
283 mess = '. Try using upper case'
284
285 else:
286 mess = ''
287
288 print('Item {item} not found in file {path}{mess}'.format(
289 item=item, path=settings.KEYRING_FILE, mess=mess))
290
291
292def main():
293 """Command line entry point."""
294 from chart.common.args import ArgumentParser
295 parser = ArgumentParser(__doc__)
296 parser.add_argument('--gpg',
297 action='store_true',
298 help='Decrypt GPG keyring')
299 parser.add_argument('--keyring',
300 action='store_true',
301 help='Show entire keyring (if ITEM is empty) or show ITEM only')
302 parser.add_argument('ITEM',
303 nargs='?',
304 # dest='item', # throws an error?!
305 help='Keyring item to display')
306 # parser.add_argument('--encrypt-gpg',
307 # metavar='FILE',
308 # help='Encrypt FILE as new GPG keyring')
309 args = parser.parse_args()
310 if args.keyring:
311 if args.ITEM is None:
312 show_keyring()
313
314 else:
315 show_keyring_item(args.ITEM)
316
317 parser.exit()
318
319 if args.gpg:
320 show_gpg_keyring()
321 parser.exit()
322
323 parser.error('No actions specified')
324
325if __name__ == '__main__':
326 main()