Salt 的 Redis Cluster 外部认证令牌存储后端(salt.tokens.rediscluster)深度解析

📅 发布时间:2026/9/25 3:48:34
Salt 的 Redis Cluster 外部认证令牌存储后端(salt.tokens.rediscluster)深度解析
运维配置管理后端【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址https://gitcode.com/gh_mirrors/sa/salt点击查看免费下载导读本文基于当前仓库中 salt/tokens/rediscluster.py 模块及其 Sphinx 文档存根 salt.tokens.rediscluster 展开讲解 Salt master 如何借助 Redis Cluster 分布式存储 eauthexternal authentication令牌覆盖配置方式、四个核心接口的实现细节、与LoadAuth的调用关系、测试证据以及该后端当前已标记弃用的事实。读完本文你将掌握 Redis Cluster 令牌后端的完整运行机制、decode_responses相关坑点并了解官方建议的替代方案。一、背景Salt 的 eauth 令牌存储体系Salt 的外部认证eauth体系允许 master 将认证产生的令牌token缓存在不同的后端存储中。在 salt/tokens/init.py 中定义了一个统一的令牌存储接口每个存储后端都必须实现以下四个方法方法职责mk_token(opts, tdata)铸造一枚新的唯一令牌并存入存储get_token(opts, tok)根据令牌值取回对应的令牌数据rm_token(opts, tok)从存储中删除指定令牌list_tokens(opts)列出存储中全部令牌当前仓库中 salt/tokens/ 目录提供两个内置后端localfs默认后端将令牌以文件形式写入 master 的token_dir见 salt/tokens/localfs.pyrediscluster本篇文章的主角将令牌写入 Redis Cluster。这些后端通过 salt/loader/init.py 中的eauth_tokens(opts)加载器加载对应_module_dirs(opts, tokens)并由 salt/auth/init.py 的LoadAuth类统一调度。二、快速上手配置与依赖模块的 docstring 明确给出了使用方式只需启动一个 Redis Cluster 并将所有 hashslot 分配给已连接的节点然后在 master 配置中设置主机与端口两个选项# /etc/salt/master 或 /etc/salt/master.d/xxx.conf eauth_redis_host: localhost eauth_redis_port: 6379eauth_redis_hostRedis Cluster 任一节点的地址默认localhosteauth_redis_portRedis Cluster 任一节点的端口默认6379。这两个默认值在源码_redis_client()中通过opts.get(eauth_redis_host, localhost)与opts.get(eauth_redis_port, 6379)实现见 salt/tokens/rediscluster.py。同时模块声明了硬性依赖redis-py-cluster Python 包。加载时__virtual__()会检测该包是否可用def __virtual__(): if not HAS_REDIS: return ( False, Could not use redis for tokens; rediscluster python client is not installed., ) return __virtualname__即未安装rediscluster客户端时该后端会被 loader 跳过master 会记录一条 rediscluster python client is not installed 的错误说明。要让 master 真正启用该后端还需在配置中指定 token 驱动。在 salt/config/init.py 中eauth_tokens的默认值是localfs因此需要显式改为eauth_tokens: rediscluster三、核心实现四个接口逐一拆解3.1_redis_client()建立连接并返回客户端def _redis_client(opts): redis_host opts.get(eauth_redis_host, localhost) redis_port opts.get(eauth_redis_port, 6379) try: return rediscluster.StrictRedisCluster(hostredis_host, portredis_port) except rediscluster.exceptions.RedisClusterException as err: log.warning( Failed to connect to redis at %s:%s - %s, redis_host, redis_port, err ) return None连接失败时返回None并记录 WARNING 级别的日志所有公共接口都会先检查_redis_client(opts)的返回值是否为None从而保证后端不可用时优雅降级返回空结果而不是抛异常。值得注意的是decode_responses被刻意关闭。docstring 中对此有专门说明——令牌值由salt.payload以 msgpack 序列化必须以 bytes 形式往返传输list_tokens又会对取回的每个 key 调用.decode()这同样要求 bytes。一旦开启decode_responsesTrueget()/keys()会返回str将同时破坏get_tokenmsgpack 拒绝str和list_tokensstr没有.decode且外层宽泛的except会把每次读取静默变成空结果。这是该模块历史上真实踩过的回归坑详见下文测试部分。3.2mk_token()铸造令牌def mk_token(opts, tdata): redis_client _redis_client(opts) if not redis_client: return {} hash_type getattr(hashlib, opts.get(hash_type, DEFAULT_HASH_TYPE)) tok str(hash_type(os.urandom(512)).hexdigest()) try: while redis_client.get(tok) is not None: tok str(hash_type(os.urandom(512)).hexdigest()) except Exception as err: log.warning( Authentication failure: cannot get token %s from redis: %s, tok, err ) return {} tdata[token] tok try: redis_client.set(tok, salt.payload.dumps(tdata)) except Exception as err: log.warning( Authentication failure: cannot save token %s to redis: %s, tok, err ) return {} return tdata要点令牌生成方式取os.urandom(512)的 512 字节随机数用opts.get(hash_type, DEFAULT_HASH_TYPE)指定的哈希算法DEFAULT_HASH_TYPE定义于 salt/config/init.py计算摘要后转为十六进制字符串。hash_type默认值可在 master 配置中调整如hash_type: sha256。唯一性保障通过while redis_client.get(tok) is not None循环重试确保令牌不与已有 key 冲突。存储格式tdata含新生成的token字段以及调用方写入的start、expire、name、eauth等字段经salt.payload.dumps()底层即 msgpack序列化为 bytes 后以令牌为 key 调用set()写入。失败语义任何异常均记 WARNING 并返回空 dict与localfs的契约保持一致。3.3get_token()取回令牌数据def get_token(opts, tok): redis_client _redis_client(opts) if not redis_client: return {} try: tdata salt.payload.loads(redis_client.get(tok)) return tdata except Exception as err: log.warning( Authentication failure: cannot get token %s from redis: %s, tok, err ) return {}读取路径是mk_token的逆过程get()取回 bytessalt.payload.loads()底层msgpack.unpackb反序列化。读取失败同样返回空 dict调用方据此判定该令牌不存在。这正是为什么decode_responsesTrue会致命loads()要求 bytes 输入收到str会抛TypeError被宽泛except吞掉后所有有效令牌都表现为不存在。3.4rm_token()删除令牌def rm_token(opts, tok): redis_client _redis_client(opts) if not redis_client: return try: redis_client.delete(tok) return {} except Exception as err: log.warning(Could not remove token %s: %s, tok, err)成功时返回空 dict失败时返回None即不返回。3.5list_tokens()列出全部令牌def list_tokens(opts): ret [] redis_client _redis_client(opts) if not redis_client: return [] try: return [k.decode(utf8) for k in redis_client.keys()] except Exception as err: log.warning(Failed to list keys: %s, err) return []keys()返回的每个 key 是 bytes这里逐个decode(utf8)转回str。注意ret []实际上未被使用函数直接返回列表推导式的结果。四、在认证流程中的调用关系LoadAuthsalt/auth/init.py在构造时通过salt.loader.eauth_tokens(opts)加载令牌后端并使用opts[eauth_tokens]作为模块选择键self.tokens salt.loader.eauth_tokens(opts) tokens_cluster_id opts[eauth_tokens.cluster_id] or opts[cluster_id] self.cache salt.cache.factory( opts, driveropts[eauth_tokens.cache_driver], cluster_idtokens_cluster_id )典型调用链以mk_token为例客户端向 master 发送mk_token请求LoadAuth.mk_token(load)先执行authenticate_eauth(load)校验用户名/密码/外部认证后端组装tdata包含starttime.time()、expiretime.time() token_expire、nameload_name(load)、eauth等字段若配置keep_acl_in_token: True还会写入auth_list若有用户组则写入groupssalt/auth/init.py当opts[eauth_tokens.cache_driver] rediscluster时调用self.tokens{}.mk_token.format(...)即本模块的mk_token后续请求携带令牌时LoadAuth.get_tok(tok)走get_token取回数据authenticate_token(load)校验令牌中的eauth类型是否在external_auth白名单内并依据expire判断是否过期salt/auth/init.py。令牌过期清理则由LoadAuth.clean_expired_tokens()通过list_tokens()遍历、逐枚检查expire并调用rm_token()完成salt/auth/init.py。与令牌生命周期相关的 master 配置默认值见 salt/config/init.py配置项默认值说明eauth_tokenslocalfs令牌存储后端模块名token_expire4320012 小时令牌默认过期秒数token_expire_user_overrideFalse是否允许用户自定义过期时间keep_acl_in_tokenFalse是否将权限列表随令牌一并存储eauth_tokens.cache_driverNone关联的缓存驱动eauth_tokens.cluster_idNone集群标识缺省回退到cluster_id五、测试证据decode_responses回归防护仓库中 tests/pytests/unit/tokens/test_rediscluster.py 专门为rediscluster令牌后端编写了单元测试其 docstring 记录了一次真实回归_redis_client曾使用decode_responsesTrue导致get()/keys()返回str进而get_token中msgpack.unpackb对str抛TypeErrorlist_tokens中str.decode(utf8)抛AttributeError而两处宽泛的except Exception将错误吞掉并返回{}/[]使故障对运维完全不可见——eauth 令牌永远读不到但 Salt 只记录 WARNING、表现得像令牌不存在。测试用MagicMock替换rediscluster.StrictRedisCluster来固定客户端行为覆盖了以下关键场景test_redis_client_does_not_enable_decode_responses断言客户端构造函数收到的 kwargs 中decode_responses is not True从源头锁死回归test_get_token_returns_full_data_for_existing_token/test_get_token_returns_empty_dict_for_missing_token验证 msgpack bytes 的往返与不存在即返回空 dict的契约test_list_tokens_returns_decoded_str_keys验证keys()返回的list[bytes]被正确解码为list[str]test_mk_token_then_get_token_round_trip模拟完整生命周期——mk_token通过set()写入序列化负载get_token再读回两端必须一致。阅读该测试文件可以快速理解本模块的正确行为边界也适合作为改造或替换后端时的行为参照。六、重要现状该后端已标记弃用从源码可以确认rediscluster令牌后端在当前仓库中已被标记弃用。在 salt/auth/init.py 中所有走rediscluster分支的方法mk_token、get_tok、list_tokens、rm_token、clean_expired_tokens都会先触发salt.utils.versions.warn_until( 3010, The rediscluster token backend has been deprecated, and will be removed in the Calcium release. Please use the redis_cache cache backend instead., )即使用该后端会在运行期收到弃用告警提示其将在Calcium版本中被移除官方建议的替代方案是改用redis_cache缓存后端配合eauth_tokens.cache_driver配置使用。因此如果你正在规划新的部署或准备升级 master不应再新采用rediscluster令牌后端若已在生产使用应规划迁移到redis_cache。若只是研究历史实现或阅读旧配置本模块依然是理解Salt 令牌存储后端接口契约 Redis 集群接入模式的最小完整范例。七、小结salt.tokens.rediscluster通过 salt/tokens/rediscluster.py 实现向 Salt eauth 体系提供基于 Redis Cluster 的令牌存取接口契约为mk_token/get_token/rm_token/list_tokens四件套配置仅需eauth_redis_host默认localhost与eauth_redis_port默认6379并依赖redis-py-cluster包令牌为 512 字节随机数的哈希摘要值以 msgpacksalt.payload序列化存储且客户端必须保持 bytes 语义禁用decode_responses当前仓库中该后端已弃用将于 Calcium 版本移除官方建议改用redis_cache缓存后端。赞分享运维配置管理后端【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址https://gitcode.com/gh_mirrors/sa/salt点击查看免费下载相关推荐Gatus外部端点令牌安全认证的实现机制Gatus外部端点令牌安全认证的实现机制 概述 在现代分布式系统中服务监控是确保系统稳定性的关键环节。Gatus作为一个面向开发者的自动化状态页面提供了强后端健康检查告警turborepo-auth 源码深度解析Turbo 的 Vercel 认证、SSO 校验与令牌存储架构turborepo auth 源码深度解析Turbo 的 Vercel 认证、SSO 校验与令牌存储架构 本篇文章基于当前仓库 crates/turborep构建工具开发工具CLIChuanhuChatGPT后端分布式缓存Redis Cluster部署与配置ChuanhuChatGPT后端分布式缓存Redis Cluster部署与配置 引言为什么需要Redis Cluster 在ChuanhuChatGPT这人工智能大模型AI 应用AI AgentRAG本地部署微调后端上一篇从零开始创建DokuWiki自定义插件新手友好的完整开发指南下一篇SyRI终极指南5步完成基因组重排与同线性分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考