c# - 手工制作的 Chef API 请求 - 获取 "invalid signature for user"

标签 c# chef-infra

出于各种原因,我需要能够在 C# 中发出 Chef API 请求。我遵循了指南 here ( header 规范)和 here (Bash 例子)但是已经走到了死胡同。我发送的每个请求都返回 401 Unauthorized with the content

{"error":["Invalid signature for user or client 'myuser'"]}

我还在本地配置 Knife 以使用 Fiddler 作为 HTTP 代理,这样我就可以检查 Knife HTTP 请求并尽可能准确地复制可见 header ,但自然我看不到它生成的规范 header ,也看不到服务器期望的 header .

但是,我已经通过这种方式确认我为内容(空)和路径生成的哈希值与 Knife 生成的哈希值相同。

这是我的代码。从 PEM 格式加载 RSA 私钥是使用从 Christian Etter 的博客中获取的扩展方法 - 抱歉,链接用完了。

const string path = "/cookbooks"
const string basePath = "https://chefserver.internal:443";

var timestamp = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
var method = "GET";
var clientName = "myuser";

var hashedPath = ToBase64EncodedSha1String(path);
var hashedBody = ToBase64EncodedSha1String(String.Empty);

var canonicalHeader = String.Format("Method:{0}\nHashed Path:{1}\nX-Ops-Content-Hash:{2}\nX-Ops-Timestamp:{3}\nX-Ops-UserId:{4}",
        method, hashedPath, hashedBody, timestamp, clientName);

var privateKey = File.ReadAllText("C:\\chef\\myuser.private.pem");
string signature;
byte[] rawData;

using (var rsa = new RSACryptoServiceProvider())
{
    rsa.PersistKeyInCsp = false;
    rsa.LoadPrivateKeyPEM(privateKey);
    using (var sha1 = new SHA1CryptoServiceProvider())
    {
        rawData = rsa.SignData(Encoding.UTF8.GetBytes(canonicalHeader), sha1);
        signature = Convert.ToBase64String(rawData);
    }
}

var client = new HttpClient();
var message = new HttpRequestMessage();
message.Method = HttpMethod.Get;
message.RequestUri = new Uri(basePath + path);
message.Headers.Add("Accept", "application/json");
message.Headers.Add("Host", "chefserver.internal:443");
message.Headers.Add("X-Chef-Version", "11.12.4");
message.Headers.Add("X-Ops-Timestamp", timestamp);
message.Headers.Add("X-Ops-Sign", "version=1.0;");
message.Headers.Add("X-Ops-Userid", clientName);
message.Headers.Add("X-Ops-Content-Hash", hashedBody);
message.Headers.Add("User-Agent", "Chef Knife/11.4.0 (ruby-1.9.2-p320; ohai-6.16.0; x86_64-darwin11.3.0; +http://opscode.com)");

var currentItem = new StringBuilder();
var i = 1;
foreach (var l in signature)
{
    currentItem.Append(l);
    if (currentItem.Length == 60)
    {
        message.Headers.Add("X-Ops-Authorization-" + i, currentItem.ToString());
        i++;
        currentItem = new StringBuilder();
    }
}
message.Headers.Add("X-Ops-Authorization-" + i, currentItem.ToString());

var response = await client.SendAsync(message);
var content = await response.Content.ReadAsStringAsync();

还有 helper

private string ToBase64EncodedSha1String(string input)
{
        return
            Convert.ToBase64String(new    SHA1CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(input)));
}

最佳答案

我遇到了类似的问题,发现 .NET 加密库不支持完成这项工作所需的内容。我最终使用 BouncyCaSTLe 加密库来完成此任务。

我制作了一个简单的 C# 类库来包装它。在这里看看:https://github.com/mattberther/dotnet-chef-api

关于c# - 手工制作的 Chef API 请求 - 获取 "invalid signature for user",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24241696/

相关文章:

ruby - chef -- 将属性散列传递给资源

C# 操作 block await 不能按预期工作

c# - 在 asp.net 中发送邮件-也使用 web 配置

C# Emit 创建动态属性 ToString 方法

c# - 每个符号重复多少次

chef-infra - Vagrant::Butcher "sudo: no tty present and no askpass program specified"尝试 "cat/etc/chef/client.pem"时

c# - 将 System.Windows.Media.Color 转换为 System.Drawing.Color

chef-infra - 如何在没有 JSON 文件的情况下在 Chef 客户端中传递属性?

chef-infra - 使用 Chef 管理网站用户、虚拟主机和 PHP-FPM 池

unit-testing - 如何创建在另一个配方中定义的虚拟资源而不在测试运行中包含另一个配方?