如何保護PostgreSQL資料庫安全? | goteleport

banq發表於2021-04-03

我們將概述保護資料庫安全的最佳做法。我們從最流行的開源資料庫之一PostgreSQL開始,它將介紹您需要考慮的幾個安全級別:
  • 網路級安全
  • 運輸級安全
  • 資料庫級安全

 

PostgreSQL的網路級安全性

  • 防火牆

在理想情況下,您的PostgreSQL伺服器將完全隔離,並且不允許任何入站連線(SSH或psql)。不幸的是,這種空白的設定並不是PostgreSQL支援的開箱即用的東西。
要提高資料庫伺服器的安全性,您可以做的第二件事是,鎖定對使用防火牆執行資料庫的節點的埠級別的訪問。預設情況下,PostgreSQL在TCP埠5432上進行偵聽。根據作業系統的不同,阻止其他埠的方式可能有所不同。但是,使用Linux使用最廣泛的iptables防火牆實用程式,可以做到以下幾點:

# Make sure not to drop established connections.
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow SSH.
iptables -A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

# Allow PostgreSQL.
iptables -A INPUT -p tcp -m state --state NEW --dport 5432 -j ACCEPT

# Allow all outbound, drop everything else inbound.
iptables -A OUTPUT -j ACCEPT
iptables -A INPUT -j DROP
iptables -A FORWARD -j DROP


注意:更新iptables規則時,最好使用 iptables-apply 工具,該工具會自動回滾所做的更改,以防您被鎖住。
上面的PostgreSQL規則將允許任何人連線到埠5432。您可以透過僅接受來自某些IP地址或子網的連線來使其更加嚴格:

# Only allow access to PostgreSQL port from the local subnet.
iptables -A INPUT -p tcp -m state --state NEW --dport 5432 -s 192.168.1.0/24 -j ACCEPT

回到我們的理想情況,要完全阻止與埠5432的入站連線,將需要某種本地代理,該代理必須維護與客戶端節點的持久出站連線,並具有將流量代理到本地的能力。 
此技術稱為“反向隧道”,可以使用SSH遠端埠轉發功能進行演示。您可以透過在執行PostgreSQL資料庫的節點上執行以下命令來開啟反向隧道:

ssh -f -N -T -R 5432:localhost:5432 user@<client-host>


當然,<client-host>應該可以從PostgreSQL節點訪問並執行SSH守護程式。該命令會將資料庫伺服器上的埠5432轉發到客戶端計算機上的埠5432,您將能夠透過隧道連線到資料庫:

psql "host=localhost port=5432 user=postgres dbname=postgres"

  • PostgreSQL偵聽地址

優良作法是使用listen_addresses配置檔案指令來限制伺服器正在偵聽客戶端連線的地址。如果執行的節點PostgreSQL具有多個網路介面,請使用它來確保伺服器僅在客戶端將透過其連線的介面上偵聽:

listen_addresses = 'localhost, 192.168.0.1'


如果連線到資料庫的客戶端始終駐留在同一節點上(或者說與PostgreSQL作為副車容器一起位於同一Kubernetes主機中),則禁用TCP套接字偵聽可以完全消除圖中的網路。將偵聽地址設定為空字串會使伺服器僅接受Unix域套接字連線:

listen_addresses = ''


PostgreSQL傳輸級安全性
隨著全球大多數Web轉移到HTTP,幾乎沒有理由不對資料庫連線使用強大的傳輸加密。PostgreSQL本地支援TLS(由於遺留原因,TLS在文件,配置和CLI中仍稱為SSL),並提供了將其用於伺服器和客戶端身份驗證的方法。

  • 伺服器TLS

對於伺服器身份驗證,您首先需要獲得伺服器將提供給連線客戶端的證照。讓我們加密使獲得X.509證照免費變得非常容易,例如使用certbot CLI工具:

certbot certonly --standalone -d postgres.example.com


