
环境Easysearch 2.4.0-2975 认证Basic X-API-TOKEN这个功能的由来几个月前我在极限科技的交流群里提过一个想法能不能参考 OpenSearch 的 OpenID ConnectOIDC登录让 Easysearch 也支持用 JWT 鉴权来访问 API。最近我又去看了看功能发现已经发布了 API Key。简单来说就是在 Easysearch 的 UI 上直接申请一个 API Key再给它设置类似 OpenSearch 精细访问控制的那套东西这个 Key 能访问集群的哪些 API、能操作哪个索引、哪些文档以及要隐藏哪些字段。方便的地方随时可以创建权限在申请时就划定好生成后在控制台直接复制出来就能用。给程序接入的时候很快。还差的一步目前它不是 JWT Token所以还没有规范化 —— 我指的是类似 OpenID Connect 那种规范的集成从 Token 就能看出持有者是谁、属于哪个组。希望厂家后面能把这部分补齐。不过比起原来的做法它已经往前走了一步。一般我们使用 ES 的时候都是用用户名和密码凭据在传播的过程中如果被抓包Base64 解码就能还原出来而要吊销和轮换也远远没有 API Key 那么方便 —— 后者可以只停掉、只更换这一个 Key不用去动那个所有程序都在共用的账号。OpenSearch 里也有这套精细访问控制用过 OpenSearch 的话这里的权限模型应该很熟悉限制可以访问哪些索引、执行哪些操作再细分到哪些文档和字段可见就是精细访问控制Fine-grained Access ControlFGAC。但有一处根本的不同OpenSearch 把这套权限定义在角色上角色再映射给用户、后端角色或 OIDC 的 claim——凭据和授权是两层。Easysearch 的 Access Token 则把权限描述直接写在凭据自己身上中间没有角色这一层。于是改权限只能更新这一个 token而不是改一个角色让所有使用者一起生效审计日志里看到的也是 token而不是人或组。这也是它目前还谈不上「规范化」的地方——缺的不只是 JWT 那个格式而是一个能追溯的身份主体。作为交换token 有角色没有的东西expire_in。省略时服务端默认只给 1 小时这对临时凭据反而正合适。OpenSearchEasysearch Access Token权限挂在哪角色上PUT /_plugins/_security/api/roles/{role}角色再经 role mapping 绑到用户 / backend_roles / OIDC-SAML 的 claim凭据自己身上POST /_security/access_token的 body 里直接写cluster/indicestoken 文档以权限描述形式存在.security索引改权限改一个角色所有映射到它的人一起生效只能更新这一个 tokenPUT /_security/access_token/{token_id}过期角色没有过期概念要时效凭据得靠外部 IdP 的 JWT 自己过期expire_in省略时默认只有 1 小时审计里看到谁用户 / 后端角色能追到人和组token 本身看不出是谁、哪个组还有什么维度多一层tenant_permissionstenant_patternskibana_all_read/kibana_all_write另有description/hidden/reserved没有租户概念多的是name/description/expire_in这类凭据元数据两边的配置概念可以对照不过字段名并不完全一样配置含义Easysearch 2.4.0 Access TokenOpenSearch Security 角色集群操作权限clustercluster_permissions允许访问的索引indices[].namesindex_permissions[].index_patterns允许执行的操作indices[].privilegesindex_permissions[].allowed_actions字段过滤规则indices[].field_securityindex_permissions[].fls文档级过滤DLSindices[].queryindex_permissions[].dls字段脱敏indices[].field_maskindex_permissions[].masked_fields上表对照的是权限描述不能把一边的请求体原样交给另一边。后面的命令沿用 Easysearch 2.4.0 的 Access Token 和 X-API-TOKEN 请求头OpenSearch 的角色管理则按它自己的 Security REST API 配置。这次怎么测管理员用用户名密码认证也就是 HTTP Basic负责写入和清理测试文档。API Key 则用来测试读取、搜索和写入。Easysearch 文档里把这类凭据叫作 Access Token下文说的 API Key 指的就是它。1.管理员 Basic 认证— 写入带有 test 字段的文档2.受限 API Key— 分别测试 health、get、search 和 write3.检查结果— 查看状态码和返回的字段这次测试的版本使用下面这个请求头传递 TokenX-API-TOKEN: access-tokenToken 无效时响应里会出现 invalid access token。Token 有效但权限不够时响应会指出缺少哪个 action也就是具体的操作权限。排查时先看清这两种错误免得权限没配对却一直在换 Token。准备测试下面的命令沿用本地地址 https://localhost:9200。密码和 Token 都换成了占位符跟着操作时替换成自己的值。API Key 的权限配置放在后面单独说明。用户名admin密码admin-passwordBasic 凭据base64-credentialsAPI Tokenaccess-tokencurl -k \ -H Authorization: Basic base64-credentials \ https://localhost:9200/curl -k \ -H X-API-TOKEN: access-token \ https://localhost:9200/_cluster/health用户名密码是怎么传过去的先看管理员使用的 HTTP Basic。客户端把用户名和密码用冒号连起来做一次 Base64 编码再放进请求头原始内容admin:admin-password 编码Base64(admin:admin-password) 最终请求头Authorization: Basic base64-credentials用 curl -u 时这一步由 curl 完成也可以自己传入编码后的请求头。下面两种写法的认证方式相同# 让 curl 生成 Authorization 请求头curl -u admin:admin-password https://example.invalid/ # 手工传入已经编码的请求头curl -H Authorization: Basic base64-credentials \ https://example.invalid/Base64 不是加密这段编码可以还原出用户名和密码所以编码后的值也得当密码保管。传输时需要 HTTPS由 TLS 保护请求头写进脚本、终端历史或日志里的凭据则需要另外妥善处理。在这次使用内部用户认证的实例里请求大致经过下面几步1. 客户端发送 Authorization 请求头curl 把 用户名:密码 编码后放进 Authorization: Basic ...再通过 TLS 连接发送给 Easysearch。2. Easysearch 读取认证信息请求没有携带凭据时服务返回 401并通过 WWW-Authenticate: Basic realmSecurity 提示客户端提供 Basic 认证信息。3. 服务端解码并验证密码安全模块解码出用户名和密码交给内部用户后端校验。用户配置中保存的是密码校验信息。4. 加载用户的角色密码校验通过后Easysearch 读取这个用户关联的角色和安全属性用于后续的权限检查。5. 检查这次操作有没有权限写文档会检查 indices:data/write/index搜索会检查 indices:data/read/search。身份正确但没有对应权限仍然会得到 403。所以密码正确也可能访问不了索引。碰到 401先检查认证信息碰到带具体 action 的 403则该检查角色或 Token 的权限配置。用户名密码认证Authorization: Basic base64(user:password) 服务端 验证用户 加载角色 检查权限Easysearch Access TokenX-API-TOKEN: access-token 服务端 查找 Token 检查状态与有效期 使用 Token 权限描述用户名密码对应用户及其角色API Key 则按自己的权限配置接受检查。程序只需要读取数据时我更愿意给它一个限定索引、有有效期的只读 Token后续也方便单独管理。给 API Key 配好权限这次用到的权限如下。这里只摘出权限字段省略了 Token 和有效期cluster:monitor/health 允许查看集群健康状态。下面的 indices 把范围限定在 test 索引且只允许 indices:data/read/get也就是按 ID 读取单条文档。先用管理员写入一条文档文档里只放两个字段a 留着读取test 用来验证过滤效果。命令中的 -k 会跳过证书校验这里是为了连接本地使用自签名证书的测试实例正式使用时应配置受信任的证书或指定 CA 文件。curl -k -u admin:admin-password \ -H Content-Type: application/json \ -X PUT \ https://localhost:9200/test/_doc/fgac-probe?op_typecreaterefreshtrue \ -d { a: 42, test: must-be-hidden }管理员写入返回HTTP 201{ _index: test, _id: fgac-probe, result: created}写入后我又用管理员读了一次确认 a 和 test 两个字段都在。换成 API Key 读取先看集群健康状态curl -k \ -H X-API-TOKEN: access-token \ https://localhost:9200/_cluster/health请求返回 HTTP 200健康检查权限通过了。当时集群状态是 yellow因为单节点环境中有副本分片未分配。这个状态反映的是分片情况接口本身已经正常返回。再按 ID 读取文档curl -k \ -H X-API-TOKEN: access-token \ https://localhost:9200/test/_doc/fgac-probe这个请求对应配置里的 indices:data/read/get也返回了 HTTP 200。test 字段确实被隐藏了同一条文档分别用管理员和 API Key 读取管理员 Basic 认证{ found: true, _source: { a: 42, test: must-be-hidden }}受限 API Key{ found: true, _source: { a: 42 }}两边的 found 都是 true。管理员能看到两个字段API Key 的 _source 里只剩下 a。我又加上 _source_includestest只请求这个被隐藏的字段curl -k \ -H X-API-TOKEN: access-token \ https://localhost:9200/test/_doc/fgac-probe?_source_includestest返回仍然是 HTTP 200但 _source 为空{ found: true, _source: {}}指定字段名也没有读到它这次请求仍有读取文档的权限所以返回 200。字段过滤后没有可返回的内容_source 就成了空对象。至少在这条 GET 请求里客户端加上字段选择参数也拿不到被排除的字段。搜索、写入和其他索引能不能访问读取成功后我继续用同一个 API Key 搜索、写入并尝试访问其他索引。连同前面的两个请求结果如下和我的相符请求状态实际结果GET /_cluster/health200允许集群健康权限生效GET /test/_doc/fgac-probe200允许单文档 GET 权限生效GET /test/_search403拒绝缺少 indices:data/read/searchPUT /test/_doc/another-id403拒绝缺少 indices:data/write/indexGET /not-authorized/_doc/1403拒绝索引范围不匹配搜索请求的报错直接指出了缺少的权限HTTP 403{ error: { type: security_exception, reason: no permissions for indices:data/read/search and access token }, status: 403}写入请求也返回了 403。随后我用管理员账号读取准备写入的那个 ID得到 404 和 found: false确认这次写入没有创建文档。测试结束后清理文档curl -k -u admin:admin-password \ -X DELETE \ https://localhost:9200/test/_doc/fgac-probe?refreshtrue删除返回 result: deleted。再用管理员读取返回 404 和 found: false这条测试文档已经清理掉了。跟着操作时留意这几处· 这次使用的 Easysearch 2.4.0-2963 接收 X-API-TOKEN 请求头。其他产品示例里的 Authorization: ApiKey 或 Authorization: Bearer 写法不能直接照搬。· 判断字段过滤是否生效要看返回的 _source。这次测试中隐藏字段后请求仍然返回 200不能只盯着状态码。测试范围以上记录对应本地 Easysearch 2.4.0-2963以及文中列出的请求和权限配置。换版本或调整权限后可以用同一组请求重新检查结果。对这次只按 ID 读取文档的需求配置已经够用程序能拿到 a读不到 test搜索和写入也都被拒绝了。以后程序多用到一个接口再补上对应权限并把允许和拒绝的请求各跑一遍。参考链接· https://docs.opensearch.org/latest/security/authentication-backends/openid-connect/· https://docs.opensearch.org/latest/security/access-control/users-roles/· https://docs.opensearch.org/latest/security/access-control/field-level-security/· https://docs.infinilabs.com/easysearch/v2.4.0/docs/operations/security/access-control/access-token/· https://docs.opensearch.org/latest/security/access-control/api/Easysearch 2.4.0-2975 本地测试记录 · 示例中的密码和 Token 均为占位符。