跳转至

Vertica 访问控制最佳实践

编译:JiangChong

原文:Best Practices for Creating Access Policies on Vertica(2023-06-01)

📝 文章说明:本文基于 Vertica 官方 KB 原文翻译整理。原文发布于 2023 年,仅讨论了表级 Access Policy(行级和列级数据过滤),涵盖了创建方法和性能测试。译者根据当前 Vertica 技术架构(v26.2),补充了 认证层面、网络层面、Eon 子集群级别访问隔离 三种纵深防御方案,并新增了多层访问控制对比表、决策树和完整配置示例。新增内容位于「访问控制的完整层次」到「多层访问控制方案选型决策树」章节。

1. 概述

Vertica 分析数据库的访问策略作用于列和行,为表中的数据提供额外安全性。您可以创建访问策略,通过将访问策略应用于表来限制哪些用户可以访问某些数据。访问策略标识您要限制的任何行、列或角色。

例如,您可以创建一个访问策略,以防止特定角色查看员工表中的员工薪资。拥有该角色的用户在针对该表运行查询或报表时将无法看到员工薪资。

完整的访问控制体系不只包括表级 Access Policy。本文首先介绍 Access Policy 的创建和性能测试(原文内容),然后补充认证层、网络层和 Eon 子集群级访问控制方案,帮助你构建纵深防御的安全架构。

2. 如何创建访问策略

按如下步骤创建访问策略:

  1. 创建表:
=> CREATE TABLE customers_table(custID INT, password VARCHAR, SSN VARCHAR);
  1. 在现有表上创建行或列访问策略。以下示例展示了如何创建列访问策略:
=> CREATE ACCESS POLICY on customers_table
 FOR COLUMN SSN
 CASE
 WHEN enabled_role('manager') THEN SSN
 ELSE substr(SSN, 8, 4)
 END
 ENABLE;

该策略允许 manager 角色查看完整的 SSN 列,并限制其他角色仅查看 SSN 列的最后四位数字。

  1. 运行以下 SQL 查询:
=> SELECT * FROM customers_table;

在后台,Vertica 使用该访问策略,并将此查询重写为并执行:

=> SELECT * FROM (SELECT custID, password, CASE WHEN enabled_role('manager')
 THEN SSN ELSE substr(SSN, 8, 4) END AS SSN FROM customers_table) customers_table;

3. 性能影响

当 Vertica 针对表运行此重写后的查询时,该过程在性能方面会比第 3 步中的简单 SELECT 语句更昂贵。

针对包含访问策略的表运行的 SQL 查询在执行前会被重写,以实现访问策略定义的行为。

要确定 SQL 查询可能有多昂贵,请使用下一节中的详细信息测试重写后的查询,以确定其对系统性能的影响。

下一节解释了如何测试此重写后的 SQL 查询,以收集处理信息,确保您获得最佳性能。

4. 如何测试性能

在实施访问策略之前,请务必针对表测试重写后的查询。使用以下示例作为指南,确定重写后查询的内容。

在本示例中:

  • User1 已被授予 manager 角色,并且是 DBADMIN 用户
  • User2 不是 manager
  • john 不是 manager

使用 \timing 元命令来确定在访问策略之外运行查询所需的时间。有关更多信息,请参阅 Vertica 文档中的 \timing

4.1 列访问策略测试示例

此示例展示了仅启用列访问策略时的查询和输出。

两个用户查询 customers 表

User1=> SELECT * FROM customers;

 name |     ssn     |   type
-----+-------------+--------------
 john | 123-44-6789 | active
 adam | 123-45-6789 | active
 alex | 123-54-6789 | inactive
(3 rows)
User2=> SELECT * FROM customers where type = 'active';

 name |     ssn     |  type
-----+-------------+--------
 adam | 123-45-6789 | active
 john | 123-44-6789 | active
(2 rows)

User1(DBADMIN 用户)创建以下列访问策略

User1=> CREATE ACCESS POLICY ON customers
 FOR column ssn
 CASE
 WHEN enabled_role('manager') THEN ssn
 ELSE
 substr(ssn, 8, 4)
 END
 ENABLE;