請記住,預設情況下,certbot使用HTTP-01 ACME質詢來驗證證照請求,該證照請求所請求域的有效DNS指向開啟的節點和埠80。
如果由於某種原因而無法使用“讓我們加密”,並且想在本地生成所有機密,則可以使用openssl CLI工具進行操作:

# Make a self-signed server CA.
openssl req -sha256 -new -x509 -days 365 -nodes \
    -out server-ca.crt \
    -keyout server-ca.key

# Generate server CSR. Put the hostname you will be using to connect to
# the database in the CN field.
openssl req -sha256 -new -nodes \
    -subj "/CN=postgres.example.com" \
    -out server.csr \
    -keyout server.key

# Sign a server certificate.
openssl x509 -req -sha256 -days 365 \
    -in server.csr \
    -CA server-ca.crt \
    -CAkey server-ca.key \
    -CAcreateserial \
    -out server.crt

當然,在生產環境中,您需要確保在這些證照的有效日期之前對其進行更新。
  • 客戶端TLS

客戶端證照身份驗證允許伺服器透過驗證客戶端提供的X.509證照是否由受信任的證照頒發機構簽名來驗證連線的客戶端的身份。
使用不同的證照頒發機構來頒發客戶端和伺服器證照是一個好主意,因此讓我們建立一個客戶端CA並使用它來簽署客戶端證照:

# Make a self-signed client CA.
openssl req -sha256 -new -x509 -days 365 -nodes \
    -out client-ca.crt \
    -keyout client-ca.key

# Generate client CSR. CN must contain the name of the database role you
# will be using to connect to the database.
openssl req -sha256 -new -nodes \
    -subj "/CN=alice" \
    -out client.csr \
    -keyout server.key

# Sign a client certificate.
openssl x509 -req -sha256 -days 365 \
    -in client.csr \
    -CA client-ca.crt \
    -CAkey client-ca.key \
    -CAcreateserial \
    -out client.crt

請注意,客戶端證照的CommonName(CN)欄位必須包含客戶端連線到的資料庫帳戶的名稱。PostgreSQL伺服器將使用它來建立客戶端的身份。
  •  TLS配置

綜合所有內容,您現在可以配置PostgreSQL伺服器以接受TLS連線:

ssl = on
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'
ssl_ca_file = '/path/to/client-ca.crt'

# This setting is on by default but it’s always a good idea to
# be explicit when it comes to security.
ssl_prefer_server_ciphers = on

# TLS 1.3 will give the strongest security and is advised when
# controlling both server and clients.
ssl_min_protocol_version = 'TLSv1.3'


配置的最後一項工作是更新PostgreSQL伺服器基於主機的身份驗證檔案(pg_hba.conf),以要求所有連線均使用TLS,並使用X.509證照對客戶端進行身份驗證:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
hostssl all             all             ::/0                    cert
hostssl all             all             0.0.0.0/0               cert

現在,連線到資料庫伺服器的客戶端將必須出示由客戶端證照頒發機構簽名的有效證照:

psql "host=postgres.example.com \
      user=alice \
      dbname=postgres \
      sslmode=verify-full \
      sslrootcert=/path/to/server-ca.crt \
      sslcert=/path/to/client.crt \
      sslkey=/path/to/client.key"


請注意,預設情況下,psql不會執行伺服器證照驗證,因此必須將“ sslmode”設定為verify-full或verify-ca,具體取決於您是否使用與X.509證照的CN欄位中編碼的主機名相同的主機名連線到PostgreSQL伺服器。
為了減少命令的冗長性,而不必每次都想連線到資料庫時都輸入TLS機密的路徑,可以使用PostgreSQL連線服務檔案。它允許您將連線引數分組為“服務”,然後可以透過“服務”引數在連線字串中對其進行引用。
建立~/.pg_service.conf包含以下內容:

