RE:NODE

PostgreSQL sslmode: prefer, require და verify-full

ავტორი RE:NODE-ის გუნდი
15 წუთის საკითხავი


sslmode ორ ცალკე საკითხს წყვეტს: დაშიფრულია თუ არა PostgreSQL-თან კავშირი საერთოდ და ამოწმებს თუ არა კლიენტი, რომ მეორე ბოლოში სწორედ ის სერვერია, რომელსაც უკავშირდებოდა. ექვსი მნიშვნელობიდან ორივე კითხვაზე "დიახ" მხოლოდ ორი პასუხობს - verify-ca და verify-full - და მხოლოდ verify-full ამოწმებს ჰოსტის სახელსაც. libpq-ის ნაგულისხმევი prefer პირველ კითხვაზე პასუხობს "შესაძლოა", მეორეზე კი "არა", ისიც უხმოდ. თუ კავშირი რაიმე ისეთ ქსელში გადის, რომელიც შენი არ არის, დააყენე sslmode=verify-full, მიეცი კლიენტს სწორი root სერტიფიკატი sslrootcert-ში და pg_hba.conf-ში hostssl ხაზებით აიძულე სერვერი, უარყოს ყველაფერი, რაც TLS არ არის.

პოსტის დანარჩენი ნაწილი ეხება იმას, თუ რას აკეთებს თითოეული რეჟიმი ქსელში, სად უნდა მოხვდეს სერტიფიკატის ფაილები, როგორ ჩავრთოთ TLS სერვერზე, რას უშვება ერთი და იგივე პარამეტრით თითოეული დრაივერი (ისინი ერთმანეთს არ ემთხვევა) და როგორ წავიკითხოთ შეცდომები. გარედან დაკავშირების საფუძვლები - connection string-ები, pg_hba.conf-ის თანმიმდევრობა, psql-ის ბრძანებები - აღწერილია პოსტში PostgreSQL-თან დისტანციური დაკავშირება; აქ მხოლოდ TLS-ის ნაწილს განვიხილავთ უფრო ღრმად.

ექვსი რეჟიმი და რას ამოწმებს თითოეული#

PostgreSQL TLS კავშირს უჩვეულოდ იწყებს. კლიენტი ხსნის ჩვეულებრივ TCP კავშირს 5432 პორტზე და აგზავნის პატარა SSLRequest პაკეტს იმის სათხოვნელად, ისაუბრებს თუ არა სერვერი TLS-ით. სერვერი პასუხობს ერთი ბაიტით: S - დიახ, N - არა. sslmode-ის მთელი საქმე ამ ბაიტის გარშემო ხდება - რას აკეთებს კლიენტი, როცა N მიიღებს, და რამდენს ამოწმებს მას შემდეგ, რაც handshake დაიწყება.

რეჟიმიდაშიფვრარა ხდება, თუ სერვერი უარს ამბობს
disableარასდროსჩვეულებრივი კავშირი, მოთხოვნა არ იგზავნება
allowმხოლოდ თუ სერვერი აიძულებსჯერ ჩვეულებრივი, TLS მხოლოდ უარის შემდეგ
preferთუ სერვერი სთავაზობსუხმოდ უბრუნდება ჩვეულებრივ კავშირს
requireყოველთვისკავშირი ვერ ხერხდება
verify-caყოველთვისკავშირი ვერ ხერხდება
verify-fullყოველთვისკავშირი ვერ ხერხდება

და მეორე კითხვა, რომელიც თავდამსხმელის წინააღმდეგ მნიშვნელოვანია:

რეჟიმისერტიფიკატების ჯაჭვი შემოწმებულიაჰოსტის სახელი შემოწმებულია
disable, allow, preferარაარა
requireარა (იხილე შენიშვნა ქვემოთ)არა
verify-caდიახ, sslrootcert-ის მიხედვითარა
verify-fullდიახ, sslrootcert-ის მიხედვითდიახ

