get_secrets.py
edited
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))