102 lines
2.6 KiB
Python
102 lines
2.6 KiB
Python
from os import environ
|
|
from os.path import expanduser
|
|
from subprocess import check_output, CalledProcessError
|
|
|
|
from bundlewrap.exceptions import FaultUnavailable
|
|
from bundlewrap.utils import Fault
|
|
from bundlewrap.utils.dicts import merge_dict
|
|
from bundlewrap.utils.text import bold, yellow
|
|
from bundlewrap.utils.ui import io
|
|
|
|
ENVIRONMENT = merge_dict(environ, {
|
|
'PASSWORD_STORE_DIR': expanduser(environ.get('BW_PASS_DIR', '~/.password-store')),
|
|
})
|
|
|
|
DUMMY_MODE = environ.get('BW_PASS_DUMMY_MODE', '0') == '1'
|
|
|
|
cache = {}
|
|
|
|
|
|
def _get_contents_from_pass(identifier: str):
|
|
try:
|
|
return cache[identifier]
|
|
except KeyError:
|
|
# Not yet fetched from pass
|
|
pass
|
|
|
|
with io.job('{p} fetching {identifier}'.format(
|
|
p=bold('pass'),
|
|
identifier=identifier,
|
|
)):
|
|
try:
|
|
pass_output = check_output(
|
|
['pass', 'show', identifier],
|
|
env=ENVIRONMENT,
|
|
).decode('UTF-8').splitlines()
|
|
except FileNotFoundError:
|
|
raise FaultUnavailable('pass not found')
|
|
except CalledProcessError as e:
|
|
raise FaultUnavailable('pass exited {} when trying to get secret "{}"'.format(
|
|
e.returncode,
|
|
identifier,
|
|
))
|
|
|
|
if not pass_output:
|
|
raise ValueError('BUG: `pass show {}` did not return any output!'.format(
|
|
identifier
|
|
))
|
|
|
|
cache[identifier] = {
|
|
'password': pass_output[0].strip(),
|
|
'attrs': {},
|
|
}
|
|
|
|
if len(pass_output) > 1:
|
|
for line in pass_output[1:]:
|
|
if not ':' in line:
|
|
continue
|
|
|
|
attr, value = line.split(':', 1)
|
|
|
|
cache[identifier]['attrs'][attr] = value.strip()
|
|
|
|
return cache[identifier]
|
|
|
|
|
|
def _password(identifier):
|
|
if DUMMY_MODE:
|
|
return 'PASS DUMMY PASSWORD'
|
|
else:
|
|
secret = _get_contents_from_pass(identifier)
|
|
return secret['password']
|
|
|
|
|
|
def password(identifier):
|
|
return Fault(
|
|
'bwpass password',
|
|
_password,
|
|
identifier=identifier,
|
|
)
|
|
|
|
|
|
def _attr(identifier, attr):
|
|
if DUMMY_MODE:
|
|
return 'PASS DUMMY {} ATTRIBUTE'.format(attr)
|
|
else:
|
|
secret = _get_contents_from_pass(identifier)
|
|
try:
|
|
return secret['attrs'][attr]
|
|
except KeyError:
|
|
raise FaultUnavailable('attribute {} not found for identifier {}'.format(
|
|
attr,
|
|
identifier,
|
|
))
|
|
|
|
|
|
def attr(identifier, attr):
|
|
return Fault(
|
|
'bwpass attribute {}'.format(attr),
|
|
_attr,
|
|
identifier=identifier,
|
|
attr=attr,
|
|
)
|