如何保护PostgreSQL数据库安全? | goteleport


我们将概述保护数据库安全的最佳做法。我们从最流行的开源数据库之一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灵活的用户特权系统。