数据库安全入门:从账号权限到数据备份的基础配置

数据库安全入门:从账号权限到数据备份的基础配置

数据库安全入门:从账号权限到数据备份的基础配置

数据库安全不只是给管理员账号设置一个复杂密码。一次常见的安全事故,可能源于应用使用了超级用户、数据库端口暴露在公网、备份文件无人保护,或者团队从未验证过能否恢复数据。

入门阶段不必试图一次解决所有问题。更有效的做法是先明确数据库的访问边界,再配置合适的账号和权限,保护网络连接与凭据,最后建立可验证的备份和恢复流程。本文以 PostgreSQL 为例,给出可直接改造的基础配置。不同版本和托管服务的默认设置可能不同,执行前应先核对当前环境。

一、先界定保护对象和适用场景

这里讨论的配置适用于个人项目、内部系统和中小型生产环境,重点是降低常见误配置的风险。示例假设:

  • 数据库为 PostgreSQL,示例命令使用常见的 psql、pg_dump 和 pg_restore 工具。
  • 应用使用单独的数据库和账号连接,不使用数据库超级用户。
  • 数据库只接受来自受信任应用服务器或管理网络的连接。
  • 备份写入受访问控制的存储位置,并且有计划地测试恢复。

这套做法不能替代针对法规、行业标准、高可用、审计和灾难恢复的完整设计,也不能保证系统绝对安全。数据库安全取决于操作系统、网络、应用代码、云平台、人员权限和运维流程等多个环节。若使用托管数据库,很多底层配置由服务商管理,应以服务商文档和控制台实际能力为准。

二、先梳理账号:不要让应用成为管理员

数据库账号的权限决定了被盗用后攻击者能够做什么。应用通常只需要读写业务表,不需要创建角色、安装扩展、修改服务器参数或管理其他数据库。给应用使用超级用户,会把应用漏洞的影响范围扩大到整个数据库实例。

推荐按用途分开账号,例如:

  • 管理员账号:用于角色管理、数据库配置和维护。尽量只在管理操作中使用。
  • 迁移账号:用于发布流程中创建或修改表结构。不要作为长期运行的应用账号。
  • 应用账号:仅拥有业务运行所需的连接权限、表权限和序列权限。
  • 只读账号:仅用于报表或查询任务,避免获得写权限。

以下 SQL 示例假定使用有权创建数据库和角色的管理员连接执行。示例中的密码不是可直接投入生产的密码;生产环境应通过受控的密钥管理方式设置凭据,而不是将真实密码写进 SQL 文件、代码仓库或命令历史。

-- 以下操作以 PostgreSQL 管理员身份执行。
-- 将 appdb、app_runtime 和密码管理方式替换为适合环境的值。

CREATE DATABASE appdb;

CREATE ROLE app_runtime
  LOGIN
  NOSUPERUSER
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION
  NOBYPASSRLS;

GRANT CONNECT ON DATABASE appdb TO app_runtime;

连接到目标数据库后,再配置 schema 和对象权限:

\c appdb

-- 如果业务使用 public schema,可按需限制其权限。
-- 先确认没有其他业务依赖这些默认授权。
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

-- 允许应用访问 schema,但不允许它在其中创建对象。
GRANT USAGE ON SCHEMA public TO app_runtime;

-- 对已有表授予所需的读写权限。
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO app_runtime;

-- 对已有序列授予常见的使用权限。
GRANT USAGE, SELECT
ON ALL SEQUENCES IN SCHEMA public
TO app_runtime;

PUBLIC 是 PostgreSQL 中代表所有角色的伪角色。撤销 schema 的 CREATE 权限前,应先确认应用、迁移工具和其他数据库对象是否依赖该权限。权限变更可能导致已有部署流程失败,不能不经检查直接复制到生产环境。

让未来创建的对象也有权限

上面的 ON ALL TABLES 和 ON ALL SEQUENCES 只影响执行时已经存在的对象,不会自动覆盖将来创建的新表。PostgreSQL 可以通过默认权限配置未来对象的授权,但配置只对执行该命令的对象创建者生效。因此,迁移由哪个角色执行,决定了是否需要为那个角色配置默认权限。

-- 以将来负责创建业务表的角色执行。
-- 例如在迁移角色迁移连接中执行以下命令。

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_runtime;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app_runtime;

默认权限不会自动修正已存在对象,也不代表所有创建者的对象都会受影响。若多个角色都能执行数据库迁移,应逐一确认它们的默认权限;也可以统一规定由单一迁移角色创建对象。PostgreSQL 的默认权限、schema 所有者和对象所有者是不同的权限边界,排查授权问题时需要分别检查。

只读账号示例

报表账号可以仅获得查询权限。与应用账号类似,已有对象和未来对象需要分别处理:

CREATE ROLE app_readonly
  LOGIN
  NOSUPERUSER
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION
  NOBYPASSRLS;

GRANT CONNECT ON DATABASE appdb TO app_readonly;

\c appdb

GRANT USAGE ON SCHEMA public TO app_readonly;

GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;

-- 由负责创建对象的角色执行,覆盖未来创建的表。
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO app_readonly;

只读权限不等于数据绝对安全:查询结果仍可能包含敏感信息,账号也可能通过大量查询影响数据库负载。因此,还应限制查询来源、保护查询凭据,并在适用时对敏感列或行实施更细粒度控制。

三、连接安全:先缩小网络入口,再考虑数据库设置

若数据库服务能被任意互联网地址访问,即使账号权限配置良好,也会增加扫描、口令猜测和漏洞利用的机会。优先在云安全组、防火墙或网络 ACL 中,仅允许应用服务器网段、VPN 或受控的运维出口连接数据库端口。PostgreSQL 默认端口通常是 5432,但实际端口可能已被更改。

PostgreSQL 的 listen_addresses 决定服务器监听哪些网络地址;连接认证规则则由 pg_hba.conf 控制。网络防火墙与数据库自身认证规则需要配合使用,不能只依赖其中一层。

# postgresql.conf 示例:只监听本机和指定内网地址。
# 具体地址应替换为服务器实际配置,修改后通常需要重载或重启,
# 以服务版本和配置项要求为准。
listen_addresses = '127.0.0.1,10.20.0.10'

# pg_hba.conf 示例:只允许指定应用网段通过 TLS 连接 appdb。
# SCRAM-SHA-256 是 PostgreSQL 支持的口令认证方法之一。
# CIDR 网段应替换为实际、尽可能小的可信网段。
hostssl  appdb  app_runtime  10.20.1.0/24  scram-sha-256

hostssl 规则要求连接使用 SSL/TLS;它本身并不负责配置证书,也不等于客户端一定验证了服务器身份。客户端应按驱动支持的方式启用证书验证,生产环境不要为了“连得上”而关闭证书校验。证书颁发、轮换和主机名验证需要结合部署平台配置。

pg_hba.conf 按顺序匹配规则。较宽泛的规则如果排在前面,可能使后面的限制规则无法生效。修改后可以检查配置是否有效,并核对服务器实际加载的规则;远程连接失败时,也应检查数据库监听地址、安全组、路由、认证方法和客户端 SSL 设置,而不是仅修改口令。

不要将“换一个不常见的数据库端口”当作主要防护。更改端口可能减少一些简单扫描噪声,但不能替代防火墙、身份验证和最小权限。

四、凭据管理与应用连接

数据库口令应足够长、不可复用,并由密钥管理系统、部署平台的 Secret 功能或受限权限的配置文件注入。不要把密码提交到 Git 仓库,也不要将密码直接拼进可被进程列表、日志或 shell 历史记录记录的命令中。环境变量可以作为部署时传递配置的一种方式,但并非天然安全:它们仍可能被有权限查看进程或运行环境的人读取。

以下 Python 示例使用环境变量提供连接信息。示例使用 Psycopg 3;运行前安装驱动,并在部署环境中设置相应变量。sslmode=verify-full 要求 TLS 并验证服务器证书和主机名,前提是客户端能找到受信任的 CA 证书,且连接主机名与证书匹配。

import os
import psycopg

conninfo = (
    f"host={os.environ['DB_HOST']} "
    f"port={os.environ.get('DB_PORT', '5432')} "
    f"dbname={os.environ['DB_NAME']} "
    f"user={os.environ['DB_USER']} "
    f"password={os.environ['DB_PASSWORD']} "
    "sslmode=verify-full"
)

with psycopg.connect(conninfo) as conn:
    with conn.cursor() as cur:
        cur.execute(
            "SELECT id, email FROM customers WHERE id = %s",
            (123,)
        )
        rows = cur.fetchall()
        print(rows)

参数化查询中的占位符写法取决于数据库驱动。上例中的 %s 是 Psycopg 的参数占位符,不是 Python 字符串格式替换。不要使用字符串拼接把用户输入写入 SQL:

# 不安全:输入内容被拼进 SQL 字符串。
query = "SELECT * FROM customers WHERE email = '" + email + "'"

# 安全的基本方式:使用驱动提供的参数绑定。
cur.execute(
    "SELECT * FROM customers WHERE email = %s",
    (email,)
)

参数化查询主要用于避免把数据值当作 SQL 语法执行。它不能自动保护动态表名、列名或排序方向,因为许多驱动不允许将这些 SQL 标识符作为普通参数绑定。确实需要动态标识符时,应使用驱动提供的标识符引用接口,或从严格的白名单中选择;不要把未经验证的用户输入直接拼接进 SQL。

连接凭据轮换也需要有计划。可先创建或更新新凭据,在应用部署完成并确认连接正常后再撤销旧凭据,避免轮换时造成服务中断。对于无法支持短期凭据的系统,可以通过定期轮换、访问审计和限制凭据可见范围降低风险。

五、备份不等于恢复:建立可验证的流程

备份的价值取决于能否在需要时恢复。制定策略前,先明确两个目标:

  • RPO(恢复点目标):可接受丢失多少时间的数据。它影响备份频率和是否需要连续归档日志。
  • RTO(恢复时间目标):可接受服务中断多久。它影响恢复流程、基础设施准备和演练频率。

较小的系统可以从每日逻辑备份开始,但这不适合所有场景。若业务要求较低的数据丢失窗口,单纯的每日备份可能不够,需要评估 PostgreSQL 的 WAL 归档与时间点恢复(PITR)。WAL 归档通常需要进行实例级配置、持续监控归档状态,并保留基础备份及其对应的日志;它不是只运行一次 pg_dump 就能获得的能力。

使用 pg_dump 创建逻辑备份

自定义格式备份可以用于选择性恢复,并由 pg_restore 还原。下面的命令通过环境变量传递密码,避免把密码直接写在命令行中;也可以使用权限严格的 .pgpass 文件或平台提供的凭据注入机制。实际部署时还应配置备份目录权限、文件保留周期和异地副本。

# 需在安全的运维环境中设置环境变量,不能将真实口令写入脚本仓库。
export PGPASSWORD="$DB_PASSWORD"

# -Fc:使用 pg_dump 自定义格式,输出单个归档文件。
pg_dump \
  --host="$DB_HOST" \
  --port="${DB_PORT:-5432}" \
  --username="$DB_USER" \
  --dbname="$DB_NAME" \
  --format=custom \
  --file="appdb_$(date +%F).dump"

unset PGPASSWORD

PGPASSWORD 便于演示,但环境变量可能在某些系统或运行环境中被有权限的进程查看。生产备份任务应根据平台选择更合适的凭据管理方式,并确保备份任务账号只拥有所需的读取权限。还要避免把备份文件写入 Web 可访问目录。

pg_dump 备份单个数据库中的数据库对象和数据,不会完整替代实例级配置、角色等全局对象的备份。可以另行使用 pg_dumpall --globals-only 导出全局对象定义,但其中可能包含角色信息,文件应按敏感数据保护。恢复前须检查目标实例版本兼容性、角色依赖和权限配置。

# 导出 PostgreSQL 全局对象定义。
# 输出可能含敏感角色信息,应存放在受限位置。
pg_dumpall \
  --host="$DB_HOST" \
  --port="${DB_PORT:-5432}" \
  --username="$ADMIN_USER" \
  --globals-only \
  > globals.sql

命令中的 > 是经过 HTML 转义后的输出重定向符号,放入 shell 中执行时应写成普通的 > 字符。实际命令如下:

pg_dumpall \
  --host="$DB_HOST" \
  --port="${DB_PORT:-5432}" \
  --username="$ADMIN_USER" \
  --globals-only \
  > globals.sql

在复制本文代码到终端时,应将 HTML 显示的 > 还原为 shell 的重定向符号 >。如果编辑器提供复制原始代码的功能,应以实际字符为准。

在隔离环境中测试恢复

不要只检查备份文件是否存在或大小是否变化。定期在隔离实例或临时数据库中执行恢复,确认数据可读、对象完整、应用可以正常连接。自定义格式备份可按以下方式还原:

# 创建空的测试数据库,避免覆盖生产库。
createdb \
  --host="$RESTORE_HOST" \
  --username="$RESTORE_ADMIN" \
  appdb_restore_test

pg_restore \
  --host="$RESTORE_HOST" \
  --username="$RESTORE_ADMIN" \
  --dbname=appdb_restore_test \
  --no-owner \
  appdb_2025-01-01.dump

