简体   繁体   English

AWS S3 生成签名的 Urls ''AccessDenied''

[英]AWS S3 Generating Signed Urls ''AccessDenied''

I am using NodeJs to upload files to AWS S3.我正在使用 NodeJs 将文件上传到 AWS S3。 I want the client to be able to download the files securely.我希望客户端能够安全地下载文件。 So I am trying to generate signed URLs, that expire after one usage.所以我正在尝试生成签名的 URL,这些 URL 在使用一次后就会过期。 My code looks like this:我的代码如下所示:

Uploading上传中

const s3bucket = new AWS.S3({
    accessKeyId: 'my-access-key-id',
    secretAccessKey: 'my-secret-access-key',
    Bucket: 'my-bucket-name',
})
const uploadParams = {
    Body: file.data,
    Bucket: 'my-bucket-name',
    ContentType: file.mimetype,
    Key: `files/${file.name}`,
}
s3bucket.upload(uploadParams, function (err, data) {
    // ...
})

Downloading下载中

const url = s3bucket.getSignedUrl('getObject', {
    Bucket: 'my-bucket-name',
    Key: 'file-key',
    Expires: 300,
})

Issue问题

When opening the URL I get the following:打开 URL 时,我得到以下信息:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
    <Code>AccessDenied</Code>
    <Message>
        There were headers present in the request which were not signed
    </Message>
    <HeadersNotSigned>host</HeadersNotSigned>
    <RequestId>D63C8ED4CD8F4E5F</RequestId>
    <HostId>
        9M0r2M3XkRU0JLn7cv5QN3S34G8mYZEy/v16c6JFRZSzDBa2UXaMLkHoyuN7YIt/LCPNnpQLmF4=
    </HostId>
</Error>

I coultn't manage to find the mistake.我无法找到错误。 I would really appreciate any help:)我真的很感激任何帮助:)

Your code is correct, double check the following things:您的代码是正确的,请仔细检查以下事项:

  1. Your bucket access policy.您的存储桶访问策略。

  2. Your bucket permission via your API key.您通过 API 密钥获得的存储桶权限。

  3. Your API key and secret.您的 API 密钥和秘密。

  4. Your bucket name and key.您的存储桶名称和密钥。

