1
|
6
|
import json
|
2
|
6
|
import re
|
3
|
6
|
import io
|
4
|
6
|
import logging
|
5
|
6
|
import os
|
6
|
6
|
import traceback
|
7
|
6
|
import tempfile
|
8
|
6
|
import stat
|
9
|
6
|
import shutil
|
10
|
6
|
try:
|
11
|
6
|
import psutil
|
12
|
0
|
except Exception:
|
13
|
0
|
psutil = None
|
14
|
|
|
15
|
6
|
from django.conf import settings
|
16
|
6
|
from cyborgbackup.main.expect import run
|
17
|
6
|
from cyborgbackup.main.models.settings import Setting
|
18
|
6
|
from cyborgbackup.main.utils.encryption import decrypt_field
|
19
|
|
|
20
|
6
|
logger = logging.getLogger('cyborgbackup.main.modules.queriers')
|
21
|
|
|
22
|
|
|
23
|
6
|
class Querier:
|
24
|
6
|
client = None
|
25
|
6
|
client_user = 'root'
|
26
|
|
|
27
|
6
|
def querier(self, module, client, params):
|
28
|
0
|
try:
|
29
|
0
|
setting_client_user = Setting.objects.get(key='cyborgbackup_backup_user')
|
30
|
0
|
self.client_user = setting_client_user.value
|
31
|
0
|
except Exception:
|
32
|
0
|
self.client_user = 'root'
|
33
|
0
|
self.client = client
|
34
|
0
|
if hasattr(self, 'querier_%s' % module):
|
35
|
0
|
return getattr(self, 'querier_%s' % module)(params)
|
36
|
|
else:
|
37
|
0
|
return {}
|
38
|
|
|
39
|
6
|
def querier_proxmox(self, args):
|
40
|
0
|
cmd = ["/usr/bin/pvesh", "get", "/cluster/resources", "--type", "vm", "--output-format=json"]
|
41
|
0
|
output, rc = self._run(cmd, sudo=True)
|
42
|
0
|
vms = []
|
43
|
0
|
if rc == 0:
|
44
|
0
|
objs = json.loads(output[0])
|
45
|
0
|
for obj in objs:
|
46
|
0
|
vms.append({
|
47
|
|
'vmid': obj['vmid'],
|
48
|
|
'name': obj['name'],
|
49
|
|
'type': obj['type'],
|
50
|
|
'node': obj['node'],
|
51
|
|
'status': obj['status']
|
52
|
|
})
|
53
|
0
|
return vms
|
54
|
|
else:
|
55
|
0
|
return -1
|
56
|
|
|
57
|
6
|
def querier_mysql(self, args):
|
58
|
0
|
cmd = ['mysql', '-NBe', '\'SHOW DATABASES\'']
|
59
|
|
# cmd = ['echo', '\'\n\nshow databases;\nshow tables;\'', '|', 'mysql']
|
60
|
0
|
queryargs = {}
|
61
|
0
|
if 'user' in args.keys():
|
62
|
0
|
cmd += ['-u{}'.format(args['user'])]
|
63
|
0
|
if 'password' in args.keys():
|
64
|
0
|
cmd += ['-p'.format(args['password'])]
|
65
|
0
|
queryargs['password'] = args['password']
|
66
|
0
|
if 'port' in args.keys():
|
67
|
0
|
cmd += ['-P{}'.format(args['port'])]
|
68
|
0
|
output, rc = self._run(cmd, queryargs=queryargs)
|
69
|
0
|
dbs = []
|
70
|
0
|
if rc == 0:
|
71
|
0
|
for line in output:
|
72
|
0
|
dbs.append({'name': line})
|
73
|
0
|
return dbs
|
74
|
|
else:
|
75
|
0
|
return -1
|
76
|
|
|
77
|
6
|
def querier_postgresql(self, args):
|
78
|
0
|
cmd = ['psql', '-F\',\'', '-t', '-A', '-c', '\'SELECT datname FROM pg_database;\'']
|
79
|
0
|
if self.client_user == 'root':
|
80
|
0
|
cmd = ['cd', '/tmp', '&&', 'sudo', '-u', 'postgres'] + cmd
|
81
|
0
|
output, rc = self._run(cmd)
|
82
|
0
|
dbs = []
|
83
|
0
|
if rc == 0:
|
84
|
0
|
for line in output:
|
85
|
0
|
dbs.append({'name': line})
|
86
|
0
|
return dbs
|
87
|
|
else:
|
88
|
0
|
return -1
|
89
|
|
|
90
|
6
|
def _run(self, cmd, sudo=False, queryargs=None):
|
91
|
0
|
args = []
|
92
|
0
|
rc = -1
|
93
|
0
|
finalOutput = []
|
94
|
|
|
95
|
0
|
if self.client_user != 'root' and sudo:
|
96
|
0
|
args = ['sudo', '-E']+args
|
97
|
0
|
args += cmd
|
98
|
|
|
99
|
0
|
kwargs = {}
|
100
|
0
|
try:
|
101
|
0
|
env = {}
|
102
|
0
|
for attr in dir(settings):
|
103
|
0
|
if attr == attr.upper() and attr.startswith('CYBORGBACKUP_'):
|
104
|
0
|
env[attr] = str(getattr(settings, attr))
|
105
|
|
|
106
|
0
|
path = tempfile.mkdtemp(prefix='cyborgbackup_module', dir='/tmp/')
|
107
|
0
|
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
|
108
|
0
|
kwargs['private_data_dir'] = path
|
109
|
|
|
110
|
0
|
if 'private_data_dir' in kwargs.keys():
|
111
|
0
|
env['PRIVATE_DATA_DIR'] = kwargs['private_data_dir']
|
112
|
0
|
passwords = {}
|
113
|
0
|
for setting in Setting.objects.filter(key__contains='ssh_key'):
|
114
|
0
|
set = Setting.objects.get(key=setting.key.replace('ssh_key', 'ssh_password'))
|
115
|
0
|
passwords['credential_{}'.format(setting.key)] = decrypt_field(set, 'value')
|
116
|
0
|
kwargs['passwords'] = passwords
|
117
|
|
|
118
|
|
|
119
|
0
|
private_data = {'credentials': {}}
|
120
|
0
|
for sets in Setting.objects.filter(key__contains='ssh_key'):
|
121
|
|
# If we were sent SSH credentials, decrypt them and send them
|
122
|
|
# back (they will be written to a temporary file).
|
123
|
0
|
private_data['credentials'][sets] = decrypt_field(sets, 'value') or ''
|
124
|
0
|
private_data_files = {'credentials': {}}
|
125
|
0
|
if private_data is not None:
|
126
|
0
|
listpaths = []
|
127
|
0
|
for sets, data in private_data.get('credentials', {}).items():
|
128
|
|
# OpenSSH formatted keys must have a trailing newline to be
|
129
|
|
# accepted by ssh-add.
|
130
|
0
|
if 'OPENSSH PRIVATE KEY' in data and not data.endswith('\n'):
|
131
|
0
|
data += '\n'
|
132
|
|
# For credentials used with ssh-add, write to a named pipe which
|
133
|
|
# will be read then closed, instead of leaving the SSH key on disk.
|
134
|
0
|
if sets:
|
135
|
0
|
name = 'credential_{}'.format(sets.key)
|
136
|
0
|
path = os.path.join(kwargs['private_data_dir'], name)
|
137
|
0
|
run.open_fifo_write(path, data)
|
138
|
0
|
listpaths.append(path)
|
139
|
0
|
if len(listpaths) > 1:
|
140
|
0
|
private_data_files['credentials']['ssh'] = listpaths
|
141
|
0
|
elif len(listpaths) == 1:
|
142
|
0
|
private_data_files['credentials']['ssh'] = listpaths[0]
|
143
|
|
|
144
|
0
|
kwargs['private_data_files'] = private_data_files
|
145
|
|
|
146
|
|
# May have to serialize the value
|
147
|
0
|
kwargs['private_data_files'] = private_data_files
|
148
|
0
|
cwd = '/tmp'
|
149
|
|
|
150
|
0
|
new_args = []
|
151
|
0
|
new_args += ['ssh', '-Ao', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null']
|
152
|
0
|
new_args += ['{}@{}'.format(self.client_user, self.client.hostname)]
|
153
|
0
|
new_args += ['\"echo \'####CYBMOD#####\';', ' '.join(args), '; exitcode=\$?; echo \'####CYBMOD#####\'; exit \$exitcode\"']
|
154
|
0
|
args = new_args
|
155
|
|
|
156
|
|
# If there is an SSH key path defined, wrap args with ssh-agent.
|
157
|
0
|
private_data_files = kwargs.get('private_data_files', {})
|
158
|
0
|
if 'ssh' in private_data_files.get('credentials', {}):
|
159
|
0
|
ssh_key_path = private_data_files['credentials']['ssh']
|
160
|
|
else:
|
161
|
0
|
ssh_key_path = ''
|
162
|
0
|
if ssh_key_path:
|
163
|
0
|
ssh_auth_sock = os.path.join(kwargs['private_data_dir'], 'ssh_auth.sock')
|
164
|
0
|
args = run.wrap_args_with_ssh_agent(args, ssh_key_path, ssh_auth_sock)
|
165
|
|
# args = cmd
|
166
|
|
|
167
|
0
|
expect_passwords = {}
|
168
|
0
|
d = {}
|
169
|
|
|
170
|
0
|
for k, v in kwargs['passwords'].items():
|
171
|
0
|
d[re.compile(r'Enter passphrase for .*' + k + r':\s*?$', re.M)] = k
|
172
|
0
|
d[re.compile(r'Enter passphrase for .*' + k, re.M)] = k
|
173
|
0
|
d[re.compile(r'Bad passphrase, try again for .*:\s*?$', re.M)] = ''
|
174
|
|
|
175
|
0
|
for k, v in d.items():
|
176
|
0
|
expect_passwords[k] = kwargs['passwords'].get(v, '') or ''
|
177
|
|
|
178
|
0
|
if queryargs and 'password' in queryargs.keys():
|
179
|
0
|
expect_passwords[re.compile(r'Enter password: \s*?$', re.M)] = queryargs['password']
|
180
|
|
|
181
|
0
|
stdout_handle = io.StringIO()
|
182
|
|
|
183
|
0
|
_kw = dict(
|
184
|
|
expect_passwords=expect_passwords,
|
185
|
|
job_timeout=120,
|
186
|
|
idle_timeout=None,
|
187
|
|
pexpect_timeout=getattr(settings, 'PEXPECT_TIMEOUT', 5),
|
188
|
|
)
|
189
|
0
|
status, rc = run.run_pexpect(
|
190
|
|
args, cwd, env, stdout_handle, **_kw
|
191
|
|
)
|
192
|
0
|
stdout_handle.flush()
|
193
|
0
|
output = stdout_handle.getvalue().split('\r\n')
|
194
|
0
|
finalOutput = []
|
195
|
0
|
start = False
|
196
|
0
|
for line in output:
|
197
|
0
|
if 'Enter password: ' in line:
|
198
|
0
|
line = line.replace('Enter password: ', '')
|
199
|
0
|
if line == '####CYBMOD#####' and not start:
|
200
|
0
|
start = True
|
201
|
0
|
if start and line != '####CYBMOD#####' and line != '':
|
202
|
0
|
finalOutput += [line]
|
203
|
|
|
204
|
0
|
shutil.rmtree(kwargs['private_data_dir'])
|
205
|
0
|
except Exception:
|
206
|
0
|
tb = traceback.format_exc()
|
207
|
0
|
if settings.DEBUG:
|
208
|
0
|
logger.exception('Exception occurred while running task')
|
209
|
|
finally:
|
210
|
0
|
try:
|
211
|
0
|
logger.info('finished running, producing events.')
|
212
|
0
|
except Exception:
|
213
|
0
|
logger.exception('Error flushing stdout and saving event count.')
|
214
|
0
|
print(rc)
|
215
|
0
|
print(status)
|
216
|
0
|
return finalOutput, rc
|