diff --git a/truenas_kmip_unlock.py b/truenas_kmip_unlock.py index 6ca6d88..0027f5d 100644 --- a/truenas_kmip_unlock.py +++ b/truenas_kmip_unlock.py @@ -9,7 +9,7 @@ import base64 import argparse import configparser import getpass -import requests +from truenas_api_client import Client import platform import subprocess import time @@ -19,39 +19,12 @@ except ImportError: import json import hmac as pyhmac import datetime -import urllib3 -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) from kmip.core import enums from kmip.demos import utils from kmip.pie import client -def request(resource, api_key, method='GET', data=None): - if data is None: - data = '' - else: - data = json.dumps(data) - url = 'https://127.0.0.1/api/v2.0/{}'.format(resource) - logger.debug('Request URL: {}'.format(url)) - logger.debug('Request Data: {}'.format(data)) - r = requests.request( - method, - url, - data=data, - headers={'Content-Type': 'application/json', 'Authorization': 'Bearer {}'.format(api_key)}, - verify=False - ) - logger.debug('Request Status Code: {}'.format(r.status_code)) - if r.ok: - try: - logger.debug('Request Returned JSON: {}'.format(r.json())) - return {'ok': r.ok, 'status_code': r.status_code, 'response': r.json()} - except: - logger.debug('Request Returned Text: {}'.format(r.text)) - return {'ok': r.ok, 'status_code': r.status_code, 'response': r.text} - raise ValueError(r) - def build_logger(level): logger = logging.getLogger() logger.setLevel(level) @@ -296,7 +269,7 @@ def edit_pool_details(pools, pool_to_edit): array[pool_name] = dict() array[pool_name]["pool_passphrase"] = pool_passphrase return array - + def select_pool(pools, wording = "edit"): print("Which pool would you like to {}:".format(wording)) print(" ") @@ -324,13 +297,13 @@ if __name__ == '__main__': dest="config", help="Edit pools configuration." ) - parser.add_argument( - "-r", - "--restartJails", - action="store_true", - dest="restartJails", - help="Restarts jails if running" - ) + #parser.add_argument( + # "-r", + # "--restartAppsVMs", + # action="store_true", + # dest="restartAppsVMs", + # help="Restarts apps and VMs if running" + #) parser.add_argument ( "-v", "--verbose", @@ -367,9 +340,13 @@ if __name__ == '__main__': user_input = input("n/q> ") if user_input.casefold() == "n": config = dict() + config['URI'] = input("Please enter your hostname or IP address (default: localhost): ") or "localhost" + if ask_for_confirmation("Would you like to verify the TLS cert?"): + config['Verify TLS'] = True + else: + config['Verify TLS'] = False config['API Key'] = input("Please enter your API key: ") config['Pools'] = new_pool_details() - config['jailStoragePool'] = select_pool(config['Pools'], "select for jail storage") write_config_file(config, secrets_config_file) break elif user_input.casefold() == "q": @@ -383,20 +360,28 @@ if __name__ == '__main__': for pool in config['Pools']: print(pool) print(" ") - print("Jail storage pool: {}".format(config['jailStoragePool'])) + print("URI: {}".format(config['URI'])) + print("Verify TLS Cert: {}".format(config['Verify TLS'])) print(" ") - print("a) Edit API Key") + print("h) Edit Host, TLS, and API Key") print("e) Edit pool") - print("j) Edit jail storage pool") print("n) New pool") print("d) Delete pool") print("q) Quit config") - user_input = input("a/e/j/n/d/q> ") - # Edit API Key - if user_input.casefold() == "a": - config['API Key'] = input("Please enter your API key: ") + user_input = input("h/e/n/d/q> ") + # Edit Host, TLS, and API Key + if user_input.casefold() == "h": + if ask_for_confirmation("Would you like to edit the hostname or IP address?\nCurrent Value: {}".format(config['URI'])): + config['URI'] = input("Please enter your hostname or IP address: ") + if ask_for_confirmation("Would you like to edit verify TLS cert?\nCurrent Value: {}".format(config['Verify TLS'])): + if ask_for_confirmation("Would you like to verify the TLS cert?"): + config['Verify TLS'] = True + else: + config['Verify TLS'] = False + if ask_for_confirmation("Would you like to edit the API key?"): + config['API Key'] = input("Please enter your API key: ") write_config_file(config, secrets_config_file) - # Editing an account + # Editing a pool elif user_input.casefold() == "e": pool_to_edit = select_pool(config['Pools']) pool_details = edit_pool_details(config['Pools'], pool_to_edit) @@ -404,27 +389,22 @@ if __name__ == '__main__': config['Pools'].update(pool_details) write_config_file(config, secrets_config_file) break - # Editing jail storage pool - elif user_input.casefold() == "j": - config['jailStoragePool'] = select_pool(config['Pools'], "select for jail storage") - write_config_file(config, secrets_config_file) - break - # Createing a new account + # Createing a new pool elif user_input.casefold() == "n": pool_details = new_pool_details() config['Pools'].update(pool_details) write_config_file(config, secrets_config_file) break - - # Deleting an account + # Deleting a pool elif user_input.casefold() == "d": pool_to_delete = select_pool(config['Pools'], "delete") if not ask_for_confirmation("Are you sure you wish to delete {} pool? ".format(pool_to_delete)): break del config['Pools'][pool_to_delete] if len(config['Pools']) == 0: - # no more accounts, remove secrets file - os.remove(secrets_config_file) + if ask_for_confirmation("There are no pools left in the config, do you want to remove the secrets file?"): + # no more accounts, remove secrets file if requests + os.remove(secrets_config_file) else: write_config_file(config, secrets_config_file) break @@ -435,119 +415,46 @@ if __name__ == '__main__': # Catch all for non-valid characters else: - print("This value must be one of the following characters: a, e, j, n, d, q.") + print("This value must be one of the following characters: h, e, n, d, q") if not os.path.exists(secrets_config_file): - print("No configuration file found. Please run {} -c for configuration.".format(script_name)) + print("No configuration file found. Please run {} -c/--config for configuration".format(script_name)) sys.exit(-1) # run the decryption of the keys and unlock the pool config = read_config_file(secrets_config_file) - api_key = config['API Key'] - jail_storage_pool = config['jailStoragePool'] - API_POOLS = request('pool', api_key)['response'] - API_DATASETS = request('pool/dataset', api_key)['response'] - for pool_dataset_name in config['Pools']: - if pool_dataset_name != 'DEFAULT': - name = pool_dataset_name - pool_passphrase = config['Pools'][name]['pool_passphrase'] - logger.info("Attempting to unlock {} pool.".format(name)) - for pool in API_POOLS: - if pool['name'] == name: - # GELI encrypted pool - if 'encrypt' in pool: - if pool['encrypt'] == 2: - if pool['is_decrypted'] == False: - logger.info('Pool {} is locked'.format(pool['name'])) - services_to_restart = request('pool/unlock_services_restart_choices', api_key, "POST", pool['id'])['response'] - first = True - for service_to_restart in services_to_restart: - if service_to_restart == "jails": - logger.debug('Skipping adding the {} service'.format(services_to_restart[service_to_restart])) - continue - if first: - pool_services = "'{}'".format(service_to_restart) - first = False - else: - pool_services += ", '{}'".format(service_to_restart) - logger.debug('Restarting {} services when unlocking pool.'.format(pool_services)) - response = request('pool/id/{}/unlock'.format(pool['id']), api_key, 'POST', {'passphrase': '{}'.format(pool_passphrase), 'services_restart': "[" + pool_services + "]"}) - if response['ok']: - logger.info('Pool {} was unlocked and services restarted successfully'.format(pool['name'])) - else: - logger.error('Pool {} was NOT unlocked and services NOT restarted'.format(pool['name'])) - else: - logger.info('Pool {} is already unlocked'.format(pool['name'])) - # start detection of locked zfs dataset - else: - # loop through the datasets to find the one we are looking for - for dataset in API_DATASETS: - if dataset['name'] == name: - if dataset['locked'] == True: - logger.info('Dataset {} is locked'.format(dataset['name'])) - response = request('pool/dataset/unlock', api_key, 'POST', {'id': '{}'.format(dataset['id']), 'unlock_options': {'recursive': True, 'datasets': [{'name': '{}'.format(dataset['name']), 'passphrase': '{}'.format(pool_passphrase)}]}}) - if response['ok']: - logger.info('Dataset {} was unlocked successfully'.format(dataset['name'])) - # restart services since the dataset was unlocked - logger.info('Restarting services') - try: - services_to_restart = request('pool/unlock_services_restart_choices', api_key, "POST", pool['id'])['response'] - except: - services_to_restart = request('pool/dataset/unlock_services_restart_choices', api_key, "POST", dataset['name'])['response'] - for service_to_restart in services_to_restart: - if service_to_restart == "jails": - logger.debug('Skipping restarting the {} service'.format(services_to_restart[service_to_restart])) - continue - logger.debug('Restarting the {} service'.format(services_to_restart[service_to_restart])) - response = request('service/restart', api_key, 'POST', {'service': service_to_restart}) - if response['ok']: - logger.debug('Service {} was restarted successfully'.format(services_to_restart[service_to_restart])) - else: - logger.error('Service {} was NOT restarted'.format(services_to_restart[service_to_restart])) - else: - logger.error('Dataset {} was NOT unlocked'.format(dataset['name'])) - else: - logger.info('Dataset {} is already unlocked'.format(dataset['name'])) - ''' - # make sure jail storage pool is set - logger.info('Setting jail storage pool to {}'.format(jail_storage_pool)) - response = request('jail/activate', api_key, 'POST', '{}'.format(jail_storage_pool)) - if response['ok']: - logger.info('Successfully set jail storage pool to {}'.format(jail_storage_pool)) - logger.debug("Sleeping for 120 seconds") - time.sleep(120) - # restart the jails service - #jails_service_name = 'jails' - #logger.debug('Restarting the {} service'.format(jails_service_name)) - #response = request('service/restart', api_key, 'POST', {'service': jails_service_name}) - #if response['ok']: - #logger.debug('Service {} was restarted successfully'.format(jails_service_name)) - #else: - #logger.error('Service {} was NOT restarted'.format(jails_service_name)) - # start any jails that are set to start on boot but are most likely on an encrypted pool - API_JAILS = request('jail', api_key)['response'] - for jail in API_JAILS: - if jail['boot'] == 1: - logger.info('Jail {} is set to start on boot'.format(jail['id'])) - if jail['state'] == 'down': - logger.info('Jail {} is stopped'.format(jail['id'])) - logger.info('Starting jail {}'.format(jail['id'])) - response = request('jail/start', api_key, 'POST', '{}'.format(jail['id'])) - if response['ok']: - logger.info('Started jail {} successfully'.format(jail['id'])) - else: - logger.error('Jail {} was NOT started'.format(jail['id'])) - else: - if opts.restartJails: - logger.info('Jail {} is already started but --restartJails was passed in. Restarting jail.'.format(jail['id'])) - response = request('jail/restart', api_key, 'POST', '{}'.format(jail['id'])) - if response['ok']: - logger.info('Restarted jail {} successfully'.format(jail['id'])) + URI = config['URI'] + VERIFY_TLS = config['Verify TLS'] + API_KEY = config['API Key'] + with Client(uri="wss://{}/api/current".format(URI), verify_ssl=VERIFY_TLS) as c: + if c.call("auth.login_with_api_key", API_KEY): + logger.debug('Successfully logged into {}'.format(URI)) + else: + logger.error('Unable to login to {}'.format(URI)) + SYSTEM_INFORMATION = c.call("system.info") + logger.debug('System Info: {}'.format(SYSTEM_INFORMATION)) + POOL_DATASETS = c.call("pool.dataset.details") + logger.debug('Pool Datasets: {}'.format(POOL_DATASETS)) + for config_pool in config['Pools']: + config_pool_name = config_pool + pool_passphrase = config['Pools'][config_pool_name]['pool_passphrase'] + logger.info("Attempting to unlock {} pool".format(config_pool_name)) + for pool_dataset in POOL_DATASETS: + pool_name = pool_dataset['name'] + if pool_name == config_pool_name: + pool_encrypted = pool_dataset['encrypted'] + pool_unlocked = pool_dataset['key_loaded'] + if pool_encrypted == True and pool_unlocked == False: + logger.info('Dataset {} is locked'.format(pool_name)) + unlock_args = {"datasets": [{"name": pool_name, "passphrase": pool_passphrase}]} + + unlock_status = c.call("pool.dataset.unlock", pool_name, unlock_args, job=True) + # return will look like this: {'unlocked': ['test'], 'failed': {}} + if pool_name in unlock_status.get('unlocked', []): + logger.info('Dataset {} was unlocked successfully'.format(pool_name)) else: - logger.error('Jail {} was NOT restarted'.format(jail['id'])) + logger.error('Dataset {} was NOT unlocked'.format(pool_name)) else: - logger.info('Jail {} is already started'.format(jail['id'])) - else: - logger.error('Unable to set jail storage pool to {}'.format(jail_storage_pool) - ''' + logger.info('Dataset {} is already unlocked'.format(pool_name)) + client.close() sys.exit(0) \ No newline at end of file