Cloud WAF/Docs 中文 EN
Cloud WAF 是企业级 Web 防护管理平台。后端 Go + Gin REST API,前端 Vue 3 SPA,附带 zcloud CLI 工具用于自动化运维。

本文档属于 Cloud WAF — 企业 Web 防护管理平台
CLI 工具:zcloud · 5 大模块:guard / sys / analytics / cli_release / auth
完整 API 索引:/api/openapi.json · 文档地图:/sitemap.xml · AI 速读:/llms.txt


认证说明

概述

Cloud WAF 当前支持两种认证方式。所有受保护接口都要求请求头携带 Authorization,同一请求只能选择其中一种通道:

场景 请求头 说明
人工登录 / Web 控制台 / CLI 交互登录 Authorization: Bearer <token> token 由 POST /api/auth/login 颁发,通常有效期为数小时(受系统安全策略约束)
脚本 / CI / 第三方系统集成 Authorization: ApiKey zck_<prefix>.<secret> API Key 是机器调用凭证,明文仅签发时返回一次,适合长期自动化对接

推荐:人工操作使用 Bearer 会话;机器对接优先使用 API Key。历史的“账号密码换短期 token + 401 自动重登”仍可用,但只建议作为兼容方案。

API Key 机器调用凭证

API Key 是发给脚本、CI、第三方系统使用的机器调用凭证。它代表某个用户在某个 OEM 下发起请求,并受到两层限制:

  1. 签发用户当前拥有的 RBAC 权限;
  2. API Key 自身的 scopes 列表。

最终有效权限为:effective_perms = user.RBAC ∩ key.scopescope 只能收窄,不能放大;如果 scopes 为空数组,则完整继承签发用户当前 RBAC。

签发 API Key

curl -sS -X POST https://waf.example.com/api/sys/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
        "name": "生产对接",
        "scopes": ["guard.domain.list", "guard.domain.view"],
        "expires_in_days": 90
      }'

响应中的 data.api_key 是完整明文:

{
  "code": 0,
  "message": "ok",
  "data": {
    "key_id": "8f21c0c5-55ae-4cbd-a60a-8e64a6e2b1d0",
    "api_key": "zck_abc12345.A1b2C3d4E5f6G7h8I9j0K1L2M3n4O5p6Q7r8S9t0",
    "prefix": "zck_abc12345",
    "last4": "5t0",
    "expires_at": 1732982400000
  }
}

api_key 明文仅此一次返回。后续列表接口只返回 prefixlast4,无法找回完整 secret;丢失后请吊销并重新签发。

使用 API Key 调接口

curl -sS https://waf.example.com/api/guard/domains \
  -H "Authorization: ApiKey zck_abc12345.A1b2C3d4..." \
  -H 'Accept-Language: zh-CN'

API Key 与 Bearer token 的区别

维度 Bearer token API Key
主要用途 人登录后的短期会话 机器、脚本、CI、第三方系统长期调用
获取方式 POST /api/auth/login POST /api/sys/api-keys
请求头 Authorization: Bearer <token> Authorization: ApiKey zck_<prefix>.<secret>
生命周期 通常数小时,受自动登出策略影响 默认 90 天,最大 365 天,可主动吊销
权限边界 当前用户 RBAC 当前用户 RBAC ∩ Key scopes
明文保存 客户端保存 token 只在签发响应返回一次,服务端不保存明文

Scope 匹配规则(重要)

API Key 的 scopes 校验语义与用户角色 RBAC 刻意分离,请务必先理解后再签发:

安全建议

登录链路图(文字版)

┌─────────────┐                                  ┌────────────────┐
│ 客户端 / CLI │ ──── POST /api/auth/login ───▶ │ Cloud WAF 后端 │
└─────────────┘    {username, password}          └────────────────┘
       │                                                  │
       │ ◀────── 200 OK {data: {token, user}} ───────────│
       │                                                  │
       │ ──── GET /api/sys/users (Bearer token) ────────▶│
       │ ◀────── 200 OK {code:0, data:[...]} ────────────│
       │                                                  │
       │ ──── (token 过期) GET /api/... ────────────────▶│
       │ ◀────── 401 Unauthorized ───────────────────────│
       │                                                  │
       │ ──── POST /api/auth/login (重新登录) ──────────▶│
       │ ◀────── 200 OK {data: {token: <新>}} ──────────│

完整 curl 示例

1) 登录

curl -sS -X POST https://waf.example.com/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{
        "username": "admin",
        "password": "YOUR_PASSWORD"
      }'

返回:

{
  "code": 0,
  "message": "ok",
  "data": {
    "token": "eyJhbGciOi...",
    "user": { "uuid": "abc-123", "username": "admin", "role_id": 1 }
  }
}

2) 携带 token 调用接口

TOKEN='eyJhbGciOi...'
curl -sS https://waf.example.com/api/sys/users \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept-Language: zh-CN'

3) 注销

curl -sS -X POST https://waf.example.com/api/auth/logout \
  -H "Authorization: Bearer $TOKEN"

注销后该 token 立刻失效,后续使用返回 401。

CLI 多 profile 机制

CLI 把凭据写到 ~/.zcloud/credentials.toml,结构如下:

active = "default"

[profiles.default]
api_url = "https://waf.example.com"
username = "admin"
token = "eyJhbGciOi..."
oem_id = "default"

[profiles.prod]
api_url = "https://prod.example.com"
username = "ops"
token = "eyJhbGciOi..."

切换 profile:

zcloud config profiles list
zcloud config profiles activate prod