For bucket policy you can use the following:对于存储桶策略,您可以使用以下内容:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucket/*"
        }
    ]
}

Change bucket with your bucket name.使用您的存储桶名称更改存储桶。

For users and access key permission (#2), you should follow these steps:对于用户和访问密钥权限 (#2),您应该按照以下步骤操作:

1-Goto AWS Identity and Access Management (IAM) and click on Policies link and click on "Create policy" button. 1-转到 AWS Identity and Access Management (IAM) 并单击策略链接,然后单击“创建策略”按钮。

在此处输入图片说明

2-Select the JSON tab. 2-选择 JSON 选项卡。

在此处输入图片说明

3-Enter the following statement, make sure change the bucket name and click on "review policy" button. 3-输入以下语句,确保更改存储桶名称并单击“查看策略”按钮。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::YOURBUCKETNAME"
        }
    ]
}

在此处输入图片说明

4-Enter a name for your policy and click on "Create policy" button. 4-为您的策略输入一个名称,然后单击“创建策略”按钮。

在此处输入图片说明

5-Click on Users link, and find your current username (You already have the access key and secret for that) 5-单击用户链接,然后找到您当前的用户名(您已经拥有访问密钥和密码)

在此处输入图片说明

6-Click on "add permission" button. 6-单击“添加权限”按钮。

在此处输入图片说明

7-Add the policy we created in the previous step and save. 7-添加我们在上一步中创建的策略并保存。

在此处输入图片说明

Finally, make sure your bucket not accessible from Public, add the correct content type to your file and set signatureVersion: 'v4'最后,确保您的存储桶无法从 Public 访问,将正确的内容类型添加到您的文件并设置signatureVersion: 'v4'

The final code should be like this, thanks @Vaisakh PS:最终代码应该是这样的,感谢@Vaisakh PS:

const s3bucket = new AWS.S3({
    signatureVersion: 'v4',
    accessKeyId: 'my-access-key-id',
    secretAccessKey: 'my-secret-access-key',
    Bucket: 'my-bucket-name',
})
const uploadParams = {
    Body: file.data,
    Bucket: 'my-bucket-name',
    ContentType: file.mimetype,
    Key: `files/${file.name}`,
}
s3bucket.upload(uploadParams, function (err, data) {
    // ...
})
const url = s3bucket.getSignedUrl('getObject', {
    Bucket: 'my-bucket-name',
    Key: 'file-key',
    Expires: 300,
})

Highest upvoted answer here technically works but isn't practical since it's opening up the bucket to be public.这里最高赞的答案在技术上是可行的,但并不实用,因为它正在公开公开。

I had the same problem and it was due to the role that was used to generate the signed url.我有同样的问题,这是由于用于生成签名 url 的角色。 The role I was using had this:我使用的角色是这样的:

- Effect: Allow
  Action: 
    - "s3:ListObjects"
    - "s3:GetObject"
    - "s3:GetObjectVersion"
    - "s3:PutObject"
  Resource:
    - "arn:aws:s3:::(bucket-name-here)"

But the bucket name alone wasn't enough, I had to add a wildcard on the end to designate access to whole bucket:但是只有桶名是不够的,我不得不在末尾添加一个通配符来指定对整个桶的访问:

- Effect: Allow
  Action: 
    - "s3:ListObjects"
    - "s3:GetObject"
    - "s3:GetObjectVersion"
    - "s3:PutObject"
  Resource:
    - "arn:aws:s3:::(bucket-name-here)/*"

Your code looks good but I think you are missing the signatureVersion: 'v4' parameter while creating the s3bucket object.您的代码看起来不错,但我认为您在创建s3bucket对象时缺少signatureVersion: 'v4'参数。 Please try the below updated code.请尝试以下更新的代码。

const s3bucket = new AWS.S3({
    signatureVersion: 'v4',
    accessKeyId: 'my-access-key-id',
    secretAccessKey: 'my-secret-access-key',
    Bucket: 'my-bucket-name',
})
const uploadParams = {
    Body: file.data,
    Bucket: 'my-bucket-name',
    ContentType: file.mimetype,
    Key: `files/${file.name}`,
}
s3bucket.upload(uploadParams, function (err, data) {
    // ...
})
const url = s3bucket.getSignedUrl('getObject', {
    Bucket: 'my-bucket-name',
    Key: 'file-key',
    Expires: 300,
})

For more about signatureVersion: 'v4' see the below links有关signatureVersion: 'v4'更多信息signatureVersion: 'v4'请参阅以下链接

https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html

https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html

You can also try out the below nodejs library that create presigned url您还可以尝试以下创建presigned url nodejs

https://www.npmjs.com/package/aws-signature-v4 https://www.npmjs.com/package/aws-signature-v4

I kept having a similar problem but mine were due to region settings.我一直有类似的问题,但我的是由于区域设置。 In our back end we had some configuration settings for the app.在我们的后端,我们有一些应用程序的配置设置。

One of which was "region": "us-west-2" so the presigned url was created with this region but when it was called on the front end the region was set to "us-west-1" .其中之一是"region": "us-west-2"所以预先签名的 url 是用这个区域创建的,但是当它在前端被调用时,该区域被设置为"us-west-1"

Changing it to be the same fixed the issue.将其更改为相同的解决了问题。

如果您的 s3 文件已加密,请确保您的策略还可以访问加密密钥和相关操作。

I had the same issue when i'm locally testing my lambda function its works but after deploy it didn't work.当我在本地测试我的 lambda 函数时,我遇到了同样的问题,但在部署后它不起作用。 once i add the s3 full access to lambda function it worked .一旦我添加了 s3 对 lambda 函数的完全访问权限,它就起作用了

I battled with this as well with an application using Serverless Framework.我也在使用 Serverless Framework 的应用程序中解决了这个问题。

My fix was adding S3 permissions to the IAM Role inside of the serverless.yml file.我的修复是向 serverless.yml 文件中的 IAM 角色添加 S3 权限。

I'm not exactly sure how s3 makes the presigned URL but it turns out they take your IAM role into account.我不确定 s3 是如何生成预签名 URL 的,但事实证明他们考虑了您的 IAM 角色。

Adding all s3 actions did the trick.添加所有 s3 操作即可解决问题。 This is what the IAM role looks like for S3 👇这就是 S3 中 IAM 角色的样子👇

iamRoleStatements:
  - Effect: Allow
      Action:
        - 's3:*'
      Resource:
        - 'arn:aws:s3:::${self:custom.imageBucket}/*'

After banging my head for many hours with this same issue.在用同样的问题敲打我的头好几个小时之后。 I noticed that my account had a MFA setup, making the generation of the signed url with only the accessKeyId and secretAccesKey useless.我注意到我的帐户有一个 MFA 设置,使得仅使用 accessKeyId 和 secretAccessKey 生成的签名 url 毫无用处。

The solution was installing this https://github.com/broamski/aws-mfa解决方案是安装这个https://github.com/broamski/aws-mfa

After running it, it asks to create a.aws/credentials file, where you must input your access id / secret and aws_mfa_device.运行后,它会要求创建一个 .aws/credentials 文件,您必须在其中输入您的访问 ID / 密码和 aws_mfa_device。 The later will look something like后者看起来像

aws_mfa_device = arn:aws:iam::youruserid:mfa/youruser

The data can be found in your user in the aws console (Website)可以在aws控制台(网站)中的用户中找到数据

After that you will find that credentials are populated with new keys with 1 week duration iirc.之后,您会发现凭据中填充了持续时间为 1 周的新密钥 iirc。

Then simply generate a url again然后简单地再次生成一个url

AWS.config.update({ region: 'xxx' });
var s3 = new AWS.S3();

var presignedGETURL = s3.getSignedUrl('putObject', {
    Bucket: 'xxx',
    Key: 'xxx', //filename
    Expires: xxx, //time to expire in seconds,
    ContentType: 'xxx'
});

And this time it will work.而这一次它会起作用。

Remember to NOT pass any credentials to AWS.config, since they will be automatically picked from the.aws/credentials folder.请记住不要将任何凭据传递给 AWS.config,因为它们会自动从 .aws/credentials 文件夹中选取。

I saw this problem recently when moving from a bucket that was created a while ago to one created recently.我最近在从不久前创建的存储桶移动到最近创建的存储桶时看到了这个问题。

It appears that v2 pre-signed links (for now) continue to work against older buckets while new buckets are mandated to use v4.似乎 v2 预签名链接(目前)继续针对旧存储桶工作,而新存储桶必须使用 v4。

Revised Plan – Any new buckets created after June 24, 2020 will not support SigV2 signed requests, although existing buckets will continue to support SigV2 while we work with customers to move off this older request signing method.修订后的计划 – 2020 年 6 月 24 日之后创建的任何新存储桶将不支持 SigV2 签名请求,尽管在我们与客户合作取消这种旧的请求签名方法的同时,现有存储桶将继续支持 SigV2。

Even though you can continue to use SigV2 on existing buckets, and in the subset of AWS regions that support SigV2, I encourage you to migrate to SigV4, gaining some important security and efficiency benefits in the process.即使您可以继续在现有存储桶上以及在支持 SigV2 的 AWS 区域子集中使用 SigV2,我还是鼓励您迁移到 SigV4,从而在此过程中获得一些重要的安全性和效率优势。

https://docs.amazonaws.cn/AmazonS3/latest/API/sigv4-query-string-auth.html#query-string-auth-v4-signing-example https://docs.amazonaws.cn/AmazonS3/latest/API/sigv4-query-string-auth.html#query-string-auth-v4-signing-example

Our solution involved updating the AWS SDK to use this by default;我们的解决方案涉及更新 AWS 开发工具包以默认使用它; I suspect newer versions probably already default this setting.我怀疑较新的版本可能已经默认此设置。

https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-other.html#config-setting-aws-s3-usesignatureversion4 https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-other.html#config-setting-aws-s3-usesignatureversion4

To allow a signed URL for an S3 PUT to also be downloadable by anyone, add:要允许任何人也可以下载 S3 PUT 的签名 URL,请添加:

    const s3Params = {
      Bucket, Key, ContentType,
      // This ACL makes the uploaded object publicly readable. You must also uncomment
      // the extra permission for the Lambda function in the SAM template.
      ACL: 'public-read'
    }

The ACL: 'public-read' at the end is key to allowing you to download after upload.末尾的ACL: 'public-read'是允许您在上传后下载的关键。

But in order to set ACLs on the new file from a signed URL, the caller must have s3:PutObjectACL permission so you'll also need to grant that permission to the URL signer:但是,为了通过签名 URL 在新文件上设置 ACL,调用者必须具有s3:PutObjectACL权限,因此您还需要将该权限授予 URL 签名者:

        - Statement:
          - Effect: Allow
            Resource: (BUCKET_ARN)/*
            Action:
              - s3:putObjectAcl

where BUCKET_ARN is your bucket ARN, so something like:其中BUCKET_ARN是您的存储桶 ARN,类似于:

  Resource: "arn:aws:s3:::My-Bucket-Name/*"

See this link for more.有关更多信息,请参见此链接

I think it's also possible to just get away with only s3:PutObject if the whole bucket is marked public.我认为如果整个存储桶都标记为公共,也可以只使用s3:PutObject This used to be easy to do (a checkbox) but now seems overly complex.这曾经很容易做到(一个复选框),但现在看起来过于复杂。 However, I think you can just add the policy found in Step 2 at this link .但是,我认为您可以在此链接中添加在步骤 2 中找到的策略。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM