feat(redis): 支持 TLS + 密码 + REDIS_URL(接托管 Redis,如 heicode-rd)

orchestrator/redis_client.py 之前只支持裸 redis.Redis(host,port,db)
明文连接,无法连 Azure Redis Enterprise(强制 TLS + access key)。

改动:
- 新增 REDIS_URL(优先),rediss:// 自动启用 TLS,凭据写在 URL;
  否则用离散 REDIS_HOST/PORT/DB + 可选 REDIS_PASSWORD / REDIS_SSL。
- 完全向后兼容:都不设时维持现有明文 redis-service:6379 行为。
- 凭据只读 env(经 Secret/secret_ref 注入),日志只打脱敏目标,
  绝不输出 URL / 密码。
- 新增 scripts/test-redis-connection-config.py(无需真实 redis)。
- k8s manifest 补 Secret 引用示例;DELIVERY.md 补环境变量表。

验证:新单测 3 项 + REDIS_FAKE 回退 + test-runtime-contract /
test-contract-freeze / test-merge-smoke 全 PASS。

影响范围:仅 agent_swarm(orchestrator 连接层)。
不改 Manager↔Swarm 契约 / 计费 / 审计字段 / 发布链路。
涉及密钥:仅新增「从环境读取」路径,无任何密钥写入代码或日志。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fasthei
2026-06-12 16:50:27 +08:00
co-authored by Claude Opus 4.8
parent b83638a475
commit aa498fc318
4 changed files with 167 additions and 12 deletions
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Unit test for RedisClient connection config (TLS / password / REDIS_URL).
Validates that env config maps to the right redis-py connection kwargs WITHOUT
opening a connection — so it runs anywhere, no live Redis needed. Covers:
- default plaintext in-cluster behaviour (backward compat)
- discrete REDIS_PASSWORD / REDIS_SSL
- REDIS_URL precedence + rediss:// (TLS)
- logs never leak the URL/password (credential-safe target description)
Run: python scripts/test-redis-connection-config.py
"""
import importlib
import os
import sys
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if REPO_ROOT not in sys.path:
sys.path.insert(0, REPO_ROOT)
REDIS_ENV_VARS = (
"REDIS_URL", "REDIS_HOST", "REDIS_PORT", "REDIS_DB",
"REDIS_PASSWORD", "REDIS_SSL", "REDIS_FAKE", "ALLOW_MEMORY_STORE",
)
def _fresh_client(env):
"""Reset the relevant env, apply `env`, and return a freshly built RedisClient."""
for k in REDIS_ENV_VARS:
os.environ.pop(k, None)
os.environ.update(env)
import orchestrator.redis_client as rc
importlib.reload(rc)
return rc.RedisClient(), rc
def _conn_kwargs(client_obj):
"""Pull the connection kwargs redis-py resolved for the built client."""
real = client_obj._make_real_client()
return real.connection_pool.connection_kwargs, real
def test_default_plaintext():
c, _ = _fresh_client({})
kw, real = _conn_kwargs(c)
assert kw.get("host") == "redis-service", kw
assert kw.get("port") == 6379, kw
assert "password" not in kw or kw["password"] is None, kw
# No TLS: connection class must not be the SSL one.
assert "SSL" not in real.connection_pool.connection_class.__name__, kw
assert "TLS=False" in c._target_desc()
print("ok: default plaintext (backward compatible)")
def test_discrete_password_and_ssl():
c, _ = _fresh_client({
"REDIS_HOST": "heicode-rd.redisenterprise.cache.azure.net",
"REDIS_PORT": "10000",
"REDIS_PASSWORD": "s3cr3t-from-secret",
"REDIS_SSL": "1",
})
kw, real = _conn_kwargs(c)
assert kw.get("host") == "heicode-rd.redisenterprise.cache.azure.net", kw
assert kw.get("port") == 10000, kw
assert kw.get("password") == "s3cr3t-from-secret", kw
assert "SSL" in real.connection_pool.connection_class.__name__, "expected TLS connection class"
# target description must NOT leak the password
assert "s3cr3t" not in c._target_desc(), c._target_desc()
assert "TLS=True" in c._target_desc()
print("ok: discrete REDIS_PASSWORD + REDIS_SSL → authenticated TLS")
def test_url_precedence_and_tls():
c, _ = _fresh_client({
"REDIS_URL": "rediss://:url-password@heicode-rd.example.net:10000/0",
# discrete vars set too — URL must win
"REDIS_HOST": "redis-service",
"REDIS_SSL": "",
})
kw, real = _conn_kwargs(c)
assert kw.get("host") == "heicode-rd.example.net", kw
assert kw.get("port") == 10000, kw
assert kw.get("password") == "url-password", kw
assert "SSL" in real.connection_pool.connection_class.__name__, "rediss:// must enable TLS"
desc = c._target_desc()
assert "url-password" not in desc and "heicode-rd.example.net" not in desc, desc
assert "TLS=True" in desc
print("ok: REDIS_URL precedence + rediss:// TLS, credentials not logged")
def main():
try:
import redis # noqa: F401
except Exception:
print("SKIP: redis-py not installed; connection-config test requires it")
return 0
test_default_plaintext()
test_discrete_password_and_ssl()
test_url_precedence_and_tls()
print("\nALL PASS: redis connection config")
return 0
if __name__ == "__main__":
sys.exit(main())