უფრო დეტალურად:

  • `disable` TLS მოთხოვნას არასდროს აგზავნის. გონივრულია მხოლოდ Unix socket-ზე ან იმავე მანქანაზე loopback კავშირისთვის, სადაც დასაცავი ქსელი არ არსებობს.
  • `allow` ჯერ ჩვეულებრივ კავშირს ცდილობს და TLS-ით მხოლოდ მაშინ ცდის თავიდან, თუ სერვერმა ჩვეულებრივი უარყო - ჩვეულებრივ იმიტომ, რომ pg_hba.conf-ში შენთვის მხოლოდ hostssl ხაზებია. ის თავსებადობისთვის არსებობს და თითქმის არავის უნდა აერჩია.
  • `prefer` TLS-ს ითხოვს, იყენებს, თუ სერვერი თანხმდება, და უხმოდ აგრძელებს ჩვეულებრივი ტექსტით, თუ სერვერი უარს ამბობს ან handshake ვერ სრულდება. სერტიფიკატზე არაფერს ამოწმებს.
  • `require` დაშიფვრას მოითხოვს და ვერ მიღებისას ვარდება, მაგრამ იღებს ნებისმიერ სერტიფიკატს, რომელსაც სერვერი წარადგენს, შუაში მდგომი ვინმეს მიერ შექმნილ self-signed სერტიფიკატსაც.
  • `verify-ca` დაშიფვრას მოითხოვს და ამოწმებს, რომ სერვერის სერტიფიკატი ეყრდნობა CA-ს იმ root ფაილიდან, რომელსაც ენდობი.
  • `verify-full` ყოველივე ამას აკეთებს და ამოწმებს, რომ სახელი, რომელსაც დაუკავშირდი (host პარამეტრი), ემთხვევა სერტიფიკატის Subject Alternative Name-ს, ხოლო SAN-ის არარსებობისას - Common Name-ს.

რატომ არის ნაგულისხმევი prefer სუსტი#

prefer აშიფრავს, როცა შეუძლია, ამიტომ უსაფრთხოდ გვეჩვენება. პრობლემა ისაა, რომ დაშიფვრის გადაწყვეტილება მიიღება ღია ტექსტში, სერტიფიკატის მონაწილეობამდე. ნებისმიერს, ვისაც შენსა და მონაცემთა ბაზას შორის ტრაფიკის შეცვლა შეუძლია - მტრული Wi-Fi ქსელი, გატეხილი როუტერი, არასწორად გამართული proxy - შეუძლია SSLRequest-ს სერვერის მაგივრად N უპასუხოს. კლიენტი მხრებს აიჩეჩავს და ავტორიზაციის ნაწილსაც და ყველა მოთხოვნასაც ღია ტექსტში გაგზავნის. კლიენტის მხარეს არაფერი ილოგება და არც შეცდომა ჩნდება.

მაშინაც კი, როცა prefer TLS სესიას იღებს, ის არასდროს ამოწმებს, ვისი სერტიფიკატი მიიღო, ამიტომ თავდამსხმელს ისევე ადვილად შეუძლია TLS თვითონ დაასრულოს და რეალურ სერვერთან მეორე კავშირი გახსნას. დაშიფვრა იმ მხარის წინააღმდეგ, რომელიც არ დაგიდგენია, მხოლოდ ტრაფიკის პასიური მოსმენისგან გიცავს და სხვა არაფრისგან.

ეს პაროლისთვისაც არ არის მხოლოდ თეორიული საფრთხე. scram-sha-256 ავტორიზაციისას თვით პაროლი არ გადადის ქსელში, რაც ეხმარება, მაგრამ შემდგომი სესია - ყველა სტრიქონი, რასაც კითხულობ და წერ - გადადის. ძველი md5 მეთოდის ან password-ის შემთხვევაში კი დაქვეითებული კავშირი გადასცემს მონაცემებს, რომლებიც შეიძლება გამეორდეს ან გატეხილ იქნას.

რატომ რჩება ნაგულისხმევად მაინც prefer? იმიტომ, რომ ის კავშირს ყუთიდან ისე ამუშავებს, როგორც TLS-იან, ისე TLS-ის გარეშე სერვერებთან, ხოლო მისი შეცვლა უამრავ არსებულ კონფიგურაციას გააფუჭებდა. ეს თავსებადობის არჩევანია და არა უსაფრთხოების რეკომენდაცია, რასაც PostgreSQL-ის დოკუმენტაციაც ამბობს. გამოსავალი მარტივია: ნუ დაეყრდნობი მას. მიუთითე რეჟიმი ყველა connection string-ში ან PGSSLMODE-ში, რომ ქცევა ჩაწერილი იყოს და არა მემკვიდრეობით მიღებული.

უხეში წესი:

სიტუაციაგამოსაყენებელი რეჟიმი
Unix socket ან იმავე მანქანის loopbackdisable ან დატოვე ნაგულისხმევი
კერძო ქსელი, რომელსაც თავიდან ბოლომდე აკონტროლებმინიმუმ require
ყველაფერი ინტერნეტითverify-full
სერვერის სერტიფიკატი საჯარო CA-სგან, PG 16+ კლიენტიverify-full და sslrootcert=system

sslmode-ის დაყენება: URI, keyword-ები და PGSSLMODE#

არის სამი ადგილი, საიდანაც libpq-ზე დაფუძნებული კლიენტი რეჟიმს იღებს, და აშკარად მითითებული მნიშვნელობა ყოველთვის ჯობია სარეზერვოს.

bash
# URI form - parameters go after the ?$ psql "postgresql://appuser@db.example.com:5432/shop?sslmode=verify-full&sslrootcert=/etc/ssl/pg/root.crt"# Keyword form - easier to read in scripts$ psql "host=db.example.com dbname=shop user=appuser sslmode=verify-full sslrootcert=/etc/ssl/pg/root.crt"# Environment variables - used when the string leaves them out$ export PGSSLMODE=verify-full$ export PGSSLROOTCERT=/etc/ssl/pg/root.crt$ psql -h db.example.com -U appuser -d shop

TLS-თან დაკავშირებული გარემოს ცვლადებია PGSSLMODE, PGSSLROOTCERT, PGSSLCERT, PGSSLKEY და PGSSLCRL. მათ კითხულობენ psql, pg_dump, pg_restore, psycopg და ყველაფერი, რაც libpq-ზეა აგებული. ისინი მოსახერხებელი გზაა მთელი მანქანა ან container მკაცრი გახადო ყველა სკრიპტის რედაქტირების გარეშე, და დამაბნეველიც, როცა shell პროფილში დარჩენილი PGSSLMODE=disable იგივე სტრიქონს სხვაგან განსხვავებულად ამუშავებს. როცა ორ მანქანაზე ქცევა განსხვავდება, პირველი გასაშვები ბრძანებაა env | grep ^PG. არსებობს ასევე უფრო ძველი PGREQUIRESSL ცვლადი, რომელიც PGSSLMODE-ის სასარგებლოდ მოძველებულია; თუ ორივეა დაყენებული, PGSSLMODE იმარჯვებს.

URI-ში პარამეტრების მნიშვნელობები, ყველაფერივით, percent-decode-დება. Windows-ის გზა, როგორიცაა C:\certs\root.crt, უცვლელად გადის, მაგრამ გზას, რომელიც შეიცავს &, # ან ?-ს, escape სჭირდება, ხოლო ინტერვალები %20 უნდა იყოს. keyword ფორმა ამ ყველაფერს თავიდან აცილებს.

რომ დარწმუნდე, რა მიიღე სინამდვილეში, შეხედე banner-ს, როცა psql უკავშირდება. TLS სესია დაახლოებით ასეთ ხაზს ბეჭდავს:

code
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)

უფრო ახალი კლიენტები მეტ ველს ამატებენ, მაგალითად ALPN პროტოკოლს PostgreSQL 17-ზე. ასეთი ხაზის არარსებობა TLS-ის არარსებობას ნიშნავს. იგივეს ამბობს \conninfo psql-ის შიგნით, ხოლო ნებისმიერი კლიენტიდან სერვერს პირდაპირ ჰკითხავ:

sql
SELECT ssl, version, cipherFROM pg_stat_sslWHERE pid = pg_backend_pid();

მოაშორე WHERE პირობა და დააკავშირე pg_stat_activity-სთან (როლით, რომელსაც სხვა სესიების ნახვა შეუძლია), რომ ყველა კავშირი ერთდროულად შეამოწმო - სწრაფი გზა იმ ერთი სერვისის მოსაძებნად, რომელიც ჯერ კიდევ ღია ტექსტით უკავშირდება.

სერტიფიკატის ფაილები: sslrootcert, sslcert, sslkey#

კლიენტის მხარეს სამი ფაილია ჩართული და libpq-ს თითოეულისთვის ნაგულისხმევი მდებარეობა აქვს:

პარამეტრინაგულისხმევი Linux-სა და macOS-ზედანიშნულება
sslrootcert~/.postgresql/root.crtCA, რომელმაც სერვერის სერტიფიკატს მოაწერა ხელი
sslcert~/.postgresql/postgresql.crtკლიენტის სერტიფიკატი, თუ სერვერი ითხოვს
sslkey~/.postgresql/postgresql.keyკლიენტის სერტიფიკატის კერძო გასაღები

