Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions testlib/src/main/resources/zssdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
try:
import urllib3
except ImportError:
print 'urlib3 is not installed, run "pip install urlib3"'
print('urllib3 is not installed, run "pip install urllib3"')
sys.exit(1)

import string
Expand All @@ -16,11 +16,15 @@
import traceback
import base64
import hmac
import sha
from hashlib import sha1
import datetime
import time

try:
int_types = (int, long)
except NameError:
int_types = (int,)

CONFIG_HOSTNAME = 'hostname'
CONFIG_PORT = 'port'
CONFIG_POLLING_TIMEOUT = 'default_polling_timeout'
Expand Down Expand Up @@ -55,7 +59,7 @@ def wrap(*args, **kwargs):
try:
func(*args, **kwargs)
except:
print traceback.format_exc()
print(traceback.format_exc())

return wrap

Expand Down Expand Up @@ -189,7 +193,7 @@ def _check_params(self):
if value is not None and isinstance(value, str) and annotation.empty_string is False and len(value) == 0:
raise SdkError('invalid parameter[%s], it cannot be an empty string' % param_name)

if value is not None and (isinstance(value, int) or isinstance(value, long)) \
if value is not None and isinstance(value, int_types) \
and annotation.number_range is not None and len(annotation.number_range) == 2:
low = annotation.number_range[0]
high = annotation.number_range[1]
Expand Down Expand Up @@ -253,10 +257,14 @@ def calculateAccessKey(self, url, date):
path = elements[2].split("/", 2)
path = path[2].split("?")

h = hmac.new(self.accessKeySecret, self.HTTP_METHOD + "\n"
+ date + "\n"
+ "/" + path[0], sha1)
Signature = base64.b64encode(h.digest())
msg = self.HTTP_METHOD + "\n" + date + "\n" + "/" + path[0]
if isinstance(msg, str):
msg = msg.encode('utf-8')
secret = self.accessKeySecret
if isinstance(secret, str):
secret = secret.encode('utf-8')
h = hmac.new(secret, msg, sha1)
Signature = base64.b64encode(h.digest()).decode('utf-8')
Comment on lines +260 to +267
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 搜索是否有其他文件引用了类似的签名计算逻辑,以确认预期格式

echo "=== Searching for HMAC signature patterns in the codebase ==="
rg -n "Content_MD5|Content_Type|application/json" --type py -C 3

echo ""
echo "=== Searching for calculateAccessKey implementations ==="
ast-grep --pattern 'def calculateAccessKey($$$) { $$$ }'

echo ""
echo "=== Checking zssdk_bak.py for original implementation ==="
fd -t f "zssdk_bak.py" --exec cat {}

Repository: MatheMatrix/zstack

Length of output: 50376


签名消息格式已更改 - 需要确认是否为预期变更

与原始实现(zssdk_bak.py)相比,HMAC 签名消息格式发生了重大变化:

原始格式:

HTTP_METHOD + "\n" + "\n" + "application/json\n" + date + "\n" + "/" + path[0]

新格式:

HTTP_METHOD + "\n" + date + "\n" + "/" + path[0]

新版本移除了:

  • "\n"(Content_MD5 占位符)
  • "application/json\n"(Content_Type 占位符)

此更改将产生完全不同的 HMAC 签名。若服务端仍期望原始签名格式进行认证验证,此修改将导致所有带有访问密钥的请求均失败,造成严重的认证中断。

新增的 UTF-8 编码处理(第 256-260 行)是为了 Python 3 兼容性,这部分是正确的。

请确认这是有意的 API 签名格式升级,还是无意的遗漏或需要同步更新的服务端验证逻辑。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testlib/src/main/resources/zssdk.py` around lines 256 - 263, The HMAC message
construction in zssdk.py (variable msg built from HTTP_METHOD, date and path) no
longer matches the original signed format used in zssdk_bak.py—it's missing the
Content_MD5 and Content_Type placeholders ("\\n" and "application/json\\n"),
which will break server-side verification; restore msg to the original
concatenation: HTTP_METHOD + "\n" + "\n" + "application/json\n" + date + "\n" +
"/" + path[0] (keeping the existing UTF-8 encoding steps for msg and
accessKeySecret and then computing hmac with hmac.new(secret, msg, sha1) and
base64 encoding as done in Signature generation) so the signature matches the
server expectation.

return "ZStack %s:%s" % (self.accessKeyId, Signature)

def call(self, cb=None):
Expand Down Expand Up @@ -482,13 +490,13 @@ def _json_http(
if body is not None and not isinstance(body, str):
body = json.dumps(body).encode('utf-8')

print '[Request]: %s url=%s, headers=%s, body=%s' % (method, uri, headers, body)
print('[Request]: %s url=%s, headers=%s, body=%s' % (method, uri, headers, body))
if body:
headers['Content-Length'] = len(body)
rsp = pool.request(method, uri, body=body, headers=headers)
else:
rsp = pool.request(method, uri, headers=headers)

print '[Response to %s %s]: status: %s, body: %s' % (method, uri, rsp.status, rsp.data)
print('[Response to %s %s]: status: %s, body: %s' % (method, uri, rsp.status, rsp.data))
Comment on lines +493 to +500
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

请求/响应日志包含敏感信息,建议脱敏或仅在调试模式输出

Line [489] 和 Line [496] 直接打印 headers/body,会暴露 Authorization 与业务负载,存在安全与合规风险。

🔒 建议修复
+def _redact_headers(headers):
+    h = dict(headers or {})
+    if HEADER_AUTHORIZATION in h:
+        h[HEADER_AUTHORIZATION] = "***"
+    return h
...
-    print('[Request]: %s url=%s, headers=%s, body=%s' % (method, uri, headers, body))
+    print('[Request]: %s url=%s, headers=%s, body=%s' % (
+        method, uri, _redact_headers(headers), '<redacted>' if body else body
+    ))
...
-    print('[Response to %s %s]: status: %s, body: %s' % (method, uri, rsp.status, rsp.data))
+    print('[Response to %s %s]: status: %s, body: %s' % (
+        method, uri, rsp.status, '<redacted>'
+    ))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testlib/src/main/resources/zssdk.py` around lines 489 - 496, The
request/response prints expose sensitive data (headers/body) — update the
logging around the print statements that reference headers, body and rsp (the
request/response variables) to redact or omit sensitive fields: remove or mask
Authorization and other credentials in headers before logging, avoid printing
full request body (or sanitize payload fields) and only emit detailed
headers/body when a debug flag or logger level is enabled; keep the response
status but redact rsp.data similarly. Ensure changes touch the print calls that
output '[Request]: %s url=%s, headers=%s, body=%s' and '[Response to %s %s]:
status: %s, body: %s' so sensitive values are never logged in normal operation.

return rsp