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()