2022-11-26 12:49:57 +00:00
|
|
|
import os
|
2023-01-19 01:57:21 +00:00
|
|
|
import json
|
2022-11-26 12:49:57 +00:00
|
|
|
import subprocess
|
2023-09-25 09:13:44 +00:00
|
|
|
import distutils
|
|
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
from dateutil import parser
|
|
|
|
from dateutil.tz import tzutc
|
|
|
|
tzinfos = {"CDT": tzutc()}
|
2022-11-26 12:49:57 +00:00
|
|
|
|
2023-09-25 09:13:44 +00:00
|
|
|
bw_sync_interval = float(os.environ.get(
|
|
|
|
'BW_SYNC_INTERVAL', 900))
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2022-11-26 20:33:31 +00:00
|
|
|
class BitwardenCommandException(Exception):
|
|
|
|
pass
|
|
|
|
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2023-09-25 09:13:44 +00:00
|
|
|
def get_secret_from_bitwarden(logger, id, force_sync=False):
|
|
|
|
sync_bw(logger, force=force_sync)
|
2023-04-21 14:50:33 +00:00
|
|
|
return command_wrapper(logger, command=f"get item {id}")
|
2022-11-26 12:49:57 +00:00
|
|
|
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2023-09-25 09:13:44 +00:00
|
|
|
def sync_bw(logger, force=False):
|
|
|
|
|
|
|
|
def _sync(logger):
|
|
|
|
status_output = command_wrapper(logger, command=f"sync")
|
|
|
|
logger.info(f"Sync successful {status_output}")
|
|
|
|
return
|
|
|
|
|
|
|
|
if force:
|
|
|
|
_sync(logger)
|
|
|
|
return
|
|
|
|
|
|
|
|
last_sync = last_sync_bw(logger)
|
|
|
|
now = datetime.now(tzutc())
|
|
|
|
sync_interval = timedelta(seconds=bw_sync_interval)
|
|
|
|
bw_is_out_of_sync_inverval = (now - last_sync) >= sync_interval
|
|
|
|
global_force_sync = bool(distutils.util.strtobool(
|
|
|
|
os.environ.get('BW_FORCE_SYNC', "false")))
|
|
|
|
needs_sync = force or global_force_sync or bw_is_out_of_sync_inverval
|
|
|
|
logger.debug(f"last_sync: {last_sync}")
|
|
|
|
logger.debug(
|
|
|
|
f"force: {force}, global_force_sync: {global_force_sync}, bw_is_out_of_sync_inverval: {bw_is_out_of_sync_inverval}, needs_sync: {needs_sync}")
|
|
|
|
|
|
|
|
if needs_sync:
|
|
|
|
status_output = _sync(logger)
|
|
|
|
logger.info(f"Sync successful {status_output}")
|
|
|
|
|
|
|
|
|
|
|
|
def last_sync_bw(logger):
|
|
|
|
null_datetime_string = "0001-01-01T00:00:00.000Z"
|
|
|
|
|
|
|
|
# retruns: {"success":true,"data":{"object":"string","data":"2023-09-22T13:50:09.995Z"}}
|
|
|
|
last_sync_output = command_wrapper(
|
|
|
|
logger, command="sync --last", use_success=False)
|
|
|
|
|
|
|
|
# if not last_sync_output:
|
|
|
|
# return parser.parse(null_datetime_string, tzinfos=tzinfos)
|
|
|
|
|
|
|
|
if not last_sync_output or not last_sync_output.get("success"):
|
|
|
|
logger.error("Error getting last sync time.")
|
|
|
|
return parser.parse(null_datetime_string, tzinfos=tzinfos)
|
|
|
|
|
|
|
|
# in case no sync was done yet, null is returned from api
|
|
|
|
# use some long ago date...
|
|
|
|
last_sync_string = last_sync_output.get(
|
|
|
|
"data").get("data", null_datetime_string)
|
|
|
|
last_sync = parser.parse(last_sync_string, tzinfos=tzinfos)
|
|
|
|
return last_sync
|
2023-07-12 09:05:01 +00:00
|
|
|
|
|
|
|
|
2022-11-26 12:49:57 +00:00
|
|
|
def unlock_bw(logger):
|
2023-04-21 14:50:33 +00:00
|
|
|
status_output = command_wrapper(logger, "status", False)
|
|
|
|
status = status_output['data']['template']['status']
|
2023-01-19 01:57:21 +00:00
|
|
|
if status == 'unlocked':
|
|
|
|
logger.info("Already unlocked")
|
|
|
|
return
|
2023-04-21 14:50:33 +00:00
|
|
|
token_output = command_wrapper(logger, "unlock --passwordenv BW_PASSWORD")
|
|
|
|
os.environ["BW_SESSION"] = token_output["data"]["raw"]
|
2022-11-26 12:49:57 +00:00
|
|
|
logger.info("Signin successful. Session exported")
|
|
|
|
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2023-04-21 14:50:33 +00:00
|
|
|
def command_wrapper(logger, command, use_success: bool = True):
|
2022-11-26 12:49:57 +00:00
|
|
|
system_env = dict(os.environ)
|
2023-04-21 12:39:06 +00:00
|
|
|
sp = subprocess.Popen(
|
2023-04-21 14:50:33 +00:00
|
|
|
[f"bw --response {command}"],
|
2023-04-21 12:39:06 +00:00
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
close_fds=True,
|
|
|
|
shell=True,
|
|
|
|
env=system_env)
|
2022-11-26 12:49:57 +00:00
|
|
|
out, err = sp.communicate()
|
2023-09-25 09:13:44 +00:00
|
|
|
|
2023-04-24 07:54:53 +00:00
|
|
|
if "DEBUG" in system_env:
|
2023-06-24 16:31:00 +00:00
|
|
|
logger.info(out.decode(encoding='UTF-8'))
|
|
|
|
resp = json.loads(out.decode(encoding='UTF-8'))
|
2023-04-21 14:50:33 +00:00
|
|
|
if resp["success"] != None and (not use_success or (use_success and resp["success"] == True)):
|
|
|
|
return resp
|
|
|
|
logger.warn(resp)
|
|
|
|
return None
|
2022-11-26 20:33:31 +00:00
|
|
|
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2022-11-26 20:33:31 +00:00
|
|
|
def parse_login_scope(secret_json, key):
|
2023-04-21 14:50:33 +00:00
|
|
|
return secret_json["data"]["login"][key]
|
2022-11-26 20:33:31 +00:00
|
|
|
|
2023-04-21 12:39:06 +00:00
|
|
|
|
2022-11-26 20:33:31 +00:00
|
|
|
def parse_fields_scope(secret_json, key):
|
2023-05-10 08:35:00 +00:00
|
|
|
if "fields" not in secret_json["data"]:
|
2023-01-20 01:57:06 +00:00
|
|
|
return None
|
2023-04-21 14:50:33 +00:00
|
|
|
for entry in secret_json["data"]["fields"]:
|
2022-11-26 20:33:31 +00:00
|
|
|
if entry['name'] == key:
|
|
|
|
return entry['value']
|