PostgreSQL安全最佳實踐

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫是黑客眼中的“聖盃”,需要我們像對待“花朵”一樣精心呵護它。本文主要介紹數據庫保護的最佳實踐。首先,從最常用的開源數據庫PostgreSQL開始,我們將一一介紹諸位需要考慮的幾個安全層級:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網絡層安全"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"傳輸層安全"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫層安全"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"PostgreSQL中網絡層的安全"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"防火牆"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理想情況下,PostgreSQL服務器應當是完全隔離,不允許任何入站申請、SSH或psql的。然而,PostgreSQL沒有對這類網閘設置提供開箱即用的支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們最多也只能通過設置防火牆,鎖定數據庫所在節點的端口級訪問來提升數據庫服務器的安全性。默認情況下,PostgreSQL監聽TCP端口5432。而根據操作系統的不同,鎖定其他端口的方式也會有所不同。以Linux最常用的防火牆"},{"type":"codeinline","content":[{"type":"text","text":"iptables"}]},{"type":"text","text":"爲例,下面這幾行代碼就可輕鬆完成任務:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"# 確保已有連接不被drop\niptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n\n# 允許SSH.\niptables -A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT\n\n# 允許PostgreSQL.\niptables -A INPUT -p tcp -m state --state NEW --dport 5432 -j ACCEPT\n\n# 允許所有出站,drop所有入站\niptables -A OUTPUT -j ACCEPT\niptables -A INPUT -j DROP\niptables -A FORWARD -j DROP"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在更新iptables的規則時,建議使用"},{"type":"link","attrs":{"href":"https:\/\/man7.org\/linux\/man-pages\/man8\/iptables-apply.8.html?fileGuid=jWHTCry9KtVKqy3D","title":"","type":null},"content":[{"type":"text","text":"iptables-apply"}]},{"type":"text","text":"工具。這樣,哪怕你不小心把自己也鎖在外面了,它也能自動回滾更改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這條PostgreSQL規則會允許所有人連接到端口5432上。當然,你也可以將其修改爲只接受特定IP地址或子網讓限制更加嚴格:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"# 僅允許本地子網訪問PostgreSQL端口\niptables -A INPUT -p tcp -m state --state NEW --dport 5432 -s 192.168.1.0\/24 -j ACCEPT"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續討論我們的理想情況。想要完全限制到5432端口的所有入站連接需要一個某種類型的本地代理,由它維持一個到客戶端節點的持久出站連接,並且能將流量代理到本地PostgreSQL實例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種代理被稱作是“反向通道”。具體使用方法可以通過SSH遠程端口轉發的功能進行演示。運行下列指令可以在PostgreSQL數據庫運行的節點上打開一個反向通道:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"ssh -f -N -T -R 5432:localhost:5432 [email protected]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PostgreSQL的節點需要能訪問,其上的SSH守護進程(daemon)也要處於運行狀態。下列指令會將數據庫服務器上端口5432轉發到客戶端機器的端口5432上,這樣你就可以通過這個通道連接數據庫了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"psql \"host=localhost port=5432 user=postgres dbname=postgres\""}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"PostgreSQL監聽地址"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過配置文件指令"},{"type":"codeinline","content":[{"type":"text","text":"listen_addresses"}]},{"type":"text","text":"來限制服務器監聽客戶端連接的地址是個好習慣。如果運行PostgreSQL的節點上有多個網絡接口,"},{"type":"codeinline","content":[{"type":"text","text":"listen_addresses"}]},{"type":"text","text":"可以確保服務器只會監聽客戶端所連接的一個或多個接口:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"listen_addresses = 'localhost, 192.168.0.1'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果連接到數據庫的客戶端總是駐留在同一節點上,或是與數據庫共同駐留在同一個Kubernetes pod上,PostgreSQL作爲 sidecar 容器運行,禁用套接字監聽(socket)可以完全消除網絡的影響。將監聽地址設置爲空字符串可以使服務器只接受Unix域的套接字連接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"listen_addresses = ''"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"PostgreSQL中傳輸層級的安全"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當世界上大部分的網絡都轉投向HTTP的懷抱時,爲數據庫連接選擇強傳輸加密也變成了一個必備項目。PostgreSQL本身即支持TLS(因爲歷史遺留問題,在文檔、配置文件,以及CLI中仍被稱作是SSL),我們也可以使用它進行服務器端和客戶端認證。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"服務器端TLS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於服務器的認證,我們首先需要爲服務器準備一份用於和相連接的客戶端認證的證書。在Let's Encrypt上,我們可以找到免費提供的X.509證書,具體使用方法以"},{"type":"link","attrs":{"href":"https:\/\/certbot.eff.org\/?fileGuid=jWHTCry9KtVKqy3D","title":"","type":null},"content":[{"type":"text","text":"certbot"}]},{"type":"text","text":"的命令行工具爲例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"certbot certonly --standalone -d postgres.example.com"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,certbot默認使用ACME規範的HTTP-01挑戰來驗證證書請求,這裏我們就需要確保請求域指向節點的DNS有效,並且端口80處於開放狀態。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了Let's Encrypt之外,如果想在本地生成所有的信息,我們還可以選擇openssl命令行工具:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"# 生成一個自簽名的服務器CA\nopenssl req -sha256 -new -x509 -days 365 -nodes \\\n -out server-ca.crt \\\n -keyout server-ca.key\n \n# 生成服務器CSR,CN裏填需要連接到數據庫的主機名 \nopenssl req -sha256 -new -nodes \\\n -subj \"\/CN=postgres.example.com\" \\\n -out server.csr \\\n -keyout server.key\n \n# 簽證書\nopenssl x509 -req -sha256 -days 365 \\\n -in server.csr \\\n -CA server-ca.crt \\\n -CAkey server-ca.key \\\n -CAcreateserial \\\n -out server.crt"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在生產環境中記得在證書過期前更新。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"客戶端TLS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過驗證客戶端提供的X.509證書是由可信的證書頒發機構(CA)簽名,服務器便可以驗證連接客戶端的身份。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建議使用不同的CA來分別給客戶端和服務器端發證書。下列代碼創建了一個客戶端CA,並用它來給客戶簽證書:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"# 生成一個自簽名的客戶端CA\nopenssl req -sha256 -new -x509 -days 365 -nodes \\\n -out client-ca.crt \\\n -keyout client-ca.key\n \n# 生成客戶端CSR。CN必須填用於連接的數據庫角色名\nopenssl req -sha256 -new -nodes \\\n -subj \"\/CN=alice\" \\\n -out client.csr \\\n -keyout server.key\n \n# 簽證書\nopenssl x509 -req -sha256 -days 365 \\\n -in client.csr \\\n -CA client-ca.crt \\\n -CAkey client-ca.key \\\n -CAcreateserial \\\n -out client.crt"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏需要注意,客戶端證書裏的CommonName(CN)字段必須要包含用於連接的數據庫賬號。PostgreSQL服務器將用CN來創建客戶端的身份。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"TLS配置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小結一下,我們現在可以配置PostgreSQL服務器來接收TLS連接了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"ssl = on\nssl_cert_file = '\/path\/to\/server.crt'\nssl_key_file = '\/path\/to\/server.key'\nssl_ca_file = '\/path\/to\/client-ca.crt'\n# 這裏默認是on,但出於安全考慮,特意寫出來總是沒錯的 \nssl_prefer_server_ciphers = on\n# TLS 1.3能提供最強的安全保護。在控制服務器和客戶端時建議使用ssl_min_protocol_version = 'TLSv1.3'"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們還需要再配置的就只剩下用於更新PostgreSQL服務器的基於主機的認證文件("},{"type":"codeinline","content":[{"type":"text","text":"pg_hba.conf"}]},{"type":"text","text":")了。它可以要求所有的連接使用TLS,並通過X.509證書對客戶端進行認證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"# TYPE DATABASE USER ADDRESS METHOD\nhostssl all all ::\/0 cert\nhostssl all all 0.0.0.0\/0 cert"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,連接到數據庫服務器的客戶端就需要提供由客戶端CA簽名的有效證書了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"psql \"host=postgres.example.com \\\n user=alice \\\n dbname=postgres \\\n sslmode=verify-full \\\n sslrootcert=\/path\/to\/server-ca.crt \\\n sslcert=\/path\/to\/client.crt \\\n sslkey=\/path\/to\/client.key\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,默認情況下,psql不會進行服務器證書認證,所以我們需要將“sslmode”設置爲“"},{"type":"codeinline","content":[{"type":"text","text":"verify-full"}]},{"type":"text","text":"”或者“"},{"type":"codeinline","content":[{"type":"text","text":"verify-ca"}]},{"type":"text","text":"”。具體設置要看你是否使用了編碼後的X.509中CN字段的主機名對服務器進行連接。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了簡化指令,並且避免在每次連接到數據庫時都要重新輸入一遍到TLS的路徑,可以通過使用PostgreSQL的連接服務文件來解決。它可以將連接參數分組到“services”中,通過連接字符串中的“service”參數進行引用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用下列代碼創建“"},{"type":"codeinline","content":[{"type":"text","text":"~\/.pg_service.conf"}]},{"type":"text","text":"”文件:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"[example]\nhost=postgres.example.com\nuser=alice\nsslmode=verify-full\nsslrootcert=\/path\/to\/server-ca.crt\nsslcert=\/path\/to\/client.crt\nsslkey=\/path\/to\/client.key"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,當連接到數據庫時,我們只需要指定服務名和想要連接的數據庫名即可:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"psql \"service=example dbname=postgres\""}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"PostgreSQL中數據庫層級安全"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"角色"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前爲止,我們已經探討的內容有以下幾點:如何從未認證的網絡連接中保護PostgreSQL數據庫,如何使用強加密進行數據傳輸,以及如何通過共同的TLS認證讓服務器與客戶互相信任對方身份。接下來,我們將繼續分析用戶在這裏能做什麼,連接到數據庫後他們可以訪問什麼,以及如何驗證他們的身份。這一步通常被稱作是“授權”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PostgreSQL有一套完整的、依據角色(role)建立的用戶權限系統。在現代的PostgreSQL(8.1及以上版本)中,“角色”是“用戶”的同義詞。無論你用什麼樣的數據庫賬戶名,比如psql中的\"user=alice\",它其實都是一個可以連接數據庫的、擁有LOGIN屬性的角色。也就是說,下面兩條指令其實效果一樣:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"CREATE USER alice;\nCREATE ROLE alice LOGIN;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了登入的權限外,角色還可以擁有其他屬性:可以通過所有的權限檢查的"},{"type":"codeinline","content":[{"type":"text","text":"SUPERUSER"}]},{"type":"text","text":",可以創建數據庫的"},{"type":"codeinline","content":[{"type":"text","text":"CREATEDB"}]},{"type":"text","text":",可以創建其他角色的"},{"type":"codeinline","content":[{"type":"text","text":"CREATEROLE"}]},{"type":"text","text":",等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了屬性外,角色被授予的權限可以分爲兩大類:一是其他角色的成員身份(membership),二是數據庫對象的權限。下面我們將介紹這兩類都是如何工作的。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"授予角色權限"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設,我們需要跟蹤服務器清單:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"CREATE TABLE server_inventory (\n id int PRIMARY KEY,\n description text,\n ip_address text,\n environment text,\n owner text,\n);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認情況下,PostgreSQL安裝會包含一個用於引導數據庫的超級用戶角色,通常被稱作是“postgres”。使用這一角色進行所有的數據庫操作相當於在Linux系統中經常使用“root”登入,永遠不是個好主意。所以,我們要創建一個無權限角色,根據需要爲其授予最小權限。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過創建一個“組角色”並授權其他角色(角色與用戶一一對應)爲組內成員,可以避免爲每一個用戶或角色單獨分配權限的麻煩。假如說,我們想要授予開發者Alice和Bob查看服務器清單,但不允許其修改的權限:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"-- 創建一個自身沒有登入能力的組角色,授予其在服務器清單表中進行SELECT的權限\nGRANT SELECT ON server_inventory TO developer;\n-- 創建兩個用戶賬號,在登入權限的基礎上繼承“developer”權限 \nCREATE ROLE alice LOGIN INHERIT;\nCREATE ROLE bob LOGIN INHERIT;\n-- 分配給兩個用戶賬號“developer”組角色\nGRANT developer TO alice, bob;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,當連接到數據庫時,Alice和Bob都會繼承“developer”組角色中的權限,並且可以查詢數據庫清單表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"SELECT"}]},{"type":"text","text":"權限默認對錶的所有列有效,但這一點也是可以更改的。假設我們只想讓實習生查看服務器的大致信息但又不想讓他們連接到服務器,那麼就可以選擇隱藏IP地址:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"CREATE ROLE intern;\nGRANT SELECT(id, description) ON server_inventory TO intern;\nCREATE ROLE charlie LOGIN INHERIT;\nGRANT intern TO charlie;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其他常用數據庫對象權限還有“"},{"type":"codeinline","content":[{"type":"text","text":"INSERT"}]},{"type":"text","text":"”、“"},{"type":"codeinline","content":[{"type":"text","text":"UPDATE"}]},{"type":"text","text":"”、“"},{"type":"codeinline","content":[{"type":"text","text":"DELETE"}]},{"type":"text","text":"”,以及“"},{"type":"codeinline","content":[{"type":"text","text":"TRUNCATE"}]},{"type":"text","text":"”,分別對應它們各自的SQL語句。我們還可以爲連接到特定數據庫、新建模式或模式中新建對象、執行函數等等分配權限。PostgreSQL文檔中的"},{"type":"link","attrs":{"href":"https:\/\/www.postgresql.org\/docs\/13\/ddl-priv.html?fileGuid=jWHTCry9KtVKqy3D","title":"","type":null},"content":[{"type":"text","text":"權限"}]},{"type":"text","text":"章節提供了完整列表。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"行級安全策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PostgreSQL權限系統的更高級的玩法是行級安全策略(RLS),它允許你爲表中的部分行分配權限。這既包括可以用"},{"type":"codeinline","content":[{"type":"text","text":"SELECT"}]},{"type":"text","text":"查詢的行,也包括可以"},{"type":"codeinline","content":[{"type":"text","text":"INSERT"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"UPDATE"}]},{"type":"text","text":"以及"},{"type":"codeinline","content":[{"type":"text","text":"DELETE"}]},{"type":"text","text":"的行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想要使用行級安全,我們需要準備兩件事情:在表中啓用RLS,爲其定義一個用於控制行級訪問的策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續之前的例子,假設我們只想允許用戶更新他們自己的服務器。那麼,第一步,啓用表中的RLS:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"ALTER TABLE server_inventory ENABLE ROW LEVEL SECURITY;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果沒有定義任何策略的話,PostgreSQL會默認到“拒絕”策略上,這就意味着除了表的創建者\/所有者之外,沒有任何角色能夠訪問。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"行安全策略是一個布爾表達式,是PostgreSQL用於判定所有需要返回或更新的行的條件。SELECT語句返回的行將對照USING子語句所指定的表達式進行檢查,而通過INSERT,UPDATE或DELETE語句更新的行將對照WITH CHECK表達式進行檢查。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,讓我們定義幾個策略,允許用戶查看所有服務器,但只能更新他們自己服務器,這個“自己”是由表中的“owner”字段決定的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"CREATE POLICY select_all_servers\n ON server_inventory FOR SELECT\n USING (true);\nCREATE POLICY update_own_servers\n ON server_inventory FOR UPDATE\n USING (current_user = owner)\n WITH CHECK (current_user = owner);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注,只有表的所有者可以創建或更新RLS。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"審計"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到目前爲止,我們主要都在討論先手防禦的安全措施。根據其中一項基本的安全原則——深度防禦,我們分析了這些措施是如何通過互相疊加,幫助減緩(假想中的)攻擊者進攻系統的進程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"留存準確且詳細的審計追蹤記錄是系統安全屬性中常常被忽視的一點。監控數據庫服務器端的網絡層或節點層訪問並不在本文的討論範圍內,但當涉及到PostgreSQL服務器本身時,不妨先來看看我們有什麼可選項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想要更清晰地觀測數據庫內情況時,最常用的方法是啓用詳細日誌記錄。在服務器配置文件中添加以下指令,開啓對所有連接嘗試以及已執行SQL的日誌記錄。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"plain"},"content":[{"type":"text","text":"; 記錄成功與非成功連接嘗試 \nlog_connections = on\n; 記錄終止對話\nlog_disconnections = on\n; 記錄所有執行過的SQL語句\nlog_statement = all"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不幸的是,對於標準的自託管PostgreSQL,這幾乎是在不安裝其他插件的情況下你能做到的全部了。雖然總比沒有強,但在少數數據庫服務器和簡單的“grep”之外,它的延展性並不好。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果想要更高級的PostgreSQL審計解決方案,諸如"},{"type":"link","attrs":{"href":"https:\/\/github.com\/pgaudit\/pgaudit?fileGuid=jWHTCry9KtVKqy3D","title":"","type":null},"content":[{"type":"text","text":"pgAudit"}]},{"type":"text","text":"這樣的第三方插件是個不錯的選擇。自託管的PostgreSQL需要手動安裝,但對於一些託管版本的PostgreSQL,比如AWS RDS這些,本身就有,我們只需啓用即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於記錄的語句,pgAudit提供了更多的結構和粒度。但要記住,它並沒有脫離日誌的範圍,也就是說,如果需要將審計日誌以結構化的格式傳輸到外部SIEM系統以作更詳細的分析時,恐怕會很難。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"PostgreSQL中基於證書的訪問"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/goteleport.com\/blog\/introducing-database-access\/?fileGuid=jWHTCry9KtVKqy3D","title":"","type":null},"content":[{"type":"text","text":"Teleport數據庫訪問"}]},{"type":"text","text":"(Teleport for Database Access)是一款開源項目,在它的幫助下,我們可以實現本文中介紹的所有用於保護PostgreSQL和其他數據庫的最佳實踐。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶可以通過單點登錄流程訪問數據庫,使用短期X.509證書代替常規憑證進行認證。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據庫不需暴露在公網中,可以使用Teleport內置的反向通道子系統在網閘環境中安全運行。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"管理員和審計人員可以在審計日誌中查看數據庫活動,例如與特定用戶標識相關聯的會話和SQL語句,並可以選擇將其傳送至外部系統。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與任何以安全爲前提設計的系統一樣,正確地保護對數據庫實例的訪問需要在網絡協議棧的多個層次上採取保護措施。在這篇文章中,我們從網絡和傳輸安全開始,探討了如何使用PostgreSQL靈活的用戶權限系統。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/goteleport.com\/blog\/securing-postgres-postgresql?fileGuid=jWHTCry9KtVKqy3D"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章