Windows-ზე იგივე სახელები %APPDATA%\postgresql\-ის ქვეშაა. გაუქმებული სერტიფიკატების სია შეიძლება მიეთითოს sslcrl-ით (ნაგულისხმევი root.crl), თუ საკუთარ CA-ს აწარმოებ და სერტიფიკატებს აუქმებ.

sslrootcert ის პარამეტრია, რომელიც ყველას სჭირდება verify-ca-სა და verify-full-ისთვის. რა ჩაიწერება მასში, იმაზეა დამოკიდებული, ვინ მოაწერა ხელი სერვერის სერტიფიკატს:

  • შენი საკუთარი CA ან ჰოსტინგის კერძო CA. CA სერტიფიკატის ფაილი PEM ფორმატში. თუ სერვერი ერთ self-signed სერტიფიკატს იყენებს CA-ს გარეშე, ეს სერტიფიკატი თავისივე root-ია და ამ ფაილში ჩაიწერება.
  • საჯარო CA, მაგალითად Let's Encrypt. PostgreSQL 16 და უფრო ახალ კლიენტებზე sslrootcert=system ოპერაციული სისტემის trust store-ს იყენებს. მისი დაყენება ნაგულისხმევ რეჟიმსაც verify-full-ად ცვლის და სუსტ რეჟიმებს შეცდომად აქცევს, რაც შეგნებულად არის გაკეთებული: ყველა საჯარო CA-ს ჰოსტის სახელის შემოწმების გარეშე ნდობა ნებისმიერს, ვისაც რაიმე საჯარო სერტიფიკატი აქვს, საშუალებას მისცემდა შენი მონაცემთა ბაზის როლი მოირგოს. ძველ კლიენტებზე sslrootcert მიუთითე სისტემის bundle ფაილზე, მაგალითად Debian-სა და Ubuntu-ზე /etc/ssl/certs/ca-certificates.crt.

sslcert და sslkey მხოლოდ მაშინ არის საჭირო, როცა სერვერი კლიენტის სერტიფიკატს ითხოვს - ან როგორც შესვლის მეთოდს (cert), ან პაროლის გვერდით დამატებით შემოწმებად. გასაღების ფაილი სხვას არ უნდა შეეძლოს წაკითხვა: libpq Unix-ზე უარყოფს გასაღებს, რომელსაც ჯგუფის ან ყველას უფლებები აქვს, ამიტომ chmod 0600 ~/.postgresql/postgresql.key დაყენების ნაწილია. PostgreSQL 16-მა დაამატა sslcertmode (disable, allow, require), რომელიც განსაზღვრავს, გაგზავნის თუ არა კლიენტი სერტიფიკატს საერთოდ.

PostgreSQL 17-მა დაამატა კიდევ ერთი კავშირის პარამეტრი, რომლის ცოდნაც ღირს: sslnegotiation=direct გამოტოვებს SSLRequest გაცვლას და TLS handshake-ს მაშინვე იწყებს, რითაც ერთ round trip-ს ზოგავს. ის PostgreSQL 17 სერვერს და require ან უფრო მკაცრ sslmode-ს მოითხოვს. ნაგულისხმევი postgres კლასიკური მოლაპარაკებაა და ყველგან მუშაობს.

სერვერის მხარე: ssl = on, სერტიფიკატები და hostssl#

კლიენტზე არაფერი აქვს მნიშვნელობა, თუ სერვერი TLS-ს არასდროს სთავაზობს. სერვერზე, რომელსაც შენ ადმინისტრირებ, მას postgresql.conf-ის სამი პარამეტრი რთავს:

postgresql.conf
ssl = onssl_cert_file = 'server.crt'ssl_key_file = 'server.key'ssl_min_protocol_version = 'TLSv1.2'

ფარდობითი გზები data დირექტორიის მიმართ განისაზღვრება. Debian-ისა და Ubuntu-ს პაკეტებს ssl = on მოჰყვება და ის ინსტალაციისას გენერირებულ snakeoil სერტიფიკატზე მიუთითებს, რაც require-ისთვის საკმარისია, მაგრამ შემოწმებისთვის უსარგებლოა, რადგან მას არავინ ენდობა და მისი სახელი მანქანის ჰოსტის სახელია. ssl_min_protocol_version PostgreSQL 13-სა და უფრო ახალზე ნაგულისხმევად TLSv1.2-ია, ამიტომ ზემოთ მოცემული ხაზი მხოლოდ ნაგულისხმევს აფიქსირებს.

