雲計算

OSS – C SDK 深度案例

作者:張醫博

概述:

客戶使用 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.

image.png

  • 通過日誌可以明顯看出客戶端簽名和服務端簽名不一致導致校驗失敗;
  • 根據簽名算法可以知道這算計算簽名的參數中 /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 抓包, 得到報文

image.png

  1. 從報文中可以看到 Host 是客戶端傳的時候就沒有 bucket ;
  2. 而 vod 是用戶 objectkey 文件前綴而已;
  3. 通過上述幾點可以知道問題一定是用戶代碼上哪裡出現的變量寫錯或者用法不對;

懷疑2、

請用戶在本地 debug ,將關鍵簽名的變量信息打印出來看是否完整;

關鍵信息位置

image.png

用戶自己 debug 出來變量的位置

image.png

通過客戶 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 拼成一個 完成域名再上傳;
    小結

該問題核心在於

  1. 要對 OSS SDK 的用法熟悉,知道如何進行本地對比用戶測試;
  2. 要清楚排查思路知道蒐集哪些有價值的信息;
  3. 層級遞進,從簽名算法著眼,推測到是代碼中的使用;
  4. 善用 tcpdump/wireshark 抓包分析請求報文;

Leave a Reply

Your email address will not be published. Required fields are marked *