该访问策略允许已被授予 manager 角色的用户查看 customers 表中 ssn 列的全部内容,并限制其他用户仅查看最后四位数字。

以 User2(非 manager)身份运行查询

启用该访问策略后,运行以下查询:

User2=> SELECT * FROM customers WHERE type = 'active';

查询转换

启用列访问策略后,Vertica 将上述查询转换为:

User2=> SELECT * FROM (SELECT customers.name, (substr(customers.ssn,
8, 4))::VARCHAR(80) AS ssn, customers.type FROM public.customers)
customers WHERE type = 'active';

在转换后的查询中:

  • SELECT * FROMcustomers WHERE type = 'active' 表示转换前的查询文本。
  • Vertica 将 (select customers.name, (substr(customers.ssn, 8, 4))::varchar(80) AS ssn, customers.type from public.customers) 作为子查询替换到查询中,这代表了决定返回哪些数据的访问策略部分。

如果您知道要创建的访问策略以及用户可能运行的 SQL 查询,您可以使用此信息确定转换后查询的语法。然后在访问策略之外测试该查询,以确定性能结果。

User2(非 manager)的输出为:

name | ssn  |  type
-----+------+--------
adam | 6789 | active
john | 6789 | active
(2 rows)

以 User1(manager 角色)身份运行查询

启用该访问策略后,运行以下查询:

User1=> select * from customers where type = 'active';

查询转换

启用列访问策略后,Vertica 将上述查询转换为:

User1=> SELECT * FROM(SELECT customers.name, customers.ssn,
customers.type, customers.epoch FROM public.customers) customers WHERE
type= 'active';

与之前 User2 运行查询的示例类似:

  • SELECT * FROMcustomers WHERE type = 'active' 表示转换前的查询文本。
  • Vertica 将 (SELECT customers.name, customers.ssn, customers.type, customers.epoch FROM public.customers) 作为子查询替换到查询中,这代表了决定返回哪些数据的访问策略部分。

使用此信息确定转换后查询的语法,然后在访问策略之外测试该查询,以确定性能结果。

User1(manager)的输出为:

name |     ssn     |  type
-----+-------------+-------
adam | 123-45-6789 | active
john | 123-44-6789 | active
(2 rows)

4.2 行访问策略测试示例

此示例展示了仅启用行访问策略时的查询和输出。

User1(DBADMIN 用户)创建以下行访问策略

User1=> CREATE access policy on customers FOR rows WHERE name = current_user() enable;

该访问策略仅允许与表中用户名匹配的用户查看该行的内容。

以用户 john 身份运行查询

john=> SELECT * FROM customers WHERE type = 'active';

查询转换

启用行访问策略后,Vertica 将上述查询转换为:

SELECT * FROM (SELECT customers.name, customers.ssn, customers.type
FROM public.customers WHERE (customers.name = 'john'::VARCHAR(128)))
customers WHERE type = 'active';

在转换后的查询中:

  • SELECT * FROMcustomers WHERE type = 'active' 表示转换前的查询文本。
  • Vertica 将 SELECT customers.name, customers.ssn, customers.type FROM public.customers WHERE (customers.name = 'john'::varchar(128))) 作为子查询替换到查询中,这代表了决定返回哪些数据的访问策略部分。

同样,在实施访问策略之前,使用此信息确定转换后查询的语法,然后在访问策略之外测试该查询,以确定性能结果。

用户 john 的输出为:

name |     ssn     |  type
-----+-------------+--------
john | 123-44-6789 | active
(1 row)

4.3 行和列访问策略组合测试示例

此示例展示了在表上同时启用上述行和列访问策略时的输出。

以用户 john 身份运行查询

john=> SELECT * FROM customers WHERE type = 'active';

查询转换

同时启用行和列访问策略后,Vertica 将上述查询转换为:

SELECT * FROM (SELECT customers.name, (substr(customers.ssn, 8, 4))
::varchar(80) AS ssn, customers.type FROM public.customers WHERE
(customers.name = 'john'::VARCHAR(128))) customers WHERE type =
'active';

