{"id":8612,"date":"2022-02-22T23:40:46","date_gmt":"2022-02-23T04:40:46","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=8612"},"modified":"2022-02-22T23:47:32","modified_gmt":"2022-02-23T04:47:32","slug":"8612","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=8612","title":{"rendered":"Postgresql Through an SSH Tunnel in Python"},"content":{"rendered":"\n<p>Our production Postgresql servers have a fairly restrictive IP access control list &#8212; which means you cannot VPN in and query the server. We&#8217;ve been using <a href=\"https:\/\/www.rushworth.us\/lisa\/?p=8575\" data-type=\"post\" data-id=\"8575\">DBeaver with an SSH tunnel<\/a> to connect, but it&#8217;s a bit time consuming to run a query across <em>all<\/em> of the servers for monitoring and troubleshooting. To work around the restriction, I built a python script that uses an SSH tunnel to relay communications to the Postgresql servers. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport psycopg2\nfrom sshtunnel import SSHTunnelForwarder\n\nfrom config import strSSHRelayHost, iSSHRelayPort, strSSHRelayUser, strSSHAuthKeyFile, dictHost\n# In the config.py, dictHost should contain the following information\n# dictHost = {&quot;host&quot;:&quot;dbserver.example.com&quot;,&quot;port&quot;:5432,&quot;database&quot;: &quot;dbname&quot;, &quot;username&quot;:&quot;dbuser&quot;, &quot;password&quot;:&quot;S3cr3tPhr@5e&quot;}\n\n# Example query -- listing out locks \nsqlQuery = &quot;WITH RECURSIVE l AS (  SELECT pid, locktype, mode, granted, ROW(locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid) obj FROM pg_locks ), pairs AS ( SELECT w.pid waiter, l.pid locker, l.obj, l.mode FROM l w JOIN l ON l.obj IS NOT DISTINCT FROM w.obj AND l.locktype=w.locktype AND NOT l.pid=w.pid AND l.granted  WHERE NOT w.granted ), tree AS ( SELECT l.locker pid, l.locker root, NULL::record obj, NULL AS mode, 0 lvl, locker::text path, array_agg(l.locker) OVER () all_pids FROM ( SELECT DISTINCT locker FROM pairs l WHERE NOT EXISTS (SELECT 1 FROM pairs WHERE waiter=l.locker) ) l  UNION ALL  SELECT w.waiter pid, tree.root, w.obj, w.mode, tree.lvl+1, tree.path||&#039;.&#039;||w.waiter, all_pids || array_agg(w.waiter) OVER () FROM tree JOIN pairs w ON tree.pid=w.locker AND NOT w.waiter = ANY ( all_pids )) SELECT (clock_timestamp() - a.xact_start)::interval(3) AS ts_age, replace(a.state, &#039;idle in transaction&#039;, &#039;idletx&#039;) state, (clock_timestamp() - state_change)::interval(3) AS change_age, a.datname,tree.pid,a.usename,a.client_addr,lvl, (SELECT count(*) FROM tree p WHERE p.path ~ (&#039;^&#039;||tree.path) AND NOT p.path=tree.path) blocked, repeat(&#039; .&#039;, lvl)||&#039; &#039;||left(regexp_replace(query, &#039;s+&#039;, &#039; &#039;, &#039;g&#039;),100) query FROM tree JOIN pg_stat_activity a USING (pid) ORDER BY path&quot;\n\nwith SSHTunnelForwarder( (strSSHRelayHost, iSSHRelayPort), ssh_username=strSSHRelayUser, ssh_private_key=strSSHAuthKeyFile, local_bind_address=(&quot;localhost&quot;,55432), remote_bind_address=(dictHost.get(&#039;host&#039;), dictHost.get(&#039;port&#039;))) as server:\n# Alternately, you can use password authentication\n#with SSHTunnelForwarder( (strSSHRelayHost, iSSHRelayPort), ssh_username=strSSHRelayUser, ssh_password=strSSHRelayUserPass, local_bind_address=(&quot;localhost&quot;,55432), remote_bind_address=(dictHost.get(&#039;host&#039;), dictHost.get(&#039;port&#039;))) as server:\n    if server is not None:\n        server.start()\n        server._check_is_started()\n        #print(&quot;Tunnel server connected&quot;)\n        params = {&#039;database&#039;: dictHost.get(&#039;database&#039;),&#039;user&#039;: dictHost.get(&#039;username&#039;),&#039;password&#039;: dictHost.get(&#039;password&#039;), &#039;host&#039;: server.local_bind_host, &#039;port&#039;: server.local_bind_port}\n        conn = psycopg2.connect(**params)\n        cursor = conn.cursor()\n        cursor.execute(sqlQuery)\n        column_names = &#x5B;desc&#x5B;0] for desc in cursor.description]\n        print(column_names)\n        rows = cursor.fetchall()\n        for row in rows:\n            print(row)\n        cursor.close()\n        if conn is not None:\n            conn.close()\n        server.stop()\n    else:\n        print(&quot;Unable to establish SSH tunnel&quot;)\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>Our production Postgresql servers have a fairly restrictive IP access control list &#8212; which means you cannot VPN in and query the server. We&#8217;ve been using DBeaver with an SSH tunnel to connect, but it&#8217;s a bit time consuming to run a query across all of the servers for monitoring and troubleshooting. To work around &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[33,1522],"tags":[1349,664],"class_list":["post-8612","post","type-post","status-publish","format-standard","hentry","category-coding","category-postgresql","tag-postgresql","tag-python"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/8612","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=8612"}],"version-history":[{"count":3,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/8612\/revisions"}],"predecessor-version":[{"id":8616,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/8612\/revisions\/8616"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=8612"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=8612"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=8612"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}