60 Commits

Author SHA1 Message Date
65ccd2ade2 update to only reload services that are enabled and running 2025-10-24 02:41:20 +00:00
807b5c21d6 added services to reload 2025-10-24 02:39:27 +00:00
a24c81160f Merge pull request 'updated for new api' (#2) from Scale-24 into master
Reviewed-on: #2

Updated to new api
2025-10-24 01:43:20 +00:00
eb226df901 updated for new api 2025-10-24 01:41:46 +00:00
b6aa8121b8 Add try catch for services to restart to work on core and scale for dataset unlock 2024-08-31 15:40:10 +00:00
92c693abc4 commented out jail starts since they do work with scale, will need to fix this for backwards comp 2024-08-31 15:21:22 +00:00
d73c245475 Updated to test for encrypt key in pool to keep backwards compatibility while working with scale 2024-08-31 15:17:08 +00:00
7b08763c64 Updated as simplejson is not available on truenas scale 2024-08-31 15:06:50 +00:00
c541a93994 Increased sleep to 120 seconds 2024-07-07 22:24:24 +00:00
c609216830 update help toc correctly show program name 2023-11-03 15:46:14 -04:00
cce20aa267 upped sleep to 60 seconds 2023-07-26 17:07:09 -04:00
f868336a8b upped sleep to 30 seconds 2023-07-26 16:58:05 -04:00
7a2b4d52c9 add a delay after activating pool for jail storage 2023-07-26 16:46:27 -04:00
cac20e14b6 Commented out restart of jail service 2023-07-26 15:25:21 -04:00
e7db02dce3 added logic to remove jails service restart before we have selected the jail storage pool 2023-07-26 15:23:00 -04:00
dac79ffa7c removed extra / 2023-07-26 15:16:26 -04:00
bb1a0616ed updated to use pool id instead of dataset id which is not int 2023-07-26 15:16:08 -04:00
4aeec47d1b added missing end brackets 2023-07-23 23:19:32 -04:00
acb4ac3921 fixed spacing 2023-07-23 23:18:28 -04:00
aa8574da13 fixed spacing 2023-07-23 23:17:57 -04:00
d55a5fff83 fixed spacing 2023-07-23 23:17:27 -04:00
e01b3d11ec Added lookup for services before unlocking so we can restart the correct services enabled 2023-07-23 23:16:45 -04:00
f8009f9ad3 Added empty line before jail storage pool output 2023-07-23 15:06:28 -04:00
13abab0b67 Updated to work with new config process 2023-07-23 15:05:52 -04:00
fe752ff3fb added force restart of jails 2023-07-21 23:49:34 -04:00
4025bca3cb numbered array didn't work :( 2023-07-21 23:44:14 -04:00
ff5b2e2d74 No point in asking for jail storage pool when creating a pool since there is only one in the config 2023-07-21 23:42:36 -04:00
72048eee94 mixed up e and j code 2023-07-21 23:40:29 -04:00
4f55ac6cce Updated to have jail storage pool and activate pool after unlock 2023-07-21 23:20:06 -04:00
a7d84132d6 I forgot the pool unlock already restarts the services 2023-04-02 17:46:23 -04:00
46e2edca74 switch to hmac compare digest for password confirmation verify 2023-04-02 14:53:31 -04:00
4c94efef26 spelling mistakes 2023-04-02 13:45:35 -04:00
d93ef77fd7 space 2023-04-02 13:45:08 -04:00
5f7e60e220 removed api key from debug log as it is easy to create a new one 2023-04-02 13:43:54 -04:00
20d6b44f6c updated wording 2023-04-02 13:43:14 -04:00
87a021529f moved config menu into for loop for better visuals 2023-04-02 13:03:01 -04:00
91908ae777 updated missed pool variable to config 2023-04-02 13:01:31 -04:00
1b9329d471 update git ignore to include secrets.config 2023-04-01 23:28:21 -04:00
8050d080cb added commands to restart services on pool unlock too 2023-04-01 22:59:02 -04:00
736b3b6d4c removed comments 2023-04-01 22:52:57 -04:00
1c63615f99 removed config print 2023-04-01 22:40:52 -04:00
9f724c7af2 missing ( 2023-04-01 22:39:20 -04:00
98d282f478 print config for debugging 2023-04-01 22:37:21 -04:00
fe371810c0 updated variable name from pools to config
Pools variable name doesn't make sense since the API key is also in there.  Better to call it config as it contains the config
2023-04-01 22:36:39 -04:00
c85ca8c51c changed wording from account to config 2023-04-01 22:34:30 -04:00
87137cf231 removed test output 2023-04-01 21:56:16 -04:00
84fa00d32b Lots of updates to new version 2023-01-30 08:46:38 -05:00
e9738f2954 imported time, commented out the log file, updated the parser and client config 2022-11-25 21:50:05 -05:00
f1dc883708 added logger, write/read json config, ask confirmation, and decrypt version functions 2022-11-25 20:39:38 -05:00
2334fc0b5c split the boot and start jail logic 2021-03-27 11:15:21 -04:00
a0d9576133 fixed syntax 2021-03-27 11:09:14 -04:00
c30d5e145d fixed syntax 2021-03-27 11:07:34 -04:00
c53f84fba4 fixed missing bracket 2021-03-27 11:06:58 -04:00
04058d485d added code to start jails at boot that are on encrypted pool 2021-03-27 11:05:58 -04:00
ba3aed8665 update path for client.conf from pykmip to conf 2021-02-14 18:38:46 -05:00
674b642745 add hmac module 2021-02-14 18:36:26 -05:00
a7dac0c680 updated logger command 2021-02-14 18:35:51 -05:00
a960cc9edb updated encrypt/decrypt functions 2021-02-13 13:59:40 -05:00
d976c6b3a9 moved folder pykmip to conf 2021-02-01 13:42:11 -05:00
eb5bcacc37 Merge pull request 'truenas-12' (#1) from truenas-12 into master
Reviewed-on: #1
2020-10-20 22:40:00 -04:00
5 changed files with 383 additions and 176 deletions

1
.gitignore vendored
View File

@@ -124,4 +124,5 @@ dmypy.json
*.key
*.crt
*secrets.ini
*secrets.config
*client.conf

View File

@@ -1,5 +1,5 @@
# truenas-kmip-unlocker
Encrytped secrets are stored within the [secrets.ini](secrets.ini.sample) file.
Encrytped secrets are stored within the secrets.config file.
**This will work with native zfs encryption but it will only unlock the top encrypted dataset and the children must be encrypted with the same passphrase. This continues to support GELI encrpyted pools.**
@@ -26,11 +26,12 @@ suppress_ragged_eofs=True
#### Encrypt your secrets
* Encrypt your api key for truenas
* Encrypt the passphrase for your pool/dataset
* Select jail storage pool
* Encrypt your remaining pool/dataset passphrases as needed
Run the following command to encrypt your secrets, it will ask for your pool/dataset passphrase that you want to encrypt and to confirm it before outputting the encrypted passphrase. Take the encrypted secret and create a new section in the config ini file for the pool/dataset; your encrypted api key goes into the DEFAULT section. The section name will be the pool/dataset name and the only key in that section is the encrypted_key which will be this value.
Run the following command to configure your API, jail storage pool, and pool names and passphrases.
```shell
python truenas-kmip-unlock.py --encrypt
python truenas-kmip-unlock.py --config
```
#### Create Task
@@ -40,7 +41,7 @@ python /root/truenas-kmip-unlocker/truenas-kmip-unlock.py
```
#### Debugging
Nothing is logged to a file for this. Everything is output to the console. To enabled debug mode, pass the **[-v|--verbose]** argument when running the command. If the verbose argument is passed in, all passphrases will be outputted in plain text to the console. This is to ensure the decryption is working correctly.
Nothing is logged to a file for this. Everything is output to the console. To enabled debug mode, pass the **[-v|--verbose]** argument when running the command.
```python
python /root/truenas-kmip-unlocker/truenas-kmip-unlock.py --verbose
```

View File

@@ -1,14 +0,0 @@
# THIS IS A SAMPLE FILE, PLEASE COPY IT AND EDIT THE COPY
# INI secrets file holds the encrypted secrest for the script
# the default section holds the encrypted api key and then the other sections are the pool/datasets names and encrypted keys to unlock the pools/datasets
# the section name is the name of the pool/dataset
# only one variable should be in each section called encrypted_key
# example
#[DEFAULT]
#encrypted_api_key = encrypted_api_key goes here
#[media]
#encrypted_key = some encrypted key goes here
#
[DEFAULT]
encrypted_api_key =

View File

@@ -6,48 +6,95 @@ import logging
import sys
import secrets
import base64
import optparse
import argparse
import configparser
import requests
import getpass
from truenas_api_client import Client
import platform
import subprocess
import simplejson as json
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import time
try:
import simplejson as json
except ImportError:
import json
import hmac as pyhmac
import datetime
from getpass import getpass
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
def build_logger(level):
logger = logging.getLogger()
logger.setLevel(level)
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
)
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)
# log to file
#fileHandler = logging.FileHandler(log_file)
#fileHandler.setFormatter(formatter)
#logger.addHandler(fileHandler)
# log to console
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(formatter)
logger.addHandler(consoleHandler)
return logger
def create_key(client):
def write_config_file(array, config_file):
logger.debug("Starting to write config file and encrypt contents")
logger.debug("Using config file: {}".format(config_file))
logger.debug("Converting config from array to json")
array_json = json.dumps(array)
logger.debug("Encrypting config json")
encrypted_array_json = encrypt(array_json)
logger.debug("Attempting to write encrypted config to file")
try:
f = open(config_file, "w")
#f.write(array_json)
f.write(encrypted_array_json)
f.close()
logger.debug("Successfully wrote encrypted config to file")
except Exception as e:
logger.error("Unable to write encrypted config to file. Error: {}".format(e))
sys.exit(-1)
logger.debug("Finshed writing config file and encrypting contents")
def read_config_file(config_file):
logger.debug("Starting to read config file and decrypt contents")
logger.debug("Using config file: {}".format(config_file))
logger.debug("Attempting to read encrypted config from file")
try:
with open(config_file) as f:
config = f.read()
logger.debug("Successfully read encrypted config from file")
except Exception as e:
logger.error("Unable to read encrypted config from file. Error: {}".format(e))
sys.exit(-1)
logger.debug("Decrypting config contents")
decrypted_array_json = decrypt(config)
logger.debug("Convert config from json to array")
#array = json.loads(config)
array = json.loads(decrypted_array_json)
logger.debug("Finished reading config file and decrypting contents")
return array
def ask_for_confirmation(question):
logger.debug("Asking user for confirmation")
logger.debug("Question: {}".format(question))
print(question)
while True:
confirmation = input("y/n> ")
logger.debug("User answered: {}".format(confirmation))
if confirmation.casefold() == "y":
return True
elif confirmation.casefold() == "n":
return False
else:
print("This value must be one of the following characters: y, n.")
def create_encryption_key():
# Create an encryption key.
try:
key_id = client.create(
@@ -59,9 +106,9 @@ def create_key(client):
]
)
logger.debug("Successfully created a new encryption key.")
logger.debug("Secret ID: {}".format(key_id))
logger.debug("Encryption Key ID: {}".format(key_id))
except Exception as e:
logger.error(e)
logger.error("Unable to create encryption key. Error: {}".format(e))
sys.exit(-1)
# Activate the encryption key so that it can be used.
@@ -70,13 +117,39 @@ def create_key(client):
logger.debug("Successfully activated the encryption key.")
return key_id
except Exception as e:
logger.error(e)
logger.error("Unable to activate encryption key. Error: {}".format(e))
sys.exit(-1)
def create_hmac_key():
# Create an encryption key.
try:
key_id = client.create(
enums.CryptographicAlgorithm.AES,
256,
cryptographic_usage_mask=[
enums.CryptographicUsageMask.MAC_GENERATE,
enums.CryptographicUsageMask.MAC_VERIFY
]
)
logger.debug("Successfully created a new HMAC key.")
logger.debug("HMAC Key ID: {}".format(key_id))
except Exception as e:
logger.error("Unable to create hmac key. Error: {}".format(e))
sys.exit(-1)
def encrypt(client, data):
# Activate the HMAC key so that it can be used.
try:
client.activate(key_id)
logger.debug("Successfully activated the HMAC key.")
return key_id
except Exception as e:
logger.error("Unable to activate hmac key. Error: {}".format(e))
sys.exit(-1)
def encrypt(data):
try:
data = data.encode('UTF-8')
key_id = create_key(client)
key_id = create_encryption_key()
iv = secrets.token_bytes(16)
cipher_text, autogenerated_iv = client.encrypt(
data,
@@ -91,34 +164,54 @@ def encrypt(client, data):
iv
)
)
logger.debug("Successfully encrypted the data: {}".format(data))
cipher_text_base64_bytes = base64.b64encode(cipher_text)
cipher_text_base64 = cipher_text_base64_bytes.decode('ascii')
logger.debug("Cipher text (raw): {}".format(cipher_text))
logger.debug("Cipher txt (encoded): {}".format(cipher_text_base64))
logger.debug("IV (raw): {}".format(iv))
iv_base64_bytes = base64.b64encode(iv)
iv_base64 = iv_base64_bytes.decode('ascii')
logger.debug("IV (encoded): {}".format(iv_base64))
padded_key_id = str(key_id).zfill(9)
logger.debug("Padding Key ID {} with zeros: {}".format(key_id,padded_key_id))
cipher_data = base64.b64encode(padded_key_id.encode('UTF-8') + iv + cipher_text).decode()
logger.debug("padded_key_id + iv + cipher_text (encoded): {}".format(cipher_data))
return cipher_data
hmac_key_id, hmac = client.mac(
key_id.encode() + iv + cipher_text,
uid = create_hmac_key(),
algorithm = enums.CryptographicAlgorithm.HMAC_SHA512
)
logger.debug("Successfully encrypted the data.")
array = dict()
array['version'] = 1
array['cipher_key_id'] = key_id
array['cipher_text'] = base64.b64encode(cipher_text).decode()
array['iv'] = base64.b64encode(iv).decode()
array['hmac_key_id'] = hmac_key_id
array['hmac'] = base64.b64encode(hmac).decode()
logger.debug("Dict of info: {}".format(array))
array_json = json.dumps(array)
array_json_b64 = base64.b64encode(array_json.encode('utf-8')).decode()
return array_json_b64
except Exception as e:
logger.error(e)
logger.error("Unable to encrypt data. Error: {}".format(e))
sys.exit(-1)
def decrypt(client, data):
def decrypt(data):
array_json = base64.b64decode(data)
array = json.loads(array_json)
if array['version'] == 1:
return decrypt_v1(array)
else:
logger.error("Unable to detemine encryption version.")
return False
def decrypt_v1(array):
try:
cipher_data = base64.b64decode(data)
padded_key_id = cipher_data[:9].decode('UTF-8')
iv = cipher_data[9:25]
cipher_text = cipher_data[25:]
logger.debug("Removing padding from Key ID: {}".format(padded_key_id))
key_id = padded_key_id.lstrip('0')
logger.debug("Decrypting with Key ID: {}".format(key_id))
logger.debug("Decrypting with IV (raw): {}".format(iv))
logger.debug("Decrypting cipher text (raw): {}".format(cipher_text))
logger.debug("Dict of info: {}".format(array))
key_id = array['cipher_key_id']
iv = base64.b64decode(array['iv'])
cipher_text = base64.b64decode(array['cipher_text'])
hmac_key_id = array['hmac_key_id']
hmac = base64.b64decode(array['hmac'])
hmac_key_id_test, hmac_test = client.mac(
key_id.encode() + iv + cipher_text,
uid = hmac_key_id,
algorithm = enums.CryptographicAlgorithm.HMAC_SHA512
)
if pyhmac.compare_digest(hmac, hmac_test):
logger.debug("HMAC matches.")
else:
logger.error("HMAC does not match, data is corrupted/tampered.")
sys.exit(-1)
plain_text = client.decrypt(
cipher_text,
uid=key_id,
@@ -134,118 +227,244 @@ def decrypt(client, data):
)
logger.debug("Successfully decrypted the data.")
plain_text = plain_text.decode('utf-8')
logger.debug("Plain text: '{}'".format(plain_text))
return plain_text
except Exception as e:
logger.error(e)
logger.error("Unable to decrypt data. Error: {}".format(e))
sys.exit(-1)
def new_pool_details():
print("Requesting pool details to add to config.")
pool_name = input("Please enter pool name: ")
while True:
pool_passphrase = getpass.getpass("Please enter pool encryption passphrase: ")
pool_passphrase_confirm = getpass.getpass("Please confirm pool encryption passphrase: ")
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
return array
def edit_pool_details(pools, pool_to_edit):
if ask_for_confirmation("Would you like to edit the pool name?\nCurrent Value: {}".format(pool_to_edit)):
pool_name = input("Please enter pool name: ")
else:
pool_name = pool_to_edit
if ask_for_confirmation("Would you like to edit the pool encryption passphraset?"):
while True:
pool_passphrase = getpass.getpass("Please enter pool encryption passphrase: ")
pool_passphrase_confirm = getpass.getpass("Please confirm pool encryption passphrase: ")
if pyhmac.compare_digest(pool_passphrase, pool_passphrase_confirm):
break
else:
print("The pool encryption passphrases do not match, please try again.")
else:
pool_passphrase = pools[pool_to_edit]['pool_passphrase']
array = dict()
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(" ")
pool_names = list(pools)
for i in range(0, len(pools)):
pretty_number = i + 1
print("{}) {}".format(pretty_number, pool_names[i]))
print(" ")
while True:
pool_to_modify = int(input("Please enter number relating to the pool you wish to {}: ".format(wording))) - 1
try:
return pool_names[pool_to_modify]
except IndexError as error:
print("you entered a number out of range, please try again")
if __name__ == '__main__':
# get directory of script
cwd = os.path.dirname(os.path.realpath(__file__))
# Build and parse arguments
parser = utils.build_cli_parser(enums.Operation.DECRYPT)
parser.add_option(
"-e",
"--encrypt",
parser = argparse.ArgumentParser(
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",
action="store_true",
dest="encrypt",
help="Encrypt a passphrase/password."
dest="config",
help="Edit pools configuration."
)
parser.add_option(
"-d",
"--decrypt",
action="store_true",
dest="decrypt",
help="Decrypt a passphrase/password."
)
parser.add_option (
#parser.add_argument(
# "-r",
# "--restartAppsVMs",
# action="store_true",
# dest="restartAppsVMs",
# help="Restarts apps and VMs if running"
#)
parser.add_argument (
"-v",
"--verbose",
action="store_true",
dest="debug",
help="Output debug/verbose info to the console for troubleshooting."
)
opts, args = parser.parse_args(sys.argv[1:])
if opts.debug:
logger = utils.build_console_logger(logging.DEBUG)
else:
logger = utils.build_console_logger(logging.INFO)
config = opts.config
passphrase = opts.message
client = client.ProxyKmipClient(config=config, config_file=cwd + '/pykmip/client.conf')
client.open()
if opts.encrypt:
if not passphrase:
while True:
passphrase = getpass("Please enter your pool/dataset unlock passphrase to encrypt: ")
passphrase2 = getpass("Please enter the passphrase again: ")
if passphrase == passphrase2:
break
else:
print("The passphrases do not match, please try again.")
encrypted_passphrase = encrypt(client, passphrase)
print("Your encrypted passphrase: {}".format(encrypted_passphrase))
sys.exit(0)
elif opts.decrypt:
if not passphrase:
passphrase = input("Please enter the encryted passphrase to decrypt: ")
decrypted_passphrase = decrypt(client, passphrase)
print("Your decrypted passphrase: {}".format(decrypted_passphrase))
sys.exit(0)
opts = parser.parse_args()
script_directory = os.path.dirname(os.path.realpath(__file__))
script_name = os.path.basename(__file__)
secrets_config_file = os.path.join(script_directory, "secrets.config")
pykmip_client_config_file = os.path.join(script_directory, "conf", "client.conf")
log_file = os.path.join(script_directory, "log.log")
datetime_string = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
if opts.debug:
logger = build_logger(logging.DEBUG)
else:
# run the decryption of the keys and unlock the pool
parser = configparser.ConfigParser()
parser.read(cwd + '/secrets.ini')
encrypted_api_key = parser.get('DEFAULT', 'encrypted_api_key')
api_key = decrypt(client, encrypted_api_key)
logger.debug("API Key: {}".format(api_key))
POOLS = request('pool', api_key)['response']
DATASETS = request('pool/dataset', api_key)['response']
for pool_dataset_name in parser.sections():
if pool_dataset_name != 'DEFAULT':
name = pool_dataset_name
encrypted_key = parser.get(name, 'encrypted_key')
logger.debug("Attempting to decrypt {} pool with encrypted key: {}".format(name,encrypted_key))
decrypted_key = decrypt(client, encrypted_key)
for pool in 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(decrypted_key), 'services_restart': ['cifs', 'nfs']})
if response['ok']:
logger.info('Pool {} was unlocked successfully'.format(pool['name']))
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 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(decrypted_key)}]}})
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']))
#print('pool {0} is decrypted: {1}'.format(pool['name'],pool['is_decrypted']))
# this line should be after the actual unlock on the disk
#logger.debug("Decrypted {0} pool key.".format(name))
sys.exit(0)
client.close()
logger = build_logger(logging.INFO)
client = client.ProxyKmipClient(config_file=pykmip_client_config_file)
client.open()
if opts.config:
while True:
if not os.path.exists(secrets_config_file):
print("No pools found, do you want to add a new one?")
print(" ")
print("n) New config")
print("q) Quit config")
while True:
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()
write_config_file(config, secrets_config_file)
break
elif user_input.casefold() == "q":
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("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")
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)
del config['Pools'][pool_to_edit]
config['Pools'].update(pool_details)
write_config_file(config, secrets_config_file)
break
# 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 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:
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
# Quit the config
elif user_input.casefold() == "q":
sys.exit(0)
# Catch all for non-valid characters
else:
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/--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)
URI = config['URI']
VERIFY_TLS = config['Verify TLS']
API_KEY = config['API Key']
# Services excluded from being reloaded
EXCLUDED_SERVICES = ["ssh", "snmp", "ups"]
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('Dataset {} was NOT unlocked'.format(pool_name))
else:
logger.info('Dataset {} is already unlocked'.format(pool_name))
services = c.call("service.query")
for service in services:
if service['enable'] and service['enable'] == "RUNNING" and service['service'] not in EXCLUDED_SERVICES:
logger.debug('Service {} is enabled'.format(service['service']))
reloaded = c.call("service.control", "RELOAD", service['service'], job=True)
if reloaded:
logger.info('Service {} successfully reloaded'.format(service['service']))
else:
logger.error('Service {} was NOT reloaded'.format(service['service']))
client.close()
sys.exit(0)