TiledViz
Loading...
Searching...
No Matches
TVSecure.py
1#! /usr/bin/env python
2import subprocess
3import threading
4
5import time
6
7import docker
8import sys,os,stat,psutil
9import traceback
10import argparse
11import json
12import datetime
13import re
14import configparser
15
16import tarfile
17from io import BytesIO
18# import tempfile
19import pickle
20
21import socket, requests
22
23sys.path.append(os.path.abspath('./TVDatabase'))
24from TVDb import tvdb
25from TVDb import models
26
27sys.path.append(os.path.realpath('./TVConnections/'))
28from connect import sock
29
30import code
31
32import logging
33
34errors=sys.stderr
35listerrors={"createError":1,"ImageError":2,"APIError":3,"start":4}
36
37
38TVrunDir=os.environ['HOME']+'/.tiledviz'
39TVconf=TVrunDir+"/tiledviz.conf"
40configExist=False
41if (os.path.isdir(TVrunDir)):
42 if (os.path.isfile(TVconf)):
43 configExist=True
44else:
45 os.mkdir(TVrunDir)
46 mode = os.stat(TVrunDir).st_mode
47 mode -= (mode & (stat.S_IRWXG | stat.S_IRWXO))
48 os.chmod(TVrunDir,mode)
49
50if (configExist):
51 config = configparser.ConfigParser()
52 config.optionxform = str
53 config.read(TVconf)
54
55 # Max number of connections before relaunch TVSecure
56 NbSecureConnection=int(config['TVSecure']['NbSecureConnection'])
57
58 # Max number of bites keep in flaskdock log for TVSecure to be analysed.
59 NbBitesLog=config['TVSecure']['NbBitesLog']
60
61 # Start port for ssh connection between TVConnection.py and TVSecure.py through ssh
62 ConnectionPort=int(config['TVSecure']['ConnectionPort'])
63
64 # Start port for connection between TVConnection.py and TVSecure.py through socat and docker0
65 ActionPort=int(config['TVSecure']['ActionPort'])
66
67 # Wait in second for nodes.json (a tile set is ready on supercomputer)
68 Swait=int(config['TVSecure']['Swait'])
69
70 # Maximum waiting for nodes.json before stoping container
71 Mwait=int(config['TVSecure']['Mwait'])
72
73 # Firewall with NFT (best way to convert bool string in python bool
74 FirewallT=json.loads(config['TVSecure']['FirewallT'].lower())
75
76 # Port for SSH server
77 SSHport=config['TVSecure']['SSHport']
78
79else:
80 NbSecureConnection=59
81 NbBitesLog="500k"
82 # Default init connection PORT
83 ConnectionPort=54040
84 ActionPort=64040
85 Swait=10
86 Mwait=1800
87 FirewallT=False
88 SSHport="22"
89if FirewallT :
90 import signal
91 import nftables
92 nft = nftables.Nftables()
93
94
95nbLinesLogs=100
96timeAliveServ=0.5
97timeAliveConn=0.3
98timeWait=2
99CONNECTION_RESOL='1350x660'
100
101DEBUG_ANALYSE=False
102debug_Flask=False
103
104#sys.path.append(os.path.abspath('./'))
105
106POSTGRES_HOST='postgres'
107POSTGRES_IP='172.17.0.2'
108POSTGRES_PORT='6431'
109POSTGRES_USER='tiledviz'
110POSTGRES_DB='TiledViz'
111POSTGRES_PASSWORD='m_test/@03'
112secretKey="my Preci0us secr_t key for t&sts."
113SMTP_PASSWORD="m_smtp_p@sw0rd"
114flaskaddr=os.getenv('SERVER_NAME')+"."+os.getenv('DOMAIN')
115
116client = docker.from_env()
117def parse_args(argv):
118 parser = argparse.ArgumentParser(
119 'Launch Flask docker with postgres parameters. Launch on-demand connections.')
120 parser.add_argument('--POSTGRES_HOST', default=POSTGRES_HOST,
121 help='POSTGRES_HOST (default: '+POSTGRES_HOST+')')
122 parser.add_argument('--POSTGRES_IP', default=POSTGRES_IP,
123 help='POSTGRES_IP (default: '+POSTGRES_IP+')')
124 parser.add_argument('--POSTGRES_PORT', default=POSTGRES_PORT,
125 help='POSTGRES_PORT (default: '+POSTGRES_PORT+')')
126 parser.add_argument('--POSTGRES_USER', default=POSTGRES_USER,
127 help='POSTGRES_USER (default: '+POSTGRES_USER+')')
128 parser.add_argument('--POSTGRES_DB', default=POSTGRES_DB,
129 help='POSTGRES_DB (Default: '+POSTGRES_DB+')')
130 parser.add_argument('--POSTGRES_PASSWORD', default='"'+POSTGRES_PASSWORD+'"',
131 help='POSTGRES_PASSWORD (default: "'+POSTGRES_PASSWORD+'")')
132 parser.add_argument('--secretKey', default=secretKey,
133 help='secretKey (default: "'+secretKey+'")')
134 parser.add_argument('--SMTP_PASSWORD', default='"'+SMTP_PASSWORD+'"',
135 help='SMTP_PASSWORD (default: "'+SMTP_PASSWORD+'")')
136 args = parser.parse_args(argv[1:])
137 return args
138
139TVvolume=docker.types.Mount(target='/TiledViz',source=os.getenv('PWD'),type='bind',read_only=False)
140TVWconf=docker.types.Mount(target='/.tiledviz',source=TVrunDir,type='bind',read_only=True)
141
142SSLpath=os.path.dirname(os.path.dirname(os.getenv('SSLpublic')))
143TVssl=docker.types.Mount(target=SSLpath,source=SSLpath,type='bind',read_only=True)
144
145Confpath=os.path.join(os.getenv('HOME'),".tiledviz")
146TVconf=docker.types.Mount(target="/home/myuser/.tiledviz",source=Confpath,type='bind',read_only=True)
147
148TVnginx=docker.types.Mount(target='/var/log/nginx',source='/var/log/nginx',type='bind',read_only=False)
149
150flaskc={"max-size": NbBitesLog, "max-file": "3"}
151TVlogs=docker.types.LogConfig(type=docker.types.LogConfig.types.JSON, config=flaskc)
152
153threads={}
154
155sqltConnections={"username":"none","connectionid":-1}
156Connections=[sqltConnections] * NbSecureConnection
157usedConnections=[False] * NbSecureConnection
158countConnections=[0] * NbSecureConnection
159
160# Get string of exec_run output
161def container_exec_out(thecontainer, cmd, user='root'):
162 res=thecontainer.exec_run(cmd=cmd,user=user,stream=True,demux=False,detach=False)
163 return "".join([ str(out,'utf-8') for out in res.output ])
164
165# Get the new last part string between old string1 and new string2 for the log output.
166# from https://stackoverflow.com/a/46757885
167def NewStringFinder(string1, string2):
168 answer = ""
169 len1, len2 = len(string1), len(string2)
170 ansK=0
171 for i in range(len1):
172 for j in range(len2):
173 lcs_temp=0
174 match=''
175 while ((i+lcs_temp < len1) and (j+lcs_temp<len2) and string1[i+lcs_temp] == string2[j+lcs_temp]):
176 match += string2[j+lcs_temp]
177 lcs_temp+=1
178 if (len(match) > len(answer)):
179 answer = match
180 ansK=j+lcs_temp
181 return string2[ansK:len2]
182
183class FlaskDocker(threading.Thread):
184 def __init__(self,
185 POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT,
186 POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD,
187 SMTP_PASSWORD=SMTP_PASSWORD,
188 secretKey=secretKey):
189
190 self.thread = threading.Thread(target=self.run, name="TVSecureServer",
191 args=( POSTGRES_HOST, POSTGRES_IP, POSTGRES_PORT,
192 POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD,
193 SMTP_PASSWORD,
194 secretKey))
195
196 logging.debug("After def thread.")
197 threads["flaskdock"]=self.thread
198 self.thread.start()
199
200 def run(self,
201 POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT,
202 POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD,
203 SMTP_PASSWORD=SMTP_PASSWORD,
204 secretKey=secretKey ):
205
206 logging.debug("In thread "+threading.current_thread().name)
207 self.oldtime=time.time()
208
209 #self.healthcheck={"test":[]}
210 #self.healthcheck={"test":["NONE"]}
211
212 #socket.gethostbyname(socket.gethostname())
213 self.commandFlask=[POSTGRES_HOST,POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, flaskaddr, str(os.getuid()),str(os.getgid()), SMTP_PASSWORD, secretKey]
214
215 # Si on passe secretKey et password comme secret (seulement comme services dans un swarm!), on doit modifier TVWeb/FlaskDocker/launch_flask
216 # pgpassword=client.secrets.create(name="POSTGRES_PASSWORD",data=POSTGRES_PASSWORD)
217 # flsecret=client.secrets.create(name="secretKey",data=secretKey)
218 # client.swarm.init(
219 # advertise_addr='eth0', listen_addr='0.0.0.0:5000',
220 # force_new_cluster=False, snapshot_interval=5000,
221 # log_entries_for_slow_followers=1200
222 # )
223 # client.service.create(....,secrets=[pgpassword,flsecret],...)
224 # $ docker service create --name redis --secret my_secret_data redis:alpine
225 # $ docker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets
226
227 # total 4
228 # -r--r--r-- 1 root root 17 Dec 13 22:48 my_secret_data
229
230 # $ docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
231
232 # postgres service
233 self.postgresHost={POSTGRES_HOST:POSTGRES_IP}
234 # Flask external port + Firewall
235 self.flaskPORT={'443/tcp':('0.0.0.0',443),'80/tcp':('0.0.0.0',80),'5000/tcp':('0.0.0.0',5000)}
236
237 if FirewallT :
238 logging.warning("Add rules for %s" % (str(self.flaskPORT)))
239 nft.cmd("add rule ip filter TILEDVIZ tcp dport 443 accept")
240 nft.cmd("add rule ip filter TILEDVIZ tcp dport 80 accept")
241 #nft.cmd("add rule ip filter TILEDVIZ tcp dport 5000 accept")
242 nft.cmd("add rule ip filter TILEDVIZ tcp dport " + str(ConnectionPort) + " accept")
243
244 for i in range(NbSecureConnection):
245 self.flaskPORT[str(ConnectionPort+i)+'/tcp']=('0.0.0.0',ConnectionPort+i)
246
247 if FirewallT :
248 # Firewall open ports
249 logging.warning("Add rule for port %d" % (ConnectionPort+i))
250 nft.cmd("add rule ip filter TILEDVIZ tcp dport " + str(ConnectionPort+i) + " accept")
251 # healthcheckN=docker.types.Healthcheck(interval=50000000) #test=['NONE'])
252
253 if (debug_Flask):
254 ENVFlask=["debug_Flask=true"]
255 else:
256 ENVFlask=["debug_Flask=false"]
257
258 # ENVFlask=ENVFlask+["DATABASE_URL=postgresql://"+POSTGRES_USER+"@"+POSTGRES_HOST+":"+POSTGRES_PORT]
259 # logging.error("Before create Flask docker. env variables :" +str(ENVFlask))
260
261 # Detect or create flask container :
262 try:
263 self.containerFlask=client.containers.create(
264 name="flaskdock", image="flaskimage",
265 mounts=[TVvolume,TVWconf,TVssl,TVnginx], extra_hosts=self.postgresHost,
266 command=self.commandFlask,
267 ports=self.flaskPORT,
268 environment=ENVFlask,
269 log_config=TVlogs,
270 detach=True) #auto_remove=True,
271 #healthcheck=self.healthcheck,
272 #healthcheck=healthcheckN,
273
274 except docker.errors.ContainerError:
275 logging.error("The container exits with a non-zero exit code and detach is False.", exc_info=True)
276 sys.exit(listerrors["createError"])
277 except docker.errors.ImageNotFound:
278 logging.error("The specified image does not exist.", exc_info=True)
279 sys.exit(listerrors["ImageError"])
280 except docker.errors.APIError:
281 logging.error("The server returns an error.", exc_info=True)
282 sys.exit(listerrors["APIError"])
283
284 self.user="flaskusr"
285 self.home="/home/flaskusr"
286 self.idFlask = self.containerFlask.id
287
288 self.daterun=datetime.datetime.now()
289 logging.warning("Ready to start flaskdock.")
290 try :
291 self.containerFlask.start()
292 except docker.errors.APIError :
293 logging.error("Error start Flask", exc_info=True)
294 sys.exit(listerrors["start"])
295
296 logging.warning("After start Flask, containers list :"+str(client.containers.list()))
297
298 self.containerFlask.reload()
299 ipFlask = self.containerFlask.attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
300 logging.debug("We have built container for user "+self.user+" with postgresql user "+POSTGRES_USER+" and password '"+POSTGRES_PASSWORD+"' with IP "+ipFlask+".")
301 logging.warning("Flask container status :"+str(self.containerFlask.status))
302
303 # string from Flask for new connection
304
305 createnewconnection=r'WARNING:.*addconnection:\s*(?P<username>\w+)\s*;\s*(?P<hostname>[^ ;]+)\s*;\s*(?P<connection>\w+)\s*;\s*(?P<containers>\w+)\s*;\s*(?P<scheduler>\w+)\s*;\s*(?P<idTS>\d+)\s*;\s*(?P<idCon>\d+)\s*;\s*(?P<nbTiles>\d+)\s*;\s*(?P<Debug>\d)'
306 create_newconnection = re.compile(r''+createnewconnection)
307
308 # string from Flask for edit old connection
309 editoldconnection=r'WARNING:.*editconnection:\s*(?P<username>\w+)\s*;\s*(?P<hostname>[^ ;]+)\s*;\s*(?P<connection>\w+)\s*;\s*(?P<containers>\w+)\s*;\s*(?P<scheduler>\w+)\s*;\s*(?P<idTS>\d+)\s*;\s*(?P<idCon>\d+)'
310 edit_oldconnection = re.compile(r''+editoldconnection)
311
312 # string from Flask for kill old connection
313 quitoldconnection=r'WARNING:.*removeconnection:\s*(?P<username>\w+)\s*;\s*(?P<idTS>\d+)\s*;\s*(?P<idCon>\d+)'
314 quit_oldconnection = re.compile(r''+quitoldconnection)
315
316 # string from Flask for kill old connection
317 killoldconnection=r'WARNING:.*killconnection:\s*(?P<username>\w+)\s*;\s*(?P<idTS>\d+)\s*;\s*(?P<idCon>\d+)'
318 kill_oldconnection = re.compile(r''+killoldconnection)
319
320 # string from Flask for action
321 actionoldconnection=r'WARNING:.*action:\s*(?P<username>\w+)\s*;\s*(?P<idTS>\d+)\s*;\s*(?P<idCon>\d+)\s*;\s*(?P<selection>[0-9,]*)'
322 action_oldconnection = re.compile(r''+actionoldconnection)
323
324 oldLogs=""
325 Logs=""
326
327 logging.warning("Start log detection loop.")
328 while True:
329 # Detect all command to request new thread here
330
331 NewLog=NewStringFinder(oldLogs, Logs)
332 if (DEBUG_ANALYSE):
333 if (len(NewLog) > 0):
334 logging.error("Get new log "+NewLog)
335
336 # newconnection
337 create_newconnect=False
338 # editconnection
339 edit_oldconnect=False
340 # delconnection
341 quit_oldconnect=False
342 # killconnection
343 kill_oldconnect=False
344 # actions
345 action_oldconnect=False
346 if (len(NewLog) > 0):
347 # newconnection
348 create_newconnect=create_newconnection.search(NewLog)
349 if (not create_newconnect): create_newconnect=False
350 # editconnection
351 edit_oldconnect=edit_oldconnection.search(NewLog)
352 if (not edit_oldconnect): edit_oldconnect=False
353 # delconnection
354 quit_oldconnect=quit_oldconnection.search(NewLog)
355 if (not quit_oldconnect): quit_oldconnect=False
356 # kill tunnel connection
357 kill_oldconnect=kill_oldconnection.search(NewLog)
358 if (not kill_oldconnect): kill_oldconnect=False
359 # saveconnection
360 # restartconnection
361 # actions
362 action_oldconnect=action_oldconnection.search(NewLog)
363 if (not action_oldconnect): action_oldconnect=False
364
365 #if (DEBUG_ANALYSE):
366 # logging.error("Before create_newconnect :"+str(create_newconnect))
367
368 find_connect=True
369 if (create_newconnect):
370 if (DEBUG_ANALYSE):
371 logging.error("Match create connection ")
372 match_connect=create_newconnect
373 elif (edit_oldconnect):
374 if (DEBUG_ANALYSE):
375 logging.error("Match edit connection ")
376 match_connect=edit_oldconnect
377 elif (quit_oldconnect):
378 if (DEBUG_ANALYSE):
379 logging.error("Match quit connection ")
380 match_connect=quit_oldconnect
381 elif (kill_oldconnect):
382 if (DEBUG_ANALYSE):
383 logging.error("Match edit connection ")
384 match_connect=kill_oldconnect
385 elif (action_oldconnect):
386 if (DEBUG_ANALYSE):
387 logging.error("Match action connection :"+NewLog)
388 match_connect=action_oldconnect
389 else:
390 find_connect=False
391
392 test_notconnect=False
393 if (find_connect):
394 if (DEBUG_ANALYSE):
395 logging.error("After test create_newconnect :"+str(match_connect.groups()))
396 # test if there is no existing connection
397 boolTestNotAlreadyConnect=(not any([ (match_connect.group("username") == theConnection["username"] and
398 match_connect.group("idCon") == theConnection["connectionid"])
399 for theConnection in Connections]))
400 test_notconnect=(len(Connections) == 0 or boolTestNotAlreadyConnect)
401
402 if (DEBUG_ANALYSE):
403 logging.error("after test_notconnect "+str(test_notconnect))
404
405 if create_newconnect:
406 logging.warning("Create new connect :"+str(create_newconnect.groups()))
407 if test_notconnect:
408 # logging.warning("Get connection parameters :"
409 # +" "+create_oldconnect.group("username")+" "+create_oldconnect.group("hostname")
410 # +" "+create_oldconnect.group("connection")+" "+create_oldconnect.group("containers")
411 # +" "+create_oldconnect.group("scheduler")+" "+create_oldconnect.group("idTS")
412 # +" "+create_oldconnect.group("idCon") +" "+create_newconnect.group("nbTiles")
413 # +" "+create_newconnect.group("Debug"))
414 logging.debug("Connection container type :"+create_newconnect.group("containers"))
415
416 logging.warning(f"Connection table count {countConnections} free {usedConnections}")
417
418 FindFree=True
419 notUsedConnections=[i for i,x in enumerate(usedConnections) if not x]
420 if (len(notUsedConnections) == 0):
421 FindFree=False
422 logging.error("ERROR : Full Connection pool. No new connection possible.", exc_info=True)
423 else:
424 # Find the first free Connection
425 notUsedCountConnections=[ countConnections[i] for i in notUsedConnections ]
426
427 numConnects=min(notUsedCountConnections)
428 indices = [i for i, x in enumerate(notUsedCountConnections) if x == numConnects]
429
430 firstFree=notUsedConnections[indices[0]]
431
432 logging.warning("Connection pool slot :"+str(firstFree))
433
434 if (FindFree):
435 countConnections[firstFree]+=1
436 usedConnections[firstFree]=True
437 logging.warning(f"Connection table for new connection count {countConnections} free {usedConnections}")
438
439 # then create the new connection
440 Connections[firstFree]=({"username":create_newconnect.group("username"),
441 "hostname":create_newconnect.group("hostname"),
442 "connection":create_newconnect.group("connection"),
443 "containers":create_newconnect.group("containers"),
444 "tilesetid":create_newconnect.group("idTS"),
445 "connectionid":create_newconnect.group("idCon"),
446 "ThisConnection":""})
447
448 nbTiles=int(create_newconnect.group("nbTiles"))
449 debug=bool(int(create_newconnect.group("Debug")))
450 if (debug):
451 logging.warning("Debug mode for connection.")
452 ThisConnection=ConnectionDocker(self.containerFlask, self.user, nbTiles, debug, firstFree,
453 POSTGRES_HOST, POSTGRES_IP, POSTGRES_PORT, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD)
454 Connections[firstFree]["ThisConnection"]=ThisConnection
455 else:
456 logging.warning("A connection already exists with parameters :"+create_newconnect.group("username")+" "+create_newconnect.group("hostname")+" "+str(create_newconnect.group("idCon")))
457 logging.error("connections : "+str( [ theConnection["username"]+" "+theConnection["hostname"]+" "+str(theConnection["connectionid"]) for theConnection in Connections] ))
458
459 elif (edit_oldconnect):
460 logging.warning("Edit old connect :"+str(edit_oldconnect.groups()))
461 if (not test_notconnect ):
462 logging.warning("Edit connection parameters :"
463 +" "+edit_oldconnect.group("username")+" "+edit_oldconnect.group("hostname")
464 +" "+edit_oldconnect.group("connection")+" "+edit_oldconnect.group("containers")
465 +" "+edit_oldconnect.group("scheduler")+" "+edit_oldconnect.group("idTS")
466 +" "+edit_oldconnect.group("idCon") )
467 logging.debug("Connection container type :"+edit_oldconnect.group("containers"))
468 for theConnection in Connections:
469 if( edit_oldconnect.group("username") == theConnection["username"] and
470 edit_oldconnect.group("hostname") == theConnection["hostname"] and
471 edit_oldconnect.group("idCon") == theConnection["connectionid"] ):
472 logging.warning("Update script for connection "+str(theConnection["connectionid"]))
473 theConnection["ThisConnection"].callfunction("updateScripts")
474 logging.warning("Reconnect "+str(theConnection["connectionid"]))
475 theConnection["ThisConnection"].callfunction("reconnect")
476 else:
477 logging.warning("No connection found with parameters :"+edit_oldconnect.group("username")+" "+edit_oldconnect.group("hostname")+" "+str(edit_oldconnect.group("idCon")))
478 #logging.error("connections : "+str( [ theConnection["username"]+" "+theConnection["hostname"]+" "+str(theConnection["connectionid"]) for theConnection in Connections] ))
479
480 elif (quit_oldconnect):
481 logging.warning("Quit old connect :"+str(quit_oldconnect.groups()))
482 if (not test_notconnect ):
483 logging.warning("Quit connection parameters :"+quit_oldconnect.group("username")
484 +" "+quit_oldconnect.group("idTS")+" "+quit_oldconnect.group("idCon"))
485 logging.debug("Connection container type :"+quit_oldconnect.group("idCon"))
486 for theConnection in Connections:
487 if( quit_oldconnect.group("username") == theConnection["username"] and
488 quit_oldconnect.group("idCon") == theConnection["connectionid"] ):
489 logging.warning("Before quit connection "+str(theConnection["connectionid"]))
490
491 ThisConnection=theConnection["ThisConnection"]
492 theConnection["ThisConnection"].callfunction("quitConnection")
493 try:
494 ConnectName=ThisConnection.threadName
495 ConnectNum=ThisConnection.ConnectNum
496
497 iquit=0
498 while (not ThisConnection.hasQuit ):
499 time.sleep(1)
500 iquit=iquit+1
501 if (iquit > 20):
502 logging.error(f"Connection {ConnectName} has never quitted : {ConnectNum}")
503 ThisConnection.quitConnection()
504
505 logging.warning(f"Connection table count {countConnections} free {usedConnections}")
506 outHandler.flush()
507 except Exception as err:
508 logging.error("Error while stoping Connection with id "+str(quit_oldconnect.group("idCon"))+" : "+str(err), exc_info=True)
509
510 else:
511 logging.error("No connection found with parameters :"+quit_oldconnect.group("username")+" "+str(quit_oldconnect.group("idCon")))
512 #logging.error("connections : "+str( [ theConnection["username"]+" "+theConnection["hostname"]+" "+str(theConnection["connectionid"]) for theConnection in Connections] ))
513
514 elif (kill_oldconnect):
515 logging.warning("Kill old connect :"+str(kill_oldconnect.groups()))
516 if (not test_notconnect ):
517 logging.warning("Kill connection parameters :"+kill_oldconnect.group("username")
518 +" "+kill_oldconnect.group("idTS")+" "+kill_oldconnect.group("idCon"))
519 logging.debug("Connection container type :"+kill_oldconnect.group("idCon"))
520 for theConnection in Connections:
521 if( kill_oldconnect.group("username") == theConnection["username"] and
522 kill_oldconnect.group("idCon") == theConnection["connectionid"] ):
523 logging.warning("Kill connection "+str(theConnection["connectionid"]))
524 theConnection["ThisConnection"].callfunction("killTunnel")
525 else:
526 logging.error("No connection found with parameters :"+kill_oldconnect.group("username")+" "+str(kill_oldconnect.group("idCon")))
527 #logging.error("connections : "+str( [ theConnection["username"]+" "+theConnection["hostname"]+" "+str(theConnection["connectionid"]) for theConnection in Connections] ))
528
529 elif (action_oldconnect):
530 logging.warning("action old connect :"+str(action_oldconnect.groups()))
531 if (not test_notconnect ):
532 logging.warning("Action connection parameters :"+action_oldconnect.group("username")
533 +" "+action_oldconnect.group("idTS")+" "+action_oldconnect.group("idCon")
534 +" "+action_oldconnect.group("selection"))
535 logging.debug("Connection container type :"+action_oldconnect.group("idCon"))
536 for theConnection in Connections:
537 if( action_oldconnect.group("username") == theConnection["username"] and
538 action_oldconnect.group("idCon") == theConnection["connectionid"] ):
539 #logging.warning("Action connection "+str(theConnection["connectionid"]))
540 logging.warning("Action connection "+str(theConnection["connectionid"])+" function "+str("action="+action_oldconnect.group("selection")))
541 theConnection["ThisConnection"].callfunction("action="+action_oldconnect.group("selection"))
542 else:
543 logging.error("No connection found with parameters :"+action_oldconnect.group("username")+" "+str(action_oldconnect.group("idCon")))
544 #logging.error("connections : "+str( [ theConnection["username"]+" "+theConnection["hostname"]+" "+str(theConnection["connectionid"]) for theConnection in Connections] ))
545
546 time.sleep(timeAliveServ)
547 # logging.debug("Log loop")
548
549 oldLogs=Logs
550 try:
551 Logs=str(self.containerFlask.logs(timestamps=True,since=int(self.oldtime),tail=nbLinesLogs))
552 except:
553 pass
554 self.oldtime=time.time()
555
556 def isalive(self):
557 # try:
558 # self.containerFlask.reload()
559 # except:
560 # return False
561 logging.debug("Flask container status :"+str(self.containerFlask.status))
562 return self.containerFlask.status == "running"
563
564 def getLog(self,nbLines):
565 # print(re.sub(r'\*n',r'\\n',str(self.Logs,'utf-8')))
566 self.Logs=self.containerFlask.logs(timestamps=True,since=int(self.oldtime),tail=nbLines).decode("utf-8")
567 self.oldtime=time.time()
568
569 logging.warning(str(self.Logs))
570
571 def getContainerFlask(self):
572 return self.containerFlask
573
574class ConnectionDocker(threading.Thread):
575
576 def __init__(self,containerFlask, userflask, nbTiles, debug, ConnectNum,
577 POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT,
578 POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD):
579 threading.Thread.__init__(self)
580 logging.warning("Thread Connection creation Num :"+str(ConnectNum))
581 self.threadName="TVConnect%s" % (ConnectNum)
582 self.ConnectNum=ConnectNum
583 self.thread = threading.Thread(target=self.run,name=self.threadName,
584 args=(containerFlask, userflask, nbTiles, debug, ConnectNum,
585 POSTGRES_HOST, POSTGRES_IP, POSTGRES_PORT,
586 POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD,))
587 self.thread.start()
588 # super(StoppableThread, self).__init__()
589 self._stop_event = threading.Event()
590 time.sleep(1)
591 logging.warning("Thread Connection creation : %s with name %s" % (str(self.thread),self.threadName))
592 threads[self.name]=self.thread
593
594 def run(self,containerFlask, userflask, nbTiles, debug, ConnectNum,
595 POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT,
596 POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD):
597
598 self.user="myuser"
599 self.home='/home/'+self.user
600 self.debug=debug
601
602 # With ACL, only userflask can use SSL keys
603 self.userflask=userflask
604
605 self.oldtime=time.time()
606
607 self.nbTiles=nbTiles
608 self.ConnectNum=ConnectNum
609 self.name="connectiondock"+str(Connections[self.ConnectNum]["connectionid"])
610 self.tilesetId=int(Connections[self.ConnectNum]["tilesetid"])
611 self.connectionId=int(Connections[self.ConnectNum]["connectionid"])
612 self.hasQuit=False
613
614 self.containerFlask = containerFlask
615 self.IPFlask=self.containerFlask.attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
616
617 self.websockifyPID=-1
618
619 self.call_list=[]
620 logging.warning("Name of thread : %s " % (threading.current_thread().name))
621 logging.warning("Connection creation :"+self.name)
622 self.dir='/tmp/'+self.name
623 if (not os.path.isdir(self.dir)): os.mkdir(self.dir)
624
625 # sshd
626 s=socket.socket();
627 s.bind(("", 0));
628 self.PORTssh=s.getsockname()[1]
629 s.close()
630 if FirewallT :
631 # Create the ConnectionDocker's Firewall CHAIN
632 logging.warning("Create the ConnectionDocker's Firewall CHAIN")
633 nft.cmd("destroy chain ip filter " + str(self.name))
634 nft.cmd("add chain ip filter " + str(self.name))
635 nft.cmd("add rule ip filter " + str(self.name) + " tcp dport " + str(self.PORTssh) + " accept")
636 nft.cmd("add rule ip filter TILEDVIZ jump " + str(self.name))
637
638 VncVolume=docker.types.Mount(source=self.dir,target=self.home+"/.vnc",type='bind',read_only=False)
639 #XLocale=docker.types.Mount(source="/usr/share/X11/locale",target="/usr/share/X11/locale",type='bind')
640
641 if (debug):
642 self.commandConnect=[str(self.connectionId),POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD,'-r',CONNECTION_RESOL,'-u',str(os.getuid()),'-g',str(os.getgid()),'-p',str(self.PORTssh),'-d']
643 self.cont_auto_remove=False
644 else:
645 self.commandConnect=[str(self.connectionId),POSTGRES_HOST,POSTGRES_PORT,POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD,'-r',CONNECTION_RESOL,'-u',str(os.getuid()),'-g',str(os.getgid()),'-p',str(self.PORTssh)]
646 self.cont_auto_remove=True
647
648 logging.debug("Input param commandConnect : '"+str(self.commandConnect)+"'")
649
650 self.postgresHost={POSTGRES_HOST:POSTGRES_IP}
651
652 if ( os.path.exists( "/dev/nvidia0" ) ):
653 list_gpu_dev=["/dev/nvidia0:/dev/nvidia0:rw","/dev/nvidiactl:/dev/nvidiactl:rw"]
654 else:
655 logging.debug("ConnectionDocker : no GPU device find in /dev")
656 list_gpu_dev=[]
657
658 outHandler.flush()
659
660 #healthcheckN=docker.types.Healthcheck(interval=50000000) #test=['NONE'])
661
662 # Ports for tiles
663 self.listPortsTiles={str(self.PORTssh)+'/tcp':('0.0.0.0',self.PORTssh)};
664 self.listPorts=[self.PORTssh]
665 listSock=[]
666
667 for t in range(self.nbTiles):
668 already=True
669
670 while (already):
671 s=socket.socket();
672 s.bind(("", 0));
673 port=s.getsockname()[1]
674
675 if (not port in self.listPorts):
676 already=False
677 self.listPorts.append(port)
678 listSock.insert(0,s)
679 self.listPortsTiles[str(port)+'/tcp']=('0.0.0.0',port);
680 if FirewallT :
681 NFTcmd="add rule ip filter " + str(self.name) + " tcp dport " + str(port) + " accept"
682 logging.warning(NFTcmd)
683 nft.cmd(NFTcmd)
684 else:
685 logging.error("Build %d find again port %d ports list %s" % (t,port,str(self.listPorts)))
686 s.close()
687 time.sleep(0.1)
688 #logging.warning("Build connection with "+str(self.nbTiles)+" ports : "+str(self.listPortsTiles))
689
690 # open port for Action in localhost only
691 self.actionPort=ActionPort+self.ConnectNum
692 self.listConnectPorts=self.listPortsTiles|{f"{ActionPort}/tcp":('0.0.0.0',self.actionPort)}
693 logging.warning(f"Build connection with {self.nbTiles} ports and action port {self.actionPort} : {self.listConnectPorts}")
694 if FirewallT :
695 NFTcmd=f"add rule ip filter {self.name} iif lo tcp dport {self.actionPort} accept"
696 logging.warning(NFTcmd)
697 nft.cmd(NFTcmd)
698
699 # Open port in firewall here ?
700
701 # Wake up docker server ?
702 #client = docker.from_env()
703 logging.warning("Before start "+self.name+", containers list :"+str(client.containers.list()))
704
705 # Detect or create flask container :
706 try:
707 for s in listSock:
708 s.close()
709
710 self.containerConnect=client.containers.create(
711 name=self.name, image="mageiaconnect",
712 mounts=[VncVolume,TVssl,TVconf],
713 extra_hosts=self.postgresHost,
714 command=self.commandConnect,
715 ports=self.listConnectPorts,
716 devices=list_gpu_dev,
717 auto_remove=self.cont_auto_remove, detach=True)
718 #,XLocale
719 # healthcheck=healthcheckN,
720
721 except docker.errors.ContainerError:
722 logging.error("The container exits with a non-zero exit code and detach is False.", exc_info=True)
723 sys.exit(listerrors["createError"])
724 except docker.errors.ImageNotFound:
725 logging.error("The specified image does not exist.", exc_info=True)
726 sys.exit(listerrors["ImageError"])
727 except docker.errors.APIError:
728 logging.error("The server returns an APIError.", exc_info=True)
729 sys.exit(listerrors["APIError"])
730 except Exception as err:
731 logging.error("Another error during container creation : ", exc_info=True)
732
733 self.daterun=datetime.datetime.now()
734 logging.warning("Ready to start "+self.name+".")
735 try:
736 Outstart=self.containerConnect.start()
737 logging.warning(f"Connection started {Outstart}.")
738 except docker.errors.APIError :
739 logging.error("The container can't start.", exc_info=True)
740 sys.exit(listerrors["start"])
741
742 logging.warning("After start "+self.name+", containers list :"+str(client.containers.list()))
743
744 searchpassword=r'Random Password Generated:\s*(?P<passwd>[-._+0-9a-zA-Z]+)'
745 search_passwd = re.compile(r''+searchpassword)
746 while True:
747 info_passwd=self.grepLog(100,search_passwd)
748 if info_passwd:
749 self.password=info_passwd.group("passwd")
750 break
751 time.sleep(0.5)
752 logging.warning("After password.")
753
754 self.containerConnect.reload()
755 ipconnect = self.containerConnect.attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
756 logging.debug("We have built container for user '"+self.user+"' with IP "+ipconnect+".")
757 logging.warning("User container status :"+str(self.containerConnect.status))
758 time.sleep(timeWait)
759
760 # Create temporary user on flask docker :
761 self.flaskusr="connect"+str(self.connectionId)
762 flaskhome="/home/"+self.flaskusr
763
764 uid=str(os.getuid()+1+self.connectionId)
765 gid=str(uid)
766 uid=str(uid)
767 commandAdduser="bash -c 'groupadd -r -g "+gid+" "+self.flaskusr+ \
768 " && useradd -r -u "+uid+" -g "+self.flaskusr+" "+self.flaskusr+" && cp -rp /etc/skel "+flaskhome+\
769 " && chown -R "+self.flaskusr+":"+self.flaskusr+" "+flaskhome+"'"
770 self.LogAddUser=container_exec_out(self.containerFlask, commandAdduser)
771 logging.debug("Add user "+self.flaskusr+" on Flask container."+re.sub(r'\*n',r'\\n',str(self.LogAddUser)))
772
773 # Get id_ed25519.pub for tunneling VNC flux
774 authorized_key="No such file or directory"
775 re_wrong_key=re.compile(r''+authorized_key)
776 match_authorized=re_wrong_key.search(authorized_key)
777 count_authorized=0
778 while(match_authorized):
779 time.sleep(timeWait)
780 commandAuthKey = "cat "+self.home+"/.ssh/id_ed25519.pub"
781 self.LogAuthKey = container_exec_out(self.containerConnect, commandAuthKey)
782 self.LogAuthorized_key = re.sub(r'\n',r'',self.LogAuthKey)
783 # print("Key : ",self.LogAuthorized_key)
784 authorized_key=re.sub(r'\*n',r'\\n',self.LogAuthorized_key)
785 match_authorized=re_wrong_key.search(authorized_key)
786 logging.debug("Authorized_key from connection container : \n'"+authorized_key+"'")
787 count_authorized=count_authorized+1
788 if (count_authorized > 20):
789 logging.error("Authorized_key error from connection container : \n'"+authorized_key+"'")
790 break
791
792 # Put this key in flask docker
793 commandBuildSsh="mkdir "+flaskhome+"/.ssh"
794 self.LogBuildSsh=container_exec_out(self.containerFlask, commandBuildSsh,user=self.flaskusr)
795 logging.debug("Create .ssh to Flask docker :\n'"+re.sub(r'\*n',r'\\n',str(self.LogBuildSsh))+"'")
796 commandBuildSsh="chmod 700 "+flaskhome+"/.ssh"
797 self.LogBuildSsh=container_exec_out(self.containerFlask, commandBuildSsh,user=self.flaskusr)
798 logging.debug("Protect .ssh to Flask docker :\n'"+re.sub(r'\*n',r'\\n',str(self.LogBuildSsh))+"'")
799
800 # Use awk to insert key in .ssh/authorized_key file !
801 commandBuildSsh="awk 'BEGIN {print \""+authorized_key+"\" >>\""+flaskhome+"/.ssh/authorized_keys\"}' /dev/null"
802 self.LogBuildSsh=container_exec_out(self.containerFlask, commandBuildSsh,user=self.flaskusr)
803 logging.debug("Add autorized_key to Flask docker :\n'"+re.sub(r'\*n',r'\\n',str(self.LogBuildSsh))+"'")
804
805 # List .ssh/authorized_key in Flask
806 # commandBuildSsh="ls -la "+flaskhome+"/.ssh/authorized_keys"
807 # self.LogBuildSsh=self.containerFlask.exec_run(cmd=commandBuildSsh,user=self.flaskusr)
808 # logging.debug("List .ssh/authorized_key in Flask docker :\n"+re.sub(r'\*n',r'\\n',str(self.LogBuildSsh.output,'utf-8')))
809
810 # Test connection type and launch a script (in the start xterm full-screen ?)
811 # in python in container to manage the expect or get ssh private for HPC connection
812
813 # Test free port in Flaskdock for ssh
814 commandTestFreePort="bash -c 'echo \"PORT=\"$(python -c \"import socket; s=socket.socket(); s.bind((\\\"\\\", 0)); print(s.getsockname()[1]); s.close()\" )'"
815 self.LogTestFreePort=container_exec_out(self.containerFlask, commandTestFreePort,user=self.flaskusr)
816 internPort=int(re.sub(r'PORT=([0-9]*)',r'\1',self.LogTestFreePort))
817 logging.warning("Free port for ssh/websockify for user "+self.flaskusr+" on Flask container. "+str(internPort))
818
819 # Port for websockify
820 externPort=ConnectionPort+self.ConnectNum
821
822 # Get connection and tileset informations :
823 self.ConnectionDB=session.query(models.Connections).filter(models.Connections.id == int(self.connectionId)).one()
824 self.TileSetDB=session.query(models.TileSets).filter_by(id=self.tilesetId).one()
825 session.refresh(self.TileSetDB)
826 self.updateScripts()
827
828 # TODO : Secure protect that connection ! == no other port possible 5902 or no interactive connection for connenct# users
829 # Tunnel to Flask : Give access from vncconnection page to this container
830 vnc_command="if [ X\\\"\\$( pgrep -fla x11vnc )\\\" == X\\\"\\\" ]; then /opt/vnccommand; fi &"
831 self.tunnel_script=os.path.join(self.home,".vnc","tunnel_flask")
832 self.tunnel_command="ssh -4 -T -N -nf -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -R 0.0.0.0:"+str(internPort)+":localhost:5902 "+self.flaskusr+"@"+self.IPFlask+" &"
833 scriptTunnel="awk 'BEGIN {print \""+vnc_command+" \\n "+self.tunnel_command+"\" >>\""+self.tunnel_script+"\"}' > /dev/null &"
834 logging.debug("awk command to build tunnel script : "+scriptTunnel)
835 logging.debug("User container status :"+str(self.containerConnect.status))
836
837 self.LogScrTunnel=self.containerConnect.exec_run(cmd=scriptTunnel,user=self.user,detach=True)
838 time.sleep(0.5)
839 self.LogModTunnel=self.containerConnect.exec_run(cmd="chmod u+x "+self.tunnel_script,user=self.user,detach=True)
840 logging.debug("User container status :"+str(self.containerConnect.status))
841
842 self.kill_tunnel_script=os.path.join(self.home,".vnc","kill_tunnel_flask")
843 out_kill_tunnel=os.path.join(self.home,".vnc","out_killtunnel")
844 killTunnel='Tunnel=$(pgrep -f \\"ssh.*@'+self.IPFlask+'\\" );\\nif [ X\\"$Tunnel\\" != X\\"\\" ]; '+\
845 'then \\n pgrep -fla \\"ssh.*@'+self.IPFlask+'\\" > '+out_kill_tunnel+';\\n'+\
846 ' kill -9 $Tunnel 2>&1 >> '+out_kill_tunnel+';\\n fi'
847
848 scriptTunnel="awk 'BEGIN {print \""+killTunnel+"\" >>\""+self.kill_tunnel_script+"\"}' > "+out_kill_tunnel
849
850 logging.debug("awk command to build kill tunnel script : "+scriptTunnel)
851
852 self.LogScrTunnel=self.containerConnect.exec_run(cmd=scriptTunnel,user=self.user,detach=True)
853 time.sleep(0.5)
854 self.LogModTunnel=self.containerConnect.exec_run(cmd="chmod u+x "+self.kill_tunnel_script,user=self.user,detach=True)
855 logging.warning("tunnel script built.")
856 logging.debug("User container status :"+str(self.containerConnect.status))
857
858 self.connect()
859 logging.debug("User container status :"+str(self.containerConnect.status))
860
861 # Add password for temporary connection
862 commandBuildVNC="awk 'BEGIN {print \""+self.password+"\" >>\""+flaskhome+"/vncpassword\"}' /dev/null"
863 self.LogBuildVNC=container_exec_out(self.containerFlask, commandBuildVNC,user=self.flaskusr)
864 logging.debug("Add VNC password to Flask docker :\n"+re.sub(r'\*n',r'\\n',str(self.LogBuildVNC)))
865
866 # Write connection PORT in DB for vncconnection.html
867 try:
868 self.ConnectionDB.connection_vnc=externPort-32768
869 session.commit()
870 except:
871 logging.error("Can't commit connection in DB !", exc_info=True)
872 logging.debug("Connection VNC port saved : "+str(self.ConnectionDB.connection_vnc)+" real : "+str(self.ConnectionDB.connection_vnc+32768))
873
874 self.dir_out="TVFiles/"+str(self.ConnectionDB.id_users)+"/"+str(self.ConnectionDB.id)
875
876 # Test already in use extern port from old websockify process
877 try:
878 commandTestOldWebsockify="bash -c 'pgrep -fa websockify |grep "+str(externPort)+"| grep -v pgrep'"
879 self.LogTestOldWebsockify=self.containerFlask.exec_run(cmd=commandTestOldWebsockify,user="root")
880 logging.warning("Test already in use extern port from old websockify process "+str(self.LogTestOldWebsockify.output,"utf-8"))
881 if( re.sub(r'.*('+str(externPort)+').*',r'\1',str(self.LogTestOldWebsockify)) == str(externPort) ):
882 PIDoldwebsockify=int(re.sub(r'^([0-9]*) .*',r'\1',str(self.LogTestOldWebsockify.output,"utf-8")))
883 commandKillOldWebsockify="bash -c 'kill -9 "+str(PIDoldwebsockify)+"'"
884 self.LogKillOldWebsockify=self.containerFlask.exec_run(cmd=commandKillOldWebsockify,user="root")
885 logging.warning("Kill old websockify process"+str(self.LogKillOldWebsockify))
886 except Exception as err:
887 logging.error("Error while testing or killing old websockify process "+str(externPort)+" : "+str(err), exc_info=True)
888
889 # SSL encrypt : use websockify and SSL public/secret keys.
890 self.SSLpublic=os.getenv('SSLpublic')
891 self.SSLprivate=os.getenv('SSLprivate')
892 # Call websockify server for this connection
893 commandLaunchWebsockify="bash -c 'cd /TiledViz/TVConnections/; source /flask_venv/bin/activate; "+\
894 "./wss_websockify "+self.SSLpublic+" "+self.SSLprivate+ \
895 " "+str(externPort)+" "+str(internPort)+" /TiledViz/TVWeb"+ \
896 " 2>&1 > /tmp/websockify_$(date +%F_%H-%M-%S).log &'"
897 logging.debug("commandLaunchWebsockify : "+commandLaunchWebsockify)
898 logging.warning("commandLaunchWebsockify.")
899 self.LogLaunchWebsockify=self.containerFlask.exec_run(cmd=commandLaunchWebsockify,user="root",detach=True)
900 #user="root"
901 #user=self.userflask
902
903 # Get websockify PID :
904 commandWebsockifyPID="bash -c 'echo $(pgrep -f \"^python3 .*"+str(internPort)+"\" |sort |head -1)'"
905 tries=0
906 while True:
907 time.sleep(1)
908 self.LogWebsockifyPID=container_exec_out(self.containerFlask, commandWebsockifyPID,user=self.flaskusr)
909 #user="root"
910 if ( str(self.LogWebsockifyPID) != '' ):
911 try:
912 self.websockifyPID=int(self.LogWebsockifyPID)
913 break
914 except:
915 logging.error("Wait for websockify PID " + str(self.LogWebsockifyPID))
916 tries=tries+1
917 else:
918 break
919 if (tries > 50):
920 self.websockifyPID=-1
921 break
922 logging.warning("PID for websockify for user "+self.flaskusr+" on Flask container. "+str(self.websockifyPID))
923
924 # Connect to TVConnection in connectionDocker to send actions commands.
925 self.actionPort=ActionPort+self.ConnectNum
926 search_docker0_ip="ip -4 addr show docker0 | grep -Po 'inet \\K[\\d.]+'"
927 p=subprocess.Popen(search_docker0_ip, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
928 output, errs = p.communicate()
929 ipdocker0=output.decode('utf-8').replace('\n','')
930 logging.warning("ConnectionDocker : connection for actions with port %d and ip for docker0 %s" % (self.actionPort,ipdocker0))
931
932 listPortsTilesFile=os.path.join(self.dir_out,"listPortsTiles.pickle")
933 logging.warning("Launch websockify and save "+listPortsTilesFile+" on Connection "+self.name)
934 try:
935 # SSL encrypt :
936 # Add ACL for myuser on SSL keys
937 # commandSetACL="bash -c 'find "+os.path.dirname(self.SSLpublic)+" -ls -execdir setfacl -m u:"+self.user+":rX {} \\+ ;"+\
938 # " find "+os.path.dirname(self.SSLprivate)+" -ls -execdir setfacl -m u:"+self.user+":rX {} \\+'"
939 # logging.warning(f"commandSetACL {commandSetACL}")
940 # self.LogSetACL=self.containerConnect.exec_run(cmd=commandSetACL,user="root",detach=True)
941
942 # use websockify and SSL public/secret keys.
943 self.listPortsTilesIE={}
944 self.listPortsTilesIE["TiledVizHost"]=flaskaddr
945 listInterPort=[]
946 inode=0
947 for key in self.listPortsTiles:
948 externPort=self.listPortsTiles[key][1]
949 if ( externPort == self.PORTssh ):
950 self.listPortsTilesIE["TiledVizConnectionPort"]=self.PORTssh
951 pass
952 else:
953 NotOKport=True
954 while(NotOKport):
955 # get internal port for ssh tunneling in connectiondock
956 s=socket.socket();
957 s.bind(("127.0.0.1", 0));
958 internPort=s.getsockname()[1];
959 s.close()
960 if (internPort in listInterPort):
961 time.sleep(0.1)
962 else:
963 # Call websockify client for this tile
964 commandLaunchWebsockify="bash -c 'cd /TiledViz/TVConnections/; source /TiledViz/TiledVizEnv_*/bin/activate; "+\
965 "./wss_websockify "+self.SSLpublic+" "+self.SSLprivate+ \
966 " "+str(externPort)+" "+str(internPort)+" /TiledViz/TVWeb"+ \
967 " 2>&1 > /tmp/websockify_"+str(inode)+"_"+str(externPort)+"_"+str(internPort)+"_$(date +%F_%H-%M-%S).log &'"
968 logging.debug("commandLaunchWebsockify : "+commandLaunchWebsockify)
969 logging.warning("commandLaunchWebsockify : "+commandLaunchWebsockify)
970 #logging.warning("commandLaunchWebsockify. "+key)
971 self.LogLaunchWebsockify=self.containerConnect.exec_run(cmd=commandLaunchWebsockify,user="root",detach=True)
972 #user="root",
973 NotOKport=False
974
975
976 # save external and internal ports
977 self.listPortsTilesIE[str(inode)]=(internPort,externPort)
978 inode=inode+1
979
980 with open(listPortsTilesFile,'wb') as portsf:
981 pickle.dump(self.listPortsTilesIE,portsf)
982 except Exception as err:
983 logging.error("Error with launch websockify and save "+listPortsTilesFile+" on Connection "+self.name+" : "+str(err), exc_info=True)
984 logging.error(str(self.listPortsTiles))
985
986 try:
987 filetar = BytesIO()
988 intar = tarfile.TarFile(fileobj=filetar, mode='w')
989 with open(listPortsTilesFile,'rb') as tf:
990 tfd=tf.read()
991 filename=os.path.basename(listPortsTilesFile)
992 tarinfo = tarfile.TarInfo(name=filename)
993 tarinfo.size = len(tfd)
994 tarinfo.mtime = time.time()
995 tarinfo.uid = os.getuid()
996 tarinfo.gid = os.getgid()
997 intar.addfile(tarinfo, BytesIO(tfd))
998 tf.close()
999 intar.close()
1000 filetar.seek(0)
1001
1002 # Use put_archive to cp config files
1003 self.LogPut=self.containerConnect.put_archive(path=self.home, data=filetar)
1004 logging.warning("Put "+listPortsTilesFile+" to connection docker :\n"+str(self.LogPut))
1005 filetar.close()
1006 except Exception as err:
1007 logging.error("Error while putting "+listPortsTilesFile+" on Connection "+self.name+" : "+str(err), exc_info=True)
1008 logging.error(str(self.listPortsTilesIE))
1009
1010 time.sleep(timeAliveConn)
1011 self.get_nodesjson()
1012
1013 search_action = re.compile(r''+"action=")
1014 self.action_OK=False
1015 logging.warning("Wait for commands.")
1016 while (not self._stop_event.is_set()):
1017 # Wrapper to private members :
1018 while( len(self.call_list) > 0 ):
1019 logging.warning("Container "+self.name+" call list : "+str(self.call_list))
1020 outHandler.flush()
1021 callfunc=self.call_list[0]
1022 if callfunc == "updateScripts":
1023 self.updateScripts()
1024 elif callfunc == "quitConnection":
1025 self.quitConnection()
1026 elif callfunc == "killTunnel":
1027 self.killTunnel()
1028 elif callfunc == "reconnect":
1029 logging.warning(f"Call reconnect in {self.ConnectionDB.id_users}/{self.ConnectionDB.id}.")
1030 HasNodes=os.path.exists(os.path.join(self.dir_out,"nodes.json"))
1031 logging.warning(f"Test nodes.json : {HasNodes}")
1032 if (not HasNodes):
1033 self.get_nodesjson()
1034 logging.warning("Before connect.")
1035 self.connect()
1036 elif (search_action.search(callfunc)):
1037 logging.warning("Action detected "+str(callfunc))
1038 if (not self.action_OK):
1040 self.action(callfunc)
1041 else:
1042 logging.error("Error with calling function "+callfunc+ " for Connection "+self.name+" .")
1043 self.call_list.pop(0)
1044
1045 if not self.isalive():
1046 self.quitConnection()
1047 # else:
1048 # logging.debug("Connection "+str(self.connectionId)+" alive.")
1049
1050 time.sleep(timeAliveConn)
1051 #logging.debug("Container loop.")
1052 time.sleep(timeAliveConn)
1053
1054
1055 def get_nodesjson(self):
1056 path_nodesjson=os.path.join(self.home,"nodes.json")
1057 path_nodesTVFile=os.path.join(self.dir_out,"nodes.json")
1058 logging.warning("New get nodes.json instance for "+str(self.connectionId)+" connection.")
1059
1060 self.nodes_ok=False
1061
1062 iter=0
1063 notlaunched=True
1064 while (notlaunched and not self._stop_event.is_set()):
1065 #logging.debug("Container "+self.name+" wake up.")
1066 if (not os.path.exists(path_nodesTVFile)):
1067 logging.warning("TRY GET "+path_nodesjson+ " file from Connection %s." % (self.name))
1068 else:
1069 logging.warning("ALREADY GET "+path_nodesTVFile+ " file from Connection Docker.")
1070 os.system("ls -la "+path_nodesTVFile)
1071 return
1072
1073 if (not self.nodes_ok):
1074 if(iter > Mwait/Swait):
1075 logging.warning("Is job started : "+str(self.nodes_ok))
1076 logging.error("Wait too much "+path_nodesjson+ " file for Connection %s to tiledset %s %d %d %d " % (self.name,str(self.TileSetDB.name),iter,Swait,Mwait))
1077 # TODO : option for automaticaly suppress connection ??
1078 # try:
1079 # self.quitConnection()
1080 # except ValueError as err:
1081 # logging.warning(path_nodesjson+" ValueError for iter %d." % (iter))
1082 return
1083
1084 try:
1085 # Get back nodes.json from connection docker ?
1086 bits, stat = self.containerConnect.get_archive(path=path_nodesjson)
1087 logging.warning("GET "+path_nodesjson+ " file from Connection Docker.")
1088
1089 if (self.debug):
1090 logging.error("Infos "+str(stat))
1091 else:
1092 logging.warning("Infos "+str(stat))
1093
1094 outHandler.flush()
1095 try:
1096 if stat["size"]==0 :
1097 logging.error(" nodes.json size == 0")
1098 # code.interact(banner="Test stat0 :",local=dict(globals(), **locals()))
1099 raise ValueError
1100 # raise NodeSizeError
1101 # Write nodes.json file in TVFile dir.
1102 filetar = BytesIO()
1103 for chunk in bits:
1104 filetar.write(chunk)
1105 # logging.error("chunk "+str(len(chunk)))
1106 filetar.seek(0)
1107
1108 # tf=tempfile.NamedTemporaryFile(mode="w+b",dir="/tmp",prefix="",suffix=".tar",delete=False)
1109 # tf.write(filetar.read())
1110 # tf.close()
1111
1112 mytar=tarfile.TarFile(fileobj=filetar, mode='r')
1113 mytar.extractall(self.dir_out)
1114 mytar.close()
1115 # os.system("ls -la "+self.dir_out)
1116 filetar.close()
1117
1118 self.nodes_ok=True
1119 outHandler.flush()
1120 notlaunched=False
1121
1122 self.killTunnel()
1123 logging.warning("Job started.")
1124 except ValueError as err:
1125 logging.warning(path_nodesjson+" ValueError for iter %d." % (iter))
1126 time.sleep(Swait)
1127 pass
1128
1129 except Exception as err:
1130 logging.error("Error with GET "+path_nodesjson+". tar error.", exc_info=True)
1131 time.sleep(Swait)
1132 pass
1133
1134 outHandler.flush()
1135 except (docker.errors.NotFound, requests.exceptions.HTTPError) as err:
1136 logging.warning(path_nodesjson+" NotFound for iter %d." % (iter))
1137 outHandler.flush()
1138 time.sleep(Swait)
1139 pass
1140
1141 except:
1142 logging.error("Job not correctly started", exc_info=True)
1143 outHandler.flush()
1144 time.sleep(Swait)
1145 pass
1146 iter=iter+1
1147
1148 def callfunction (self,myfunc):
1149 logging.debug("From Flask thread calling function "+myfunc+ " for Connection "+self.name+" .")
1150 self.call_list+=[myfunc]
1151 #logging.error("From Flask thread Container "+self.name+" call list : "+str(self.call_list))
1152
1153 def updateScripts(self):
1154 logging.warning("updateScripts : Config files for tileset "+str(self.TileSetDB.config_files)+" and connection "+str(self.ConnectionDB.config_files))
1155
1156 logging.debug("User container status :"+str(self.containerConnect.status))
1157 # Create a memory archive file for config files
1158 filetar = BytesIO()
1159 intar = tarfile.TarFile(fileobj=filetar, mode='w')
1160 ConnConfigFiles=self.ConnectionDB.config_files
1161 for filename in ConnConfigFiles:
1162 tmpfile=ConnConfigFiles[filename].replace("/TiledViz",".")
1163 tf=open(tmpfile,'rb')
1164 tfd=tf.read()
1165 tarinfo = tarfile.TarInfo(name=filename)
1166 tarinfo.size = len(tfd)
1167 tarinfo.mtime = time.time()
1168 tarinfo.uid = os.getuid()
1169 tarinfo.gid = os.getgid()
1170 intar.addfile(tarinfo, BytesIO(tfd))
1171 tf.close()
1172
1173 logging.debug("User container status :"+str(self.containerConnect.status))
1174 TSConfigFiles=self.TileSetDB.config_files
1175 for filename in TSConfigFiles:
1176 tmpfile=TSConfigFiles[filename].replace("/TiledViz",".")
1177 tf=open(tmpfile,'rb')
1178 tfd=tf.read()
1179 tarinfo = tarfile.TarInfo(name=filename)
1180 tarinfo.size = len(tfd)
1181 tarinfo.mtime = time.time()
1182 tarinfo.uid = os.getuid()
1183 tarinfo.gid = os.getgid()
1184 intar.addfile(tarinfo, BytesIO(tfd))
1185 tf.close()
1186
1187 logging.warning("Config files for tileset "+str(self.TileSetDB.name)+" and connection "+str(self.ConnectionDB.id))
1188 logging.warning(str(intar.getnames()))
1189 logging.debug("User container status :"+str(self.containerConnect.status))
1190 intar.close()
1191 filetar.seek(0)
1192
1193 # Use put_archive to cp config files
1194 self.LogPut=self.containerConnect.put_archive(path=self.home, data=filetar)
1195 logging.warning("Put config file to connection docker :\n"+str(self.LogPut))
1196 filetar.close()
1197 logging.debug("User container status :"+str(self.containerConnect.status))
1198
1199 def killTunnel(self):
1200 # stop tunnel ssh for VNC
1201 self.LogTunnel=self.containerConnect.exec_run(cmd="sh -c "+self.kill_tunnel_script,user=self.user,detach=True)
1202
1203 logging.warning("Kill tunnel end to Flask docker")
1204 # logging.warning("Kill tunnel to Flask docker :\n"+re.sub(r'\*n',r'\\n',str(self.LogTunnel)))
1205
1206 def startActionConnection(self):
1207
1208 # Server in TVSecure wait for connection from TVConnection in connectionDocker to send actions commands.
1209 logging.warning(f"Try start ActionConnection on port {self.actionPort}")
1210 try:
1212 logging.warning(f"Action client connection on {self.actionPort}.")
1213 outHandler.flush()
1214
1215 self.action_OK=True
1216 except Exception as err:
1217 print(exc_info=True)
1218 logging.error(f"Error with Action server {self.ConnectionDB.id} : {err}")
1219
1220 def quitConnection(self):
1221 # Send Action "remove TiledSet"
1222 try:
1223 if (not self.action_OK):
1225 self.action("action=1,,")
1226 logging.warning("Remove TiledSet on HPC server.")
1227 except:
1228 pass
1229
1230 # Quit Websockify for user
1231 commandKillWebsockify="bash -c 'kill "+str(self.websockifyPID)+"'"
1232 self.LogKillWebsockify=container_exec_out(self.containerFlask,commandKillWebsockify,user="root")
1233
1234 logging.warning("Kill websokify PID "+str(self.websockifyPID)+" on Flask container. "+re.sub(r'\*n',r'\\n',str(self.LogKillWebsockify)))
1235
1236 # Erase login on flask
1237 commandRmuser="bash -c 'userdel -r -f "+self.flaskusr+"'"
1238 self.LogRmUser=container_exec_out(self.containerFlask, commandRmuser)
1239 logging.warning("Rm user "+self.flaskusr+" on Flask container."+re.sub(r'\*n',r'\\n',str(self.LogRmUser)))
1240
1241 # End action connection:
1242 try:
1243 if ("ActionConnect" in dir(self)):
1244 self.ActionConnect.close()
1245
1246 except Exception as err:
1247 logging.error("Error while stoping Action connection "+str(self.ConnectionDB.id)+" : "+str(err), exc_info=True)
1248
1249 # suppress connection docker
1250 try:
1251 #if ( self.containerConnect.status == "running" ):
1252 self.containerConnect.stop()
1253 if ( not self.cont_auto_remove ):
1254 self.containerConnect.remove(v=True,force=True)
1255 except Exception as err:
1256 #logging.error("Error while stoping Connection docker "+str(self.ConnectionDB.id)+" : "+str(err), exc_info=True)
1257 pass
1258
1259 if FirewallT :
1260 # Close Firewall ports
1261 # remove jump connectiondock rule in TILEDVIZ chain
1262 logging.warning(f"remove jump {self.name} rule in TILEDVIZ chain")
1263 nft.set_handle_output("True")
1264 rc, output, error = nft.cmd("list table ip filter")
1265 jumprule="jump " + self.name + " # handle "
1266 logging.warning(f"remove {jumprule}.")
1267 matches = re.findall(jumprule+"[0-9]+",output)
1268 try:
1269 handle_num = re.sub(jumprule,"", matches[0])
1270 nft.cmd("delete rule ip filter TILEDVIZ handle " + handle_num)
1271 except:
1272 logging.warning(f"Rule {jumprule} not found.")
1273 nft.cmd(f"destroy chain ip filter {self.name}")
1274
1275 logging.warning(f"Connection {self.name} clean usedConnections list for {self.ConnectNum}." )
1276 Connections[self.ConnectNum]=sqltConnections
1277 usedConnections[self.ConnectNum]=False
1278
1279 logging.warning("End of quitConnection for "+self.name+", containers list :"+str(client.containers.list()))
1280 self.hasQuit=True
1281
1282 logging.warning(f"Connection {self.name} suppression thread {self.ConnectNum}." )
1283 self._stop_event.set()
1284 threads[self.name].join(timeout=1)
1285
1286 return
1287
1288 def connect(self):
1289 logging.debug("Tunnel command in "+self.tunnel_script+" : "+self.tunnel_command)
1290 logging.debug("User container status :"+str(self.containerConnect.status))
1291 self.LogTunnel=self.containerConnect.exec_run(cmd="sh -c "+self.tunnel_script,user=self.user,detach=True)
1292 time.sleep(1)
1293 # outHandler.flush()
1294 # testTunnel="sh -c \'pgrep -fla \"ssh.*"+self.flaskusr+"\"\'"
1295 # self.LogTestTunnel=container_exec_out(self.containerConnect, testTunnel,user=self.user)
1296 # logging.debug("Tunnel to Flask docker :\n"+re.sub(r'\*n',r'\\n',str(self.LogTestTunnel)))
1297 logging.warning(f"Container connected : {self.LogTunnel}")
1298 # logging.warning("Tunnel command in "+self.tunnel_script+" : "+self.tunnel_command)
1299 logging.debug("User container status :"+str(self.containerConnect.status))
1300 outHandler.flush()
1301
1302 def action(self,callfunct):
1303 # Get action num + selection of tiles (if needed by the function)
1304 actionlist=re.sub(r'action=',r'',callfunct)
1305 self.ActionConnect.send_server(actionlist)
1306 logging.warning("Action for tileset %s. command %s" % (self.tilesetId,actionlist))
1307
1308 if (re.sub(r',.*',r'',actionlist)=="0"):
1309 path_nodesjson=os.path.join(self.home,"nodes.json")
1310
1311 count_exist_new_nodes=0
1312 not_loaded=True
1313 while(not_loaded and not self._stop_event.is_set()):
1314 time.sleep(2)
1315 try:
1316 # Get back nodes.json from connection docker ?
1317 bits, stat = self.containerConnect.get_archive(path=path_nodesjson)
1318 logging.warning("GET renew "+path_nodesjson+ " file from Connection Docker.")
1319 logging.warning("Infos "+str(stat))
1320
1321 # Write new nodes.json file in TVFile dir.
1322 filetar = BytesIO()
1323 for chunk in bits:
1324 filetar.write(chunk)
1325 filetar.seek(0)
1326
1327 # tf=tempfile.NamedTemporaryFile(mode="w+b",dir="/tmp",prefix="",suffix=".tar",delete=False)
1328 # tf.write(filetar.read())
1329 # filetar.seek(0)
1330 # tf.close()
1331 # logging.error("Temp tar file: %s" % (tf.name))
1332
1333 mytar=tarfile.TarFile(fileobj=filetar, mode='r')
1334 mytar.extractall(self.dir_out)
1335 mytar.close()
1336 filetar.close()
1337 logging.warning("New nodes.json downloaded from %s to %s." % (path_nodesjson,self.dir_out))
1338 outHandler.flush()
1339 #os.system('diff '+self.dir_out+'/nodes.json '+self.dir_out+'/tmp/nodes.json')
1340 not_loaded=False
1341 except Exception as err:
1342 count_exist_new_nodes=count_exist_new_nodes+1
1343 NbIter=10
1344 if ( count_exist_new_nodes > NbIter):
1345 logging.error("Fail to renew "+path_nodesjson+ " from Connection Docker.", exc_info=True)
1346 return
1347
1348
1349 def isalive(self):
1350 # try:
1351 # self.containerFlask.reload()
1352 # except:
1353 # return False
1354 logging.debug("User container "+self.name+" status :"+str(self.containerFlask.status))
1355 return self.containerFlask.status == "running"
1356
1357 def grepLog(self,nbLines,re_searchstr):
1358 self.Logs=str(self.containerConnect.logs(since=int(self.oldtime),tail=nbLines))
1359 self.oldtime=time.time()
1360
1361 #logging.debug("\ngrepLog :\n"+self.Logs+"\n")
1362 m = re_searchstr.search(self.Logs)
1363 # m = re.search(searchstr,self.Logs,flags=0)
1364 #print(m,"\n\n")
1365 if m:
1366 for g in m.groups():
1367 logging.debug(g)
1368 return m
1369
1370if __name__ == '__main__':
1371 logFormatter = logging.Formatter("TVSecure %(asctime)s - %(threadName)s - %(levelname)s: %(message)s ")
1372 rootLogger = logging.getLogger()
1373 rootLogger.setLevel(logging.DEBUG)
1374 fileHandler = logging.FileHandler("TVSecure.log")
1375 fileHandler.setLevel(logging.WARNING) #DEBUG
1376 fileHandler.setFormatter(logFormatter)
1377 rootLogger.addHandler(fileHandler)
1378 outHandler = logging.StreamHandler(sys.stdout)
1379 outLevel=logging.WARNING
1380 #=logging.DEBUG
1381 outHandler.setLevel(outLevel)
1382 outHandler.setFormatter(logFormatter)
1383 rootLogger.addHandler(outHandler)
1384 #rootLogger.handlers[0].flush()
1385
1386 if FirewallT :
1387 # Firewall policy
1388 logging.warning("Firewall policy")
1389 nft.cmd("destroy chain ip filter TILEDVIZ")
1390 nft.cmd("add chain ip filter TILEDVIZ { type filter hook input priority 0 ; policy drop ; }")
1391 nft.cmd("add rule ip filter TILEDVIZ ct state related,established accept")
1392 nft.cmd("add rule ip filter TILEDVIZ tcp dport 22 accept")
1393 nft.cmd("add rule ip filter TILEDVIZ tcp dport "+SSHport+" accept")
1394
1395 args = parse_args(sys.argv)
1396 #print("call args :",str(args))
1397
1398 args.__dict__['host']=args.POSTGRES_HOST
1399 args.__dict__['login']=args.POSTGRES_USER
1400 args.__dict__['port']=args.POSTGRES_PORT
1401 args.__dict__['databasename']=args.POSTGRES_DB
1402 metadata, conn, engine, pool, session = tvdb.SQLconnector(args)
1403
1404 # Hack to see thread names in htop
1405 try:
1406 import prctl
1407 def set_thread_name(name):
1408 logging.debug("For thread "+threading.current_thread().name+ " give name %s " % (name))
1409 prctl.set_name(name)
1410
1411 def _thread_name_hack(self):
1412 set_thread_name(self.name)
1413 logging.debug("For thread "+threading.current_thread().name+ " hack name %s " % (self.name))
1414 try:
1415 self._bootstrap_inner()
1416 except:
1417 if self._daemonic and _sys is None:
1418 return
1419 raise
1420 #threading.Thread.__bootstrap_original(self)
1421 logging.debug("For thread "+threading.current_thread().name+ " end of hack name %s " % (self.name))
1422
1423 # threading.Thread._bootstrap_original = threading.Thread._bootstrap
1424 threading.Thread._bootstrap = _thread_name_hack
1425
1426 except ImportError:
1427 logging.debug('No python-prctl module. No thread names')
1428 def set_thread_name(name): pass
1429
1430 logging.debug("Before FlaskDock.")
1431
1432 FlaskDock= FlaskDocker(POSTGRES_HOST=args.POSTGRES_HOST,
1433 POSTGRES_IP=args.POSTGRES_IP,
1434 POSTGRES_PORT=args.POSTGRES_PORT,
1435 POSTGRES_DB=args.POSTGRES_DB,
1436 POSTGRES_USER=args.POSTGRES_USER,
1437 POSTGRES_PASSWORD=args.POSTGRES_PASSWORD,
1438 SMTP_PASSWORD=args.SMTP_PASSWORD,
1439 secretKey=args.secretKey)
1440 time.sleep(4)
1441 #FlaskDock.getLog(35)
1442
1443 # try:
1444 # from IPython import embed
1445 # embed()
1446 # except:
1447 # code.interact(banner="Hand to Flask :",local=dict(globals(), **locals()))
1448
1449 # TODO : destroy other Connection rules as in nft_clean_rules.py
1450 if FirewallT :
1451 def signal_handler(sig, frame):
1452 logging.error("destroy chain ip filter TILEDVIZ")
1453 nft.cmd("destroy chain ip filter TILEDVIZ")
1454 rc, output, error = nft.cmd("list table ip filter")
1455 matches = re.findall(r'connectiondock[0-9]+',output)
1456 print(matches)
1457 for i in matches :
1458 nft.cmd("destroy chain ip filter " + i)
1459 sys.exit(0)
1460
1461 signal.signal(signal.SIGINT, signal_handler)
1462
1463 while (FlaskDock.isalive()):
1464 time.sleep(30)
1465 # time.sleep(5)
1466 # logging.debug("Is Alive.")
1467
1468 logging.critical('Finish')
grepLog(self, nbLines, re_searchstr)
Definition TVSecure.py:1357
run(self, containerFlask, userflask, nbTiles, debug, ConnectNum, POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT, POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD)
Definition TVSecure.py:596
action(self, callfunct)
Definition TVSecure.py:1302
run(self, POSTGRES_HOST=POSTGRES_HOST, POSTGRES_IP=POSTGRES_IP, POSTGRES_PORT=POSTGRES_PORT, POSTGRES_DB=POSTGRES_DB, POSTGRES_USER=POSTGRES_USER, POSTGRES_PASSWORD=POSTGRES_PASSWORD, SMTP_PASSWORD=SMTP_PASSWORD, secretKey=secretKey)
Definition TVSecure.py:204