ტესტისთვის self-signed სერტიფიკატი, PostgreSQL-ის დოკუმენტაციის ნიმუშის მიხედვით:

bash
$ openssl req -new -x509 -days 365 -nodes -text \    -out server.crt -keyout server.key -subj "/CN=db.example.com"$ chmod 0600 server.key$ chown postgres:postgres server.key server.crt

სერვერი თავის გასაღებზე ისეთივე მკაცრია, როგორც კლიენტი: TLS-ს არ ჩართავს, თუ server.key ჯგუფს ან სხვებს აქვთ წაკითხვის უფლება, თუ ფაილის მფლობელი root არ არის და რეჟიმი 0640. PostgreSQL 10-ში და უფრო ახალში SSL პარამეტრები reload-ზე ამოქმედდება (SELECT pg_reload_conf();), მაგრამ შეცდომა სხვადასხვანაირად იქცევა იმის მიხედვით, როგორ გამოიყენე: reload ლოგში წერს SSL configuration was not reloaded, ხოლო restart, როცა ssl = on და სერტიფიკატი წაუკითხავია, საერთოდ ვერ ეშვება. ნებისმიერი ცვლილების შემდეგ შეამოწმე სერვერის ლოგი.

ყველაფრისთვის, რასაც კლიენტები გადაამოწმებენ, გამოიყენე სერტიფიკატი, რომლის Subject Alternative Name-შიც ზუსტად ის ჰოსტის სახელია, რომლითაც კლიენტები უკავშირდებიან. db.example.com-ის სერტიფიკატი verify-full-ს ვერ გადის, როცა აპლიკაცია IP მისამართით უკავშირდება, და პირიქით.

TLS-ის ჩართვა არავის აიძულებს მის გამოყენებას. ეს pg_hba.conf-ის საქმეა:

pg_hba.conf
# TYPE     DATABASE  USER     ADDRESS          METHODhostssl    shop      appuser  0.0.0.0/0        scram-sha-256hostssl    shop      backup   203.0.113.20/32  certhostnossl  all       all      0.0.0.0/0        reject

hostssl ხაზები მხოლოდ TLS კავშირებს ემთხვევა, hostnossl - მხოლოდ ჩვეულებრივს, ხოლო host - ორივეს. თუ დისტანციური მისამართებისთვის მხოლოდ hostssl ხაზებია, დაქვეითებული prefer კლიენტი უბრალოდ ვერ შეძლებს შესვლას - რაც სერვერს იმ კლიენტების წინააღმდეგ დამცველად აქცევს, რომლებსაც არ აკონტროლებ. cert მეთოდი მხოლოდ კლიენტის სერტიფიკატით ავტორიზდება და სერტიფიკატის Common Name-ს როლის სახელს ადარებს (ან map-ის მეშვეობით). იმისთვის, რომ პაროლთან ერთად ვალიდური კლიენტის სერტიფიკატიც მოითხოვო, დაამატე clientcert=verify-ca ან clientcert=verify-full hostssl ხაზს; მაშინ სერვერს სჭირდება ssl_ca_file, რომელიც იმ CA-ზე მიუთითებს, რომელიც კლიენტის სერტიფიკატებს აწერს ხელს. ძველი clientcert=1 ფორმა PostgreSQL 14-ში ამოიღეს.

plain TCP 5432SencryptedClientsslmode, sslrootcertSSLRequestპასუხი S ან NTLS handshakeჯაჭვი, ჰოსტის სახელიpg_hba.confhostssl ან hostAuthenticationscram, cert
რომელ ეტაპზე სრულდება შემოწმების თითოეული ნახევარი

