Files
comun/docs/conexiuni-tunel-ssh-odbc.md
2026-09-17 13:33:56 +03:00

9.1 KiB

Conexiuni la bazele de productie: tunel SSH (Bitvise) + ODBC/TNS

Cum se ajunge, de pe statia de lucru, la un Oracle care nu e expus direct in internet. Valabil pentru toate proiectele ROA. Scopul documentului: sa nu se mai piarda o sesiune intreaga redescoperind lantul.

Fara credentiale in git. Acest fisier NU contine parole, si nici un fisier din repo nu trebuie sa contina. Parolele stau in profilul Bitvise (criptate cu DPAPI, legate de contul Windows) si in capul celui care lucreaza. Daca ai nevoie de o parola pe care nu o ai, cere-o - nu o cauta prin fisiere si nu o ghici: Oracle poate avea FAILED_LOGIN_ATTEMPTS in profil, iar cateva incercari gresite blocheaza contul de schema in PRODUCTIE.

Lantul, pe scurt

statie -> [tunel SSH Bitvise] -> 127.0.0.1:1521 -> Oracle de pe serverul de productie
                                       ^
                    aliasul TNS din instantclient\tnsnames.ora arata AICI
                                       ^
                    DSN-ul ODBC cu acelasi nume e ce primeste goConn.Connect()

Trei nume trebuie sa se potriveasca, si e usor de ratat: profilul Bitvise (ce port local deschide), aliasul TNS (ce host:port foloseste) si DSN-ul ODBC (ce alias TNS foloseste).

1. Tunelul SSH

Profilele Bitvise (.tlp) stau in D:\GoogleDrive\ (ex. vending.tlp). Un profil contine gazda, portul, utilizatorul, parola criptata, cheile de gazda deja acceptate si regulile de port forwarding - de obicei 127.0.0.1:1521 -> localhost:1521.

Ridicare din linie de comanda, fara interfata grafica:

