【问题标题】:S3 download object using pre signed v4 signautre使用预签名 v4 签名的 S3 下载对象
【发布时间】:2019-02-05 14:16:47
【问题描述】:

在 C# 中是否有任何示例可以查看如何使用带有 AWS v4 signature 的起始策略对所有对象进行预签名,以让客户从各自的文件夹结构中下载对象,而不是分别对每个文档进行签名。

文档说:

https://s3.amazonaws.com/examplebucket/test.txt
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<your-access-key-id>/20130721/us-east-1/s3/aws4_request
&X-Amz-Date=20130721T201207Z
&X-Amz-Expires=86400
&X-Amz-SignedHeaders=host
&X-Amz-Signature=<signature-value>  

但我的签名不适用于 GET(下载)对象,在上传时正常工作

void Main()
{
    string bucket = "bucket-name-here";
    string s3Key = "s3-key-here";
    string s3Secret = "secret-here";
    string s3Region = "us-east-1";
    string Date = DateTime.UtcNow.ToString("yyyyMMdd");
    string xAmzDate = DateTime.UtcNow.ToString("yyyyMMdd") + "T000000Z";
    string expiration = DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ssK");

    string policyString = $@"{{""expiration"":""{expiration}"",""conditions"":[{{""bucket"":""{bucket}""}},{{""acl"":""private""}},[""starts-with"",""$key"",""Client_1""],[""starts-with"",""$Content-Type"",""""],[""starts-with"",""$filename"",""""],{{""x-amz-date"":""{xAmzDate}""}},{{""x-amz-credential"":""{s3Key}/{Date}/us-east-1/s3/aws4_request""}},{{""x-amz-algorithm"":""AWS4-HMAC-SHA256""}}]}}";

    var policyStringBytes = Encoding.UTF8.GetBytes(policyString);
    var policy = Convert.ToBase64String(policyStringBytes);
    //policy.Dump();


    byte[] signingKey = GetSigningKey(s3Secret, Date, s3Region, "s3");
    byte[] signature = HmacSHA256(policy, signingKey);

    var sign = ToHexString(signature);

    sign.Dump();
}

static byte[] HmacSHA256(String data, byte[] key)
{
    String algorithm = "HmacSHA256";
    KeyedHashAlgorithm kha = KeyedHashAlgorithm.Create(algorithm);
    kha.Key = key;

    return kha.ComputeHash(Encoding.UTF8.GetBytes(data));
}

private byte[] GetSigningKey(String key, String dateStamp, String regionName, String serviceName)
{
    byte[] kSecret = Encoding.UTF8.GetBytes(("AWS4" + key).ToCharArray());
    byte[] kDate = HmacSHA256(dateStamp, kSecret);
    byte[] kRegion = HmacSHA256(regionName, kDate);
    byte[] kService = HmacSHA256(serviceName, kRegion);
    byte[] kSigning = HmacSHA256("aws4_request", kService);
    return kSigning;
}

public static string ToHexString(byte[] data)
{
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
    {
        sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
    }
    return sb.ToString();
}

有关问题的更多信息:我们在 S3 上为数百个客户提供了数千个文档,它们各自的文件夹结构如下所示。现在,每次客户想要下载他们的对象时,他们都会被我们的 API 签名以创建可下载链接 > 因此每个文档都是单独签名的。

客户端 1

Client_1/Document1.xyz 
Client_1/Document2.xyz 

客户端 2

Client_2/Document1.xyz 
Client_2/Document2.xyz 

