Files
TencentDB-Agent-Memory/scripts/check_sts_permissions.py
T

241 lines
8.8 KiB
Python
Raw Normal View History

2026-05-29 17:33:12 +08:00
#!/usr/bin/env python3
"""
STS 临时密钥权限排查脚本
从 Shark 获取 COS 凭证,逐项测试各种 COS 操作权限。
用法: python3 check_sts_permissions.py
依赖: pip install cos-python-sdk-v5
"""
import json
import re
import sys
import urllib.request
import traceback
SHARK_URL = "http://tdai.gateway.cd.test.polaris:8000/meta/GetMemoryPlusCosConfig"
INSTANCE_ID = "mem-rkgqhd5z"
def fetch_cos_config():
"""从 Shark 获取 COS 配置"""
req = urllib.request.Request(
SHARK_URL,
data=b"{}",
headers={"Content-Type": "application/json"},
method="POST",
)
resp = urllib.request.urlopen(req, timeout=10)
payload = json.loads(resp.read())
print("=" * 60)
print("Shark 原始返回:")
print(json.dumps(payload, indent=2, ensure_ascii=False))
print("=" * 60)
if payload.get("code") != 0:
print(f"Shark 返回错误: code={payload.get('code')}, message={payload.get('message')}")
sys.exit(1)
return payload["data"]
def parse_cos_url(cos_url):
"""解析 COS URL 提取 bucket 和 region"""
host = cos_url.replace("https://", "").replace("http://", "").rstrip("/")
m = re.match(r"^(.+?)\.cos(?:-internal)?\.(.+?)\.(?:myqcloud\.com|tencentcos\.cn)$", host)
if not m:
raise ValueError(f"无法解析 COS URL: {cos_url}")
return m.group(1), m.group(2)
def test_permissions(data):
"""逐项测试 COS 操作权限"""
try:
from qcloud_cos import CosConfig, CosS3Client
except ImportError:
print("\n请先安装 COS SDK: pip install cos-python-sdk-v5")
sys.exit(1)
cos_url = data["CosUrl"]
secret_id = data["TmpSecretId"]
secret_key = data["TmpSecretKey"]
token = data["TmpToken"]
path_prefix = data["PathPrefix"]
expiration = data.get("ExpirationTime", "未知")
bucket, region = parse_cos_url(cos_url)
print(f"\n凭证信息:")
print(f" CosUrl: {cos_url}")
print(f" Bucket: {bucket}")
print(f" Region: {region}")
print(f" TmpSecretId: {secret_id[:10]}...{secret_id[-4:]}")
print(f" TmpSecretKey: {secret_key[:6]}...{secret_key[-4:]}")
print(f" TmpToken: {'有' if token else '无'} (长度={len(token) if token else 0})")
print(f" PathPrefix: {path_prefix}")
print(f" Expiration: {expiration}")
# 实际 prefix(按 memory service 逻辑拼接)
prefix = f"{path_prefix.rstrip('/')}/{INSTANCE_ID}/"
print(f" 实际 Key 前缀: {prefix}")
# 同时测试外网域名(开发机可能无法访问 cos-internal
# 如果 CosUrl 是内网域名,先用外网试
use_public = "cos-internal" in cos_url or "tencentcos.cn" in cos_url
if use_public:
print(f"\n 注意: CosUrl 为内网域名,将同时用外网域名测试")
config = CosConfig(
Region=region,
SecretId=secret_id,
SecretKey=secret_key,
Token=token,
)
client = CosS3Client(config)
test_key = f"{prefix}__permission_test__.txt"
results = {}
# ── Test 1: GetService (列出所有 Bucket) ──
print(f"\n{'─' * 60}")
print("Test 1: GetService (列出所有 Bucket)")
try:
resp = client.list_buckets()
buckets = resp.get("Buckets", {}).get("Bucket", [])
results["GetService"] = f"OK ({len(buckets)} buckets)"
print(f" ✅ OK - 可见 {len(buckets)} 个 Bucket")
for b in buckets[:5]:
print(f" - {b.get('Name', '?')} ({b.get('Location', '?')})")
if len(buckets) > 5:
print(f" ... 及其他 {len(buckets) - 5} 个")
except Exception as e:
results["GetService"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 2: HeadBucket (检查 Bucket 是否存在/可访问) ──
print(f"\nTest 2: HeadBucket (Bucket={bucket})")
try:
client.head_bucket(Bucket=bucket)
results["HeadBucket"] = "OK"
print(f" ✅ OK - Bucket 存在且可访问")
except Exception as e:
results["HeadBucket"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 3: GetBucket / ListObjects (在 prefix 下列出对象) ──
print(f"\nTest 3: ListObjects (Prefix={prefix})")
try:
resp = client.list_objects(Bucket=bucket, Prefix=prefix, MaxKeys=10)
contents = resp.get("Contents", [])
results["ListObjects"] = f"OK ({len(contents)} objects)"
print(f" ✅ OK - 列出 {len(contents)} 个对象")
for c in contents[:5]:
print(f" - {c['Key']} ({c['Size']} bytes)")
except Exception as e:
results["ListObjects"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 3b: ListObjects 在 PathPrefix 下(不带 instanceId)──
raw_prefix = path_prefix.rstrip("/") + "/"
print(f"\nTest 3b: ListObjects (Prefix={raw_prefix}, 不带 instanceId)")
try:
resp = client.list_objects(Bucket=bucket, Prefix=raw_prefix, MaxKeys=10)
contents = resp.get("Contents", [])
results["ListObjects(raw)"] = f"OK ({len(contents)} objects)"
print(f" ✅ OK - 列出 {len(contents)} 个对象")
for c in contents[:5]:
print(f" - {c['Key']} ({c['Size']} bytes)")
except Exception as e:
results["ListObjects(raw)"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 3c: ListObjects 在 bucket 根目录 ──
print(f"\nTest 3c: ListObjects (Prefix='', Bucket 根目录)")
try:
resp = client.list_objects(Bucket=bucket, Prefix="", MaxKeys=10)
contents = resp.get("Contents", [])
results["ListObjects(root)"] = f"OK ({len(contents)} objects)"
print(f" ✅ OK - 列出 {len(contents)} 个对象")
for c in contents[:5]:
print(f" - {c['Key']} ({c['Size']} bytes)")
except Exception as e:
results["ListObjects(root)"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 4: PutObject (写入测试文件) ──
print(f"\nTest 4: PutObject (Key={test_key})")
try:
resp = client.put_object(
Bucket=bucket,
Body=b"permission test",
Key=test_key,
ContentType="text/plain",
)
results["PutObject"] = f"OK (ETag={resp.get('ETag', '?')})"
print(f" ✅ OK - ETag={resp.get('ETag', '?')}")
except Exception as e:
results["PutObject"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 5: GetObject (读取刚写入的文件) ──
print(f"\nTest 5: GetObject (Key={test_key})")
try:
resp = client.get_object(Bucket=bucket, Key=test_key)
body = resp["Body"].get_raw_stream().read()
results["GetObject"] = f"OK ({len(body)} bytes)"
print(f" ✅ OK - 读到 {len(body)} 字节: {body[:50]}")
except Exception as e:
results["GetObject"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 6: HeadObject (获取文件元数据) ──
print(f"\nTest 6: HeadObject (Key={test_key})")
try:
resp = client.head_object(Bucket=bucket, Key=test_key)
results["HeadObject"] = f"OK (size={resp.get('Content-Length', '?')})"
print(f" ✅ OK - Content-Length={resp.get('Content-Length', '?')}")
except Exception as e:
results["HeadObject"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── Test 7: DeleteObject (删除测试文件) ──
print(f"\nTest 7: DeleteObject (Key={test_key})")
try:
client.delete_object(Bucket=bucket, Key=test_key)
results["DeleteObject"] = "OK"
print(f" ✅ OK - 删除成功")
except Exception as e:
results["DeleteObject"] = f"DENIED ({e})"
print(f" ❌ DENIED - {e}")
# ── 汇总 ──
print(f"\n{'=' * 60}")
print("权限汇总:")
print(f"{'=' * 60}")
for op, status in results.items():
icon = "✅" if status.startswith("OK") else "❌"
print(f" {icon} {op:25s}{status}")
print(f"{'=' * 60}")
ok_count = sum(1 for s in results.values() if s.startswith("OK"))
total = len(results)
print(f"\n结论: {ok_count}/{total} 项操作有权限")
if ok_count == 0:
print("⚠️ STS 临时密钥无任何 COS 操作权限!需要 Shark 团队修复 STS Policy。")
print(" 所需权限: cos:GetObject, cos:PutObject, cos:DeleteObject, cos:GetBucket, cos:HeadObject")
elif ok_count < total:
denied = [op for op, s in results.items() if not s.startswith("OK")]
print(f"⚠️ 部分操作无权限: {', '.join(denied)}")
else:
print("✅ STS 临时密钥拥有完整的 COS 操作权限,write_scenario 应该可以正常工作。")
if __name__ == "__main__":
print("STS 临时密钥权限排查")
print(f"Shark 地址: {SHARK_URL}")
print(f"实例 ID: {INSTANCE_ID}")
data = fetch_cos_config()
test_permissions(data)