Fetch credentials from TrueNAS database for custom scripting
get_secrets.py edited
130 lines 4.4 kB view raw
1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4import argparse 5import base64 6import codecs 7import sqlite3 8import json 9import sys 10 11from Cryptodome import Random 12from Cryptodome.Cipher import AES 13from Cryptodome.Util import Counter 14 15def rclone_encrypt_password(password): 16 key = bytes([0x9c, 0x93, 0x5b, 0x48, 0x73, 0x0a, 0x55, 0x4d, 17 0x6b, 0xfd, 0x7c, 0x63, 0xc8, 0x86, 0xa9, 0x2b, 18 0xd3, 0x90, 0x19, 0x8e, 0xb8, 0x12, 0x8a, 0xfb, 19 0xf4, 0xde, 0x16, 0x2b, 0x8b, 0x95, 0xf6, 0x38]) 20 21 nonce = Random.new().read(AES.block_size) 22 counter = Counter.new(128, initial_value=int(codecs.encode(nonce, "hex"), 16)) 23 cipher = AES.new(key, AES.MODE_CTR, counter=counter) 24 25 encrypted_bytes = nonce + cipher.encrypt(password.encode("utf-8")) 26 return base64.urlsafe_b64encode(encrypted_bytes).decode("ascii").rstrip("=") 27 28def decrypt_data(encrypted_b64_string, secret_key): 29 if not encrypted_b64_string: 30 return b'' 31 32 encrypted_bytes = base64.b64decode(encrypted_b64_string) 33 nonce = encrypted_bytes[:8] 34 encrypted_payload = encrypted_bytes[8:] 35 36 cipher = AES.new(secret_key, AES.MODE_CTR, counter=Counter.new(64, prefix=nonce)) 37 38 return cipher.decrypt(encrypted_payload).rstrip(b'{').decode('utf8') 39 40parser = argparse.ArgumentParser( 41 description="Extracts credentials from TrueNAS database" 42) 43parser.add_argument( 44 '--backup-credential-id', 45 type=int, 46 help='Backup credential ID' 47) 48parser.add_argument( 49 '--id', 50 type=int, 51 help='Task ID for --truecloud or --cloudsync' 52) 53group = parser.add_mutually_exclusive_group(required=True) 54group.add_argument( 55 '--truecloud', 56 action='store_true', 57 help='Use TrueCloud provider' 58) 59group.add_argument( 60 '--cloudsync', 61 action='store_true', 62 help='Use CloudSync provider' 63) 64args = parser.parse_args() 65 66if (args.truecloud or args.cloudsync) and args.id is None: 67 parser.error("--id is required when using --truecloud or --cloudsync") 68 69secret_path = '/data/pwenc_secret' 70try: 71 with open(secret_path, 'rb') as f: 72 secret = f.read() 73except FileNotFoundError: 74 print(f"Error: password secret seed encryption file not found: {secret_path}", file=sys.stderr) 75 exit(1) 76 77db_path = '/data/freenas-v1.db' 78backup_credentials_encrypted = None 79encryption_password_encrypted = None 80encryption_salt_encrypted = None 81 82try: 83 con = sqlite3.connect(db_path) 84 cur = con.cursor() 85 86 res_backup_credentials = cur.execute("SELECT attributes FROM system_cloudcredentials WHERE id = ?", (args.backup_credential_id,)) 87 row_backup_credentials = res_backup_credentials.fetchone() 88 if row_backup_credentials: 89 (backup_credentials_encrypted,) = row_backup_credentials 90 else: 91 print("Error: backup credentials not found", file=sys.stderr) 92 sys.exit(1) 93 94 if args.truecloud: 95 res_encryption = cur.execute("SELECT password FROM tasks_cloud_backup WHERE id = ?", (args.id,)) 96 row_encryption = res_encryption.fetchone() 97 if row_encryption: 98 (encryption_password_encrypted,) = row_encryption 99 else: 100 print("Error: TrueCloud Restic password not found", file=sys.stderr) 101 sys.exit(1) 102 elif args.cloudsync: 103 res_encryption = cur.execute("SELECT encryption_password, encryption_salt FROM tasks_cloudsync WHERE id = ?", (args.id,)) 104 row_encryption = res_encryption.fetchone() 105 if row_encryption: 106 (encryption_password_encrypted, encryption_salt_encrypted) = row_encryption 107 else: 108 print("Error: CloudSync encryption password not found", file=sys.stderr) 109 sys.exit(1) 110 111finally: 112 if con: 113 con.close() 114 115decrypted_backup_credentials_json_str = decrypt_data(backup_credentials_encrypted, secret) 116backup_credentials = json.loads(decrypted_backup_credentials_json_str) if decrypted_backup_credentials_json_str else {} 117encryption_password = decrypt_data(encryption_password_encrypted, secret) 118if args.cloudsync: 119 encryption_password = rclone_encrypt_password(encryption_password) 120 121final_data = { 122 "encryption_password": encryption_password, 123 "backup_credentials": backup_credentials 124} 125 126if args.cloudsync and encryption_salt_encrypted: 127 encryption_salt = decrypt_data(encryption_salt_encrypted, secret) 128 final_data["encryption_salt"] = rclone_encrypt_password(encryption_salt) 129 130print(json.dumps(final_data, indent=4, ensure_ascii=False))