作者:張醫博
概述:
客戶使用 OSS C SDK 3.5 版本,通過 get_object_to_local_file 方法直接下載下傳 OSS檔案到本地,測試過程傳回 403 簽名不對;
搜集資訊
挖取有價值的資訊很重要,通過基礎資訊可以篩選下客戶上傳日志的較長的描述,通過堆棧報錯可以找到客戶大概原因;
- OSS response header 中包含 requestID ,記錄用戶端請求到 OSS 的詳細日志;
- 堆棧報錯資訊;
分析 requestID 對應的服務端錯誤日志
服務端傳回 4xx 2xx 3xx 500 的狀态碼都會傳回 requestID;通過 requestID 我們可以過濾到服務的錯誤日志如下;
java
Method:HEAD Host:oss-cn-shanghai.aliyuncs.com URI:/vod%2Fplat%2Fupdate%2Fhaimu_21_9%2FBasketball.zip
StringToSign:HEAD\n\n\nWed, 06 Nov 2019 07:55:44 GMT\n/vod/plat/update/haimu_21_9/Basketball.zip
UserSignature:LIMmOQ/5TLs2Gk24MuNVRl+Lu0Q= OssServerSignature:P+YZauMD+fRtZjeMWkauuN6A4eU=
ErrorCode:SignatureDoesNotMatch ErrorMsg:The request signature we calculated does not match the signature you provided. Check your key and signing method.

- 通過日志可以明顯看出用戶端簽名和服務端簽名不一緻導緻校驗失敗;
- 根據簽名算法可以知道這算計算簽名的參數中 /vod/plat/update/haimu_21_9/Basketball.zip 其中 vod 是 bucket 的位置;
- Host: oss-cn-shanghai.aliyuncs.com 這個位置是錯誤,因為正常的直接通路 OSS ,Host 正确寫法應該是 bucket.oss-cn-region.aliyuncs.com(region 替換成 bucket 所在地區)
問題分析
懷疑1、
已經知道問題出現在簽名不對,而且 Host 也不對;問題出現在這裡,但是如果 Host ,但 OSS 還能識别出 bucket ,這很奇怪;于是和使用者溝通, vod 并不是使用者的 bucket,那這個桶是怎麼擷取到的呢?遂讓使用者在端上進行 Wireshark 抓包, 得到封包
- 從封包中可以看到 Host 是用戶端傳的時候就沒有 bucket ;
- 而 vod 是使用者 objectkey 檔案字首而已;
- 通過上述幾點可以知道問題一定是使用者代碼上哪裡出現的變量寫錯或者用法不對;
懷疑2、
請使用者在本地 debug ,将關鍵簽名的變量資訊列印出來看是否完整;
關鍵資訊位置
使用者自己 debug 出來變量的位置
通過客戶 debug 看到變量都正确,為什麼還會出現 bucket “丢失” 的情況呢;
懷疑3、
分析源碼
int get_object_to_local_file(char *bucketname, char *objectname, char *filename)
{
aos_pool_t *p = NULL;
aos_string_t bucket;
aos_string_t object;
oss_request_options_t *options = NULL;
aos_table_t *headers = NULL;
aos_table_t *params = NULL;
aos_table_t *resp_headers = NULL;
aos_status_t *s = NULL;
aos_string_t file;
int is_cname = 1;
if (bucketname == NULL || objectname == NULL || filename == NULL)
return -1;
aos_pool_create(&p, NULL);
options = oss_request_options_create(p);
init_sample_request_options(options, is_cname);
aos_str_set(&bucket, bucketname);
aos_str_set(&object, objectname);
headers = aos_table_make(p, 0);
aos_str_set(&file, filename);
s = oss_get_object_to_file(options, &bucket, &object, headers,
params, &file, &resp_headers);
if (aos_status_is_ok(s))
{
printf("get object to local file succeeded\n");
}
else
{
printf("get object to local file failed\n");
aos_pool_destroy(p);
return -2;
}
aos_pool_destroy(p);
return 0;
}
——> 從源碼中可以,getobject 下載下傳時形參都是傳進來的,既然客戶 debug 的變量都是正确的,那麼肯定不是傳進來變量 ,問題一定是方法内的常量導緻;對比官方源碼:
void get_object_to_local_file()
{
aos_pool_t *p = NULL;
aos_string_t bucket;
char *download_filename = "get_object_to_local_file.txt";
aos_string_t object;
int is_cname = 0;
oss_request_options_t *options = NULL;
aos_table_t *headers = NULL;
aos_table_t *params = NULL;
aos_table_t *resp_headers = NULL;
aos_status_t *s = NULL;
aos_string_t file;
aos_pool_create(&p, NULL);
options = oss_request_options_create(p);
init_sample_request_options(options, is_cname);
aos_str_set(&bucket, BUCKET_NAME);
aos_str_set(&object, OBJECT_NAME);
headers = aos_table_make(p, 0);
aos_str_set(&file, download_filename);
s = oss_get_object_to_file(options, &bucket, &object, headers,
params, &file, &resp_headers);
if (aos_status_is_ok(s)) {
printf("get object to local file succeeded\n");
} else {
printf("get object to local file failed\n");
}
aos_pool_destroy(p);
}
——> 從源碼和使用者的源碼對比發現有個 cname 的參數,這個參數使用者端是 1,官網的源碼是 0,參數的含義,是否啟用 cname 方式上傳, cname 簡稱别名,比如使用者給 bucket 綁定一個備案的域名後,那個域名就是 cname;我們追下 cname 的用法。
if (options->config->is_cname ||
is_valid_ip(raw_endpoint_str))
{
req->host = apr_psprintf(options->pool, "%.*s",
raw_endpoint.len, raw_endpoint.data);
req->uri = apr_psprintf(options->pool, "%.*s", bucket->len,
bucket->data);
} else {
req->host = apr_psprintf(options->pool, "%.*s.%.*s",
bucket->len, bucket->data,
raw_endpoint.len, raw_endpoint.data);
req->uri = apr_psprintf(options->pool, "%s", "");
}
——> 從這端代碼中可以看到,啟用了 cname 後,Host 直接取的 endpoint 值;基本上找到了和使用者用了 cname 有關系;
- 經過背景分析發現使用者端并沒有綁定 oss+域名 的映射關系,而 SDK 在啟動了 cname 上傳,把 endpoint 當做完成的域名,沒有把使用者的 bucket name 拼到 Host 中,認為啟用 cname 後, endpoint 就是完成域名;(設計就是這樣并不是缺陷)
-
讓使用者把 cname 改為 0 ,采用直接的方式,這樣 SDK 會把 bucketname 和 endpoint 拼成一個 完成域名再上傳;
小結
該問題核心在于
- 要對 OSS SDK 的用法熟悉,知道如何進行本地對比使用者測試;
- 要清楚排查思路知道搜集哪些有價值的資訊;
- 層級遞進,從簽名算法着眼,推測到是代碼中的使用;
- 善用 tcpdump/wireshark 抓包分析請求封包;