Files
TencentDB-Agent-Memory/scripts/check_sts_permissions.py
2026-05-29 17:51:28 +08:00

241 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)