--no-owner 会在恢复时不按备份中的所有者设置对象所有权,适合目标环境角色不同的情况,但也意味着必须另行确认恢复后对象归属和权限。若希望保留原有所有权,需先在目标环境准备相应角色,并根据业务需求决定是否使用该选项。恢复命令应先在测试环境验证,不要直接对生产库试跑。

备份文件可能包含全部业务数据,保密要求通常不低于在线数据库。至少要控制文件读写权限,对跨网络传输使用加密通道;如备份存储支持服务器端或客户端加密,应明确密钥由谁管理、如何轮换以及丢失密钥后的恢复方案。将备份放到另一个目录但仍在同一台机器上,不能有效应对磁盘损坏或整机故障。

六、生产环境中值得检查的几项边界

按需启用行级安全

若多个租户共享同一组表,PostgreSQL 行级安全(RLS)可以作为进一步隔离的一层。但策略必须正确覆盖查询、写入和角色继承等场景;表所有者和具备绕过 RLS 权限的角色可能不受普通策略限制。RLS 不能替代应用授权,也不能在未经测试的情况下直接开启后就视为租户隔离已经完成。

审计与监控要有明确目标

至少关注失败登录、异常连接来源、权限变更、备份任务失败、磁盘空间和连接数变化。数据库日志可能包含查询内容、账号、路径或个人数据,应限制日志访问并设定保留策略。若需要记录特定操作,应评估日志量、性能影响和敏感数据暴露风险,而不是无限制地记录所有 SQL。

升级与补丁属于安全配置的一部分

数据库版本、扩展和客户端驱动都可能涉及安全修复。升级前应查看官方发行说明,确认兼容性、备份情况和回滚方案。托管数据库的维护窗口和升级策略由服务商提供,仍需主动检查版本状态和公告。

七、常见错误与排查思路

  • 应用使用管理员账号:创建单独的运行时角色,逐项授予所需权限;开发、迁移和运行账号尽量分离。
  • 只收紧了现有表权限:新表权限可能仍不符合预期。确认对象创建者,并检查默认权限是否对该创建者生效。
  • 开放数据库端口给所有来源:收紧云防火墙和主机防火墙规则,同时检查数据库监听地址和访问控制文件。
  • 开启 TLS 却不验证证书:加密连接不必然证明连接到了正确的服务器。客户端应配置证书验证,并检查证书主机名。
  • 把密码放进代码或命令行:改用密钥管理、受限配置文件或部署 Secret,并检查代码历史、日志和脚本副本中是否已经泄露。
  • 把 SQL 参数化理解为万能防注入:参数绑定主要保护数据值;表名、列名等动态标识符仍需白名单或安全引用。
  • 备份成功就认为恢复可用:定期执行隔离恢复演练,记录耗时、失败原因和依赖的角色、证书及配置。
  • 备份与生产数据放在同一故障域:安排独立存储或异地副本,并验证访问权限、保留周期和恢复所需密钥。
  • 权限问题靠不断扩大授权解决:先确认当前连接角色、对象所有者、schema 权限及默认权限,再授予具体缺失权限,不要轻易授予超级用户权限。

八、可以照着执行的基础检查清单

  1. 列出数据库、应用、迁移、报表和运维账号,确认每个账号都有明确用途和负责人。
  2. 确认应用账号不是超级用户,且不能创建数据库、角色或复制数据,权限与实际业务相符。
  3. 检查数据库监听地址、访问控制规则及云防火墙,移除不必要的公网入口。
  4. 验证远程连接使用 TLS,并确认客户端验证服务器证书,而非仅建立加密连接。
  5. 检查凭据是否进入代码仓库、日志、工单、镜像或命令历史;必要时轮换已暴露的密码。
  6. 建立符合 RPO 和 RTO 要求的备份计划,明确备份负责人、存储位置、保留周期和告警方式。
  7. 在隔离环境中恢复备份,核对数据、对象权限和应用连接,并记录实际恢复时间。
  8. 安排数据库和驱动的版本更新,变更前备份并准备验证及回滚步骤。

数据库安全的基础不是某个单独参数,而是一组相互配合的边界:账号只拿到必要权限,网络只允许可信来源,应用安全地处理 SQL 和凭据,备份在故障后确实能够恢复。先完成这些基本措施,再根据数据敏感度、业务可用性要求和合规责任增加审计、密钥轮换、行级隔离与灾难恢复能力,通常比一次性堆叠复杂功能更可靠。

© 版权声明

相关文章