[example]
host=postgres.example.com
user=alice
sslmode=verify-full
sslrootcert=/path/to/server-ca.crt
sslcert=/path/to/client.crt
sslkey=/path/to/client.key

現在,連線到資料庫時,只需指定服務名稱和要連線到的資料庫的名稱:

psql "service=example dbname=postgres"


資料庫級安全

  • 角色概述

到目前為止,我們已經探索瞭如何保護PostgreSQL資料庫伺服器免遭未經授權的網路連線,使用強大的傳輸加密以及確保伺服器和客戶端可以透過相互TLS身份驗證來信任彼此的身份。另一個難題是弄清楚使用者在連線資料庫並驗證身份後可以做什麼以及可以訪問什麼。這通常稱為授權。
PostgreSQL具有圍繞角色概念構建的全面的使用者許可權系統。在現代PostgreSQL版本(8.1及更高版本)中,“角色”與“使用者”同義,因此您使用的任何資料庫帳戶名稱(例如psql)(例如“ user = alice”)實際上都是具有LOGIN可使其連線的屬性的角色到資料庫。實際上,以下SQL命令是等效的:

CREATE USER alice;
CREATE ROLE alice LOGIN;


除了具有登入功能之外,角色還可以具有其他屬性,這些屬性使他們可以繞過所有許可權檢查(SUPERUSER),建立資料庫(CREATEDB),建立其他角色(CREATEROLE)等。
除了屬性外,還可以向角色授予許可權,這些許可權可以分為兩類:其他角色的成員資格和資料庫物件特權。讓我們看一下它們是如何工作的。
  • 授予角色許可權

對於我們的虛構示例,我們將跟蹤伺服器清單:

CREATE TABLE server_inventory (
    id            int PRIMARY KEY,
    description   text,
    ip_address    text,
    environment   text,
    owner         text,
);

預設情況下,PostgreSQL安裝包括用於引導資料庫的超級使用者角色(通常稱為“ postgres”)。在所有資料庫操作中使用此角色等同於在Linux上始終使用“ root”登入,這從來都不是一個好主意。相反,讓我們建立一個非特權角色,並根據最小特權原則為它分配許可權。
您可以建立一個“組角色”並向該組中的其他角色(對映到各個使用者)授予成員資格,而不是分別為每個新使用者/角色分配特權。假設您要允許您的開發人員Alice和Bob檢視伺服器清單,但不能對其進行修改:

-- Create a group role that doesn't have ability to login by itself and
-- grant it SELECT privileged on the server inventory table.
CREATE ROLE developer;
GRANT SELECT ON server_inventory TO developer;

-- Create two user accounts which will inherit "developer" permissions upon
-- logging into the database.
CREATE ROLE alice LOGIN INHERIT;
CREATE ROLE bob LOGIN INHERIT;

-- Assign both user account to the "developer" group role.
GRANT developer TO alice, bob;


現在,當連線到資料庫時,Alice和Bob都將繼承“開發人員”組角色的特權,並且能夠在伺服器清單上執行查詢。
該SELECT許可權適用於所有表列預設情況下,雖然它並不一定。假設您只想允許實習生檢視伺服器的常規庫存資訊,而不會透過隱藏IP地址讓他們進行連線:

CREATE ROLE intern;
GRANT SELECT(id, description) ON server_inventory TO intern;
CREATE ROLE charlie LOGIN INHERIT;
GRANT intern TO charlie;

其他最常用的資料庫物件特權INSERT,UPDATE, DELETE以及TRUNCATE對應於相應的SQL語句,但您可以連線到特定的資料庫,該模式中建立新的模式或物件,執行功能等也分配許可權。檢視 PostgreSQL文件的Privileges部分,以檢視整個列表。
 

行級安全
PostgreSQL特權系統的更高階功能之一是行級安全性,它使您可以向表中的行子集授予特權。
要開始使用行級安全性,您需要做兩件事:為表啟用它,並定義將控制行級訪問的策略。
在我們之前的示例的基礎上,假設您要允許使用者僅更新自己的伺服器。首先,在表上啟用RLS:

ALTER TABLE server_inventory ENABLE ROW LEVEL SECURITY;


沒有定義任何策略,PostgreSQL預設使用“拒絕”策略,這意味著沒有角色(除了表所有者(通常是建立表的角色)之外)沒有任何訪問許可權。
行安全策略是布林表示式,PostgreSQL將為應該返回或更新的每一行求值。SELECT語句返回的行將根據由 USING子句指定的表示式進行檢查 ,而INSERT,UPDATE或DELETE 語句更新的行將根據WITH CHECK表示式進行檢查。
讓我們定義一些策略,這些策略允許使用者檢視所有伺服器,但只能更新自己的伺服器,這由表的“所有者”欄位確定:

CREATE POLICY select_all_servers
    ON server_inventory FOR SELECT
    USING (true);

CREATE POLICY update_own_servers
    ON server_inventory FOR UPDATE
    USING (current_user = owner)
    WITH CHECK (current_user = owner);


請注意,只有表的所有者才能為其建立或更新行安全策略。
 

稽核
到目前為止,我們主要談論的是先發制人的安全措施。遵循一項基礎安全原則,即縱深防禦,我們探索了它們如何相互疊加,以幫助減緩假設的攻擊者在系統中的前進速度。
保持準確而詳細的稽核記錄是系統經常被忽視的安全屬性之一。監視資料庫伺服器的網路級或節點級訪問不在本文的討論範圍之內,但是讓我們看一下有關PostgreSQL伺服器本身的選擇。
要增強對資料庫中發生的事件的可見性,您可以做的最基本的事情就是啟用詳細日誌記錄。將以下指令新增到伺服器配置檔案中,以開啟所有連線嘗試和所有已執行的SQL語句的日誌記錄:

; Log successful and unsuccessful connection attempts.
log_connections = on

; Log terminated sessions.
log_disconnections = on

; Log all executed SQL statements.
log_statement = all


不幸的是,這幾乎是開箱即用的標準自託管PostgreSQL安裝所能做的程度。當然,它總比沒有好,但是它不能擴充套件到少數資料庫伺服器和簡單的“抓取”能力之外。
對於更高階的PostgreSQL審計解決方案,可以使用第三方擴充套件,例如pgAudit。如果您使用的是自託管的PostgreSQL例項,則必須手動安裝擴充套件。某些託管版本(例如AWS RDS)開箱即用地支援它,因此您只需要啟用它即可。
pgAudit為記錄的語句帶來更多的結構和粒度。但是,請記住,它仍然是基於日誌的,如果要將稽核日誌以結構化的格式傳送到外部SIEM系統進行詳細分析,則使用它會帶來挑戰。
 

基於證照的PostgreSQL訪問

Teleport for Database Access 是我們構建的一個開放原始碼專案,旨在幫助您實現保護本文中討論的PostgreSQL(及其他)資料庫的最佳實踐。

  • 使用者可以透過單點登入流程訪問資料庫,並使用短期的X.509證照進行身份驗證,而不是使用常規憑據。
  • 資料庫不需要在公共Internet上公開,並且可以使用一些產品的內建反向隧道子系統在氣密環境中安全執行。
  • 管理員和審計員可以在審計日誌中檢視與特定使用者身份相關的資料庫活動,例如會話和SQL語句,還可以選擇將其傳送到外部系統。

 

結論
與任何考慮到安全性而設計的系統一樣,正確保護對資料庫例項的訪問需要在堆疊的多個級別上採取保護措施。
在本文中,我們從網路和傳輸安全性入手,探討了在多個級別上保護PostgreSQL資料庫訪問的最佳實踐,並探討了如何使用PostgreSQL靈活的使用者特權系統。



 

相關文章