diff --git a/CHANGELOG.md b/CHANGELOG.md index 71dc55a4..fc656390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v2.1.0] (unreleased) + +- Join the bounded YashanDB 23.4.1.109 read-only path with a real least-privilege database account, HTTP MCP, masking, policy denial and durable audit in one live regression. +- Verify MCP Inspector 0.16.8 and Dify MCPClient 1.17.1 against the isolated instance. Rehearse database SELECT revocation, business database outage and independent PostgreSQL audit-store outage without delivering unmasked results. +- Keep writes, transactions, EXPLAIN, unverified SQL forms, server-verified TLS, HA and complete production support outside this release's YashanDB claim. See the [candidate acceptance record](docs/v2.1.0-acceptance.md). + ## [v2.0.0] - Add a fixed two-replica Kubernetes deployment for stateless read-only MCP traffic backed by shared PostgreSQL metadata and a separate audit database. Keep B5 transactions, stateful sessions and event replay disabled in this topology. diff --git a/Dockerfile b/Dockerfile index 3c189949..ad92a353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.26.9-bookworm AS build -ARG VERSION=v2.0.0 +ARG VERSION=v2.1.0 # Overridable module proxy for restricted networks, e.g. # `docker build --build-arg GOPROXY=https://goproxy.cn,direct`. ARG GOPROXY=https://proxy.golang.org,direct @@ -48,7 +48,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build mkdir -p /out \ FROM debian:bookworm-slim AS runtime -ARG VERSION=v2.0.0 +ARG VERSION=v2.1.0 ARG REVISION LABEL org.opencontainers.image.title="AgentSQL" \ org.opencontainers.image.source="https://github.com/cuipengdba/agentsql" \ diff --git a/Makefile b/Makefile index 4efb8b02..69a20e41 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ BINARY_DIR := bin -VERSION ?= v2.0.0 +VERSION ?= v2.1.0 GO_VERSION ?= 1.26.9 GOPROXY ?= https://goproxy.cn,direct SOURCE_DATE_EPOCH ?= 1704067200 diff --git a/README.md b/README.md index d92340b6..67d0b3d2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AgentSQL -![Version v2.0.0](https://img.shields.io/badge/Version-v2.0.0-blue) +![Version v2.1.0](https://img.shields.io/badge/Version-v2.1.0-blue) [![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](go.mod) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue)](LICENSE) [![Commercial License](https://img.shields.io/badge/License-Commercial-orange)](COMMERCIAL-LICENSE.md) @@ -15,7 +15,9 @@ v0.6.0 加入治理控制台、审批单次执行和 Vastbase G100 的受限只 v1.5.0 对达梦、金仓和 Vastbase 的指定实例深入验证真实 MCP 客户端、权限撤销、断库恢复及审计。v1.6.0 在相同指定实例和受控只读子集内补齐故障关闭。v1.7.0 为瀚高 9.0.10 企业版 PG 模式增加独立 `highgo` 类型及有界只读路径,并为金仓 V9R1C10 PG 模式补充证书校验与凭据轮换。v1.8.0 在指定 Vastbase G100 Build 9 PG 模式实例验证 `verify-full`、证书和账号轮换、MCP 双客户端、脱敏与审计;主备高可用和完整生产支持仍未验收,见[发布说明](docs/release-notes-v1.8.0.md)。v1.9.0 增加 Dify / MCP Inspector 接入说明、复用 Agent 与数据源的目标 SQL 离线解释及真实调用审计定位;见[发布说明](docs/release-notes-v1.9.0.md)。v2.0.0 增加固定双网关 Kubernetes 配置和 PostgreSQL 单表列策略正式登记 API,见[发布说明](docs/release-notes-v2.0.0.md)及[双网关操作指南](deploy/kubernetes/README.md)。 -**验证范围:**v1.0.0 的列策略、人员委托及真实客户端结果见[验收记录](docs/v1.0-acceptance.md);v1.1.0 的审批与 B5 双身份结果见[验收记录](docs/v1.1.0-acceptance.md);v1.2.0 的 OAuth 互通结果见[验收记录](docs/v1.2.0-acceptance.md);v1.3.0 的 MySQL 列授权边界见[验收记录](docs/v1.3.0-acceptance.md);v1.4.0 的共享登录预算与双节点恢复见[验收记录](docs/v1.4.0-acceptance.md);v1.5.0 的三款国产数据库指定子集深度验证见[验收记录](docs/v1.5.0-acceptance.md);v1.6.0 的故障关闭与恢复证据见[验收记录](docs/v1.6.0-acceptance.md);v1.7.0 的瀚高和金仓证据见[正式发布验收](docs/v1.7.0-release-evidence.md);v1.8.0 的 Vastbase TLS 范围见[本版验收](docs/v1.8.0-acceptance.md);v1.9.0 接入向导改进见[本版验收](docs/v1.9.0-acceptance.md);v2.0.0 双网关与管理端列登记见[本版验收](docs/v2.0.0-acceptance.md)。厂商结论只覆盖指定实例、模式及合成数据。以下命令须以 [GitHub Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.0.0)、签名及镜像匿名拉取验收为准。 +v2.1.0 在 YashanDB 23.4.1.109 指定实例、独立客户端 23.4.7.100 和受限只读 SQL 子集内,验证最小权限账号的真实 HTTP MCP、R010、脱敏、独立持久审计、MCP Inspector/Dify 以及断库恢复;并补齐已认证 SQL 工具的数据源可见性预检拒绝审计。其他崖山版本、TLS、主备和完整生产支持未验证,见[发布说明](docs/release-notes-v2.1.0.md)与[候选验收](docs/v2.1.0-acceptance.md)。 + +**验证范围:**v1.0.0 的列策略、人员委托及真实客户端结果见[验收记录](docs/v1.0-acceptance.md);v1.1.0 的审批与 B5 双身份结果见[验收记录](docs/v1.1.0-acceptance.md);v1.2.0 的 OAuth 互通结果见[验收记录](docs/v1.2.0-acceptance.md);v1.3.0 的 MySQL 列授权边界见[验收记录](docs/v1.3.0-acceptance.md);v1.4.0 的共享登录预算与双节点恢复见[验收记录](docs/v1.4.0-acceptance.md);v1.5.0 的三款国产数据库指定子集深度验证见[验收记录](docs/v1.5.0-acceptance.md);v1.6.0 的故障关闭与恢复证据见[验收记录](docs/v1.6.0-acceptance.md);v1.7.0 的瀚高和金仓证据见[正式发布验收](docs/v1.7.0-release-evidence.md);v1.8.0 的 Vastbase TLS 范围见[本版验收](docs/v1.8.0-acceptance.md);v1.9.0 接入向导改进见[本版验收](docs/v1.9.0-acceptance.md);v2.0.0 双网关与管理端列登记见[本版验收](docs/v2.0.0-acceptance.md);v2.1.0 崖山指定实例和入口拒绝审计见[本版验收](docs/v2.1.0-acceptance.md)。厂商结论只覆盖指定实例、模式及合成数据。以下命令须以 [GitHub Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.1.0)、签名及镜像匿名拉取验收为准。 ```text AI Agent → LLM / MCP Client → AgentSQL 网关 → PostgreSQL / MySQL @@ -39,6 +41,7 @@ AI Agent → LLM / MCP Client → AgentSQL 网关 → PostgreSQL / MySQL - v1.3.0 MySQL 列授权:显式开启后,MySQL 8.0/8.4 指定 InnoDB 基表的受限单表 SELECT 使用独立只读业务账号与 PROCESS-only 元数据账号、表 ID 和 metadata lock 证明列权限;不扩大为所有 MySQL SELECT。 - v1.4.0 多网关登录预算:PostgreSQL metadata 部署对密码和 LDAP 登录使用共享的 60 秒 10 次预算;数据库故障返回 503,预算耗尽返回 429,SQLite 单实例仍使用本地预算。 - v2.0.0 固定双网关部署:两个 Kubernetes 网关共享 PostgreSQL metadata 与独立审计库,分开保存 B2 实例标识;无状态 MCP 只读请求通过 Service 转发,升级与回退需维护窗口。管理端可对 PostgreSQL 闭合绑定模式的单张普通表登记列策略。公开 Live Demo 仍采用单网关合成数据环境。 +- v2.1.0 崖山指定实例深度验证:临时最小权限账号在真实 MCP 中完成窄 SELECT、R010、手机号脱敏及持久审计;数据库权限撤销、业务库和独立审计库断线均按无原始结果交付验收。已认证的 SQL 工具对不可见数据源的预检拒绝也会记录无原始 SQL 的审计。 ## 生态与兼容 @@ -56,7 +59,7 @@ AgentSQL 作为数据库中立的安全网关,当前原生支持 PostgreSQL | TDSQL for PostgreSQL | 待目标环境验证 | PG 协议候选;商业版未实测,不能用 OpenTenBase 结果替代 | | TDSQL for MySQL | 待目标环境验证 | MySQL 协议候选;商业版未实测,不与 TDSQL for PostgreSQL 混同 | | OpenTenBase | 适配中 | 执行计划兼容修复实现中 | -| 崖山数据库 YashanDB | 🔵 受控只读 | 标准 parser 与窄 SELECT 路径已接通;独立客户端 23.4.7.100 的真库流水线测试通过 SELECT、列血缘、R010、手机号脱敏和三条测试审计;HTTP/MCP 与持久化审计未测 | +| 崖山数据库 YashanDB | 🔵 指定版本受控只读 | 23.4.1.109、`COMPAT_VECTOR=yashan`、独立客户端 23.4.7.100:最小权限账号经 HTTP MCP 完成受限 SELECT、R010、手机号脱敏、双客户端和独立持久审计;权限与故障关闭见[v2.1.0 验收](docs/v2.1.0-acceptance.md)。TLS、取消/超时、主备与完整生产支持未验证 | | 达梦数据库 DM | 🟢 指定实例防护链路实测通过 | DM8 Pack3、`COMPATIBLE_MODE=0`:v0.7.0 有界真库复验了 HTTP MCP 查询、列血缘、R010 表策略、手机号脱敏和持久化审计;R006 注释在解析阶段失败关闭,多行分页/JOIN/函数/系统目录未测,写入未放开 | | PolarDB for PostgreSQL | 协议路径已实测 | 指定 PG 15 社区镜像复用 `db_type=postgres`;非商业服务认证 | | PolarDB-X | 旧官方镜像有界实测 | `2.0.1` 单容器复用 `db_type=mysql`,两阶段 EXPLAIN 已适配;当前完整拓扑与商业服务待终验 | @@ -67,7 +70,7 @@ AgentSQL 作为数据库中立的安全网关,当前原生支持 PostgreSQL > **HighGo 支持边界:**仅限上述 9.0.10 企业版、PG 模式、单实例合成表的明确 schema、非恒真 WHERE、LIMIT 单表 SELECT。TLS、连接池故障切换、扩展类型/OID、其他模式及版本、生产负载、B2/B5 未验证;不代表厂商认证或完整生产支持。 -> **崖山客户端授权与运行:**已获厂家口头授权(C 客户端再分发),仓库无书面授权文件。v0.5.1 的 Linux 发行包与 GHCR 镜像构建使用独立客户端 23.4.7.100,并校验匹配架构的 SHA-256;正式资产须通过双架构内容与运行库检查后发布。自行从源码构建时需取得同版客户端、启用 `yashan` 构建标签并配置 `LD_LIBRARY_PATH`;缺少驱动或运行库时明确失败关闭。既有真库验证限于独立客户端的参数绑定、只读 SELECT、写入拒绝及指定流水线场景,详见 [YashanDB 驱动说明](docs/yashan-dialect.md#batch-71-integration)。 +> **崖山客户端授权与运行:**已获厂家口头授权(C 客户端再分发),仓库无书面授权文件。v0.5.1 的 Linux 发行包与 GHCR 镜像构建使用独立客户端 23.4.7.100,并校验匹配架构的 SHA-256;正式资产须通过双架构内容与运行库检查后发布。自行从源码构建时需取得同版客户端、启用 `yashan` 构建标签并配置 `LD_LIBRARY_PATH`;缺少驱动或运行库时明确失败关闭。v2.1.0 指定实例的真实 MCP 与故障注入证据见 [YashanDB 驱动说明](docs/yashan-dialect.md#v210-指定实例-http-mcp-候选验收)和[验收记录](docs/v2.1.0-acceptance.md)。 首批金仓 + 瀚高的证据边界、复现 SQL 与待厂商项见[联合案例验证记录](docs/joint-case-kingbase-highgo.md);公众号与技术群文案见[社区运营口径](docs/joint-case-operations.md)。 @@ -80,17 +83,17 @@ AgentSQL 作为数据库中立的安全网关,当前原生支持 PostgreSQL v0.4.0 起提供 `linux/amd64` 与 `linux/arm64` 两种原生 glibc 包:`uname -m` 为 `x86_64` / `amd64` 时使用 amd64 包,为 `aarch64` / `arm64` 时使用 arm64 包。两者均要求 glibc 2.28+ 且 systemd 为 PID 1;安装器会自动选择本机架构、校验发布包外层与包内哈希,并安装为 systemd 服务。musl / Alpine 与 CentOS 7 仍不支持: ```bash -curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.0.0/install.sh | sudo sh -s -- install --version v2.0.0 +curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.1.0/install.sh | sudo sh -s -- install --version v2.1.0 ``` 管道和 CI 的非 TTY 输出不会显示自动生成的管理员密码;root 可在 `/etc/agentsql/agentsql.env` 查看。交互终端会在首次创建凭据时显示一次,也可显式加 `--show-password`。离线环境同时取得 tarball 与同名 `.sha256` 后,校验、解压并从包根安装: -以下是 v2.0.0 的 amd64 资产名示例;v0.4.0 起 arm64 主机使用同版本的 `-linux-arm64` 资产名。 +以下是 v2.1.0 的 amd64 资产名示例;v0.4.0 起 arm64 主机使用同版本的 `-linux-arm64` 资产名。 ```bash -sha256sum -c agentsql-v2.0.0-linux-amd64.tar.gz.sha256 -tar -xzf agentsql-v2.0.0-linux-amd64.tar.gz -cd agentsql-v2.0.0-linux-amd64 +sha256sum -c agentsql-v2.1.0-linux-amd64.tar.gz.sha256 +tar -xzf agentsql-v2.1.0-linux-amd64.tar.gz +cd agentsql-v2.1.0-linux-amd64 sudo ./install.sh install ``` @@ -99,20 +102,20 @@ sudo ./install.sh install 脚本默认拉取 GitHub 最新稳定 Release 对应的精确 GHCR tag;该 tag 是 `linux/amd64` + `linux/arm64` 多架构镜像,Docker 会自动匹配运行架构。脚本生成权限为 `0600` 的 `.env`,使用 `agentsql-data` 命名卷,并固定绑定回环地址: ```bash -curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.0.0/scripts/quickstart.sh -o quickstart.sh && sh quickstart.sh --version v2.0.0 +curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.1.0/scripts/quickstart.sh -o quickstart.sh && sh quickstart.sh --version v2.1.0 ``` -GHCR 包必须由发布者设为 public,以上命令才能在未登录环境匿名拉取。先核对 `v2.0.0` 精确 tag 的公开状态与双架构 manifest;Docker 会从 manifest list 自动选择 `linux/amd64` 或 `linux/arm64`: +GHCR 包必须由发布者设为 public,以上命令才能在未登录环境匿名拉取。先核对 `v2.1.0` 精确 tag 的公开状态与双架构 manifest;Docker 会从 manifest list 自动选择 `linux/amd64` 或 `linux/arm64`: ```bash -docker pull ghcr.io/cuipengdba/agentsql:v2.0.0 +docker pull ghcr.io/cuipengdba/agentsql:v2.1.0 ``` 等价的单条 `docker run` 如下;随机值只通过当前 shell 环境传入,不写进命令行参数: ```bash export AGENTSQL_SECRET="$(openssl rand -base64 24)" AGENTSQL_ADMIN_USER=admin AGENTSQL_ADMIN_PASSWORD="$(openssl rand -base64 18)" -docker run -d --name agentsql --restart unless-stopped --security-opt no-new-privileges:true -p 127.0.0.1:7780:7780 -e AGENTSQL_SECRET -e AGENTSQL_ADMIN_USER -e AGENTSQL_ADMIN_PASSWORD -v agentsql-data:/var/lib/agentsql ghcr.io/cuipengdba/agentsql:v2.0.0 +docker run -d --name agentsql --restart unless-stopped --security-opt no-new-privileges:true -p 127.0.0.1:7780:7780 -e AGENTSQL_SECRET -e AGENTSQL_ADMIN_USER -e AGENTSQL_ADMIN_PASSWORD -v agentsql-data:/var/lib/agentsql ghcr.io/cuipengdba/agentsql:v2.1.0 ``` ### 3. 本地 Live Demo(一条命令,无需源码) @@ -121,7 +124,7 @@ docker run -d --name agentsql --restart unless-stopped --security-opt no-new-pri ```bash mkdir agentsql-demo && cd agentsql-demo -curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.0.0/deploy/quickstart/docker-compose.yml +curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.1.0/deploy/quickstart/docker-compose.yml docker compose up -d ``` @@ -164,7 +167,7 @@ docker compose --profile observability up -d --build 以下源码构建示例仅生成本机可执行文件。自行构建的文件没有经过正式发布资产的签名与分发验收: ```bash -make build VERSION=v2.0.0 +make build VERSION=v2.1.0 ./bin/agentsqlctl init-config -o config.yaml export AGENTSQL_SECRET="$(openssl rand -base64 24)" export AGENTSQL_ADMIN_USER='admin' @@ -269,7 +272,7 @@ v0.5 审计报表支持 CSV、JSONL、纯 Go 生成的 PDF,以及包含四类 ## 数据库兼容矩阵 -v2.0.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 + 21 款 NoSQL / 向量)。瀚高 HighGo 已作为独立的限定只读类型计入;金仓 KingbaseES V9 另列为指定实例 PG 协议路径,不计入 29 款;分类表共 30 行。🟢 表示解析、授权、受控执行、脱敏、审计链路在所述范围通过;🔵 受控只读=连接、元数据、只读查询子集;🔷 连接级=ping、版本、Schema、只读预览;🟡 待验证=等厂商环境。指定实例闭环不代表完整生产支持。 +v2.1.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 + 21 款 NoSQL / 向量)。瀚高 HighGo 已作为独立的限定只读类型计入;金仓 KingbaseES V9 另列为指定实例 PG 协议路径,不计入 29 款;分类表共 30 行。🟢 表示解析、授权、受控执行、脱敏、审计链路在所述范围通过;🔵 受控只读=连接、元数据、只读查询子集;🔷 连接级=ping、版本、Schema、只读预览;🟡 待验证=等厂商环境。指定实例闭环不代表完整生产支持。 | 类别 | 数据库 | 适配状态 | 边界说明 | | --- | --- | --- | --- | @@ -277,7 +280,7 @@ v2.0.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 | | MySQL 8.0 | 🟢 已适配 · 完整防护 | 全链路实测通过 | | | Oracle 23ai | 🔵 已适配 · 受控只读 | 连接 / 元数据 / 只读查询子集 | | | 达梦 DM8 | 🟢 指定场景闭环 | Pack3、COMPATIBLE_MODE=0:v1.5.0 完成客户端与权限撤销;v1.6.0 补齐审计故障、锁等待超时、取消与恢复复验;见[验收记录](docs/v1.6.0-acceptance.md) | -| | 崖山 YashanDB | 🔵 已适配 · 受控只读 | 标准 parser 接通,真库流水线通过 | +| | 崖山 YashanDB | 🔵 已适配 · 指定版本受控只读 | 23.4.1.109 指定实例的最小权限 HTTP MCP、R010、脱敏、持久审计和故障关闭通过;其他版本及生产拓扑未验证 | | | SQL Server 2025 | 🔵 已适配 · 受控只读 | 连接 / 元数据 / 只读查询子集 | | | Vastbase G100 | 🔵 指定版本受控只读 | V3.0 Build 9 PG 模式的有界 SELECT、表授权、脱敏、持久审计、客户端、权限撤销与停库恢复实测;v1.6.0 补齐故障关闭复验,v1.8.0 验证 `verify-full`、证书及凭据轮换和双客户端真调用;正式主备、其他 Build 与生产负载未测,见[验收记录](docs/v1.8.0-acceptance.md)。 | | | 瀚高 HighGo | 🔵 指定版本受控只读 | 独立 `highgo` 类型:9.0.10 企业版 PG 模式、单实例合成表的有界 SELECT、客户端、权限撤销、审计与故障关闭;见[验收记录](docs/v1.7.0-acceptance.md) | @@ -304,7 +307,7 @@ v2.0.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 | | Qdrant | 🔷 已适配 · 连接级 | 同口径 | | | Weaviate | 🔷 已适配 · 连接级 | 同口径 | -NoSQL / 向量目前是连接级、低阶支持;只读预览不代表完整查询、检索或服务端安全能力。21 款注册类型、默认端口及 HBase/Couchbase 的实际连接端口边界见 [NoSQL 支持范围](docs/nosql-support.md)。DM8、金仓、Vastbase 的指定实例在 v1.5.0 通过受控只读查询、真实 MCP 客户端、权限撤销、断库恢复、脱敏和持久审计;v1.6.0 在同一范围通过独立审计库故障、锁等待超时、客户端取消、数据库端终止及恢复复验,仍不声明完整生产支持。DM8 的 R006、多行分页、JOIN、函数和系统目录未验证,写入未放开。Oracle 仍限于既有有界证据。YashanDB 真库流水线测试已通过 SELECT、列血缘、R010、表列定向手机号脱敏及三条测试审计;该测试的认证、策略、审计存储端口为测试夹具,HTTP/MCP、持久化审计和最小权限账号与流水线的组合未测,见 [DM/Oracle 方言边界](docs/dm-oracle-dialect.md)及 [YashanDB 方言边界](docs/yashan-dialect.md)。所有实测仅基于指定容器镜像与合成用例,**并非数据库厂商官方认证**;协议兼容不等于安全语义相同。所有数据库请求须经 AgentSQL 网关,并使用专用最小权限账号;解析、授权或能力事实不明时一律 fail-closed。详情见 [SECURITY.md](SECURITY.md)。 +NoSQL / 向量目前是连接级、低阶支持;只读预览不代表完整查询、检索或服务端安全能力。21 款注册类型、默认端口及 HBase/Couchbase 的实际连接端口边界见 [NoSQL 支持范围](docs/nosql-support.md)。DM8、金仓、Vastbase 的指定实例在 v1.5.0 通过受控只读查询、真实 MCP 客户端、权限撤销、断库恢复、脱敏和持久审计;v1.6.0 在同一范围通过独立审计库故障、锁等待超时、客户端取消、数据库端终止及恢复复验,仍不声明完整生产支持。DM8 的 R006、多行分页、JOIN、函数和系统目录未验证,写入未放开。Oracle 仍限于既有有界证据。YashanDB 在 v2.1.0 指定实例中,使用真实最小权限账号、HTTP MCP 与独立持久审计库完成窄 SELECT、R010、手机号脱敏、双客户端、权限撤销和故障关闭;写入、事务、EXPLAIN、TLS、主备及完整生产支持仍未验证,见 [YashanDB 方言边界](docs/yashan-dialect.md)和[本版验收](docs/v2.1.0-acceptance.md)。所有实测仅基于指定容器镜像与合成用例,**并非数据库厂商官方认证**;协议兼容不等于安全语义相同。所有数据库请求须经 AgentSQL 网关,并使用专用最小权限账号;解析、授权或能力事实不明时一律 fail-closed。详情见 [SECURITY.md](SECURITY.md)。 控制面存储另计:SQLite 默认零配置,可 combined 存储;PostgreSQL 15+ 可 combined 或独立 metadata / audit 库(PG18 为基准)。使用 `agentsqlctl migrate-sqlite-to-postgres` 可从 SQLite 迁至单库或独立双库。 @@ -357,7 +360,7 @@ NoSQL / 向量目前是连接级、低阶支持;只读预览不代表完整查 ```bash go vet ./... go test -race -count=1 ./... -make build VERSION=v2.0.0 +make build VERSION=v2.1.0 ``` `pg_query_go` 要求 cgo;不要使用 `CGO_ENABLED=0` 或 Alpine/musl 构建。提交改动前请阅读 [SPEC](docs/SPEC.md),为行为变化补测试,并保持 `tests/corpus` 决策语料不被无意改写。 diff --git a/ROADMAP.md b/ROADMAP.md index 94ce1e5e..eb3ee64b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -30,6 +30,7 @@ AgentSQL 是面向 AI Agent 的数据库安全网关与 MCP Server。核心价 | v1.8.0 | 已发布 | Vastbase G100 V3.0 Build 9 PG 模式指定实例的 `verify-full`、证书及凭据轮换与 MCP Inspector / Dify 真调用复验;正式双架构资产、升级恢复、GHCR、官网和 Demo 验收见[发布证据](docs/v1.8.0-release-evidence.md)。临时许可不代表正式许可和主备切换验收。 | | v1.9.0 | 已发布([正式 Release](https://github.com/cuipengdba/agentsql/releases/tag/v1.9.0)) | Dify / MCP Inspector 接入说明、目标 SQL 离线解释、真实调用审计定位;正式资产、升级、GHCR、官网与 Demo 见[发布证据](docs/v1.9.0-release-evidence.md)。未取得真实客户首次接入任务完成率。 | | v2.0.0 | 已发布([正式 Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.0.0)) | 固定双网关 Kubernetes 部署及 PostgreSQL 单表列策略管理端登记;正式资产、双节点、升级恢复、GHCR、官网与 Demo 见[发布证据](docs/v2.0.0-release-evidence.md)。不承诺无停机滚动升级或任意水平扩容。 | +| v2.1.0 | 开发中 | 崖山 23.4.1.109 指定实例的最小权限账号、真实 HTTP MCP、脱敏、R010、独立持久审计与故障关闭;当前真库候选结果见[验收记录](docs/v2.1.0-acceptance.md)。正式支持范围以封版证据为准。 | ## v1.8.0–v3.0.0 候选开发顺序 diff --git a/SECURITY.md b/SECURITY.md index 8bf5f055..32359eef 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -40,7 +40,7 @@ v0.6.1 至 v0.9.0 的发布状态以 [GitHub Releases](https://github.com/cuipen - 🟢 **MySQL 8.0**:原生 `mysql` 路径;不继承 PostgreSQL B2/B5 证明。 - 🔵 **Oracle 23ai**:独立 `oracle` 严格 SELECT 子集;不能据此推断企业版或完整 Oracle 方言已通过。 - 🟢 **达梦 DM8**:Pack3、`COMPATIBLE_MODE=0` 实例的只读 SELECT、列血缘、R010 表策略、审计和手机号脱敏实测通过;R006 未验证,多行分页/JOIN/函数/系统目录未测,写入未放开。 -- 🔵 **崖山 YashanDB 23.4.1.109**:独立 `yashan` parser 与窄 SELECT 路径已接通;独立客户端真库流水线测试通过 SELECT、列血缘、R010、手机号脱敏及三条测试审计,最小权限账号的数据库写入拒绝另经原生探针验证。HTTP/MCP、持久化审计及最小权限账号与流水线的组合未测;EXPLAIN、写入继续失败关闭。 +- 🔵 **崖山 YashanDB 23.4.1.109**:独立 `yashan` parser 与窄 SELECT 路径已接通;在 Linux x86_64、`COMPAT_VECTOR=yashan` 和独立 C 客户端 23.4.7.100 上,最小权限账号经真实 HTTP MCP 完成允许、R010 拒绝、手机号脱敏和持久审计,MCP Inspector/Dify 各完成允许与拒绝。数据库 SELECT 撤权、业务库断线及独立审计库停机均验证无原始行交付。仅为指定实例及合成数据的受控只读子集;TLS、取消/超时、主备、写入、事务和 EXPLAIN 未验证或关闭,见 [v2.1.0 候选验收](docs/v2.1.0-acceptance.md)。 - 🔵 **SQL Server 2025(17.x)**:独立 `sqlserver` 严格只读子集;不提供完整 T-SQL、写入、B2/B5 或厂商认证。 - 🟢 **金仓 KingbaseES V9(PG 模式、单实例)**:仅 V9R1C10 合成表的 PG 协议基础闭环;可写 license 卷下启动成功,ksql CRUD/分页/标识符、宿主机 pgx/v5 `$1` 参数绑定 + SCRAM、只读角色拒写 `42501`、AgentSQL 受控 SELECT allow + 手机号脱敏 + 列血缘 + R006 注释拒绝 + allow/deny 审计链路实测通过;复用 `db_type=postgres`。见[真库验证记录](docs/kingbase-v9-verification.md)。 @@ -88,7 +88,7 @@ v0.6.1 至 v0.9.0 的发布状态以 [GitHub Releases](https://github.com/cuipen - **TiDB 7.5.1 / OceanBase CE 4.4.2.1**:MySQL 兼容候选,EXPLAIN 适配与回归 fixture 已落地;fixture 不是目标环境完整闭环或厂商认证。 - **PolarDB-X**:旧官方 `2.0.1` 单容器经 `mysql` 路径有界实测;完整 CN/DN/CDC、商业服务、分布式语义及 B2/B5 未证明。 -> **YashanDB 客户端再分发授权:**既有记录为厂家口头授权(C 客户端再分发),仓库无书面授权文件。v0.5.1 的 Linux 发行包与 GHCR 镜像已使用独立客户端 23.4.7.100;v0.7.0 正式资产须逐架构核对客户端内容与 SHA-256,结果以 GitHub Release 证据为准。服务器镜像自带库在既有 Go 驱动真库探针中返回 `YAS-02143`;独立客户端 23.4.7.100 通过既有原生驱动和真实数据库流水线测试。自行从源码构建时应取得匹配架构的独立客户端,在启动前设置 `LD_LIBRARY_PATH`,并以 `CGO_ENABLED=1` 与 `-tags yashan` 编入 Go 驱动;缺驱动或运行库时连接失败关闭。上述流水线测试未覆盖 HTTP/MCP 接口或持久化审计库。 +> **YashanDB 客户端再分发授权:**既有记录为厂家口头授权(C 客户端再分发),仓库无书面授权文件。正式 Linux 包与 GHCR 镜像须逐架构核对独立客户端 23.4.7.100 的内容与 SHA-256,结果以对应 Release 证据为准。服务器镜像自带库在早期 Go 驱动探针中返回 `YAS-02143`;本轮使用独立客户端通过真实 HTTP MCP 与持久审计测试。自行从源码构建时应取得匹配架构的客户端,在启动前设置 `LD_LIBRARY_PATH`,并以 `CGO_ENABLED=1` 与 `-tags yashan` 编入 Go 驱动;缺驱动或运行库时连接失败关闭。候选版补齐已认证 SQL 工具的数据源可见性预检拒绝审计,未解析 SQL 不写入该记录;验收边界见对应文档。 接入状态的可复现证据见[兼容性调研](docs/ecosystem-db-compat-research.md)、[v0.5 发布说明](docs/release-notes-v0.5.md)和[金仓/瀚高联合案例](docs/joint-case-kingbase-highgo.md)。产品线区分参见腾讯官方的 [TDSQL PostgreSQL 版](https://cloud.tencent.com/document/product/1129)、[TDSQL MySQL 版](https://cloud.tencent.com/product/dcdb)以及 OpenTenBase 的[独立 TXSQL 下载入口](https://docs.opentenbase.org/en/download/);协议相容不等于安全语义相同。 diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index 545b3d02..e18a83c2 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -1,6 +1,6 @@ -# AgentSQL v2.0.0 双网关 Kubernetes 部署 +# AgentSQL v2.1.0 双网关 Kubernetes 部署 -此清单只针对**两个节点、固定两个副本、无状态 MCP 只读请求**。运行配置把 HTTP 状态会话、事件续传和 B5 事务关闭;这不是已有单节点 B5 能力的迁移方案。真实生产支持范围以[v2.0.0 设计与验收](../../docs/v2.0.0-design.md)和本版发布证据为准。 +此清单只针对**两个节点、固定两个副本、无状态 MCP 只读请求**。运行配置把 HTTP 状态会话、事件续传和 B5 事务关闭;这不是已有单节点 B5 能力的迁移方案。双网关拓扑以[v2.0.0 设计与验收](../../docs/v2.0.0-design.md)为基线,v2.1.0 的数据库支持边界见[本版发布说明](../../docs/release-notes-v2.1.0.md)。 ## 准备 @@ -18,11 +18,11 @@ kubectl -n agentsql create secret generic agentsql-migration --from-env-file=/se kubectl -n agentsql apply -f deploy/kubernetes/configmap.yaml ``` -## 首次安装或从 v1.9.0 升级 +## 首次安装或从 v2.0.0 升级 -先对两个 PostgreSQL 库做成套备份并记录旧镜像摘要。`migrate` 可能执行 DDL;即使本版 schema 未变,也要记录检查结果。Job 不在基础 `kustomization.yaml` 中,避免普通部署隐式迁移。**B2 对活跃网关的二进制摘要要求一致,v1.9.0 与 v2.0.0 不能混合提供服务;此部署使用 `OnDelete`,版本切换必须安排维护窗口。** +先对两个 PostgreSQL 库做成套备份并记录旧镜像摘要。`migrate` 可能执行 DDL;即使本版 schema 未变,也要记录检查结果。Job 不在基础 `kustomization.yaml` 中,避免普通部署隐式迁移。**B2 对活跃网关的二进制摘要要求一致,v2.0.0 与 v2.1.0 不能混合提供服务;此部署使用 `OnDelete`,版本切换必须安排维护窗口。** -从 v1.9.0 升级时,先执行下列维护窗口操作;首次安装跳过这一段。 +从 v2.0.0 升级时,先执行下列维护窗口操作;首次安装跳过这一段。 ```sh kubectl -n agentsql scale statefulset/agentsql --replicas=0 @@ -35,11 +35,11 @@ sleep 30 ```sh kubectl -n agentsql apply -f deploy/kubernetes/jobs/migrate.yaml -kubectl -n agentsql wait --for=condition=complete job/agentsql-migrate-v2-0-0 --timeout=300s -kubectl -n agentsql logs job/agentsql-migrate-v2-0-0 +kubectl -n agentsql wait --for=condition=complete job/agentsql-migrate-v2-1-0 --timeout=300s +kubectl -n agentsql logs job/agentsql-migrate-v2-1-0 kubectl -n agentsql apply -f deploy/kubernetes/jobs/schema-check.yaml -kubectl -n agentsql wait --for=condition=complete job/agentsql-schema-check-v2-0-0 --timeout=300s -kubectl -n agentsql logs job/agentsql-schema-check-v2-0-0 +kubectl -n agentsql wait --for=condition=complete job/agentsql-schema-check-v2-1-0 --timeout=300s +kubectl -n agentsql logs job/agentsql-schema-check-v2-1-0 bash deploy/kubernetes/preflight.sh agentsql ``` diff --git a/deploy/kubernetes/jobs/migrate.yaml b/deploy/kubernetes/jobs/migrate.yaml index e0728a98..d5f46373 100644 --- a/deploy/kubernetes/jobs/migrate.yaml +++ b/deploy/kubernetes/jobs/migrate.yaml @@ -4,7 +4,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: agentsql-migrate-v2-0-0 + name: agentsql-migrate-v2-1-0 labels: app.kubernetes.io/name: agentsql spec: @@ -21,7 +21,7 @@ spec: type: RuntimeDefault containers: - name: migrate - image: ghcr.io/cuipengdba/agentsql:v2.0.0 + image: ghcr.io/cuipengdba/agentsql:v2.1.0 command: - /usr/local/bin/agentsqlctl - migrate diff --git a/deploy/kubernetes/jobs/schema-check.yaml b/deploy/kubernetes/jobs/schema-check.yaml index f58e9b1b..3691b148 100644 --- a/deploy/kubernetes/jobs/schema-check.yaml +++ b/deploy/kubernetes/jobs/schema-check.yaml @@ -3,7 +3,7 @@ apiVersion: batch/v1 kind: Job metadata: - name: agentsql-schema-check-v2-0-0 + name: agentsql-schema-check-v2-1-0 labels: app.kubernetes.io/name: agentsql spec: @@ -20,7 +20,7 @@ spec: type: RuntimeDefault containers: - name: schema-check - image: ghcr.io/cuipengdba/agentsql:v2.0.0 + image: ghcr.io/cuipengdba/agentsql:v2.1.0 command: - /usr/local/bin/agentsqlctl - migrate diff --git a/deploy/kubernetes/preflight.sh b/deploy/kubernetes/preflight.sh index 99d35d11..d49ea774 100644 --- a/deploy/kubernetes/preflight.sh +++ b/deploy/kubernetes/preflight.sh @@ -25,14 +25,14 @@ done rendered="$(kubectl kustomize "$script_dir")" if ! grep -Fq 'replicas: 2' <<<"$rendered" || ! grep -Fq 'type: OnDelete' <<<"$rendered" || - ! grep -Fq 'image: ghcr.io/cuipengdba/agentsql:v2.0.0' <<<"$rendered" || + ! grep -Fq 'image: ghcr.io/cuipengdba/agentsql:v2.1.0' <<<"$rendered" || ! grep -Fq 'instance_id_file: "/var/lib/agentsql/instance-id"' <<<"$rendered" || [[ "$(grep -Fc 'driver: postgres' <<<"$rendered")" -lt 2 ]] || grep -Fq 'sqlite_path:' <<<"$rendered" || ! grep -Fq 'stateful: false' <<<"$rendered" || ! grep -Fq 'event_store_enabled: false' <<<"$rendered" || ! grep -Fq 'auto_migrate: false' <<<"$rendered"; then - echo 'preflight failed: expected fixed two-replica v2.0.0 safety profile is missing' >&2 + echo 'preflight failed: expected fixed two-replica v2.1.0 safety profile is missing' >&2 exit 1 fi diff --git a/deploy/kubernetes/statefulset.yaml b/deploy/kubernetes/statefulset.yaml index 1330b7b2..28cd707a 100644 --- a/deploy/kubernetes/statefulset.yaml +++ b/deploy/kubernetes/statefulset.yaml @@ -39,7 +39,7 @@ spec: topologyKey: kubernetes.io/hostname initContainers: - name: schema-check - image: ghcr.io/cuipengdba/agentsql:v2.0.0 + image: ghcr.io/cuipengdba/agentsql:v2.1.0 imagePullPolicy: IfNotPresent command: - /usr/local/bin/agentsqlctl @@ -66,7 +66,7 @@ spec: mountPath: /tmp containers: - name: gateway - image: ghcr.io/cuipengdba/agentsql:v2.0.0 + image: ghcr.io/cuipengdba/agentsql:v2.1.0 imagePullPolicy: IfNotPresent args: - serve diff --git a/deploy/quickstart/docker-compose.debug.yml b/deploy/quickstart/docker-compose.debug.yml index dab1a3f5..41105abb 100644 --- a/deploy/quickstart/docker-compose.debug.yml +++ b/deploy/quickstart/docker-compose.debug.yml @@ -1,6 +1,6 @@ services: seed: - image: ghcr.io/cuipengdba/agentsql:v2.0.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.1.0-quickstart gateway: image: agentsql-quickstart-gateway:debug volumes: diff --git a/deploy/quickstart/docker-compose.yml b/deploy/quickstart/docker-compose.yml index 02cd6de5..5bdca2a7 100644 --- a/deploy/quickstart/docker-compose.yml +++ b/deploy/quickstart/docker-compose.yml @@ -49,7 +49,7 @@ services: - demo-mysql seed: - image: ghcr.io/cuipengdba/agentsql:v2.0.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.1.0-quickstart container_name: agentsql-quickstart-seed restart: "no" entrypoint: ["/usr/local/bin/agentsqlctl"] @@ -77,7 +77,7 @@ services: condition: service_healthy gateway: - image: ghcr.io/cuipengdba/agentsql:v2.0.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.1.0-quickstart container_name: agentsql-quickstart-gateway restart: unless-stopped environment: diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 18df2c2a..78b63cb2 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -2,7 +2,7 @@ AgentSQL 是面向 AI Agent 的数据库安全网关和生产级 MCP Server。元数据与审计记录默认保存在本地 SQLite,也可使用 PostgreSQL 控制面;业务数据库仍使用各自的 PostgreSQL 或 MySQL 连接。 -当前精确安装版本、下载链接和升级前检查见 [快速上手](GETTING_STARTED.md)与 [v2.0.0 发布说明](release-notes-v2.0.0.md)。固定双网关 Kubernetes 部署另见 [v2.0.0 操作指南](../deploy/kubernetes/README.md)。本文后部标有 v1.2.0 或其他旧版本号的发布命令与分阶段上线记录是历史示例;执行前须使用正式 Release 的同版资产、签名及镜像摘要。 +当前精确安装版本、下载链接和升级前检查见 [快速上手](GETTING_STARTED.md)与 [v2.1.0 发布说明](release-notes-v2.1.0.md)。固定双网关 Kubernetes 部署另见 [v2.1.0 操作指南](../deploy/kubernetes/README.md)。本文后部标有 v1.2.0 或其他旧版本号的发布命令与分阶段上线记录是历史示例;执行前须使用正式 Release 的同版资产、签名及镜像摘要。 ## 目录约定 diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 5882aee1..eb276a7b 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,6 +1,6 @@ # AgentSQL 快速上手 -> 本文说明 AgentSQL v2.0.0 的安装与使用。执行下列固定 v2.0.0 tag 的安装、Demo 和镜像命令前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产及签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.1.0 的安装与使用。执行下列固定 v2.1.0 tag 的安装、Demo 和镜像命令前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产及签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 AgentSQL 是放在 AI Agent 与业务数据库之间的数据库安全网关和生产级 MCP Server,让每条 SQL 在执行前经过身份、权限、规则和决策检查,并在可审计的边界内执行。 @@ -31,15 +31,15 @@ AgentSQL 不是 BI、ORM 或 Text2SQL,不负责把自然语言转换成 SQL, | 范围 | 当前支持 | 说明 | | --- | --- | --- | | 一键安装宿主 | `linux/amd64`、`linux/arm64`;glibc 2.28+、systemd | v0.4.0 起提供 linux/arm64 原生 glibc 包;`x86_64` / `amd64` 对应 amd64,`aarch64` / `arm64` 对应 arm64 | -| 其他宿主 | Docker / Docker Compose | v2.0.0 GHCR tag 须经 `linux/amd64` + `linux/arm64` multi-arch manifest 与匿名拉取核验;musl / Alpine、CentOS 7 与上述两种架构之外的平台不提供原生包 | +| 其他宿主 | Docker / Docker Compose | v2.1.0 GHCR tag 须经 `linux/amd64` + `linux/arm64` multi-arch manifest 与匿名拉取核验;musl / Alpine、CentOS 7 与上述两种架构之外的平台不提供原生包 | | 业务数据库 | MySQL 8;PostgreSQL 14–18 | 为 AgentSQL 创建独立、最小权限运行账号 | | 控制面存储 | SQLite;PostgreSQL 15+ | SQLite 为默认;PostgreSQL 可分离 metadata 与 audit | | 浏览器控制台 | `http://127.0.0.1:7780` | 默认绑定回环地址 | -| 本地 Live Demo | Docker Engine / Docker Desktop、Docker Compose v2 | 使用 v2.0.0 精确 tag 的 Compose 和经核验的 GHCR 镜像;无需克隆源码或构建 | +| 本地 Live Demo | Docker Engine / Docker Desktop、Docker Compose v2 | 使用 v2.1.0 精确 tag 的 Compose 和经核验的 GHCR 镜像;无需克隆源码或构建 | ## 路径 A:5 分钟零配置看效果(一条命令) -这是 v2.0.0 专用 Demo 模式:使用合成数据和固定演示身份,无需克隆源码、无需编辑任何配置、无需构建镜像。执行前须核对 v2.0.0 Git tag 中的 Compose 文件与其引用的三个 GHCR 镜像均已公开且可匿名拉取;首次启动会拉取镜像并播种固定演示数据。 +这是 v2.1.0 专用 Demo 模式:使用合成数据和固定演示身份,无需克隆源码、无需编辑任何配置、无需构建镜像。执行前须核对 v2.1.0 Git tag 中的 Compose 文件与其引用的三个 GHCR 镜像均已公开且可匿名拉取;首次启动会拉取镜像并播种固定演示数据。 只需一个 `docker-compose.yml`(在任意空目录执行): @@ -48,7 +48,7 @@ AgentSQL 不是 BI、ORM 或 Text2SQL,不负责把自然语言转换成 SQL, mkdir agentsql-demo && cd agentsql-demo # 2) 下载自包含 compose(仅这一个文件,约 2KB) -curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.0.0/deploy/quickstart/docker-compose.yml +curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.1.0/deploy/quickstart/docker-compose.yml # 3) 一条命令拉起全部容器(postgres + mysql + seed + gateway) docker compose up -d @@ -58,7 +58,7 @@ Windows(PowerShell)用等价命令: ```powershell New-Item -ItemType Directory -Force agentsql-demo | Out-Null; Set-Location agentsql-demo -Invoke-WebRequest -OutFile docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.0.0/deploy/quickstart/docker-compose.yml +Invoke-WebRequest -OutFile docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.1.0/deploy/quickstart/docker-compose.yml docker compose up -d ``` @@ -93,16 +93,16 @@ docker compose down -v **v0.4.0 起提供 linux/arm64 原生 glibc 包:**一键安装支持 `linux/amd64`(`uname -m` 为 `x86_64` / `amd64`)和 `linux/arm64`(`aarch64` / `arm64`),均要求 glibc 2.28+、systemd,且命令需要 root 权限: -以下示例固定 v2.0.0。执行前先确认正式 Release 提供该版本的 `install.sh`、`SHA256SUMS` 和签名;按 [发布资产说明](RELEASE_ASSETS.md) 使用独立核验的公钥验签清单,再用清单中的哈希核验安装器。 +以下示例固定 v2.1.0。执行前先确认正式 Release 提供该版本的 `install.sh`、`SHA256SUMS` 和签名;按 [发布资产说明](RELEASE_ASSETS.md) 使用独立核验的公钥验签清单,再用清单中的哈希核验安装器。 ```bash # 适用前提:linux/amd64 或 linux/arm64 + glibc 2.28+ + systemd -curl -fsSLo install.sh https://github.com/cuipengdba/agentsql/releases/download/v2.0.0/install.sh +curl -fsSLo install.sh https://github.com/cuipengdba/agentsql/releases/download/v2.1.0/install.sh # 先用已验签的 SHA256SUMS 核验 install.sh,再运行: -sudo sh ./install.sh install --version v2.0.0 +sudo sh ./install.sh install --version v2.1.0 ``` -除一键安装外,也可按 [部署指南](DEPLOY.md) 使用源码或 Docker Compose 构建。确认 v2.0.0 镜像已公开、双架构 manifest 和匿名拉取均通过后,运行 `docker pull ghcr.io/cuipengdba/agentsql:v2.0.0`;manifest 应匹配 `linux/amd64` / `linux/arm64`。发布状态和资产以 GitHub Releases 页面为准。 +除一键安装外,也可按 [部署指南](DEPLOY.md) 使用源码或 Docker Compose 构建。确认 v2.1.0 镜像已公开、双架构 manifest 和匿名拉取均通过后,运行 `docker pull ghcr.io/cuipengdba/agentsql:v2.1.0`;manifest 应匹配 `linux/amd64` / `linux/arm64`。发布状态和资产以 GitHub Releases 页面为准。 ### 2. 登录控制台 @@ -130,7 +130,7 @@ PostgreSQL 与 Vastbase G100 Build 9 PG 模式的指定实例可在数据源表 在「权限」中新建绑定该 Agent 与数据源的 `allow` 策略,从确实需要的单张表开始,例如 `public.customers` 或 `customers`。不要用 `*` 或 `schema.*` 做普通示例:两者虽然合法,却代表整表全列授权,会让列级白名单失效。 -安装 v2.0.0 后,控制台「接入向导」可复用同一个 Agent 和数据源选择做连接与权限预检、目标 SQL 离线解释,并生成 Dify 1.17.1 或 MCP Inspector 0.16.8 的无密钥接入说明。预检不执行 Agent SQL;策略数量大于零或离线结论为“未知”都不证明目标 SQL 已获授权。请在真实客户端分别验证允许与拒绝请求,再用响应中的审计 ID 跳转到审计详情核对。配置步骤与客户端可达地址的限制见[使用手册](USER_GUIDE.md#接入向导-onboardingv190)。 +安装 v2.1.0 后,控制台「接入向导」可复用同一个 Agent 和数据源选择做连接与权限预检、目标 SQL 离线解释,并生成 Dify 1.17.1 或 MCP Inspector 0.16.8 的无密钥接入说明。预检不执行 Agent SQL;策略数量大于零或离线结论为“未知”都不证明目标 SQL 已获授权。请在真实客户端分别验证允许与拒绝请求,再用响应中的审计 ID 跳转到审计详情核对。配置步骤与客户端可达地址的限制见[使用手册](USER_GUIDE.md#接入向导-onboardingv190)。 ### 6. 接入 MCP 客户端 @@ -185,9 +185,9 @@ curl -fsS http://127.0.0.1:7780/healthz curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7780/readyz ``` -## v2.0.0:升级前只读检查 +## v2.1.0:升级前只读检查 -正式发布并取得 v2.0.0 `agentsqlctl` 后,使用目标环境的配置先检查控制面 schema 版本。v2.0.0 不增加 schema 迁移;从 v1.9.0 升级仍须成套备份、只读检查并在隔离环境演练恢复。双网关 Kubernetes 部署另须按[维护窗口操作指南](../deploy/kubernetes/README.md)停止旧版双副本、等待 B2 租约到期后再启动新版,不支持新旧二进制同时活跃: +正式发布并取得 v2.1.0 `agentsqlctl` 后,使用目标环境的配置先检查控制面 schema 版本。v2.1.0 不增加 schema 迁移;从 v2.0.0 升级仍须成套备份、只读检查并在隔离环境演练恢复。双网关 Kubernetes 部署另须按[维护窗口操作指南](../deploy/kubernetes/README.md)停止旧版双副本、等待 B2 租约到期后再启动新版,不支持新旧二进制同时活跃: ```bash agentsqlctl migrate --check --config /etc/agentsql/config.yaml diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md index 96f135e5..6881a43d 100644 --- a/docs/INTEGRATIONS.md +++ b/docs/INTEGRATIONS.md @@ -1,6 +1,6 @@ # AgentSQL MCP 接入指南 -> 本文说明 AgentSQL v2.0.0 的 MCP 接入。安装或拉取 v2.0.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.1.0 的 MCP 接入。安装或拉取 v2.1.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 本文说明如何把 MCP 客户端接到 AgentSQL。身份、授权、规则、审批与能力边界见 [使用手册](USER_GUIDE.md),首次部署见 [快速上手](GETTING_STARTED.md);把决策事件外发到 Webhook、告警群或 Syslog 见 [通知外发指南](NOTIFICATIONS.md)。 diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 939367f5..109637b1 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -1,8 +1,8 @@ # AgentSQL 使用手册 -> 本文说明 AgentSQL v2.0.0 的功能与操作。安装或拉取 v2.0.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.1.0 的功能与操作。安装或拉取 v2.1.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 -本手册按控制台主要菜单说明 v2.0.0 的能力和操作。首次使用请先完成 [快速上手](GETTING_STARTED.md);MCP 客户端配置见 [接入指南](INTEGRATIONS.md);版本范围与验收状态见 [v2.0.0 发布说明](release-notes-v2.0.0.md)和[验收记录](v2.0.0-acceptance.md);「设置与集成 → 通知设置」的 Webhook、Syslog 与安全边界见 [通知外发指南](NOTIFICATIONS.md)。 +本手册按控制台主要菜单说明 v2.1.0 的能力和操作。首次使用请先完成 [快速上手](GETTING_STARTED.md);MCP 客户端配置见 [接入指南](INTEGRATIONS.md);版本范围与验收状态见 [v2.1.0 发布说明](release-notes-v2.1.0.md)和[验收记录](v2.1.0-acceptance.md);「设置与集成 → 通知设置」的 Webhook、Syslog 与安全边界见 [通知外发指南](NOTIFICATIONS.md)。 ## 1. 总览 `/` @@ -35,6 +35,12 @@ v1.9.0 在同一页面增加 Dify 1.17.1 / MCP Inspector 0.16.8 的接入说明 离线解释只返回“明确拒绝”或“尚不能确认允许”,并可列出对象、匹配策略 ID 与静态规则;它不连接业务库,也不执行 SQL。最后在真实客户端分别发起允许与拒绝请求,将响应的正整数审计 ID 填入向导直达审计详情,核对 Agent、数据源、决策、规则及脱敏。没有审计 ID 时按错误码先查认证、协议或依赖故障。向导不能替代逐对象授权、执行链路或生产支持验收。 +## v2.1.0:崖山指定实例受控只读验证 + +本版在 YashanDB Enterprise Edition 23.4.1.109、`COMPAT_VECTOR=yashan` 的隔离实例中,使用临时最小权限账号验证真实 HTTP MCP、表策略 R010 拒绝、手机号脱敏和持久审计。MCP Inspector 与 Dify 的允许/拒绝调用、数据库 SELECT 撤权、业务库及独立审计库停机也经过合成数据测试。该结论限指定版本、模式和窄 SELECT 子集;写入、事务、EXPLAIN、服务端 TLS、主备、客户端取消/超时仍未验证或保持关闭。详见 [候选验收](v2.1.0-acceptance.md) 与 [YashanDB 方言边界](yashan-dialect.md)。 + +已认证 Agent 对不可见数据源调用 `query` 或 `execute_write` 时,网关先写入无原始 SQL 的预检拒绝审计,再返回审计 ID;审计库故障时返回错误且不交付数据。`list_schema` 等非 SQL 工具与身份认证失败不属于该交通审计路径。 + ## v2.0.0:固定双网关与 PostgreSQL 列策略登记 双网关 Kubernetes 部署只承载无状态 MCP 只读请求。两个固定副本共享 PostgreSQL metadata 和独立 audit 库,每个副本使用自己的 B2 实例标识;B5 跨请求事务、HTTP 状态会话与事件续传在此配置中关闭。运维人员须按[双网关操作指南](../deploy/kubernetes/README.md)执行迁移检查、维护窗口切换、故障演练与回退。公开 Live Demo 为单网关合成数据环境。 @@ -769,7 +775,7 @@ SQLite → PostgreSQL 控制面迁移要点:停止旧服务写入;备份 SQL - PostgreSQL 的 B2 列级授权、B5 跨请求逻辑会话与计划事务均出厂默认开启;MySQL 不进入 B2 PostgreSQL 路径,且不支持 B5 跨请求事务。每个 operation 仍只允许一条顶层 SQL,并受预检计划和会话安全边界约束。 - 完整原生路径仍以 PostgreSQL 14–18 与 MySQL 8 为主。DM、Oracle、YashanDB 的独立最小 dialect,以及 OpenTenBase、PolarDB 等协议兼容路径均受严格边界约束;达梦 DM8 Pack3、金仓 V9R1C10 PG 模式、Vastbase G100 Build 9 PG 模式在 v1.5.0 完成指定实例的真实客户端、权限撤销和断库恢复复验,v1.6.0 在相同范围内通过独立审计库故障、真实锁等待超时、客户端取消、数据库端终止和恢复后脱敏复验。v1.7.0 的独立 HighGo 类型仅限 9.0.10 企业版 PG 模式有界 SELECT;金仓 TLS 仅限 V9R1C10 PG 模式指定实例。范围见[瀚高验收](v1.7.0-acceptance.md)与[金仓 TLS 验收](v1.7.0-dm-kingbase-depth-acceptance.md),不得视为完整方言支持或厂商认证。 - v1.0.0 的 `/mcp/2026-07-28` 是独立无状态 HTTP 入口:每次 POST 都重新验证 Agent Key、由 Key 确定租户并与旧入口共用 Agent QPS;版本 Header、`Mcp-Method`、适用时的 `Mcp-Name` 必须与请求体匹配。旧 `/mcp` 与 stdio 继续服务旧协议;新入口没有 B5 跨请求逻辑会话或计划事务工具。客户端配置和请求示例见 [MCP 接入指南](INTEGRATIONS.md)。 -- v0.4.0 起提供 linux/arm64 原生 glibc 包(`aarch64` / `arm64`),并继续提供 linux/amd64 包(`x86_64` / `amd64`);v2.0.0 GHCR tag 须在公开发布并通过双架构 manifest 与匿名拉取验收后使用。v0.5.0 已提供可选的 TOTP MFA、OIDC 和 LDAP/AD 人员认证;配置与限制见 [身份认证指南](AUTHENTICATION.md)。v2.0.0 的 Kubernetes 清单限固定双网关与指定无状态只读路径;PostgreSQL 自身 HA、法规级 WORM、SIEM、musl / Alpine 或 CentOS 7 原生一键安装仍不支持,RBAC / 多租户能力仍是 MVP。 +- v0.4.0 起提供 linux/arm64 原生 glibc 包(`aarch64` / `arm64`),并继续提供 linux/amd64 包(`x86_64` / `amd64`);v2.1.0 GHCR tag 须在公开发布并通过双架构 manifest 与匿名拉取验收后使用。v0.5.0 已提供可选的 TOTP MFA、OIDC 和 LDAP/AD 人员认证;配置与限制见 [身份认证指南](AUTHENTICATION.md)。v2.1.0 的 Kubernetes 清单限固定双网关与指定无状态只读路径;PostgreSQL 自身 HA、法规级 WORM、SIEM、musl / Alpine 或 CentOS 7 原生一键安装仍不支持,RBAC / 多租户能力仍是 MVP。 ## 在线安装包升级预演(与 schema 检查不同) 准备可信渠道提供的 HTTPS 清单,JSON 字段为 `version`、同源 HTTPS `url` 和下载内容的 `sha256`。当前仅支持版本检查和只读预演: diff --git a/docs/en/SECURITY.md b/docs/en/SECURITY.md index 52762ee2..756e1130 100644 --- a/docs/en/SECURITY.md +++ b/docs/en/SECURITY.md @@ -22,12 +22,12 @@ This table describes evidence for the **released v0.5.0 code**, not vendor certi | TDSQL for MySQL | MySQL protocol candidate, awaiting commercial target environment | Separate from TDSQL for PostgreSQL | | TXSQL | OpenTenBase community MySQL direction (TXSQL); separate MySQL route with no product-specific live test | MySQL 8 or OpenTenBase results do not establish this route | | Oracle Database 23ai | Independent `oracle` strict SELECT subset | No claim for Enterprise Edition or full Oracle SQL | -| YashanDB 23.4.1.109 | Independent `yashan` slice; connectivity, catalog, and ordinary Query fail-closed behavior retested | EXPLAIN, writes, and full security pipeline unproven | +| YashanDB 23.4.1.109 | Independent `yashan` bounded SELECT path; a least-privilege account passed real HTTP MCP allow/R010 deny, phone masking, durable audit, Inspector/Dify calls, permission revocation, and business/audit-store outage checks on one x86_64 synthetic instance | Other versions and modes, verified TLS, cancellation/timeout, HA, writes, transactions, EXPLAIN, and complete production support remain unproven; see the [v2.1.0 candidate record](../v2.1.0-acceptance.md) | | DM8 | Independent `dm` bounded SELECT subset exercised | Full grammar, writes, and column security pipeline incomplete | | TiDB 7.5.1 / OceanBase CE 4.4.2.1 | MySQL compatibility candidates; EXPLAIN adapters and regression fixtures exist | Fixtures do not establish a full target-environment exercise or certification | | PolarDB-X | Old official `2.0.1` all-in-one image exercised through `mysql`; see below | Full CN/DN/CDC, commercial service, distributed semantics, and B2/B5 unproven | -> **YashanDB client redistribution authorization:** AgentSQL v0.5.0 release artifacts (Linux tarballs, systemd native packages, GHCR runtime images) bundle the YashanDB C client (client 23.4.7.100, with yashandb-go v1.4.4) runtime libraries for the matching architecture. They are redistributed on the basis that the project maintainer has declared vendor authorization; no written authorization file was supplied to this repository. This does not change the low-tier YashanDB support scope: connectivity and metadata discovery are retested, while ordinary Query, writes, transactions, and EXPLAIN remain fail-closed. +> **YashanDB client redistribution authorization:** Release artifacts bundle matching-architecture YashanDB C client 23.4.7.100 runtime libraries with yashandb-go v1.4.4. The project maintainer has stated vendor authorization, but no written authorization file was supplied to this repository. The v2.1.0 candidate verifies only the stated bounded SELECT path on a named instance; writes, transactions, and EXPLAIN remain closed. Authenticated SQL-tool datasource-visibility denials are durably audited before return without storing unparsed SQL; unit, real-instance and audit-outage checks passed, with final release CI pending. Reproducible AgentSQL evidence is in the [compatibility research](../ecosystem-db-compat-research.md), [v0.5 release notes](../release-notes-v0.5.md), and [KingbaseES/HighGo case record](../joint-case-kingbase-highgo.md). Product distinctions follow vendor material for [TDSQL for PostgreSQL](https://cloud.tencent.com/document/product/1129), [TDSQL for MySQL](https://cloud.tencent.com/product/dcdb), and OpenTenBase's [separate TXSQL download](https://docs.opentenbase.org/en/download/). diff --git a/docs/release-notes-v2.1.0.md b/docs/release-notes-v2.1.0.md new file mode 100644 index 00000000..c47af00b --- /dev/null +++ b/docs/release-notes-v2.1.0.md @@ -0,0 +1,14 @@ +# AgentSQL v2.1.0 发布说明 + +v2.1.0 深化崖山 YashanDB 指定实例的受控只读验证。此前已有独立驱动、窄 SELECT 方言和测试夹具流水线;本版把真实最小权限账号、HTTP MCP、表权限、脱敏及持久审计串成同一条可重复的测试链,并用 MCP Inspector 与 Dify 实际调用。业务库断线和独立审计库停机均按无原始结果交付的原则验证。 + +验证对象为 YashanDB Enterprise Edition 23.4.1.109、`COMPAT_VECTOR=yashan`、`yashandb-go v1.4.4` 和独立 C 客户端 23.4.7.100 的 Linux x86_64 实例。AgentSQL 数据源使用临时只读账号,服务器自身拒绝 INSERT。单张表、明确列投影和有界 WHERE 的 SELECT 子集可按 Agent 策略执行;越权表 R010 拒绝,手机号按表列规则脱敏,决策写入持久审计库。适配器仍不开放写入、事务和 EXPLAIN。 + +## 升级与支持边界 + +- 本版无元数据或审计 schema 变更。仍应先按部署文档备份成套 metadata/audit,并演练从 v2.0.0 正式包的迁移、恢复和回退。双网关 Kubernetes 部署仍需维护窗口;本版未扩展其数据库适配范围或承诺无停机滚动升级。 +- 真实客户端与指定崖山实例的测试使用合成数据。支持声明为**指定版本、指定模式的受控只读子集已验证**,不是所有崖山版本或生产拓扑的完整生产支持。TLS 证书验证、主备切换、持续负载、复杂 SQL、函数、系统目录、客户端取消及超时终止仍以实际后续验收为准。 +- C 客户端再分发依据项目维护者所述厂家口头授权,仓库没有书面授权文件;不宣称厂商认证。自行构建时仍须取得匹配架构的客户端,并用 `CGO_ENABLED=1` 和 `-tags yashan` 编译,设置运行库路径。缺驱动或客户端库时失败关闭。 +- 本版补齐已认证 Agent 的 `query`/`execute_write` 数据源可见性预检拒绝审计。最后一条 allow 策略撤销后仍不触达业务库,但会先持久记录 `deny` 并返回审计 ID;审计不可用则返回错误。此路径不保存未经解析的 SQL,`list_schema` 等非 SQL 工具和身份认证失败仍不属于 SQL 交通审计。 + +真库测试、失败尝试及未验证范围见 [v2.1.0 验收记录](v2.1.0-acceptance.md)。正式 CI、安装包、镜像、官网和 Live Demo 的发布证据将在封版后另行补充。 diff --git a/docs/v2.1.0-acceptance.md b/docs/v2.1.0-acceptance.md new file mode 100644 index 00000000..34568247 --- /dev/null +++ b/docs/v2.1.0-acceptance.md @@ -0,0 +1,32 @@ +# AgentSQL v2.1.0 崖山指定实例候选验收 + +状态:候选功能验证中。以下是 2026-10-19 本机隔离环境的实际结果,不是正式 GitHub Release、生产客户验收或厂商认证。正式资产、升级、CI、官网和 Live Demo 须在封版后另行记录。 + +## 环境与范围 + +- YashanDB Enterprise Edition 23.4.1.109 x86_64,容器镜像 `yashandb:yashandb-image-23.4.1.109-linux-x86_64`,端口 1688,`COMPAT_VECTOR=yashan`。 +- `yashandb-go v1.4.4` 加独立 C 客户端 23.4.7.100,Linux amd64、CGO、`-tags yashan`。测试容器为 `golang:1.26-bookworm`。C 客户端再分发目前仅有项目维护者所述厂家口头授权,仓库没有书面文件。 +- 每轮随机创建 `SYS.AGSQL_V21_T_*` 授权表、`SYS.AGSQL_V21_D_*` 未授权表及只读账号,填入合成手机号 `13800135678`;SYS 只负责建表、授权和清理,AgentSQL 数据源使用新建只读账号。真库 `INSERT` 由服务器拒绝。失败尝试留下的 2 个账号和 2 张表已定向清理,再次查询该前缀为零。 +- AgentSQL 元数据使用临时 SQLite。基础与双客户端测试使用同库持久审计;独立审计测试使用隔离 PostgreSQL 18,迁移后另开连接验证。业务库与审计库故障注入分别进行。 + +## 已运行验证 + +| 验收项 | 结果与边界 | +| --- | --- | +| 原生账号与现代 HTTP MCP | `TestYashanHTTPMCPRealE2E` 实际连接只读账号,通过 Go MCP SDK 的 `/mcp/2026-07-28` 完成允许、未授权表 R010、超出窄 SQL 子集、写操作阻断、策略撤销与恢复;允许响应手机号为 `138****5678`,原值未到客户端;含可见性预检拒绝的决策在审计存储重连后可读。最终双客户端重跑退出码 0。 | +| 数据库权限变化 | SYS 撤销只读账号对授权表的 SELECT 后,真实 MCP 查询未交付数据并产生 `error` 审计;恢复权限后再次返回脱敏结果并产生 `allow` 审计。 | +| MCP Inspector 与 Dify | MCP Inspector 0.16.8 CLI、Dify 1.17.1 MCPClient 各自经 `/mcp` 执行一次 allow、一次 R010 deny;最终结果含审计 ID 10–13,手机号只以脱敏形态出现。当前探针适配旧版 `/mcp` 握手;新版入口由 Go SDK 另验。`scripts/v2.1-yashan-clients.ps1` 退出码 0。首次把旧探针指向新版入口时因缺少协议版本头返回 HTTP 400,已改为正确入口重跑。 | +| 独立持久审计 | 使用 PostgreSQL 18 独立审计库重跑真库 HTTP MCP 链路及审计重连,通过,退出码 0。停止独立审计容器时查询未向客户端交付原始行;恢复后新请求脱敏且新增持久审计。`scripts/v2.1-yashan-fault.ps1 -Target audit` 退出码 0。 | +| 业务库断线 | 停止崖山容器后查询未交付原始行,独立审计记录 `error`;恢复后返回脱敏结果并新增 `allow`。首次测试在数据库重启后清理临时账号时沿用了失效管理连接,导致最终退出码 1;改为重新建连清理后完整重跑,`-Target business` 退出码 0。 | +| MCP 可见性预检拒绝 | 发现撤销数据源最后一条 allow 策略时,原入口会拒绝但不写 SQL 交通审计。候选补丁对已认证 Agent 的 `query`/`execute_write` 在该预检处先写持久 `deny` 并返回审计 ID;未解析 SQL 不写入该审计,审计失败时返回错误且不交付数据。单元、真库、独立审计库停机重跑通过。 | +| 前端 | 类型检查通过;Vitest 17 个文件、56 项测试通过;生产构建通过。未改动控制台交互。 | +| Go 全包首轮 | `go test -short -p 1 -timeout 20m ./... -count=1` 仅 `TestParseProjectionLineageP99Budget` 的 PostgreSQL P99 为 6.184 ms,超过 5 ms 门槛;其他包通过。独占条件下单独连续 3 次重跑该性能测试通过,P99 约 1.48–1.52 ms。首轮失败仍计入记录,待最终 CI 完整重跑。 | +| Go 全包封版复测 | 同一 `go test -short -p 1 -timeout 20m ./... -count=1` 在隔离 Go 1.26.8 容器、`GOMAXPROCS=2`、离线依赖缓存中退出码 0;含 `internal/mcpserver`、`internal/parser`、`internal/store` 及其余包。`go vet ./...` 与 `go build -tags yashan ./...` 退出码均为 0。 | +| 官网本地资源 | 三份 v2.1.0 PDF 生成并验证;`website/deploy/verify-static.sh --root` 本地 8/8 项通过。公网发布与公网校验仍待正式资产验收。 | + +## 仍需完成与未验证 + +- GitHub 最终提交 CI、正式资产/双架构动态库与镜像检查;v2.0.0 正式包升级、SQLite 与 PostgreSQL 分库成套恢复、官网和 Live Demo 切换尚未在本文件宣称通过。 +- 客户端取消、SQL 超时与服务器侧停止执行尚未在此指定实例形成可复核证据;TLS 证书校验、写入、事务、EXPLAIN、复杂 JOIN、函数、系统目录、主备切换、持续负载及 aarch64 崖山服务端均未验证或维持关闭。 +- MCP 数据源可见性预检审计只覆盖已认证 Agent 的 `query` 与 `execute_write` 无权限数据源拒绝;`list_schema` 等非 SQL 工具、缺失必填参数或身份认证失败不属于该交通审计路径。目标表策略撤销且数据源仍可见时,仍由流水线记录 R010。 +- 限定结论只适用于上述产品版本、兼容模式、驱动、客户端、x86_64 隔离容器和合成数据。没有书面厂商再分发授权或客户生产验收,不使用“完整生产支持”或“厂商认证”表述。 diff --git a/docs/v2.1.0-design.md b/docs/v2.1.0-design.md new file mode 100644 index 00000000..daee2fa9 --- /dev/null +++ b/docs/v2.1.0-design.md @@ -0,0 +1,44 @@ +# AgentSQL v2.1.0:崖山 YashanDB 指定实例深化设计 + +状态:设计基线,须以本版真实验收结果修订。上一正式版是 [v2.0.0](v2.0.0-release-evidence.md)。本版解决的用户问题是:已登记为 `yashan` 的数据源虽有原生驱动和窄 SELECT 流水线证据,但尚不能凭同一个最小权限账号,在真实 HTTP MCP 请求中同时证明授权、脱敏、持久审计与故障关闭。没有这条组合链路,管理员无法判断 AI 客户端读到的是否始终是受控结果。 + +## 产品定位与范围 + +Bytebase 的[官方 SQL Editor 介绍](https://www.bytebase.com/sql-editor/)将集中授权、动态脱敏和审计作为跨多数据库的治理能力。AgentSQL 本版聚焦 AI 客户端的逐次 SQL 请求,以及崖山指定版本、指定模式的可复核安全证据;不以数据库注册数量替代逐库验证。真实客户试点仍待客户确定后另行排期。 + +验证对象是本地提供的 YashanDB Enterprise Edition 23.4.1.109,`COMPAT_VECTOR=yashan`,`yashandb-go v1.4.4` 和独立 C 客户端 23.4.7.100。正式 amd64/arm64 安装包及 GHCR 镜像需各自确认驱动、客户端和运行时加载;真库链路首先在 x86_64 指定实例验证,不能据此推断 aarch64 服务端兼容。厂家 C 客户端再分发只有既有口头授权记录,仓库没有书面授权文件或认证编号;不使用“厂商认证”或“完整生产支持”表述。 + +支持范围维持明确列投影、单表、有限 WHERE 的只读 SELECT 子集。写入、事务、EXPLAIN、复杂 JOIN、函数、系统目录和未经验证的 TLS 路径继续关闭。若实例不能提供可验证的服务器证书与客户端证书校验,TLS 标为未验证而非静默降级。 + +## 请求与失败关闭链路 + +```mermaid +flowchart LR + C[MCP Inspector 或 Dify] --> A[HTTP MCP: Agent 身份与租户] + A --> P[数据源与策略逐次读取] + P --> S[崖山 SQL 解析、对象和列血缘] + S --> E[最小权限账号的原生只读执行器] + E --> M[字段脱敏与结果预算] + M --> U[独立持久审计确认] + U --> R[向客户端交付结果] + A -->|凭据失效| X[拒绝] + P -->|策略撤销或存储不可用| X + S -->|超出子集或未授权对象| X + E -->|断库、超时或取消| X + U -->|审计写入失败| X +``` + +所有故障响应不得包含未脱敏行。审计终态与对客户端交付的允许或拒绝相匹配;不能写审计时不交付结果。原生驱动错误仅输出规范化代码与阶段,不回显 DSN、口令或原始库诊断。服务器自身的只读账号应拒绝 INSERT,即使网关意外放开语句也有第二道边界。 + +## 开发和验收门槛 + +| 项目 | 可核查的完成条件 | +| --- | --- | +| 真库与账号 | 隔离合成表、未授权表和只读账号;核对服务端版本、兼容模式、客户端/驱动身份及许可范围。只读账号能 SELECT 授权表,服务器拒绝 INSERT;测试用 SYS 仅负责建表和授权,不成为业务数据源账号。 | +| 真实 MCP | 同一只读账号经 HTTP MCP 完成允许、手机号脱敏、R010 未授权表拒绝和越界 SQL 解析拒绝;MCP Inspector 0.16.8 与 Dify 1.17.1 MCPClient 各自实际调用允许与拒绝,记录客户端版本、规则和审计 ID。 | +| 持久审计 | 允许、拒绝和错误在独立审计存储中可重新连接读取;审计写入故障时无原始行交付,恢复后新请求仍受控。不得把测试夹具的内存审计当成此项通过。 | +| 权限与故障 | 网关策略撤销、数据库 SELECT 权限撤销、业务库断线/恢复分别通过;超时和客户端取消按驱动实际表现记录,若不能安全证明停止执行,维持保守关闭并列明限制。 | +| 兼容交付 | 从 v2.0.0 正式包数据进行 SQLite 与 PostgreSQL 双库升级、旧包读取和成套备份恢复;新包 amd64/arm64 及 GHCR 精确 tag 的客户端动态库、版本、匿名拉取和运行烟测通过。 | +| 公网交付 | 独立 CI、注释 tag、15 项签名资产、GHCR、官网 PDF/文案、公网 Demo 候选与切换、`latest` 和发布后烟测逐项记录。Live Demo 可继续使用合成 PostgreSQL,不暗示公网开放商业数据库实例。 | + +本版不扩大崖山能力到批量写入或完整 SQL 方言;无法完成的某项真库故障注入必须写明为未验证,不能用其他数据库的通过结果代替。 diff --git a/docs/yashan-dialect.md b/docs/yashan-dialect.md index 6eb38000..8113f91e 100644 --- a/docs/yashan-dialect.md +++ b/docs/yashan-dialect.md @@ -305,3 +305,9 @@ fixed sanitized transcript path is `C:\Users\Administrator\AppData\Local\Temp\agentsql-yashan-b71-verify.log`. The detailed current results and untested cases are in [the batch 71 verification report](yashan-verification-b71.md). + +## v2.1.0 指定实例 HTTP MCP 候选验收 + +在上述 23.4.1.109 x86_64 实例和独立客户端 23.4.7.100 上,`TestYashanHTTPMCPRealE2E` 使用临时只读账号作为 AgentSQL 数据源,而不再以 SYS 作为业务连接。它经新版 HTTP MCP 的 Go SDK 验证单表 SELECT、R010、手机号脱敏、越界 SQL 拒绝、策略及数据库 SELECT 权限撤销/恢复,并重连读取持久审计。MCP Inspector 0.16.8 与 Dify 1.17.1 则经现有 `/mcp` 入口各完成 allow/deny 真调用。独立 PostgreSQL 审计库停机、崖山业务库断线及恢复也分别通过无原始结果交付的故障注入。详见 [v2.1.0 候选验收](v2.1.0-acceptance.md)。 + +这仍是指定版本、模式、合成数据和受限只读 SQL 子集的证据。书面再分发授权、厂商认证、服务端 TLS、客户端取消/超时、主备切换、其他平台服务端及持续负载尚未取得证据。候选补丁还会对已认证 Agent 的 SQL 工具记录数据源可见性预检拒绝;该审计不保存未经解析的 SQL,须以本版重跑结果为准。 diff --git a/internal/mcpserver/handlers.go b/internal/mcpserver/handlers.go index 84ea3c59..8b56aa9d 100644 --- a/internal/mcpserver/handlers.go +++ b/internal/mcpserver/handlers.go @@ -209,7 +209,7 @@ func (handlers *toolHandlers) query( } datasource, response := handlers.authorizedDatasource(ctx, datasourceID) if response != nil { - return *response + return handlers.auditDatasourcePreflightDenial(ctx, datasourceID, "query", *response) } ast, response := parseForTool(datasource.DBType, sql) if response != nil { @@ -239,7 +239,7 @@ func (handlers *toolHandlers) executeWrite( } datasource, response := handlers.authorizedDatasource(ctx, datasourceID) if response != nil { - return *response + return handlers.auditDatasourcePreflightDenial(ctx, datasourceID, "execute_write", *response) } ast, response := parseForTool(datasource.DBType, sql) if response != nil { @@ -425,6 +425,37 @@ func (handlers *toolHandlers) authorizedDatasource( return model.Datasource{}, &denied } +// auditDatasourcePreflightDenial records SQL-tool attempts rejected before the +// pipeline can load a datasource. Do not persist the unparsed SQL: it may +// contain credentials or literals and has not passed the normal SQL guards. +func (handlers *toolHandlers) auditDatasourcePreflightDenial( + ctx context.Context, + datasourceID string, + tool string, + response ToolResponse, +) ToolResponse { + if response.Decision != string(model.DecisionDeny) { + return response + } + agentID := handlers.agent.ID + action := "datasource_visibility_deny" + details := `{"phase":"mcp_datasource_preflight"}` + hits := `[]` + inserted, err := handlers.runtime.Store.AuditLogs().Insert(ctx, model.AuditLog{ + AgentID: &agentID, DatasourceID: &datasourceID, MCPTool: &tool, + Decision: string(model.DecisionDeny), RuleHits: &hits, + Action: &action, DetailsJSON: &details, + }) + if err != nil { + handlers.logger.Error().Str("tool", tool). + Str("error_type", fmt.Sprintf("%T", err)). + Msg("MCP datasource preflight audit failed") + return errorToolResponse("审计库不可用,已拒绝请求", "请稍后重试;若持续失败,请联系管理员") + } + response.Data = pipelineData{AuditID: inserted.ID} + return response +} + func parseForTool(dialect, sql string) (*model.AST, *ToolResponse) { if strings.TrimSpace(sql) == "" { response := errorToolResponse("sql 不能为空", "请提供一条完整且单一的 SQL") diff --git a/internal/mcpserver/handlers_test.go b/internal/mcpserver/handlers_test.go index b3a78ac6..f9bb8974 100644 --- a/internal/mcpserver/handlers_test.go +++ b/internal/mcpserver/handlers_test.go @@ -40,6 +40,38 @@ func TestHandlersListAuthorizedDatasourcesWithoutCredentials(t *testing.T) { require.NotContains(t, string(encoded), "PasswordEnc") } +func TestHandlersAuditDatasourceVisibilityDenials(t *testing.T) { + fixture := newMCPFixture(t, "readonly") + ctx := context.Background() + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, "table")) + query := "SELECT phone FROM public.customers LIMIT 1" + known := fixture.handlers.query(ctx, "ds-allowed", query) + unknown := fixture.handlers.query(ctx, "ds-not-present", query) + write := fixture.handlers.executeWrite(ctx, "ds-hidden", "UPDATE public.customers SET phone='x'", "visibility check") + for _, response := range []ToolResponse{known, unknown, write} { + require.Equal(t, "deny", response.Decision) + require.Positive(t, response.Data.(pipelineData).AuditID) + } + require.Equal(t, known.Reason, unknown.Reason) + require.Zero(t, fixture.provider.calls()) + page, err := fixture.runtime.Store.AuditLogs().Page(ctx, 1, 10) + require.NoError(t, err) + require.Equal(t, int64(3), page.Total) + for _, event := range page.List { + require.Equal(t, "deny", event.Decision) + require.Nil(t, event.SQLRaw) + require.Equal(t, "datasource_visibility_deny", *event.Action) + require.Equal(t, fixture.agent.ID, *event.AgentID) + } + canceled, cancel := context.WithCancel(ctx) + cancel() + failedAudit := fixture.handlers.auditDatasourcePreflightDenial( + canceled, "ds-hidden", "query", denyToolResponse("denied", "retry"), + ) + require.Equal(t, "error", failedAudit.Decision) + require.Nil(t, failedAudit.Data) +} + func TestHandlersListSchemaFiltersColumnsAndRejectsIdentifiers(t *testing.T) { fixture := newMCPFixture(t, "dml") columnsAllowed := "id,phone" @@ -421,17 +453,25 @@ func TestHandlersAreRaceSafe(t *testing.T) { } type mcpFixture struct { - runtime *bootstrap.Runtime - handlers *toolHandlers - agent model.Agent - executor *mcpSpyExecutor - provider *mcpExecutorProvider - redactors *staticRedactorBuilder + runtime *bootstrap.Runtime + databasePath string + handlers *toolHandlers + agent model.Agent + executor *mcpSpyExecutor + provider *mcpExecutorProvider + redactors *staticRedactorBuilder } func newMCPFixture(t *testing.T, level string) *mcpFixture { + return newMCPFixtureWithOpener(t, level, func(path string) (*store.Store, error) { + return store.OpenWithSecret(context.Background(), path, mcpTestSecret) + }) +} + +func newMCPFixtureWithOpener(t *testing.T, level string, open func(string) (*store.Store, error)) *mcpFixture { t.Helper() - metadataStore, err := store.OpenWithSecret(context.Background(), filepath.Join(t.TempDir(), "agentsql.db"), mcpTestSecret) + databasePath := filepath.Join(t.TempDir(), "agentsql.db") + metadataStore, err := open(databasePath) require.NoError(t, err) plaintext, hash, err := store.GenerateAPIKey() require.NoError(t, err) @@ -529,7 +569,7 @@ func newMCPFixture(t *testing.T, level string) *mcpFixture { return columns, nil }, } - return &mcpFixture{runtime: runtime, handlers: handlers, agent: agent, executor: spy, provider: provider, redactors: redactors} + return &mcpFixture{runtime: runtime, databasePath: databasePath, handlers: handlers, agent: agent, executor: spy, provider: provider, redactors: redactors} } type staticRedactorBuilder struct{ redactor mask.Redactor } diff --git a/internal/mcpserver/yashan_live_test.go b/internal/mcpserver/yashan_live_test.go new file mode 100644 index 00000000..bdc194bf --- /dev/null +++ b/internal/mcpserver/yashan_live_test.go @@ -0,0 +1,386 @@ +//go:build yashan && cgo + +package mcpserver + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "encoding/json" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/cuipengdba/agentsql/internal/audit" + "github.com/cuipengdba/agentsql/internal/auth" + executor "github.com/cuipengdba/agentsql/internal/authorizedexecute" + "github.com/cuipengdba/agentsql/internal/mask" + "github.com/cuipengdba/agentsql/internal/model" + "github.com/cuipengdba/agentsql/internal/pipeline" + "github.com/cuipengdba/agentsql/internal/store" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +// TestYashanHTTPMCPRealE2E joins the native driver, least-privilege account, +// HTTP MCP identity, policy, masking, and reopened durable audit in one path. +func TestYashanHTTPMCPRealE2E(t *testing.T) { + if os.Getenv("AGENTSQL_YASHAN_E2E") != "1" { + t.Skip("set AGENTSQL_YASHAN_E2E=1 and YASHAN_PASSWORD for a real YashanDB instance") + } + password := os.Getenv("YASHAN_PASSWORD") + require.NotEmpty(t, password) + host := os.Getenv("YASHAN_HOST") + if host == "" { + host = "127.0.0.1" + } + clientDir := os.Getenv("AGENTSQL_TEST_YASHAN_CLIENT_DIR") + auditFaultDir := os.Getenv("AGENTSQL_TEST_YASHAN_AUDIT_FAULT_DIR") + businessFaultDir := os.Getenv("AGENTSQL_TEST_YASHAN_BUSINESS_FAULT_DIR") + timeout := 90 * time.Second + if clientDir != "" || auditFaultDir != "" || businessFaultDir != "" { + timeout = 8 * time.Minute + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + escape := strings.NewReplacer(`\`, `\\`, `/`, `\/`, `@`, `\@`) + adminDSN := "SYS/" + escape.Replace(password) + "@" + host + ":1688?compat_vector=yashan" + admin, err := sql.Open("yasdb", adminDSN) + if err != nil || admin.PingContext(ctx) != nil { + t.Fatal("open YashanDB SYS connection failed") + } + t.Cleanup(func() { _ = admin.Close() }) + nameBytes := make([]byte, 4) + _, err = rand.Read(nameBytes) + require.NoError(t, err) + suffix := strings.ToUpper(hex.EncodeToString(nameBytes)) + table := "AGSQL_V21_T_" + suffix + deniedTable := "AGSQL_V21_D_" + suffix + reader := "AGSQL_V21_R_" + suffix + secretBytes := make([]byte, 12) + _, err = rand.Read(secretBytes) + require.NoError(t, err) + readerPassword := "Aa1" + hex.EncodeToString(secretBytes) + createdTables := make([]string, 0, 2) + createdReader := false + t.Cleanup(func() { + cleanupCtx, stop := context.WithTimeout(context.Background(), 20*time.Second) + defer stop() + cleanupDB, openErr := sql.Open("yasdb", adminDSN) + if openErr != nil || cleanupDB.PingContext(cleanupCtx) != nil { + t.Error("reconnect YashanDB administrator for cleanup failed") + return + } + defer cleanupDB.Close() + if createdReader { + if _, err := cleanupDB.ExecContext(cleanupCtx, "DROP USER "+reader+" CASCADE"); err != nil { + t.Error("remove temporary YashanDB reader failed") + } + } + for _, name := range createdTables { + if _, err := cleanupDB.ExecContext(cleanupCtx, "DROP TABLE "+name); err != nil { + t.Error("remove temporary YashanDB table failed") + } + } + }) + for _, name := range []string{table, deniedTable} { + _, err = admin.ExecContext(ctx, "CREATE TABLE "+name+" (ID NUMBER, PHONE VARCHAR2(32))") + if err != nil { + t.Fatal("create temporary YashanDB table failed") + } + createdTables = append(createdTables, name) + _, err = admin.ExecContext(ctx, "INSERT INTO "+name+" (ID, PHONE) VALUES (1, '13800135678')") + if err != nil { + t.Fatal("seed temporary YashanDB table failed") + } + } + _, err = admin.ExecContext(ctx, "CREATE USER "+reader+" IDENTIFIED BY "+readerPassword) + if err != nil { + t.Fatal("create temporary YashanDB reader failed") + } + createdReader = true + for _, statement := range []string{ + "GRANT CREATE SESSION TO " + reader, + "GRANT SELECT ON " + table + " TO " + reader, + "GRANT SELECT ON " + deniedTable + " TO " + reader, + } { + if _, err := admin.ExecContext(ctx, statement); err != nil { + t.Fatal("grant temporary YashanDB reader failed") + } + } + readerDB, err := sql.Open("yasdb", reader+"/"+escape.Replace(readerPassword)+"@"+host+":1688?compat_vector=yashan") + if err != nil || readerDB.PingContext(ctx) != nil { + t.Fatal("login as temporary YashanDB reader failed") + } + t.Cleanup(func() { _ = readerDB.Close() }) + var plain string + if err := readerDB.QueryRowContext(ctx, "SELECT PHONE FROM SYS."+table+" WHERE ID = ?", 1).Scan(&plain); err != nil || plain != "13800135678" { + t.Fatal("least-privilege YashanDB reader SELECT failed") + } + _, err = readerDB.ExecContext(ctx, "INSERT INTO SYS."+table+" (ID, PHONE) VALUES (2, 'x')") + require.Error(t, err, "database account must reject writes") + + auditDSN := os.Getenv("AGENTSQL_TEST_YASHAN_AUDIT_DSN") + openStore := func(path string) (*store.Store, error) { + if auditDSN == "" { + return store.OpenWithSecret(ctx, path, mcpTestSecret) + } + return store.OpenMetadata(ctx, store.MetadataOptions{ + Driver: store.DialectSQLite, SQLitePath: path, AutoMigrate: true, + Audit: store.AuditOptions{Separate: true, Driver: store.DialectPostgres, + PostgresDSN: auditDSN, AutoMigrate: true}, + }, mcpTestSecret) + } + fixture := newMCPFixtureWithOpener(t, "readonly", openStore) + const datasourceID = "ds-yashan-v21" + _, err = fixture.runtime.Store.Datasources().Create(ctx, model.Datasource{ + ID: datasourceID, Name: "YashanDB 23.4 least-privilege test", DBType: "yashan", + Host: host, Port: 1688, Database: "SYS", Username: reader, + ConnLimit: 2, StmtTimeoutMS: 10_000, RowLimit: 10, + }, readerPassword) + require.NoError(t, err) + policy := model.Policy{ID: "yashan-allowed-table", AgentID: fixture.agent.ID, + DatasourceID: datasourceID, ObjectType: "table", ObjectName: "SYS." + table, Action: "allow"} + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + redactor, err := mask.NewRedactor([]mask.Rule{{Schema: "SYS", Table: table, Column: "PHONE", SensitiveType: mask.TypePhone, Algorithm: mask.AlgoMask}}) + require.NoError(t, err) + fixture.redactors.redactor = redactor + gateway := executor.NewGateway(true) + t.Cleanup(func() { require.NoError(t, gateway.CloseAll()) }) + fixture.runtime.Pipeline, err = pipeline.New(pipeline.Ports{ + Authenticator: auth.NewAuthenticator(fixture.runtime.Store.Agents()), + Datasources: fixture.runtime.Store.Datasources(), Policies: fixture.runtime.Store.Policies(), + Executors: gateway, Approvals: fixture.runtime.Store.Approvals(), + Audit: audit.NewRecorder(fixture.runtime.Store.AuditLogs()), Redactors: fixture.redactors, + TaskReadGrants: fixture.runtime.Store.TaskReadGrants(), + }, mcpTestSecret) + require.NoError(t, err) + handler, err := NewHTTPHandler(fixture.runtime, statefulHTTPTestConfig(100), zerolog.Nop()) + require.NoError(t, err) + httpServer := httptest.NewServer(handler) + defer httpServer.Close() + httpClient := httpServer.Client() + httpClient.Transport = bearerTransport{base: httpClient.Transport, key: fixture.handlers.apiKey} + client := mcp.NewClient(&mcp.Implementation{Name: "yashan-v21-http-e2e", Version: "test"}, nil) + session, err := client.Connect(ctx, &mcp.StreamableClientTransport{ + Endpoint: httpServer.URL + modernMCPPath, HTTPClient: httpClient, + }, nil) + require.NoError(t, err) + defer session.Close() + call := func(name, query string) string { + t.Helper() + arguments := map[string]any{"datasource_id": datasourceID, "sql": query} + if name == "execute_write" { + arguments["reason"] = "verify read-only enforcement" + } + result, callErr := session.CallTool(ctx, &mcp.CallToolParams{Name: name, Arguments: arguments}) + require.NoError(t, callErr) + encoded, marshalErr := json.Marshal(result) + require.NoError(t, marshalErr) + return string(encoded) + } + allowSQL := "SELECT PHONE FROM SYS." + table + " WHERE ID = 1" + allowed := call("query", allowSQL) + require.Contains(t, allowed, `"decision":"allow"`) + require.Contains(t, allowed, "138****5678") + require.NotContains(t, allowed, plain) + denied := call("query", "SELECT PHONE FROM SYS."+deniedTable+" WHERE ID = 1") + require.Contains(t, denied, `"decision":"deny"`) + require.NotContains(t, denied, plain) + unsafe := call("query", "SELECT /* unsupported */ PHONE FROM SYS."+table+" WHERE ID = 1") + require.Contains(t, unsafe, `"decision":"error"`) + require.NotContains(t, unsafe, plain) + write := call("execute_write", "UPDATE SYS."+table+" SET PHONE = 'changed' WHERE ID = 1") + require.NotContains(t, write, `"decision":"allow"`) + // Keep datasource visibility while revoking the queried table. This forces + // the request through the SQL policy gate and its durable audit record. + _, err = fixture.runtime.Store.Policies().Create(ctx, model.Policy{ + ID: "yashan-visibility-anchor", AgentID: fixture.agent.ID, + DatasourceID: datasourceID, ObjectType: "table", + ObjectName: "SYS.AGSQL_V21_UNQUERIED", Action: "allow", + }) + require.NoError(t, err) + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, policy.ID)) + revoked := call("query", allowSQL) + require.Contains(t, revoked, `"decision":"deny"`) + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + recovered := call("query", allowSQL) + require.Contains(t, recovered, "138****5678") + var after string + require.NoError(t, admin.QueryRowContext(ctx, "SELECT PHONE FROM SYS."+table+" WHERE ID = 1").Scan(&after)) + require.Equal(t, plain, after) + page, err := fixture.runtime.Store.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, err) + require.Equal(t, int64(6), page.Total) + for index, decision := range []string{"allow", "deny", "error", "error", "deny", "allow"} { + require.Equal(t, decision, page.List[5-index].Decision) + } + reopened, err := openStore(fixture.databasePath) + require.NoError(t, err) + defer reopened.Close() + durable, err := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, err) + require.Equal(t, page.Total, durable.Total) + require.Equal(t, page.List[0].ID, durable.List[0].ID) + _, err = admin.ExecContext(ctx, "REVOKE SELECT ON "+table+" FROM "+reader) + if err != nil { + t.Fatal("revoke temporary YashanDB reader SELECT failed") + } + permissionRevoked := call("query", allowSQL) + require.NotContains(t, permissionRevoked, `"decision":"allow"`) + require.NotContains(t, permissionRevoked, plain) + _, err = admin.ExecContext(ctx, "GRANT SELECT ON "+table+" TO "+reader) + if err != nil { + t.Fatal("restore temporary YashanDB reader SELECT failed") + } + permissionRestored := call("query", allowSQL) + require.Contains(t, permissionRestored, `"decision":"allow"`) + require.Contains(t, permissionRestored, "138****5678") + afterPermission, err := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, err) + require.Equal(t, page.Total+2, afterPermission.Total) + require.Equal(t, "error", afterPermission.List[1].Decision) + require.Equal(t, "allow", afterPermission.List[0].Decision) + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, policy.ID)) + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, "yashan-visibility-anchor")) + visibilityDenied := call("query", allowSQL) + require.Contains(t, visibilityDenied, `"decision":"deny"`) + require.NotContains(t, visibilityDenied, plain) + afterVisibility, err := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, err) + require.Equal(t, afterPermission.Total+1, afterVisibility.Total) + require.Equal(t, "datasource_visibility_deny", *afterVisibility.List[0].Action) + require.Nil(t, afterVisibility.List[0].SQLRaw) + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + _, err = fixture.runtime.Store.Policies().Create(ctx, model.Policy{ + ID: "yashan-visibility-anchor", AgentID: fixture.agent.ID, + DatasourceID: datasourceID, ObjectType: "table", + ObjectName: "SYS.AGSQL_V21_UNQUERIED", Action: "allow", + }) + require.NoError(t, err) + afterPermission = afterVisibility + if auditFaultDir != "" { + require.NotEmpty(t, auditDSN, "audit outage probe needs an independent audit database") + signal := func(name string) { + t.Helper() + require.NoError(t, os.WriteFile(filepath.Join(auditFaultDir, name), []byte("ready\n"), 0o600)) + } + waitSignal := func(name string) { + t.Helper() + for { + if _, statErr := os.Stat(filepath.Join(auditFaultDir, name)); statErr == nil { + return + } else if !os.IsNotExist(statErr) { + require.NoError(t, statErr) + } + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for independent audit fault signal: " + name) + case <-time.After(250 * time.Millisecond): + } + } + } + signal("ready") + waitSignal("down") + failedAudit := call("query", allowSQL) + require.NotContains(t, failedAudit, `"decision":"allow"`) + require.NotContains(t, failedAudit, plain) + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, policy.ID)) + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, "yashan-visibility-anchor")) + failedPreflightAudit := call("query", allowSQL) + require.Contains(t, failedPreflightAudit, `"decision":"error"`) + require.NotContains(t, failedPreflightAudit, plain) + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + _, err = fixture.runtime.Store.Policies().Create(ctx, model.Policy{ + ID: "yashan-visibility-anchor", AgentID: fixture.agent.ID, + DatasourceID: datasourceID, ObjectType: "table", + ObjectName: "SYS.AGSQL_V21_UNQUERIED", Action: "allow", + }) + require.NoError(t, err) + signal("down_checked") + waitSignal("up") + restoredAudit := call("query", allowSQL) + require.Contains(t, restoredAudit, `"decision":"allow"`) + require.Contains(t, restoredAudit, "138****5678") + require.NotContains(t, restoredAudit, plain) + afterFault, pageErr := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, pageErr) + require.Equal(t, afterPermission.Total+1, afterFault.Total) + afterPermission = afterFault + } + if businessFaultDir != "" { + signal := func(name string) { + t.Helper() + require.NoError(t, os.WriteFile(filepath.Join(businessFaultDir, name), []byte("ready\n"), 0o600)) + } + waitSignal := func(name string) { + t.Helper() + for { + if _, statErr := os.Stat(filepath.Join(businessFaultDir, name)); statErr == nil { + return + } else if !os.IsNotExist(statErr) { + require.NoError(t, statErr) + } + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for YashanDB outage signal: " + name) + case <-time.After(250 * time.Millisecond): + } + } + } + signal("ready") + waitSignal("down") + failedBusiness := call("query", allowSQL) + require.NotContains(t, failedBusiness, `"decision":"allow"`) + require.NotContains(t, failedBusiness, plain) + signal("down_checked") + waitSignal("up") + restoredBusiness := call("query", allowSQL) + require.Contains(t, restoredBusiness, `"decision":"allow"`) + require.Contains(t, restoredBusiness, "138****5678") + require.NotContains(t, restoredBusiness, plain) + afterFault, pageErr := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, pageErr) + require.Equal(t, afterPermission.Total+2, afterFault.Total) + require.Equal(t, "error", afterFault.List[1].Decision) + require.Equal(t, "allow", afterFault.List[0].Decision) + afterPermission = afterFault + } + if clientDir == "" { + return + } + clientInfo, err := json.Marshal(map[string]string{ + "url": httpServer.URL + "/mcp", "api_key": fixture.handlers.apiKey, + "datasource_id": datasourceID, "allow_sql": allowSQL, + "deny_sql": "SELECT PHONE FROM SYS." + deniedTable + " WHERE ID = 1", + "deny_rule": "R010", + }) + require.NoError(t, err) + infoPath := filepath.Join(clientDir, "client.json") + require.NoError(t, os.WriteFile(infoPath, clientInfo, 0o600)) + defer os.Remove(infoPath) + for { + if _, statErr := os.Stat(filepath.Join(clientDir, "clients_done")); statErr == nil { + break + } else if !os.IsNotExist(statErr) { + require.NoError(t, statErr) + } + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for YashanDB Inspector and Dify MCP calls") + case <-time.After(250 * time.Millisecond): + } + } + afterClients, err := reopened.AuditLogs().Page(ctx, 1, 20) + require.NoError(t, err) + require.Equal(t, afterPermission.Total+4, afterClients.Total) +} diff --git a/internal/version/version.go b/internal/version/version.go index 43be40df..950423bc 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -2,4 +2,4 @@ package version // Version is overridden in release builds through -ldflags -X. -var Version = "v2.0.0" +var Version = "v2.1.0" diff --git a/scripts/build-ghcr-multiarch.ps1 b/scripts/build-ghcr-multiarch.ps1 index dc91b113..8a0e4184 100644 --- a/scripts/build-ghcr-multiarch.ps1 +++ b/scripts/build-ghcr-multiarch.ps1 @@ -10,7 +10,7 @@ [CmdletBinding()] param( [ValidatePattern('^v[0-9]+\.[0-9]+\.[0-9]+$')] - [string]$Version = 'v2.0.0', + [string]$Version = 'v2.1.0', [string]$Builder = 'agentsql-multiarch', diff --git a/scripts/build-release-linux.sh b/scripts/build-release-linux.sh index c5717073..3eabe5a2 100644 --- a/scripts/build-release-linux.sh +++ b/scripts/build-release-linux.sh @@ -3,7 +3,7 @@ set -eu ARCH=${ARCH:-amd64} -VERSION=${VERSION:-v2.0.0} +VERSION=${VERSION:-v2.1.0} GO_VERSION=${GO_VERSION:-1.26.9} GOPROXY=${GOPROXY:-https://goproxy.cn,direct} YASHAN_CLIENT_VERSION=${YASHAN_CLIENT_VERSION:-23.4.7.100} diff --git a/scripts/package-release.sh b/scripts/package-release.sh index b71e06ca..a61f7da8 100644 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -3,7 +3,7 @@ set -eu umask 022 -VERSION=${VERSION:-v2.0.0} +VERSION=${VERSION:-v2.1.0} ARCH=${ARCH:-amd64} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200} diff --git a/scripts/release-dryrun.ps1 b/scripts/release-dryrun.ps1 index 061eeda8..c9f6e24f 100644 --- a/scripts/release-dryrun.ps1 +++ b/scripts/release-dryrun.ps1 @@ -4,7 +4,7 @@ [CmdletBinding()] param( [ValidatePattern('^v[0-9]+\.[0-9]+\.[0-9]+$')] - [string]$Version = 'v2.0.0', + [string]$Version = 'v2.1.0', [string]$OutputDirectory, diff --git a/scripts/v2.1-yashan-clients.ps1 b/scripts/v2.1-yashan-clients.ps1 new file mode 100644 index 00000000..9ff2e0ec --- /dev/null +++ b/scripts/v2.1-yashan-clients.ps1 @@ -0,0 +1,74 @@ +param( + [string]$Container = 'yashan-v06', + [string]$TempRoot = 'D:\CodexLS\tmp\agentsql-v210', + [string]$ClientLibDir = 'D:\CodexLS\tmp\agentsql-v200\official-smoke\agentsql-v2.0.0-linux-amd64\lib\yashandb' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$repo = Split-Path -Parent $PSScriptRoot +$moduleCache = 'D:\CodexLS\cache\agentsql-go-mod-linux' +$buildCache = 'D:\CodexLS\cache\agentsql-go-build-linux' +$clientLib = $ClientLibDir +foreach ($path in @($repo, $moduleCache, $buildCache, $clientLib)) { + if (-not (Test-Path -LiteralPath $path -PathType Container)) { + throw "Required local directory is missing: $path" + } +} +if ((& docker inspect $Container --format '{{.State.Running}}').Trim() -ne 'true') { + throw 'The isolated YashanDB container is not running' +} +New-Item -ItemType Directory -Path $TempRoot -Force | Out-Null +$clientDir = Join-Path $TempRoot ('clients-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $clientDir | Out-Null +$testContainer = 'agentsql-v210-yashan-' + [guid]::NewGuid().ToString('N').Substring(0, 8) +$env:YASHAN_PASSWORD = & docker exec $Container sh -c 'printf %s "$SYS_PASSWD"' +if ([string]::IsNullOrWhiteSpace($env:YASHAN_PASSWORD)) { throw 'YashanDB test credential is unavailable' } +try { + & docker run -d --name $testContainer --network "container:$Container" ` + --mount "type=bind,source=$repo,target=/src" ` + --mount "type=bind,source=$moduleCache,target=/gomod" ` + --mount "type=bind,source=$buildCache,target=/buildcache" ` + --mount "type=bind,source=$clientLib,target=/client/lib,readonly" ` + --mount "type=bind,source=$clientDir,target=/clients" ` + --workdir /src ` + --env GOPROXY=off --env GOTOOLCHAIN=local --env GOMODCACHE=/gomod ` + --env GOCACHE=/buildcache --env CGO_ENABLED=1 --env LD_LIBRARY_PATH=/client/lib ` + --env AGENTSQL_YASHAN_E2E=1 --env YASHAN_PASSWORD --env YASHAN_HOST=127.0.0.1 ` + --env AGENTSQL_TEST_YASHAN_CLIENT_DIR=/clients ` + golang:1.26-bookworm go test -tags yashan -timeout 10m -count=1 -v ` + -run '^TestYashanHTTPMCPRealE2E$' ./internal/mcpserver | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not start YashanDB live MCP test container' } + $clientInfoPath = Join-Path $clientDir 'client.json' + $deadline = (Get-Date).AddMinutes(8) + while (-not (Test-Path -LiteralPath $clientInfoPath)) { + $running = (& docker inspect $testContainer --format '{{.State.Running}}').Trim() + if ($running -ne 'true') { + & docker logs $testContainer + throw 'YashanDB live MCP test exited before external clients connected' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for YashanDB MCP test gateway' } + Start-Sleep -Seconds 1 + } + $clientInfo = Get-Content -LiteralPath $clientInfoPath -Raw | ConvertFrom-Json + & (Join-Path $PSScriptRoot 'v1.5-mcp-client-probes.ps1') ` + -Network "container:$Container" -MCPURL $clientInfo.url -ApiKey $clientInfo.api_key ` + -DatasourceID $clientInfo.datasource_id -AllowSQL $clientInfo.allow_sql ` + -DenySQL $clientInfo.deny_sql -DenyRule $clientInfo.deny_rule + if ($LASTEXITCODE -ne 0) { throw 'YashanDB Inspector/Dify client probe failed' } + Set-Content -LiteralPath (Join-Path $clientDir 'clients_done') -Value 'four calls completed' + $deadline = (Get-Date).AddMinutes(2) + while ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -eq 'true') { + if ((Get-Date) -ge $deadline) { throw 'YashanDB live MCP test did not finish' } + Start-Sleep -Seconds 1 + } + $testExit = (& docker inspect $testContainer --format '{{.State.ExitCode}}').Trim() + & docker logs $testContainer + if ($testExit -ne '0') { throw "YashanDB live MCP test failed with exit=$testExit" } + Write-Output 'V210_YASHAN_INSPECTOR_DIFY=PASS' +} +finally { + Remove-Item Env:\YASHAN_PASSWORD -ErrorAction SilentlyContinue + Remove-Item -LiteralPath (Join-Path $clientDir 'client.json') -Force -ErrorAction SilentlyContinue + & docker rm -f $testContainer 2>$null | Out-Null +} diff --git a/scripts/v2.1-yashan-fault.ps1 b/scripts/v2.1-yashan-fault.ps1 new file mode 100644 index 00000000..d364cf06 --- /dev/null +++ b/scripts/v2.1-yashan-fault.ps1 @@ -0,0 +1,106 @@ +param( + [ValidateSet('audit', 'business')][string]$Target = 'audit', + [string]$YashanContainer = 'yashan-v06', + [string]$AuditContainer = 'agentsql-v210-audit', + [string]$TempRoot = 'D:\CodexLS\tmp\agentsql-v210', + [string]$ClientLibDir = 'D:\CodexLS\tmp\agentsql-v200\official-smoke\agentsql-v2.0.0-linux-amd64\lib\yashandb' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$repo = Split-Path -Parent $PSScriptRoot +$moduleCache = 'D:\CodexLS\cache\agentsql-go-mod-linux' +$buildCache = 'D:\CodexLS\cache\agentsql-go-build-linux' +$clientLib = $ClientLibDir +foreach ($path in @($repo, $moduleCache, $buildCache, $clientLib)) { + if (-not (Test-Path -LiteralPath $path -PathType Container)) { throw "Required local directory is missing: $path" } +} +foreach ($container in @($YashanContainer, $AuditContainer)) { + if ((& docker inspect $container --format '{{.State.Running}}').Trim() -ne 'true') { + throw "Required test container is not running: $container" + } +} +New-Item -ItemType Directory -Path $TempRoot -Force | Out-Null +$faultDir = Join-Path $TempRoot ($Target + '-fault-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $faultDir | Out-Null +$dbName = 'agv21_' + [guid]::NewGuid().ToString('N').Substring(0, 10) +$testContainer = 'agentsql-v210-' + $Target + '-fault-' + [guid]::NewGuid().ToString('N').Substring(0, 8) +$env:YASHAN_PASSWORD = & docker exec $YashanContainer sh -c 'printf %s "$SYS_PASSWD"' +$auditPassword = & docker exec $AuditContainer sh -c 'printf %s "$POSTGRES_PASSWORD"' +if ([string]::IsNullOrWhiteSpace($env:YASHAN_PASSWORD) -or [string]::IsNullOrWhiteSpace($auditPassword)) { + throw 'Test database credential is unavailable' +} +$env:AGENTSQL_TEST_YASHAN_AUDIT_DSN = "postgres://postgres:${auditPassword}@host.docker.internal:25432/${dbName}?sslmode=disable" +$faultContainer = if ($Target -eq 'audit') { $AuditContainer } else { $YashanContainer } +$faultVariable = if ($Target -eq 'audit') { 'AGENTSQL_TEST_YASHAN_AUDIT_FAULT_DIR=/fault' } else { 'AGENTSQL_TEST_YASHAN_BUSINESS_FAULT_DIR=/fault' } +$faultStopped = $false +try { + & docker exec $AuditContainer psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE $dbName" | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not create isolated audit test database' } + & docker run -d --name $testContainer ` + --mount "type=bind,source=$repo,target=/src" ` + --mount "type=bind,source=$moduleCache,target=/gomod" ` + --mount "type=bind,source=$buildCache,target=/buildcache" ` + --mount "type=bind,source=$clientLib,target=/client/lib,readonly" ` + --mount "type=bind,source=$faultDir,target=/fault" ` + --workdir /src --env GOPROXY=off --env GOTOOLCHAIN=local ` + --env GOMODCACHE=/gomod --env GOCACHE=/buildcache --env CGO_ENABLED=1 ` + --env LD_LIBRARY_PATH=/client/lib --env AGENTSQL_YASHAN_E2E=1 ` + --env YASHAN_PASSWORD --env YASHAN_HOST=host.docker.internal ` + --env AGENTSQL_TEST_YASHAN_AUDIT_DSN --env $faultVariable ` + golang:1.26-bookworm go test -tags yashan -timeout 10m -count=1 -v ` + -run '^TestYashanHTTPMCPRealE2E$' ./internal/mcpserver | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not start audit fault MCP test' } + $deadline = (Get-Date).AddMinutes(8) + while (-not (Test-Path -LiteralPath (Join-Path $faultDir 'ready'))) { + if ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker logs $testContainer + throw 'Audit fault test exited before ready signal' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for audit fault test' } + Start-Sleep -Seconds 1 + } + & docker stop $faultContainer | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not stop $Target database" } + $faultStopped = $true + Set-Content -LiteralPath (Join-Path $faultDir 'down') -Value 'stopped' + $deadline = (Get-Date).AddMinutes(2) + while (-not (Test-Path -LiteralPath (Join-Path $faultDir 'down_checked'))) { + if ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker logs $testContainer + throw 'MCP test exited during audit outage' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for fail-closed audit check' } + Start-Sleep -Seconds 1 + } + & docker start $faultContainer | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not restart $Target database" } + $faultStopped = $false + $ready = $false + for ($i = 0; $i -lt 30; $i++) { + if ($Target -eq 'audit') { + & docker exec $AuditContainer pg_isready -U postgres -d $dbName *> $null + if ($LASTEXITCODE -eq 0) { $ready = $true; break } + } else { + $port = Test-NetConnection 127.0.0.1 -Port 1688 -WarningAction SilentlyContinue + if ($port.TcpTestSucceeded) { $ready = $true; break } + } + Start-Sleep -Seconds 1 + } + if (-not $ready) { throw "$Target database did not recover" } + Set-Content -LiteralPath (Join-Path $faultDir 'up') -Value 'recovered' + $deadline = (Get-Date).AddMinutes(2) + while ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -eq 'true') { + if ((Get-Date) -ge $deadline) { throw 'Audit fault MCP test did not finish' } + Start-Sleep -Seconds 1 + } + $testExit = (& docker inspect $testContainer --format '{{.State.ExitCode}}').Trim() + & docker logs $testContainer + if ($testExit -ne '0') { throw "Audit fault MCP test failed with exit=$testExit" } + Write-Output "V210_YASHAN_${Target}_FAULT=PASS" +} +finally { + if ($faultStopped) { & docker start $faultContainer | Out-Null } + Remove-Item Env:\YASHAN_PASSWORD,Env:\AGENTSQL_TEST_YASHAN_AUDIT_DSN -ErrorAction SilentlyContinue + & docker rm -f $testContainer 2>$null | Out-Null +} diff --git a/website/deploy/verify-static.sh b/website/deploy/verify-static.sh index 4b156f0a..2706c963 100644 --- a/website/deploy/verify-static.sh +++ b/website/deploy/verify-static.sh @@ -197,9 +197,9 @@ fi pdf_fail=0 for pdf in \ - assets/docs/agentsql-getting-started-v2.0.0.pdf \ - assets/docs/agentsql-user-guide-v2.0.0.pdf \ - assets/docs/agentsql-mcp-integrations-v2.0.0.pdf; do + assets/docs/agentsql-getting-started-v2.1.0.pdf \ + assets/docs/agentsql-user-guide-v2.1.0.pdf \ + assets/docs/agentsql-mcp-integrations-v2.1.0.pdf; do pdf_path="$SITE_ROOT/$pdf" if [[ ! -f $pdf_path ]]; then printf ' 缺失 PDF: %s\n' "$pdf" diff --git a/website/public/assets/docs/agentsql-getting-started-v2.1.0.pdf b/website/public/assets/docs/agentsql-getting-started-v2.1.0.pdf new file mode 100644 index 00000000..404cb9d8 Binary files /dev/null and b/website/public/assets/docs/agentsql-getting-started-v2.1.0.pdf differ diff --git a/website/public/assets/docs/agentsql-mcp-integrations-v2.1.0.pdf b/website/public/assets/docs/agentsql-mcp-integrations-v2.1.0.pdf new file mode 100644 index 00000000..d867f673 Binary files /dev/null and b/website/public/assets/docs/agentsql-mcp-integrations-v2.1.0.pdf differ diff --git a/website/public/assets/docs/agentsql-user-guide-v2.1.0.pdf b/website/public/assets/docs/agentsql-user-guide-v2.1.0.pdf new file mode 100644 index 00000000..43ce4c56 Binary files /dev/null and b/website/public/assets/docs/agentsql-user-guide-v2.1.0.pdf differ diff --git a/website/public/demo/index.html b/website/public/demo/index.html index ebabac30..c790367b 100644 --- a/website/public/demo/index.html +++ b/website/public/demo/index.html @@ -40,7 +40,7 @@
-
AgentSQL v2.0.0 · Live Demo 安全入口
+
AgentSQL v2.1.0 · Live Demo 安全入口

先确认边界,再进入演示

此环境只用于体验 AgentSQL 的控制台与六个预置安全剧本,不是生产服务,也不连接任何真实业务数据库。

@@ -56,7 +56,7 @@

先确认边界,再进入演示

我已了解,进入演示 返回官网
-

控制台由独立的单网关演示源提供;入口地址保持为 https://agentsql.cn/demo/。公开演示未启用双网关 Kubernetes 配置或可选人员委托模式;版本边界见发布说明。

+

控制台由独立的单网关演示源提供;入口地址保持为 https://agentsql.cn/demo/。公开演示未启用双网关 Kubernetes 配置或可选人员委托模式;版本边界见发布说明。

diff --git a/website/public/index.html b/website/public/index.html index 32f51eb9..7eb801ee 100644 --- a/website/public/index.html +++ b/website/public/index.html @@ -75,14 +75,14 @@
-
v2.0.0
+
v2.1.0

AI Agent 的数据库安全网关

AgentSQL(中文名:智盾)是面向 AI Agent 的数据库安全网关与 MCP Server。模型发出的 PostgreSQL / MySQL 请求在到达数据库前,经过身份认证、SQL 解析、静态与动态风险检查、四态决策与受控执行;结果按配置脱敏,并在应用层留下持久审计。

-

v2.0.0 为固定双网关部署提供 Kubernetes 清单、维护窗口升级与故障恢复指南,并为 PostgreSQL 单表列策略增加正式登记 API。数据库支持范围保持不变;公开 Live Demo 使用同版单网关合成数据环境,双网关能力以隔离 Kubernetes 验收为准。

+

v2.1.0 在崖山 YashanDB 23.4.1.109 指定实例上,验证最小权限账号、真实 HTTP MCP、R010 拒绝、手机号脱敏、持久审计及业务库和审计库故障关闭。支持范围限于合成数据的受控只读子集;公开 Live Demo 仍使用单网关合成 PostgreSQL 数据。

v0.5.0 起 Apache-2.0 开源 · Go 单二进制交付(glibc / cgo 构建)· 自托管 · 无第三方遥测依赖

它不是 BI,也不是 ORM 或 Text2SQL,而是模型与数据库之间一道默认拒绝的安全闸门。

@@ -200,31 +200,31 @@

让模型的数据库访问可约束、可解释、可追溯

文档 / 下载

从本地演示到 MCP 接入

按真实部署状态选择入口。文档对应源码仓库 github.com/cuipengdba/agentsql。

-
01

快速上手

用本地 Live Demo 看效果,再按默认拒绝的顺序接入自己的数据库。

-
02

使用手册

按控制台菜单了解审计、Agent、数据源、权限、规则、审批与脱敏边界。

-
03

MCP 接入指南

对照客户端能力选择现有 /mcp、独立无状态 /mcp/2026-07-28 或 stdio,查看工具与拒绝边界。

+
01

快速上手

用本地 Live Demo 看效果,再按默认拒绝的顺序接入自己的数据库。

+
02

使用手册

按控制台菜单了解审计、Agent、数据源、权限、规则、审批与脱敏边界。

+
03

MCP 接入指南

对照客户端能力选择现有 /mcp、独立无状态 /mcp/2026-07-28 或 stdio,查看工具与拒绝边界。

-

更多资料

部署指南 · 在线演示说明

v2.0.0 提供 Linux amd64 / arm64 安装包与多架构镜像;Live Demo 入口仅使用合成数据。

GHCR 多架构拉取(自动匹配 amd64 / arm64)

docker pull ghcr.io/cuipengdba/agentsql:v2.0.0

一键安装(Linux amd64 / arm64)

curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.0.0/install.sh | sudo sh -s -- install --version v2.0.0

v0.4.0 起提供 linux/arm64 原生 glibc 包 · x86_64 / amd64 或 aarch64 / arm64 · glibc ≥ 2.28 · systemd · 需要 sudo/root

- +

更多资料

部署指南 · 在线演示说明

v2.1.0 提供 Linux amd64 / arm64 安装包与多架构镜像;Live Demo 入口仅使用合成数据。

GHCR 多架构拉取(自动匹配 amd64 / arm64)

docker pull ghcr.io/cuipengdba/agentsql:v2.1.0

一键安装(Linux amd64 / arm64)

curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.1.0/install.sh | sudo sh -s -- install --version v2.1.0

v0.4.0 起提供 linux/arm64 原生 glibc 包 · x86_64 / amd64 或 aarch64 / arm64 · glibc ≥ 2.28 · systemd · 需要 sudo/root

+
-

兼容性

v2.0.0 兼容矩阵

本版增加固定双网关部署和 PostgreSQL 列策略登记;数据库适配范围沿用 v1.9.0。下表按实际能力与指定环境证据分档。

+

兼容性

v2.1.0 兼容矩阵

本版深化崖山指定实例的受控只读证据,并补齐已认证 SQL 工具的数据源预检拒绝审计;其他数据库范围沿用 v2.0.0。下表按实际能力与指定环境证据分档。

8 大类 / 29 款已注册瀚高 HighGo 已计入;另列金仓 KingbaseES V9 的指定实例 PG 协议验证
- + - + @@ -266,11 +266,11 @@

让模型的数据库访问可约束、可解释、可追溯

生态与兼容

国产数据库生态适配进展

v1.8.0 在 Vastbase G100 Build 9 PG 模式的指定实例补充 TLS 服务端身份校验、证书与凭据轮换。其他版本、模式、正式主备切换和完整生产支持仍须单独验证。欢迎数据库厂商联合验证。

-

国产数据库生态适配矩阵(v2.0.0)

+

国产数据库生态适配矩阵(v2.1.0)

表格可左右滑动,查看完整验证说明。

AgentSQL v2.0.0 数据库兼容矩阵:8 大类、29 款已注册数据库,金仓 KingbaseES V9 另列,共 30 行。AgentSQL v2.1.0 数据库兼容矩阵:8 大类、29 款已注册数据库,金仓 KingbaseES V9 另列,共 30 行。
类别数据库适配状态边界说明
关系型PostgreSQL 14–18🟢 已适配 · 完整防护全链路实测通过
MySQL 8.0🟢 已适配 · 完整防护全链路实测通过
Oracle 23ai🔵 已适配 · 受控只读连接 / 元数据 / 只读查询子集
达梦 DM8🟢 指定场景闭环Pack3、COMPATIBLE_MODE=0 真库只读链路
崖山 YashanDB🔵 已适配 · 受控只读标准 parser 接通,真库流水线通过
崖山 YashanDB🔵 指定版本受控只读23.4.1.109 指定实例:最小权限 HTTP MCP、R010、脱敏、双客户端与独立持久审计通过;TLS、主备和完整生产支持未验证
SQL Server 2025🔵 已适配 · 受控只读连接 / 元数据 / 只读查询子集
Vastbase G100🔵 指定版本受控只读V3.0 Build 9 PG 模式:有界单表 SELECT、授权、脱敏、MCP 和持久审计实测;v1.8.0 验证 TLS、证书与凭据轮换。临时许可,HA、其他版本和生产负载未测
瀚高 HighGo🔵 指定版本受控只读独立 highgo 类型;9.0.10 企业版 PG 模式的有界 SELECT、权限、掩码、客户端与故障链路通过
- + @@ -282,7 +282,7 @@

让模型的数据库访问可约束、可解释、可追溯

- + @@ -300,11 +300,11 @@

让模型的数据库访问可约束、可解释、可追溯

开始使用

快速开始

在 Docker 与依赖网络已就绪时通常数分钟可完成(受网络环境影响)。GHCR tag 会自动匹配 linux/amd64 或 linux/arm64;源码路径仍以仓库 README 为准。

GHCRamd64 / arm64
# Docker 自动选择当前平台镜像
-docker pull ghcr.io/cuipengdba/agentsql:v2.0.0
+docker pull ghcr.io/cuipengdba/agentsql:v2.1.0
 
 # 推荐使用仓库 quickstart 生成凭据并启动
-curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.0.0/scripts/quickstart.sh -o quickstart.sh
-sh quickstart.sh --version v2.0.0
默认仅绑定 127.0.0.1:7780
+curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.1.0/scripts/quickstart.sh -o quickstart.sh +sh quickstart.sh --version v2.1.0
默认仅绑定 127.0.0.1:7780
Live Demo合成数据
# 复制演示环境样例并替换占位凭据
 cp examples/docker/demo.env.example demo/demo.env
 
@@ -355,7 +355,8 @@ 

让模型的数据库访问可约束、可解释、可追溯

  • v1.7.0瀚高限定只读与金仓指定实例证书校验已发布阶段
  • v1.8.0Vastbase 指定实例可校验 TLS 与轮换已发布阶段
  • v1.9.0Dify / Inspector 接入向导与审计定位已发布阶段
  • -
  • v2.0.0固定双网关部署与 PostgreSQL 列策略登记当前正式版本
  • +
  • v2.0.0固定双网关部署与 PostgreSQL 列策略登记已发布阶段
  • +
  • v2.1.0崖山指定实例受控只读与预检拒绝审计当前正式版本
  • 下一阶段国产与商业数据库适配
  • 后续方向企业身份与审计增强SSO/RBAC、WORM 选项
  • 后续方向高可用与集中管控
  • diff --git a/website/tools/build-docs-pdf.mjs b/website/tools/build-docs-pdf.mjs index e2bed769..b84564b4 100644 --- a/website/tools/build-docs-pdf.mjs +++ b/website/tools/build-docs-pdf.mjs @@ -16,7 +16,7 @@ import { tmpdir } from 'node:os'; import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -const VERSION = 'v2.0.0'; +const VERSION = 'v2.1.0'; const REPOSITORY_BLOB = `https://github.com/cuipengdba/agentsql/blob/${VERSION}`; const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); const REPOSITORY_ROOT = resolve(SCRIPT_DIR, '..', '..'); @@ -29,19 +29,19 @@ const FIXED_PDF_DATE = "D:20261019000000+00'00'"; const DOCUMENTS = [ { source: resolve(DOCS_ROOT, 'GETTING_STARTED.md'), - output: 'agentsql-getting-started-v2.0.0.pdf', + output: 'agentsql-getting-started-v2.1.0.pdf', title: '快速上手', slug: 'getting-started', }, { source: resolve(DOCS_ROOT, 'USER_GUIDE.md'), - output: 'agentsql-user-guide-v2.0.0.pdf', + output: 'agentsql-user-guide-v2.1.0.pdf', title: '使用手册', slug: 'user-guide', }, { source: resolve(DOCS_ROOT, 'INTEGRATIONS.md'), - output: 'agentsql-mcp-integrations-v2.0.0.pdf', + output: 'agentsql-mcp-integrations-v2.1.0.pdf', title: 'MCP 接入指南', slug: 'mcp-integrations', }, @@ -807,6 +807,9 @@ async function main() { 'agentsql-getting-started-v1.9.0.pdf', 'agentsql-mcp-integrations-v1.9.0.pdf', 'agentsql-user-guide-v1.9.0.pdf', + 'agentsql-getting-started-v2.0.0.pdf', + 'agentsql-mcp-integrations-v2.0.0.pdf', + 'agentsql-user-guide-v2.0.0.pdf', ]; const expectedFiles = [...DOCUMENTS.map((document) => document.output), ...archivedFiles].sort(); if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
    AgentSQL v2.0.0 国产数据库生态适配进展;状态以指定版本、镜像、拓扑及仓库证据为准。AgentSQL v2.1.0 国产数据库生态适配进展;状态以指定版本、镜像、拓扑及仓库证据为准。
    数据库适配状态说明
    中电科金仓 KingbaseES🟢 PG 协议指定子集实测通过仅 V9R1C10、PG 模式、单实例合成表:只读角色拒写,受控 SELECT、手机号脱敏、R006 拒绝和持久审计通过;v1.6.0 补齐故障关闭,v1.7.0 验证 CA 与主机名校验、错误证书拒绝、证书及账号密码轮换。其他模式、复杂查询、正式主备切换与生产负载未验证。验收记录。
    TDSQL for PostgreSQL待目标环境验证PG 协议候选;商业版未实测,不能用 OpenTenBase 结果替代。
    TDSQL for MySQL待目标环境验证MySQL 协议候选;商业版未实测,不与 TDSQL for PostgreSQL 混同。
    OpenTenBase适配中执行计划兼容修复实现中。
    崖山数据库 YashanDB🔵 受控只读独立 yashan parser 与窄 SELECT 路径已接通;独立客户端真库流水线测试通过 SELECT、列血缘、R010、表列定向手机号脱敏及三条测试审计,最小权限账号写入拒绝经原生探针验证。C 客户端再分发目前仅有厂家口头授权、尚无书面文件;正式双架构 Linux 包按发布脚本封装对应架构客户端。源码自构建仍须准备匹配客户端并用 CGO_ENABLED=1 和 -tags yashan 编译,缺运行库 fail-closed。HTTP/MCP、持久化审计、EXPLAIN、写入未测或未放开。
    崖山数据库 YashanDB🔵 指定版本受控只读Enterprise Edition 23.4.1.109、COMPAT_VECTOR=yashan、独立客户端 23.4.7.100:最小权限账号经 HTTP MCP 通过窄 SELECT、R010、手机号脱敏和持久审计;Inspector/Dify 各完成允许与拒绝。SELECT 撤权、业务库断线及独立审计库停机均验证无原始结果交付。C 客户端再分发仅有厂家口头授权、仓库无书面文件;TLS、客户端取消/超时、主备、写入、事务、EXPLAIN 和完整生产支持未验证或关闭。验收边界。
    达梦数据库 DM🟢 指定实例防护链路通过DM8 Pack3、COMPATIBLE_MODE 0:有界 HTTP MCP 真库复验覆盖查询、R010 表策略、手机号脱敏及持久审计;v1.5.0 完成客户端、权限撤销与停库恢复,v1.6.0 补齐独立审计库故障、锁等待超时、客户端取消和恢复复验。多行分页、JOIN、函数、TLS、HA 与生产负载未验证;写入未放开。
    Vastbase G100🔵 指定版本受控只读V3.0 Build 9、PG 模式、单实例合成表:独立 vastbase 类型强制只读,有界 SELECT、表授权、手机号脱敏、MCP 审批单次执行及持久审计实测;v1.6.0 补齐故障关闭,v1.8.0 验证正确和错误证书、主机名、明文拒绝、证书和密码轮换,以及 Inspector/Dify 真实调用。使用临时许可;正式主备、其他 Build 与生产负载未测。验收记录。
    PolarDB已实测PolarDB for PostgreSQL 15,指定社区镜像经 postgres 路径完成最小闭环;商业服务、HA、TLS、B2/B5 未覆盖。