# 注意:profiles create 只接受位置参数 <name>,不支持任何 flag
zcloud config profiles create staging
zcloud --profile staging config set api_url https://staging.example.com

每次 zcloud auth login 会更新当前 profile 的 token;不同 profile 互不干扰。

401 自动重试模式

token 过期是常态,正确做法是把"401 → 重登 → 重试"封装成 wrapper。

Python 伪代码

import requests

class CloudWAF:
    def __init__(self, base_url, username, password):
        self.base = base_url.rstrip('/')
        self.username = username
        self.password = password
        self.token = None
        self._login()

    def _login(self):
        r = requests.post(f"{self.base}/api/auth/login", json={
            "username": self.username,
            "password": self.password,
        })
        r.raise_for_status()
        self.token = r.json()["data"]["token"]

    def call(self, method, path, **kwargs):
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {self.token}"
        url = f"{self.base}{path}"
        r = requests.request(method, url, headers=headers, **kwargs)
        if r.status_code == 401:
            self._login()  # 重登
            headers["Authorization"] = f"Bearer {self.token}"
            r = requests.request(method, url, headers=headers, **kwargs)
        r.raise_for_status()
        return r.json()

Go 伪代码

type Client struct {
    BaseURL  string
    User     string
    Password string
    Token    string
    HTTP     *http.Client
}

func (c *Client) login() error {
    body, _ := json.Marshal(map[string]string{"username": c.User, "password": c.Password})
    resp, err := c.HTTP.Post(c.BaseURL+"/api/auth/login", "application/json", bytes.NewReader(body))
    if err != nil { return err }
    defer resp.Body.Close()
    var out struct{ Data struct{ Token string } }
    json.NewDecoder(resp.Body).Decode(&out)
    c.Token = out.Data.Token
    return nil
}

func (c *Client) Do(req *http.Request) (*http.Response, error) {
    req.Header.Set("Authorization", "Bearer "+c.Token)
    resp, err := c.HTTP.Do(req)
    if err != nil { return nil, err }
    if resp.StatusCode == 401 {
        resp.Body.Close()
        if err := c.login(); err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+c.Token)
        return c.HTTP.Do(req)
    }
    return resp, nil
}

机器对接模式(兼容方案)

机器对接推荐使用上文 API Key。只有在暂时无法签发 API Key、或需要兼容旧流水线时,才使用“账号密码换短期 token + 401 自动重登”模式。

zcloud auth login 是交互式命令(用 util.ReadPassword 读不回显的密码),不支持 --username/--password 之类的 flag。CI Pipeline / 第三方系统应直接调用登录 REST API 拿到 token,然后通过 ZCLOUD_TOKEN 环境变量注入 CLI。

推荐做法:

  1. 优先评估 API Key:如果只是调用 REST API,直接签发 API Key;如果必须复用旧 CLI 流程,再继续下面步骤
  2. 创建专用账号:在 Web 控制台为 CI 创建 ci-bot 用户,分配最小必要权限
  3. 凭据保存到 secretsCLOUDWAF_USERNAME / CLOUDWAF_PASSWORD 存到 GitHub Actions / GitLab CI secrets
  4. 任务开始时调 REST 拿 tokenPOST /api/auth/login,密码用 base64 编码后传
  5. 后续 CLI 命令读取 ZCLOUD_TOKEN 环境变量,无需写入本地 credentials 文件
  6. 永远启用 401 重试 wrapper:长任务中 token 可能过期,wrapper 拿到 401 时重新登录刷新 token
# Gitea Actions 示例
jobs:
  publish-cert:
    steps:
      - run: |
          curl -fsSL ${{ vars.WAF_URL }}/api/cli/install.sh | sh
          zcloud config set api_url ${{ vars.WAF_URL }}

          # 1) 通过 REST 接口换 token(密码 base64 编码)
          PWD_B64=$(printf '%s' "${{ secrets.WAF_PASS }}" | base64 -w0)
          TOKEN=$(curl -fsS "${{ vars.WAF_URL }}/api/auth/login" \
            -H 'Content-Type: application/json' \
            -d "{\"username\":\"${{ secrets.WAF_USER }}\",\"password\":\"$PWD_B64\"}" \
            | jq -r '.data.token')

          # 2) 注入环境变量后跑业务命令;CLI 自动读取 ZCLOUD_TOKEN
          export ZCLOUD_TOKEN="$TOKEN"
          zcloud guard certs upload --name star --cert ./fullchain.pem --key ./private.key

          # 3) 任务结束撤销 token
          zcloud auth logout

安全建议

常见问题

现象 处理
登录返回 code 非 0 检查 message,常见原因:账号锁定、密码错、密码到期
调接口反复 401 检查 token 是否粘贴完整、Authorization 头格式是否正确
API Key 调接口返回 401 检查请求头是否为 Authorization: ApiKey zck_<prefix>.<secret>,确认 key 未过期或吊销
API Key 调接口返回 code=1053 (HTTP 403) scopes 未覆盖接口所需权限。注意 scope 是精确字符串相等,写 guard.* 不会命中任何接口,需逐条列举叶子 perm key
API Key 调接口返回 code=1056 (HTTP 403) 跨 OEM 越权:请求 hostname 与签发 API Key 时绑定的 OEM 不一致,请用同 OEM hostname 重试
登录返回 200 但 data.token 为空 服务端配置异常,联系运维
多个 CLI 进程互相挤掉对方 token 用不同 profile 隔离

相关文档


Cloud WAF · 支持 Bearer 会话与 API Key 机器调用凭证双通道认证