【问题讨论】:

  • 与您的问题无关,请注意您的上传策略存在安全漏洞。 [""starts-with"",""$key"",""Client_1""] 应该是[""starts-with"",""$key"",""Client_1/""],否则对于标识符是另一个客户端的左锚定子字符串的任何客户端,Client_1 可以覆盖 Client_10、Client_11 等文件,因为您没有在策略中放置终止分隔符语句对对象键的约束。此策略在浏览器中可见,因此恶意用户可以轻松找到并利用此策略。
  • 非常感谢@Michael-sqlbot。我接受您的漏洞,虽然它在浏览器上不可见,并且浏览器看到我们 API 的散列策略 + 签名,并且这些散列值用于 put 请求的 s3 请求标头中。请注意我的代码中的 Convert.ToBase64String(policyStringBytes)。但无论如何,很棒的收获,真的很感激。对实际问题有什么建议吗?
  • 出于所有实际目的,策略内容仍然是“可见的”,即使眼球实际上不可见,因为 base64 不是散列算法。解码很简单。

标签: c# amazon-web-services amazon-s3


【解决方案1】:

S3 HTML 表单 POST 上传的签名算法允许您签署具有 ["starts-with","$key",...] 等约束的策略文档,但 S3 的预签名 URL 不支持此功能。使用预签名 URL,您签署的不是策略文档,而是“规范请求”,它是浏览器确切请求的规范化表示。所以不支持通配符或前缀。

我想到了两种选择。

CloudFront signed URLs and signed cookies 确实支持策略文档,当您使用“自定义策略”(不是“标准策略”,这更像是 S3 支持的)并且自定义策略允许在 URL 中使用 * 时,类似于["starts-with","$key",...] 但使用浏览器将请求的 URL。您只需进行一次签名,在浏览器中运行的代码可以重用该策略和签名。在 CloudFront 的背面,在使用 CloudFront 签名 URL 或签名 cookie 对 CloudFront 前端的请求进行身份验证后,在请求实际发送到存储桶时,使用 CloudFront 原始访问身份对请求进行签名。 (使用签名的 cookie,浏览器只是发出请求,并自动提交 cookie,因此工作方式相同,但浏览器不会对 URL 进行操作。

或者,您的服务器可以调用安全令牌服务中的 AssumeRole 操作,以生成一组临时凭据供客户端使用,以签署其自己的各个 URL。

当调用AssumeRole 时,您还可以传递一个可选的会话策略 文档。如果这样做,则生成的临时凭证只能执行角色策略(“允许从存储桶读取”)和会话策略(“允许从存储桶读取以特定前缀开头的键”)允许的操作。因此,获得的角色凭据将只允许用户访问他们的对象。

【讨论】:

  • AssumeRole 似乎是我的选择,因为云端会增加额外的成本。在我尝试之前,有没有其他方法可以在每个文档上添加不同的标题 {token : xyz} 然后将该令牌提供给客户?因为我正在寻找的解决方案仅适用于 GET 对象,因为上述方法适用于 PUT。
  • 我想不出具有本机支持的合适“不同标头”解决方案。请注意,使用 CloudFront 的成本通常可以忽略不计,甚至可以根据对象的大小以及查看器和存储桶的位置产生净节省。例如,在 us-east-2 中,S3 下载带宽为 0.090 美元/GB,但美国查看器的 CloudFront 下载带宽为 0.085 美元/GB。从 S3 到 CloudFront 的带宽是免费的,因此您无需支付双倍费用,您只需支付少量费用。
  • 谢谢,我会尝试两种方法,看看哪种方法更适合我们的用例。让答案不被接受一周,看看社区是否有其他建议。
  • 这完全公平合理。让我们看看是否还有其他建议。
  • 当然,我想到了还有其他替代方案,但我没有将它们包括在内,因为它们更具“创意”且不拘一格。例如,使用 Lambda@Edge 根据会话 cookie 对用户进行身份验证(可能有点像您的 token: xyz,但不是静态的)。我有一个执行此操作的“side-car”应用程序,它很简单在我的情况下,因为会话由 DynamoDB 中的主应用程序存储——因此 Lambda@Edge 可以通过以下方式查找会话数据cookie 并独立确定权限,不会对传统应用服务器施加任何负载。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2021-02-15
  • 2014-09-01
  • 1970-01-01
相关资源
最近更新 更多