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:
co-authored by
Claude Opus 4.8
parent
b83638a475
commit
aa498fc318
@@ -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())
|
||||
Reference in New Issue
Block a user