მართულ მონაცემთა ბაზაზე ამ ყველაფრის რედაქტირება ჩვეულებრივ არ შეგიძლია, ამიტომ კითხვა ისაა, რას გთავაზობს ჰოსტი. შეამოწმე: psql "...?sslmode=require" ან დაკავშირდება, ან "server does not support SSL"-ით ჩავარდება, ხოლო banner პროტოკოლს გეტყვის. შემდეგ ჰოსტს ჰკითხე, რომელმა CA-მ მოაწერა სერტიფიკატს ხელი და რომელ ჰოსტის სახელს ფარავს, რადგან სწორედ ეს სჭირდება verify-full-ს. RE:NODE-ზე PostgreSQL სერვერს უკავშირდები პლანისთვის ნაჩვენებ ჰოსტსა და პორტზე, იმ superuser პაროლით, რომელიც ამ სერვერისთვის არის გენერირებული - TLS banner შეამოწმე შენს კავშირზე და ვარაუდს ნუ დაეყრდნობი, ხოლო სერტიფიკატის ფაილები, რომლებიც აპლიკაციას სჭირდება, შეინახე მის კოდთან ერთად და ატვირთე ფაილების მენეჯერით ან SFTP-ით. TLS-ის გარშემო უსაფრთხოების გამკაცრების ნაბიჯები თავმოყრილია მონაცემთა ბაზის უსაფრთხოების ჩეკლისტში.

დრაივერები: ერთი და იგივე სიტყვები, განსხვავებული ქცევა#

ყველა libpq-ზე დაფუძნებული კლიენტი ზემოთ აღწერილივით იქცევა. დრაივერები, რომლებიც პროტოკოლს თვითონ ახორციელებენ, იმავე sslmode სიტყვებს იღებენ, მაგრამ ყოველთვის ერთსა და იმავეს არ გულისხმობენ.

psql და psycopg

psql, pg_dump, psycopg 2 და psycopg 3 libpq-ს იყენებენ, ამიტომ რეჟიმები, ნაგულისხმევი მნიშვნელობები, ფაილები და გარემოს ცვლადები იდენტურია. ნებისმიერი libpq პარამეტრი keyword არგუმენტად გადაეცემა:

Python, psycopg 3
import osimport psycopgconn = psycopg.connect(    host="db.example.com",    dbname="shop",    user="appuser",    password=os.environ["DB_PASSWORD"],    sslmode="verify-full",    sslrootcert="/app/certs/root.crt",)

node-postgres

node-postgres (pg) libpq-ს არ იყენებს. SSL პარამეტრის გარეშე ის ღია ტექსტით უკავშირდება. connection string-ში მისი parser (pg-connection-string) prefer-ს, require-ს, verify-ca-სა და verify-full-ს ერთნაირად აღიქვამს როგორც "TLS ჩართულია, სრული შემოწმებით Node-ის ნაგულისხმევი trust store-ით" - პირველ სამზე libpq-ზე მკაცრია, ამიტომ სტრიქონი, რომელიც psql-ში require-ით მუშაობს, Node-ში ვარდება შეცდომით self-signed certificate in certificate chain. სტრიქონზე uselibpqcompat=true-ს დამატება libpq-ს მნიშვნელობებზე გადართავს. sslmode=no-verify შემოწმებას მთლიანად თიშავს, რაც libpq-ში require-ს უდრის და მასზე უკეთესი არ არის.

სწორი გამოსავალი rejectUnauthorized: false არ არის, რომელიც ამ მთელ თემაში ყველაზე ხშირად კოპირებული ხაზია და სწორედ იმ შემოწმებას თიშავს, რის გამოც TLS-ს აზრი აქვს. ამის ნაცვლად Node-ს CA მიაწოდე:

Node.js, node-postgres
import fs from "node:fs";import { Pool } from "pg";const pool = new Pool({  host: "db.example.com",  database: "shop",  user: "appuser",  password: process.env.DB_PASSWORD,  ssl: { ca: fs.readFileSync("/app/certs/root.crt", "utf8") },});

ერთი ხაფანგი: თუ ერთდროულად გადასცემ sslmode-ის შემცველ connectionString-ს და ssl ობიექტს, სტრიქონიდან ამოკითხული SSL პარამეტრები შენსას ანაცვლებს და შენი ca უხმოდ იგნორირდება. გამოიყენე ან ერთი, ან მეორე.

JDBC

PostgreSQL JDBC დრაივერსაც (pgjdbc) საკუთარი რეალიზაცია აქვს. მიმდინარე ვერსიებში მისი sslmode ნაგულისხმევად prefer-ია, libpq-ის მსგავსად; ssl=true რეჟიმის გარეშე verify-full-ს ნიშნავს. მისი ნაგულისხმევი ფაილები libpq-ის ანალოგიურია, მაგრამ გასაღები განსხვავდება: ის ეძებს postgresql.pk8-ს და კითხულობს PKCS-12 (.p12, .pfx) ან PKCS-8 (.pem, .der) გასაღებებს და არა ტრადიციულ PEM გასაღებს, რომელსაც libpq იყენებს. დრაივერის უფრო ძველი ვერსიები სხვანაირად იქცეოდნენ, ამიტომ ნაგულისხმევს რომ ენდო, ჯერ შეამოწმე ვერსია შენი build ფაილში.