在上述查询中:

  • SELECT * FROMcustomers WHERE type = 'active' 表示转换前的查询文本。
  • Vertica 将 (SELECT customers.name, (substr(customers.ssn, 8, 4))::VARCHAR(80) AS ssn, customers.type FROM public.customers WHERE (customers.name = 'john'::varchar(128))) 作为子查询替换到查询中,这代表了决定返回哪些数据的行和列访问策略部分。

同样,在实施访问策略之前,使用此信息确定转换后查询的语法,然后在访问策略之外测试该查询,以确定性能结果。

用户 john 的输出为:

name | ssn  |  type
-----+------+--------
john | 6789 | active
(1 row)

5. 访问控制的完整层次

上述内容聚焦于数据层的访问控制——即通过 CREATE ACCESS POLICY 对表内的行和列进行细粒度限制。但在实际生产环境中,访问控制是一个多层体系,数据层只是最内层。一个完整的安全架构包含三个协同工作的层次(按请求流入方向由外到内排列):

客户端 ──→ [网络层] ──→ [认证层] ──→ [数据层]
          从哪里、如何连接? 谁可以连接?  能看到什么数据?
          TLS + HOST      Auth Method   Access Policy
  • 网络层:通过 IP 地址范围和 TLS 要求限制连接的来源和加密方式。这是客户端接入的第一道关口——连接是否加密、来源 IP 是否合法
  • 认证层:决定用户能否通过身份验证建立数据库连接。Vertica 支持 8 种认证方法(hash、LDAP、Kerberos、TLS、OAuth 等),每种可独立配置优先级和回退策略
  • 数据层:即本文前述的 Access Policy,对已认证用户限制其能看到的行和列

在 Eon 模式下,子集群(Subcluster)机制为上述体系增加了路由维度——可以将不同租户的客户端路由到不同的子集群,结合认证策略,实现「同一个数据库,不同子集群使用不同认证方式」的效果。

6. 认证层面的访问控制

6.1 八种客户端认证方法

Vertica 支持以下认证方法,通过 CREATE AUTHENTICATION 创建,GRANT AUTHENTICATION 授予用户或角色:

方法 说明 本地(LOCAL) 远程(HOST)
trust 仅需用户名,无需密码
reject 直接拒绝连接
hash 用户名 + 密码(SHA-512 或 MD5)
gss Kerberos (GSS-API)
ident Ident 服务器查找用户名
ldap LDAP/AD 用户名密码验证
tls X.509 客户端证书(需双向 TLS)
oauth OAuth 2.0 访问令牌

6.2 认证记录的 HOST 作用域——按 IP 范围绑定

每个认证记录可指定其生效的连接来源:

  • LOCAL:仅限节点本地连接
  • HOST 'IP/CIDR':仅限指定 IP 范围的远程连接
  • HOST TLS 'IP/CIDR':仅限 TLS 加密的远程连接
  • HOST NO TLS 'IP/CIDR':仅限明文远程连接

这意味着认证策略本身支持按 IP 段区分——不同 IP 段的客户端可使用不同的认证方法:

-- 办公网段用户使用 LDAP 认证
CREATE AUTHENTICATION corp_ldap METHOD 'ldap' HOST '10.1.0.0/16';
GRANT AUTHENTICATION corp_ldap TO analysts;

-- 生产服务器使用 Kerberos 认证
CREATE AUTHENTICATION svc_kerberos METHOD 'gss' HOST '10.2.0.0/16';
GRANT AUTHENTICATION svc_kerberos TO etl_service_user;

-- 拒绝所有非 TLS 明文连接
CREATE AUTHENTICATION reject_plain METHOD 'reject' HOST NO TLS '0.0.0.0/0';

6.3 认证优先级与回退

当用户被授予多条认证记录时,Vertica 按优先级(priority)从高到低尝试认证。可通过 PRIORITY 参数显式控制:

ALTER AUTHENTICATION svc_kerberos PRIORITY 10;   -- 高优先级
ALTER AUTHENTICATION corp_ldap PRIORITY 5;       -- 低优先级