$exe = 'C:\Program Files (x86)\Bitvise SSH Client\stnlc.exe'
Start-Process $exe -ArgumentList '-profile=D:\GoogleDrive\vending.tlp' -WindowStyle Hidden `
    -RedirectStandardOutput "$env:TEMP\stnlc_out.txt" -RedirectStandardError "$env:TEMP\stnlc_err.txt"

stnlc foloseste singur parola stocata in profil - nu o trece pe linia de comanda, ar ajunge in istoricul shell-ului. Ruleaza sub acelasi cont Windows sub care a fost salvat profilul,altfel DPAPI nu poate decripta parola.

Verificare ca tunelul chiar e sus (nu te baza pe faptul ca procesul traieste):

(Test-NetConnection 127.0.0.1 -Port 1521 -WarningAction SilentlyContinue).TcpTestSucceeded

In stnlc_out.txt linia care conteaza e Added client-to-server forwarding rule on 127.0.0.1:1521 to localhost:1521. Inchidere: opreste procesul stnlc.

Capcana: un singur port local 1521 pentru mai multe destinatii. Daca ai deja un tunel ridicat catre alt client, al doilea nu mai poate lega portul si vei interoga alta baza fara sa-ti dai seama. Verifica intotdeauna cu ce te-ai conectat (vezi pasul 4).

Capcana inrudita: interfata grafica BvSsh.exe poate tine portul 1521 legat pe 0.0.0.0 cu o sesiune moarta - sqlplus raspunde atunci ORA-12541: TNS:no listener, desi "tunelul pare sus". stnlc isi poate lega totusi propria regula pe 127.0.0.1:1521 peste ea si merge. Nu te lua dupa netstat: proba reala e interogarea din pasul 4.

2. Aliasul TNS

D:\ROA\instantclient_19_18\tnsnames.ora (si instantclient_11_2_0_2 pentru driverul ODBC pe 32 de biti). Aliasurile care merg prin tunel arata spre 127.0.0.1:

VENDING =
  (DESCRIPTION =
    (ADDRESS_LIST = (ADDRESS = (PROTOCOL = tcp)(HOST = 127.0.0.1)(PORT = 1521)))
    (CONNECT_DATA = (SERVICE_NAME = XEPDB1))
  )

Pentru sqlplus trebuie TNS_ADMIN setat pe folderul care contine tnsnames.ora:

$env:TNS_ADMIN = 'D:\ROA\instantclient_19_18'

3. DSN-ul ODBC

Aplicatia nu foloseste TNS direct: oConn.Connect(tcHost, tcUser, tcPassword) (oproceduri_comune.prg) construieste dsn=<tcHost>;Uid=...;Pwd=... - deci tcHost e un nume de DSN ODBC, nu un host si nu un alias TNS. DSN-urile sunt pe 32 de biti (VFP e pe 32 de biti), deci in HKLM\SOFTWARE\WOW6432Node\ODBC\ODBC.INI, cu driverul din instantclient_11_2_0_2.

Listare rapida:

Get-ChildItem 'HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI' |
    Select-Object -ExpandProperty PSChildName

De obicei DSN-ul are acelasi nume cu aliasul TNS (VENDING, CENTRAL, ROA_CENTRAL...). Administrare vizuala: C:\Windows\SysWOW64\odbcad32.exe (nu cel din System32, acela e pe 64 de biti si nu vede DSN-urile VFP).

4. Interogare si verificare

$env:TNS_ADMIN = 'D:\ROA\instantclient_19_18'
& 'D:\ROA\instantclient_19_18\sqlplus.exe' -S -L '<schema>/<parola>@VENDING' '@script.sql'

-L = o singura incercare de login (nu reincearca la parola gresita). Foloseste-l intotdeauna pe productie.

Prima interogare, mereu, ca sa stii unde ai nimerit:

select user || ' @ ' || sys_context('USERENV','DB_NAME') from dual;

Pentru extragere in fisier, spool scrie local, nu pe server:

set pagesize 0 feedback off heading off linesize 100 trimspool on termout off
spool D:\cale\locala\iesire.txt
select ...;
spool off

Capcana PowerShell: intr-un here-string cu ghilimele duble (@"..."@) PowerShell expandeaza $. Un regex Oracle care se termina cu $ (ancora de sfarsit) ajunge stricat in fisierul .sql si interogarea intoarce 0 randuri fara nicio eroare. Foloseste here-string cu apostrof (@'...'@) si inlocuieste caile printr-un token.

Capcana termout off: ascunde si mesajele de eroare. Daca fisierul spool iese gol, ruleaza din nou fara termout off inainte sa banuiesti datele.

Acces SSH direct cu cheie publica (metoda rapida, recomandata)

Pentru clientii la care cheia publica e instalata pe server, nu mai e nevoie de profil Bitvise / parola: se deschide tunelul direct cu ssh din ~\.ssh (cheile id_ed25519 / id_rsa).

# ridica tunelul: portul local 1521 -> Oracle (localhost:1521) de pe server
Start-Process ssh -ArgumentList @(
  '-N','-o','ExitOnForwardFailure=yes','-o','StrictHostKeyChecking=accept-new',
  '-o','BatchMode=yes',
  '-L','1521:127.0.0.1:1521',
  '-p','<PORT_SSH>','<user>@<host>') -WindowStyle Hidden

# verificare tunel
(Test-NetConnection 127.0.0.1 -Port 1521 -WarningAction SilentlyContinue).TcpTestSucceeded

BatchMode=yes = nu asteapta parola (foloseste cheia); daca pica, inseamna ca cheia publica nu e instalata pe serverul respectiv. Portul SSH nu e neaparat 22: multi clienti ROA folosesc 22122. Inchidere: opri procesul ssh (sau Get-Process ssh | Stop-Process).

Nu te baza pe shell-ul remote ca sa "vezi" ce e pe server: contul romfast de pe clienti are shell restrans (netstat/ss/hostname/head nu exista). Proba reala e interogarea sqlplus dupa ridicarea tunelului, nu comenzile de pe server.

Schemele cunoscute (fara parole)

Tunel / profil Alias TNS + DSN Schema Oracle
vending.tlp VENDING VENDING (parola standard ROA)
conpress ROA_CONPRESS xenoti
cheie publica, SSH 82.76.217.177:22122, user romfast ROA_ROMCONSTRUCT ROMCONSTRUCT (parola standard ROA), SID ROA, Oracle 10g

Profilele Bitvise sunt in D:\roa\BITVISE\ pe statia curenta (vending.tlp, conpress.tlp, clever.tlp, automotive.tlp, avis.tlp, eduard.tlp, ems.tlp, romfast.tlp, sigma.tlp, vadeco.tlp) - nu in D:\GoogleDrive\. Clientul Oracle prezent e instantclient_11_2_0_2 (TNS_ADMIN se seteaza pe el). VENDING este Oracle 18c XE, serviciu XEPDB1.

Masuratoare read-only rulata pe VENDING la 03.09.2026: ROACONT\docs\cercetare\piata_ai_2026_09\ (BACKTEST_VENDING.md plus scripturile din sql\bt_*.sql).

Restul aliasurilor din tnsnames.ora (ROA_CENTRAL, CENTRAL105, ROA_TEST, ...) sunt retea interna si nu au nevoie de tunel.

Reguli pe productie

  1. Doar SELECT. Fara INSERT/UPDATE/DELETE/DDL, fara ALTER SESSION care schimba comportament persistent. Daca ai nevoie de date de lucru, extrage-le si lucreaza local.
  2. Fara ghicit parole - vezi avertismentul de la inceput.
  3. Interogari marginite: fetch first N rows only, si evita scanarile complete pe tabele mari in orele de lucru.
  4. Inchide tunelul cand ai terminat; nu-l lasa deschis peste noapte.
  5. Datele extrase: codurile fiscale sunt publice si pot sta in repo (vezi utile\Teste\cache_anaf\codes_*_reale.txt). Denumirile de parteneri, adresele, soldurile nu - nu le comite.

De unde vin loturile de coduri fiscale pentru teste

COMUN\utile\Teste\cache_anaf\codes_1000_reale.txt a fost extras din productia vending, tabela nom_parteneri (23932 randuri, 6382 coduri romanesti distincte):

select cif from (
  select distinct regexp_replace(upper(trim(cod_fiscal)), '[^0-9]', '') as cif
    from nom_parteneri
   where cod_fiscal is not null
     and regexp_like(upper(trim(cod_fiscal)), '^(RO)?[[:space:]]*[0-9]{2,10}$')
) where length(cif) between 2 and 10
order by ora_hash(cif)
fetch first 1000 rows only;

order by ora_hash(cif) da o dispersie deterministica (aceeasi de fiecare data), spre deosebire de dbms_random; ordonarea crescuta dupa cod ar fi inclinat lotul spre firme vechi, deci spre Bucuresti. Suprapunere cu lotul mai vechi de 200: 8 coduri.

Folosire in proba end-to-end ANAF:

$env:E2E_CODURI = 'D:\ROA\ROADEF\COMUN\utile\Teste\cache_anaf\codes_1000_reale.txt'
$env:E2E_LIMITA = '1000'