application.properties
spring.datasource.url=jdbc:postgresql://db.example.com:5432/shop?sslmode=verify-full&sslrootcert=/app/certs/root.crt

sslfactory=org.postgresql.ssl.NonValidatingFactory შემოწმებას თიშავს და JDBC-ში rejectUnauthorized: false-ის ექვივალენტია. მისი ნებისმიერი ასლი ისეთ ბაგად მიიჩნიე, რომელიც უნდა მოიშორო.

სხვა დრაივერებს - Go-ს pgx-ს, .NET-ის Npgsql-ს, Rust-ის tokio-postgres-ს - თითოეულს საკუთარი ნაგულისხმევი მნიშვნელობები და დაწერილობა აქვს. გაშვებამდე წაიკითხე დრაივერის საკუთარი დოკუმენტაცია სიტყვა sslmode-ზე და CA ფაილი დეპლოიმენტში ისევე შეინახე, როგორც სხვა კონფიგურაცია; ის, თუ სად უნდა ინახებოდეს ასეთი რამ, აღწერილია პოსტში გარემოს ცვლადები და საიდუმლოებები.

SSL შეცდომები და რას ნიშნავენ ისინი#

`server does not support SSL, but SSL was required` - კლიენტმა require ან უფრო მკაცრი მოითხოვა და სერვერმა N უპასუხა. ან სერვერზე ssl = off-ია, ან სერტიფიკატი გაშვებისას ვერ ჩაიტვირთა (მოძებნე სერვერის ლოგში), ან შუაში რაღაც, მაგალითად TCP proxy, PostgreSQL-ის მაგივრად პასუხობს. node-postgres იგივე სიტუაციას ასე აცხადებს: The server does not support SSL connections. ნუ "გაასწორებ" რეჟიმის დაწევით; გაასწორე სერვერი ან ჰკითხე ჰოსტს.

`root certificate file "/home/app/.postgresql/root.crt" does not exist` - მოითხოვე verify-ca ან verify-full და sslrootcert არ მიგითითებია, ამიტომ libpq ნაგულისხმევ ადგილას დაეძებდა და ვერაფერი იპოვა. უფრო ახალი კლიენტები მინიშნებას ამატებენ sslrootcert=system-ის ან უფრო სუსტი რეჟიმის შესახებ. მიუთითე sslrootcert სწორ ფაილზე; container-ებში გახსოვდეს, რომ home დირექტორია ხშირად /root-ია ან საერთოდ არ არსებობს.

`SSL error: certificate verify failed` - სერვერის სერტიფიკატი sslrootcert-ში მითითებულ CA-ს არ ეყრდნობა. ჩვეულებრივ არასწორი ფაილია (სხვა მანქანის სერვერის სერტიფიკატი ან bundle, რომელსაც intermediate აკლია), ან საჯარო სერტიფიკატი კერძო root-ის მიმართ შემოწმდა. openssl s_client -starttls postgres -connect db.example.com:5432 (OpenSSL 1.1.1 და უფრო ახალი) აჩვენებს ჯაჭვს, რომელსაც სერვერი რეალურად აგზავნის.

`server certificate for "db.example.com" does not match host name "10.0.0.5"` - verify-full თავის საქმეს აკეთებს. დაუკავშირდი სახელით, რომელსაც სერტიფიკატი ფარავს, ან ხელახლა გამოუშვი სერტიფიკატი იმ სახელით ან მისამართით, რომელსაც იყენებ, მის Subject Alternative Name-ში.

`SSL SYSCALL error: EOF detected` ან `SSL SYSCALL error: Connection reset by peer` - TLS სესიის ქვეშ TCP კავშირი TLS-ის სწორი დახურვის გარეშე დაიხურა. ეს იშვიათად არის სერტიფიკატის პრობლემა. გავრცელებული მიზეზები: სერვერის პროცესი მოკლეს (სერვერის ლოგში მოძებნე out-of-memory შეჩერება), სერვერი გადაიტვირთა, ან NAT gateway-მ, firewall-მა ან load balancer-მა უმოქმედო კავშირი გაწყვიტა. თუ ეს pool-ში უმოქმედო კავშირებს ემართება, pool-ის idle timeout ქსელისაზე დაბლა დააყენე და ჩართე TCP keepalive - პარამეტრებს განიხილავს connection pool-ები და ლიმიტები.