对于需要多因素认证的场景,可启用 FALLTHROUGH,失败后回退到次高优先级记录:

-- TLS 认证失败后回退到 LDAP(客户端未提供证书时)
CREATE AUTHENTICATION v_tls METHOD 'tls' HOST TLS '0.0.0.0/0' FALLTHROUGH;

7. 网络层面的接入控制

7.1 强制 TLS 加密

拒绝所有非 TLS 明文连接,确保所有远程访问都经过加密:

-- 拒绝所有 IPv4 明文连接
CREATE AUTHENTICATION reject_plain_v4 METHOD 'reject' HOST NO TLS '0.0.0.0/0';
-- 拒绝所有 IPv6 明文连接
CREATE AUTHENTICATION reject_plain_v6 METHOD 'reject' HOST NO TLS '::/0';

配合 TLS 配置参数(TLSMODE)可精确控制加密级别:

TLSMODE 说明
DISABLE 禁用 TLS(默认)
ENABLE 启用 TLS,不验证客户端证书
TRY_VERIFY 有有效证书则 TLS,无证书则明文
VERIFY_CA 要求来自受信 CA 的有效证书
VERIFY_FULL 最高级别(仅节点间/数据通道支持)
-- 设置客户端-服务器 TLS 为 VERIFY_CA 模式(需先导入 CA 证书和服务器证书)
ALTER TLS CONFIGURATION server TLSMODE 'VERIFY_CA';

7.2 节点间通信加密

除了客户端连接,还应加密 Vertica 节点间的数据传输(包括 Spread 控制通道和数据通道)。详见 Vertica 节点内数据通讯加密:

-- 检查当前网络安全状态
SELECT SECURITY_CONFIG_CHECK('NETWORK');

8. Eon 模式下的子集群级别访问隔离

8.1 核心思路:认证 + 路由 = 子集群级访问控制

Vertica 目前不支持直接将认证方法与子集群绑定(认证是数据库级别的),但可以通过认证 HOST 作用域 + 连接路由的组合实现等效效果:

┌─────────────────────────────────────────────────────────────────┐
│                    同一个 Eon 数据库                              │
│                                                                 │
│  ┌──────────────────────┐       ┌──────────────────────┐        │
│  │  Subcluster A (分析)  │       │  Subcluster B (ETL)  │        │
│  │  3 节点               │       │  2 节点               │       │
│  └───────┬──────────────┘       └────────┬─────────────┘        │
│          ↑                               ↑                      │
│    Routing Rule                     Routing Rule                │
│    ROUTE '10.1.0.0/16'              ROUTE '10.2.0.0/16'         │
│          ↑                               ↑                      │
│    ┌─────┴──────────┐            ┌───────┴────────┐             │
│    │ 办公网段用户     │            │ ETL 服务器      │             │
│    │ LDAP 认证       │            │ Kerberos 认证  │             │
│    │ TLS VERIFY_CA  │            │ TLS + MFA      │             │
│    └────────────────┘            └────────────────┘             │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │              公共存储 (S3/MinIO/HDFS)                     │   │
│  │        Access Policy: 行级/列级数据过滤                    │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

效果

  • 办公网段用户(10.1.0.0/16)通过 LDAP 认证后,查询自动在 Subcluster A 上执行
  • ETL 服务器(10.2.0.0/16)通过 Kerberos 认证后,查询自动在 Subcluster B 上执行
  • 两个用户群使用不同的认证方式,查询在不同的计算节点上执行,互不干扰
  • Access Policy 在数据层提供最终的行列级过滤

8.2 配置示例:完整五步实现子集群级访问控制

以下示例展示如何为一个 Eon 数据库配置「两个子集群使用不同认证策略」的访问控制体系。

第 1 步:创建子集群

# 为分析团队创建子集群
admintools -t db_add_subcluster -d mydb \
  -c sc_analytics --hosts=node04,node05,node06 --is-secondary

# 为 ETL 团队创建子集群
admintools -t db_add_subcluster -d mydb \
  -c sc_etl --hosts=node07,node08 --is-secondary

