|
|
|
|
@@ -9,46 +9,22 @@ import base64
|
|
|
|
|
import argparse
|
|
|
|
|
import configparser
|
|
|
|
|
import getpass
|
|
|
|
|
import requests
|
|
|
|
|
from truenas_api_client import Client
|
|
|
|
|
import platform
|
|
|
|
|
import subprocess
|
|
|
|
|
import time
|
|
|
|
|
import simplejson as json
|
|
|
|
|
try:
|
|
|
|
|
import simplejson as json
|
|
|
|
|
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)
|
|
|
|
|
@@ -262,10 +238,11 @@ def new_pool_details():
|
|
|
|
|
while True:
|
|
|
|
|
pool_passphrase = getpass.getpass("Please enter pool encryption passphrase: ")
|
|
|
|
|
pool_passphrase_confirm = getpass.getpass("Please confirm pool encryption passphrase: ")
|
|
|
|
|
if pool_passphrase == pool_passphrase_confirm:
|
|
|
|
|
if pyhmac.compare_digest(pool_passphrase, pool_passphrase_confirm):
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
print("The pool encryption passphrases do not match, please try again.")
|
|
|
|
|
|
|
|
|
|
array = dict()
|
|
|
|
|
array[pool_name] = dict()
|
|
|
|
|
array[pool_name]["pool_passphrase"] = pool_passphrase
|
|
|
|
|
@@ -281,7 +258,7 @@ def edit_pool_details(pools, pool_to_edit):
|
|
|
|
|
while True:
|
|
|
|
|
pool_passphrase = getpass.getpass("Please enter pool encryption passphrase: ")
|
|
|
|
|
pool_passphrase_confirm = getpass.getpass("Please confirm pool encryption passphrase: ")
|
|
|
|
|
if pool_passphrase == pool_passphrase_confirm:
|
|
|
|
|
if pyhmac.compare_digest(pool_passphrase, pool_passphrase_confirm):
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
print("The pool encryption passphrases do not match, please try again.")
|
|
|
|
|
@@ -311,8 +288,8 @@ def select_pool(pools, wording = "edit"):
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
# Build and parse arguments
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
|
usage="%prog [options]",
|
|
|
|
|
description="Run Truenas Pool Unlcok opteration. This will unlock encrypted pools on truenas.")
|
|
|
|
|
usage="{} [options]".format(os.path.basename(__file__)),
|
|
|
|
|
description="Run Truenas pool unlock operation. This will unlock encrypted pools on truenas.")
|
|
|
|
|
parser.add_argument(
|
|
|
|
|
"-c",
|
|
|
|
|
"--config",
|
|
|
|
|
@@ -320,6 +297,13 @@ if __name__ == '__main__':
|
|
|
|
|
dest="config",
|
|
|
|
|
help="Edit pools configuration."
|
|
|
|
|
)
|
|
|
|
|
#parser.add_argument(
|
|
|
|
|
# "-r",
|
|
|
|
|
# "--restartAppsVMs",
|
|
|
|
|
# action="store_true",
|
|
|
|
|
# dest="restartAppsVMs",
|
|
|
|
|
# help="Restarts apps and VMs if running"
|
|
|
|
|
#)
|
|
|
|
|
parser.add_argument (
|
|
|
|
|
"-v",
|
|
|
|
|
"--verbose",
|
|
|
|
|
@@ -356,8 +340,12 @@ if __name__ == '__main__':
|
|
|
|
|
user_input = input("n/q> ")
|
|
|
|
|
if user_input.casefold() == "n":
|
|
|
|
|
config = dict()
|
|
|
|
|
api_key = input("Please enter your API key: ")
|
|
|
|
|
config['API Key'] = api_key
|
|
|
|
|
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()
|
|
|
|
|
write_config_file(config, secrets_config_file)
|
|
|
|
|
break
|
|
|
|
|
@@ -365,25 +353,35 @@ if __name__ == '__main__':
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
else:
|
|
|
|
|
print("This value must be one of the following characters: n, q.")
|
|
|
|
|
while True:
|
|
|
|
|
config = read_config_file(secrets_config_file)
|
|
|
|
|
print("Current pools:")
|
|
|
|
|
print(" ")
|
|
|
|
|
for pool in config['Pools']:
|
|
|
|
|
print(pool)
|
|
|
|
|
print(" ")
|
|
|
|
|
print("a) Edit API Key")
|
|
|
|
|
print("URI: {}".format(config['URI']))
|
|
|
|
|
print("Verify TLS Cert: {}".format(config['Verify TLS']))
|
|
|
|
|
print(" ")
|
|
|
|
|
print("h) Edit Host, TLS, and API Key")
|
|
|
|
|
print("e) Edit pool")
|
|
|
|
|
print("n) New pool")
|
|
|
|
|
print("d) Delete pool")
|
|
|
|
|
print("q) Quit config")
|
|
|
|
|
while True:
|
|
|
|
|
user_input = input("a/e/n/d/q> ")
|
|
|
|
|
# Edit API Key
|
|
|
|
|
if user_input.casefold() == "a":
|
|
|
|
|
api_key = input("Please enter your API key: ")
|
|
|
|
|
config['API Key'] = api_key
|
|
|
|
|
write_config_file(pools, secrets_config_file)
|
|
|
|
|
# Editing an account
|
|
|
|
|
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 a pool
|
|
|
|
|
elif user_input.casefold() == "e":
|
|
|
|
|
pool_to_edit = select_pool(config['Pools'])
|
|
|
|
|
pool_details = edit_pool_details(config['Pools'], pool_to_edit)
|
|
|
|
|
@@ -391,21 +389,21 @@ if __name__ == '__main__':
|
|
|
|
|
config['Pools'].update(pool_details)
|
|
|
|
|
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
|
|
|
|
|
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)
|
|
|
|
|
@@ -417,74 +415,46 @@ if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
# Catch all for non-valid characters
|
|
|
|
|
else:
|
|
|
|
|
print("This value must be one of the following characters: a, e, 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 to configure your accounts.".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']
|
|
|
|
|
logger.debug("API Key: {}".format(api_key))
|
|
|
|
|
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 pool['encrypt'] == 2:
|
|
|
|
|
if pool['is_decrypted'] == False:
|
|
|
|
|
logger.info('Pool {} is locked'.format(pool['name']))
|
|
|
|
|
response = request('pool/id/{}/unlock'.format(pool['id']), api_key, 'POST', {'passphrase': '{}'.format(pool_passphrase), 'services_restart': ['cifs', 'nfs']})
|
|
|
|
|
if response['ok']:
|
|
|
|
|
logger.info('Pool {} was unlocked successfully'.format(pool['name']))
|
|
|
|
|
# restart services since the dataset was unlocked
|
|
|
|
|
logger.info('Restarting the NFS service')
|
|
|
|
|
response = request('service/restart', api_key, 'POST', {'service': 'nfs'})
|
|
|
|
|
logger.info('Restarting the CIFS/SMB service')
|
|
|
|
|
response = request('service/restart', api_key, 'POST', {'service': 'cifs'})
|
|
|
|
|
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('Pool {} was NOT unlocked successfully'.format(pool['name']))
|
|
|
|
|
else:
|
|
|
|
|
logger.info('Pool {} is already unlocked'.format(pool['name']))
|
|
|
|
|
# start detection of locked zfs dataset
|
|
|
|
|
elif pool['encrypt'] == 0:
|
|
|
|
|
# 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 the NFS service')
|
|
|
|
|
response = request('service/restart', api_key, 'POST', {'service': 'nfs'})
|
|
|
|
|
logger.info('Restarting the CIFS/SMB service')
|
|
|
|
|
response = request('service/restart', api_key, 'POST', {'service': 'cifs'})
|
|
|
|
|
else:
|
|
|
|
|
logger.error('Dataset {} was NOT unlocked successfully'.format(dataset['name']))
|
|
|
|
|
else:
|
|
|
|
|
logger.info('Dataset {} is already unlocked'.format(dataset['name']))
|
|
|
|
|
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}]}
|
|
|
|
|
|
|
|
|
|
# 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']))
|
|
|
|
|
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 started successfully'.format(jail['id']))
|
|
|
|
|
logger.error('Dataset {} was NOT unlocked'.format(pool_name))
|
|
|
|
|
else:
|
|
|
|
|
logger.info('Jail {} is already started'.format(jail['id']))
|
|
|
|
|
logger.info('Dataset {} is already unlocked'.format(pool_name))
|
|
|
|
|
|
|
|
|
|
client.close()
|
|
|
|
|
sys.exit(0)
|