rust 使用Azure Monitor数据收集器API时授权标头中的签名无效

qv7cva1a  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(88)

我已经将文档中的Python示例翻译成了Rust,该示例介绍了如何为向数据收集器API发出的请求构造Authorization头中所需的签名。我的Rust程序生成了一个SharedKey <workspace_id>:<signature>,它被服务器拒绝,代码为403,响应的主体只是说:

{\\\"Error\\\":\\\"InvalidAuthorization\\\",\\\"Message\\\":\\\"An invalid signature was specified in the Authorization header\\\"}

另一方面,我从Rust程序执行中获取了RFC1123格式的相同日期,并将其硬编码到Python示例脚本中。它为authorization头生成 * 完全 * 相同的SharedKey <workspace_id>:<signature>值,并成功使用API发送日志。
响应中没有关于Rust程序签名问题性质的其他信息。据我所知,错误可能是报告了错误的问题,因为它们是完全相同的签名,一个工作,而另一个不工作。
此API返回此错误类型的其他原因是什么?
如果有人认为有用,我很乐意分享一些Rust代码,但我确信他们文档中的示例Python脚本和我的Rust程序中的授权头是相同的。所以我觉得这是别的东西。

  • 编辑:* 这里是上面链接中稍微修改过的Python示例版本,它成功地使用了API,我看到测试消息显示在日志分析工作区中:
import requests
import datetime
import hashlib
import hmac
import base64

WORKSPACE_ID = "<my workspace ID>"
SHARED_KEY = "<my primary key>"

def build_signature(message, secret):
    key_bytes = base64.b64decode(secret)
    message_bytes = bytes(message, encoding="utf-8")
    hmacsha256 = hmac.new(key_bytes, message_bytes, digestmod=hashlib.sha256).digest()
    encoded_hash = base64.b64encode(hmacsha256).decode()
    return encoded_hash

def post_data():
    data = '{"hello":"world"}'
    date_string = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
    # date_string = "Thu, 14 Sep 2023 01:27:12 GMT"
    content_length = len(data)
    string_to_hash = f"POST\n{content_length}\napplication/json\nx-ms-date:{date_string}\n/api/logs"
    hashed_string = build_signature(string_to_hash, SHARED_KEY)
    signature = f"SharedKey {WORKSPACE_ID}:{hashed_string}"
    print(signature)

    query = "api-version=2016-04-01";
    url = f"https://{WORKSPACE_ID}.ods.opinsights.azure.com/api/logs?{query}"

    headers = {
        'content-type': "application/json",
        'Authorization': signature,
        'Log-Type': "my-event-type",
        'x-ms-date': date_string
    }
    response = requests.post(url, data=data, headers=headers)
    if (response.status_code >= 200 and response.status_code <= 299):
        print('Accepted')
    else:
        print("Response: {}".format(response.content))
        print("Response code: {}".format(response.status_code))

if __name__ == "__main__":
    post_data()

下面是我的Rust版本,它产生了完全相同的授权头值,但在尝试使用它时出现了InvalidAuthorization错误:

use base64;
use chrono::Utc;
use ring::hmac;

#[derive(Clone, Debug, Error)]
enum MyError {
    MessageSendError(String),
}

struct SentinelClient {
    azure_collector_url: String,
    workspace_id: String,
    shared_key: String,
    http_client: reqwest::Client,
}

impl SentinelClient {
    fn new(workspace_id: String, shared_key: String) -> Self {
        let query = "api-version=2016-04-01";
        let azure_collector_url =
            format!("https://{workspace_id}.ods.opinsights.azure.com/api/logs?{query}");
        Self {
            azure_collector_url,
            workspace_id,
            shared_key,
            http_client: reqwest::Client::new(),
        }
    }

    fn send(&self, data: String) -> Result<(), MyError> {
        let workspace_id = &self.workspace_id;
        let date_string = format!("{}", Utc::now().format("%a, %d %b %Y %H:%M:%S GMT"));
        println!("date string {}", date_string);
        let content_length = data.to_string().len();
        let string_to_hash =
            format!("POST\n{content_length}\napplication/json\nx-ms-date:{date_string}\n/api/logs");
        let hashed_string = Self::build_signature(&string_to_hash, &self.shared_key)?;
        let signature = format!("SharedKey {workspace_id}:{hashed_string}");
        println!("{}", signature);

        let url = &self.azure_collector_url;
        let request = self
            .http_client
            .post(url)
            .json(&data)
            .header("Authorization", signature)
            .header("Log-Type", "my-event-type")
            .header("x-ms-date", date_string);

        match request.send().await {
            Ok(resp) => {
                if resp.status().is_success() {
                    println!("request successful: {:?}", &resp);
                    Ok(())
                } else {
                    let body = resp
                        .text()
                        .await
                        .map_err(|e| SinkError::MessageSendError(e.to_string()))?;
                    println!("request unsuccessful: {body}");
                    Err(MyError::MessageSendError(body))
                }
            }
            Err(e) => Err(MyError::MessageSendError(e.to_string())),
    }

    /// Build the API signature for the Azure Monitor HTTP Data Collector
    /// authorization header.
    ///
    /// See https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api?tabs=python#authorization.
    fn build_signature(message: &str, secret: &str) -> Result<String, MyError> {
        let key_bytes = base64::decode(secret).map_err(|e| {
            MyError::MessageSendError(format!("could not decode shared key: {e}"))
        })?;
        let message_bytes = message.as_bytes();
        let hmacsha256 = hmac::Key::new(hmac::HMAC_SHA256, &key_bytes);
        let encoded_hash = base64::encode(hmac::sign(&hmacsha256, message_bytes).as_ref());

        Ok(encoded_hash)
    }
}
qcbq4gxm

qcbq4gxm1#

我被告知JSON请求体必须是utf-8编码的,否则服务器会用403 Unauthorized拒绝请求,这似乎是令人难以置信的误导。在我的例子中,请求体是一个Rust String,表示一个JSON对象,它是 * utf-8编码的,我通过

http_client.post(url).json(&data)

我还不清楚为什么这个会起作用,

http_client
    .post(url)
    .body(data)
    .header(reqwest::header::CONTENT_TYPE, "application/json")

因为我的印象是json方法正是这样做的。但是json方法必须对数据的编码方式做一些body不做的事情。

相关问题