第 2 步:配置连接路由(将客户端 IP 段导向对应子集群)

首先在每个节点上创建网络地址(负载均衡组的前提):

-- 为子集群各节点创建网络地址
CREATE NETWORK ADDRESS addr04 ON node04 WITH '10.1.0.4';
CREATE NETWORK ADDRESS addr05 ON node05 WITH '10.1.0.5';
CREATE NETWORK ADDRESS addr06 ON node06 WITH '10.1.0.6';
CREATE NETWORK ADDRESS addr07 ON node07 WITH '10.2.0.7';
CREATE NETWORK ADDRESS addr08 ON node08 WITH '10.2.0.8';

然后创建负载均衡组并绑定子集群。SUBCLUSTER 形式必须FILTER 指定要包含的 IP 范围:

-- 创建负载均衡组,绑定子集群
-- FILTER 为必选参数:0.0.0.0/0 表示包含子集群内所有节点
CREATE LOAD BALANCE GROUP lbg_analytics
  WITH SUBCLUSTER sc_analytics
  FILTER '0.0.0.0/0'
  POLICY 'ROUNDROBIN';

CREATE LOAD BALANCE GROUP lbg_etl
  WITH SUBCLUSTER sc_etl
  FILTER '0.0.0.0/0'
  POLICY 'ROUNDROBIN';

-- 路由规则:按客户端 IP 段分配到不同子集群
CREATE ROUTING RULE rr_analytics
  ROUTE '10.1.0.0/16' TO LOAD BALANCE GROUP lbg_analytics;

CREATE ROUTING RULE rr_etl
  ROUTE '10.2.0.0/16' TO LOAD BALANCE GROUP lbg_etl;

从 Vertica 23.3 开始,还可以使用工作负载路由按 --workload 参数路由到子集群,而不依赖客户端 IP。语法为 CREATE ROUTING RULE 的第二形式(无规则名):CREATE ROUTING RULE ROUTE WORKLOAD 'etl' TO SUBCLUSTER sc_etl;

第 3 步:配置认证方法(不同 IP 段使用不同认证)

-- 分析团队(办公网段)→ LDAP 认证
CREATE AUTHENTICATION corp_ldap METHOD 'ldap' HOST TLS '10.1.0.0/16';
ALTER AUTHENTICATION corp_ldap SET
  host='ldap://ldap.corp.com',
  basedn='dc=corp,dc=com',
  binddn_prefix='cn=',
  binddn_suffix=',ou=analysts,dc=corp,dc=com';
GRANT AUTHENTICATION corp_ldap TO analysts_role;

-- ETL 团队(服务器网段)→ Kerberos 认证
CREATE AUTHENTICATION etl_kerberos METHOD 'gss' HOST '10.2.0.0/16';
GRANT AUTHENTICATION etl_kerberos TO etl_service_user;

-- 拒绝所有非 TLS 明文连接
CREATE AUTHENTICATION reject_plain METHOD 'reject' HOST NO TLS '0.0.0.0/0';

第 4 步:配置 TLS(可选,但强烈建议生产环境启用)

-- 创建 TLS 配置(需要先导入 CA 证书和服务器证书)
CREATE TLS CONFIGURATION server
  TLSMODE 'VERIFY_CA'
  CERTIFICATE server_cert
  CA CERTIFICATE ca_cert;

-- 配置节点间数据通道加密(可选但推荐)
ALTER TLS CONFIGURATION data_channel
  TLSMODE 'ENABLE'
  CA BUNDLE ca_bundle;
-- 验证 TLS 配置
SELECT * FROM TLS_CONFIGURATIONS;
SELECT SECURITY_CONFIG_CHECK('NETWORK');

第 5 步:配置数据层 Access Policy

-- 分析团队只能看自己部门的数据(行级限制)
CREATE ACCESS POLICY ON sales_data FOR ROWS
  WHERE department = current_user()
  ENABLE;

-- 非分析角色不可见 SSN 列(包括 ETL 用户)
CREATE ACCESS POLICY ON customers FOR COLUMN ssn
  CASE
    WHEN enabled_role('analysts_role') THEN ssn
    ELSE '***REDACTED***'
  END
  ENABLE;