`private key file "/home/app/.postgresql/postgresql.key" has group or world access` - გააკეთე ფაილზე chmod 0600. Docker-ში Windows-ის ან macOS-ის ჰოსტიდან მიმაგრებული ფაილები ხშირად ფხვიერი უფლებებით მოდის; გადააკოპირე გასაღები image-ში ან volume-ში და რეჟიმი იქ დააყენე.

`no pg_hba.conf entry for host "...", user "appuser", database "shop", no encryption` - სერვერი შენთვის მხოლოდ TLS-ს იღებს, კლიენტი კი ღია ტექსტით დაუკავშირდა. ვინმემ disable ან allow დააყენა, ან დრაივერი TLS-ის გარეშე ნაგულისხმევზე გადავიდა. მიუთითე რეჟიმი აშკარად.

სერტიფიკატების ზოგადი პრობლემებისთვის - ვადაგასული სერტიფიკატები, არარსებული intermediate-ები, არასწორი სახელები - იგივე მსჯელობა მოქმედებს, რაც ვებ სერვერებისთვის; მათ განიხილავს SSL სერტიფიკატის შეცდომები განმარტებით.

FAQ#

რა არის PostgreSQL-ში ნაგულისხმევი sslmode?

libpq-სა და ყველაფრისთვის, რაც მასზეა აგებული (psql, psycopg, pg_dump), ეს არის prefer: გამოიყენე TLS, თუ სერვერი სთავაზობს, წინააღმდეგ შემთხვევაში დაბრუნდი ღია ტექსტზე და სერტიფიკატი არასდროს შეამოწმო. დრაივერები საკუთარი რეალიზაციით თავიანთ ნაგულისხმევს ირჩევენ, node-postgres კი TLS-ს საერთოდ არ იყენებს, სანამ არ უთხრეს.

საკმარისად უსაფრთხოა sslmode=require?

ის პასიურ მოსმენას აჩერებს და კერძო ქსელში, რომელსაც სრულად აკონტროლებ, შეიძლება საკმარისიც იყოს. აქტიურ თავდამსხმელს ვერ აჩერებს, რადგან ნებისმიერი სერტიფიკატი მიიღება. ინტერნეტით გამოიყენე verify-full.

რა განსხვავებაა verify-ca-სა და verify-full-ს შორის?

ორივე ამოწმებს, რომ სერტიფიკატს ხელი მოაწერა CA-მ, რომელსაც ენდობი. verify-full ამას ამატებს, რომ სერტიფიკატი გამოცემულია იმ ჰოსტის სახელზე, რომელსაც დაუკავშირდი. საჯარო CA-ს შემთხვევაში მხოლოდ verify-ca თითქმის უსარგებლოა, რადგან ამ CA-სგან სერტიფიკატი ნებისმიერს შეუძლია საკუთარი დომენისთვის აიღოს.

სად ვიპოვო root.crt?

იმისგან, ვინც სერვერს ამუშავებს. self-signed სერვერისთვის ეს თვით სერვერის server.crt-ია; კერძო CA-სთვის - CA სერტიფიკატი; საჯარო CA-სთვის - სისტემის trust store sslrootcert=system-ით PostgreSQL 16 და უფრო ახალ კლიენტებზე.

ვრცელდება თუ არა sslmode Unix socket კავშირებზე?

არა. TLS მხოლოდ TCP-ზე გამოიყენება. /var/run/postgresql-ით კავშირი sslmode-ს უგულებელყოფს, ამიტომ ლოკალური psql მუშაობს მაშინაც, როცა PGSSLMODE=verify-full არის დაყენებული და სერტიფიკატის კვალიც არ ჩანს.

შემიძლია TLS სერვერიდან ავიძულო და არ ვენდო კლიენტებს?

დიახ. დისტანციური მისამართებისთვის pg_hba.conf-ში გამოიყენე მხოლოდ hostssl ხაზები და სიცხადისთვის დაამატე hostnossl ... reject ხაზი. მაშინ ღია ტექსტის კლიენტი შესვლას ვერ შეძლებს და სესიას არ გაჟონავს. ეს კლიენტზე verify-full-ს არ ცვლის, რადგან მხოლოდ ის აჩერებს სხვისი სახელით მოქმედებას.

გამოგადგათ?