关键理解:路由规则决定了「客户端连接到哪个子集群」,认证记录决定了「如何验证身份」,Access Policy 决定了「能看到什么数据」。三者独立配置但协同工作,形成完整的子集群级访问控制体系。

8.3 Eon 子集群隔离 vs 传统方案对比

维度 纯 Access Policy 纯认证控制 认证 + 路由 + Access Policy
认证方式差异化 ❌ 同一套认证 ✅ 可区分配 ✅ 按子集群区分
计算隔离 ❌ 共享所有节点 ❌ 共享所有节点 ✅ 不同子集群独立计算
网络层控制 ❌ 无 ✅ HOST 作用域 ✅ HOST + 路由
数据层隔离 ✅ 行列级 ❌ 无 ✅ 行列级
嘈杂邻居防护 ❌ 弱(仅资源池) ❌ 弱 ✅ 物理隔离
配置复杂度
适用场景 简单表级脱敏 统一入口多认证方式 Eon 生产多租户
Vertica 模式 均可 均可 仅 Eon

9. 多层访问控制方案选型决策树

需要访问控制
├── 仅需表级数据脱敏(掩码/过滤)?
│   └── → Access Policy(本文基础方案)
├── 需要不同用户群使用不同登录方式?
│   ├── 统一计算资源 → 认证层控制(HOST scoping + 多认证记录)
│   │   详见 [LDAP 认证最佳实践](ldap-authentication-best-practices.md) / [Vertica 与 Kerberos 认证集成原理](../../03.tech-deep-dive/kerberos-authentication-integration.md)
│   └── 需要计算隔离 →
│       └── Eon 模式?
│           ├── 是 → 认证 + 路由 + Access Policy(子集群级三层控制)
│           │   详见 Vertica 多租户实现最佳实践
│           └── 否 → Enterprise 模式:CREATE AUTHENTICATION + HOST scoping(无子集群路由)
├── 需要网络安全(强制 TLS / 限制 IP)?
│   ├── 仅强制 TLS → reject HOST NO TLS + TLSMODE
│   │   详见 [Vertica 双向 TLS 认证最佳实践](mutual-tls-auth-best-practices.md)
│   └── 按 IP 段区分访问 → HOST scoping + Routing Rules
│       详见 [Vertica 客户端连接负载均衡配置](../../09.original-research/04.system-operations/connection-load-balancing.md)
└── 需要全谱系隔离(数据 + 计算 + 认证均独立)?
    └── → Eon Sandbox 隔离(数据快照独立演进)
        详见 Vertica 多租户实现最佳实践

10. 性能考虑

在原有 Access Policy 性能测试的基础上,增加认证层和网络层会增加以下成本:

  • 认证成本:LDAP / Kerberos 认证涉及外部服务器通信(RTT),每次新连接需 1~100ms 不等。使用连接池可摊销此成本
  • TLS 握手成本:双向 TLS 握手每次新连接约 10~50ms。建议启用连接复用
  • 路由成本:Routing Rule 匹配在连接建立时执行一次,成本极低(<1ms)
  • Access Policy 重写成本:保持不变,详见本文「性能影响」和「如何测试性能」章节

⚠️ 认证和网络层成本仅在连接建立时产生,不影响查询执行性能。Access Policy 的查询重写成本则在每次查询时产生,是主要的性能关注点。

在实施访问策略时遵循这些最佳实践提示,可确保您的系统持续平稳高效地运行。此外,您还可以放心,您的敏感数据将受到良好的保护,免受未经授权用户的访问。

总结:Vertica 访问控制是一个纵深防御体系。表级 Access Policy(行列过滤)是最后一道防线,在此之前应先建立认证层(谁可以连接)和网络层(从哪里连接)。在 Eon 模式下,通过子集群路由 + HOST 作用域认证可以进一步实现「不同子集群使用不同认证策略」的计算+认证双重隔离,是多租户场景下的推荐方案。

扩展阅读