0x00 前言

在这篇文章中,我会记录一下关于S3 pre-signed url和KMS加密交互的一些玩法。比如说:

  1. 简单介绍一下什么是S3 pre-signed URL
  2. 经过KMS加密之后还能直接公开访问object吗?
  3. 经过KMS加密之后还能通过pre-signed URL的方式将object公开访问吗?
  4. 访问S3 pre-signed URL报认证错误应该怎么处理?

0x01 S3 pre-signed URL 概述

简单理解,就是我们想将一个S3桶中的对象临时公开,可以通过presign命令生成一个临时有效期的链接,而用户可以通过这个临时的URL下载并访问对象。
话不多说了,先来个命令看看咋回事,正常情况下我们使用有权限的AKSK访问指定对象时会通过如下命令:

1
2
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/webwxgeticon.jpg webwxgetion.jpg --profile root
download: s3://aaabbbccc/webwxgeticon.jpg to ./webwxgetion.jpg

如果我们没有登录去下载的话,通常会报一个访问禁止的403错误,如下:

1
2
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/webwxgeticon.jpg webwxgetion.jpg --no-sign-request
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

使用浏览器或者wget直接下载的话,会报如下错误:

1
2
3
4
5
6
7
8
9
10
[root@ip-10-0-0-64 ~]# wget https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
--2019-11-23 13:33:05-- https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
Resolving aaabbbccc.s3.cn-north-1.amazonaws.com.cn (aaabbbccc.s3.cn-north-1.amazonaws.com.cn)... 54.222.49.142
Connecting to aaabbbccc.s3.cn-north-1.amazonaws.com.cn (aaabbbccc.s3.cn-north-1.amazonaws.com.cn)|54.222.49.142|:443... connected.
HTTP request sent, awaiting response... 403 Forbidden
2019-11-23 13:33:05 ERROR 403: Forbidden.

[root@ip-10-0-0-64 ~]# curl https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>39231AA211436AF7</RequestId><HostId>a7athzq0DU1D11oBpMF9ZeNziPz9sjw1ZLY0Ga+xUmpMN1ZkhnITDci7zisSfDa+nyTcw/sbSkk=</HostId></Error>[root@ip-10-0-0-64 ~]#

image

当然,上述做法是因为我没有把对象开放公开访问的权限,所以报错是正常的。这个时候我有一个使用场景,就是我想把一个对象临时的开放给某一个用户,而这通常有两种做法,一种是直接公开,另一种是使用这里提到的pre-signed URL的方法,第一种直接公开的方法是并不可取的,因为如果有人能爆破出来这个URL的话,那他就可以直接下载这个对象了,而且在对象有效期管理上,也是一个比较复杂的事,那我们直接上pre-signed URL看看:

1
2
3
[root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbbccc/webwxgeticon.jpg --profile root
https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T133719Z&X-Amz-Signature=1bdedc3e99edc60fd266f10ec4368166112d9b311610f02910304ae6eda28503
[root@ip-10-0-0-64 ~]#

然后拿着这个pre-signed URL就可以访问对象了:

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@ip-10-0-0-64 ~]# aws s3api head-object --bucket aaabbbccc --key webwxgeticon.jpg --profile root
{
"AcceptRanges": "bytes",
"ContentType": "image/jpeg",
"LastModified": "Sat, 23 Nov 2019 13:31:01 GMT",
"ContentLength": 13048,
"ETag": "\"bade48249f0809beca0226e7ec4ef50f\"",
"Metadata": {}
}
[root@ip-10-0-0-64 ~]# curl "https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T133719Z&X-Amz-Signature=1bdedc3e99edc60fd266f10ec4368166112d9b311610f02910304ae6eda28503" --output 1.jpg
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 13048 100 13048 0 0 326k 0 --:--:-- --:--:-- --:--:-- 326k
[root@ip-10-0-0-64 ~]# md5sum 1.jpg
bade48249f0809beca0226e7ec4ef50f 1.jpg
[root@ip-10-0-0-64 ~]#

image

上面就是一个S3 pre-signed URL的正常使用流程,接下来看看各种奇葩场景。

0x02 经过KMS加密之后还能直接公开访问object吗?

众所周知,当我们给一个S3 Bucket设置KMS加密之后,当我们把对象公开之后,是没有办法直接通过URL去访问的。其背后的原理是因为当我们下载对象的时候需要向KMS服务端发起解密的请求,而直接通过浏览器URL访问,是没有带任何KMS相关的参数的,所以也就没有解密这种说法了。
我这边尽可能的多想一些场景测试看看,基本覆盖日常常见问题:

  1. 不加密的S3 Bucket,当我们公开一个对象之后,是可以直接访问的(这个没什么说的,就是预期功能),为了保证实验的顺利进行,先关闭S3 Bucket的【阻止所有公共访问】功能,然后公开一个对象看看:

image

可以看到浏览器中已经可以正常打开这个公开的对象了:

image

  1. 那如果我上传的对象使用KMS加密了呢?其再次公开的时候会出现什么现象?

image

可以看到,当我们在浏览器中直接访问是,其会报一个无效参数的错误。

1
2
3
[root@ip-10-0-0-64 ~]# curl https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/HappyFace.jpg
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>InvalidArgument</Code><Message>Requests specifying Server Side Encryption with AWS KMS managed keys require AWS Signature Version 4.</Message><ArgumentName>Authorization</ArgumentName><ArgumentValue>null</ArgumentValue><RequestId>1EE0A543A5ACF250</RequestId><HostId>vx4Jj96IbW6MGeo4+nNkCzJqtsjo7+7tnxoxr1M6SZG+otZMp3oB0Z1Qb9F+Yr9QzQc7BqsuYmY=</HostId></Error>[root@ip-10-0-0-64 ~]#

看到这个报错是提示我没有使用signV4的签名,那我使用CLI去跨账号测试,看看会提示什么:

1
2
3
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
[root@ip-10-0-0-64 ~]#

没有报参数无效,而是报了一个访问拒绝的:

1
2
3
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
[root@ip-10-0-0-64 ~]#

这就比较有意思了,对象明明已经公开了,但是仍然提示访问拒绝,那估计我把KMS CMK的加解密权限也公开之后没准通过CLI随便指定个User之后,没准就能访问了呢。

  1. 说干就干哈,KMS CMK加解密权限公开,S3 对象权限公开之后,会是啥状态呢?
    当然,不用想了,浏览器中访问,签名走的是signV2所以肯定跟上面报一个错误的:

image

比较有意思的是,我在CLI中使用另一个用户下载该对象是成功的(KMS CMK的加解密权限已公开):

1
2
3
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg
download: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg
[root@ip-10-0-0-64 ~]#

按照这个思路,如果作为一个匿名用户,也通过signV4签名的方式访问,是不是也可以下载这个经过KMS加密的公开的对象了呢,比较尴尬的,似乎signV4就是要使用AKSK的,所以没法整,匿名用户都是走的signV2。
如果我使用–no-sign-request参数呢,可以看到也是不行的,因为你没有办法让–no-sign-request访问KMS的时候走signV4:

1
2
3
4
[root@ip-10-0-0-64 ~]# aws s3 cp s3://aaabbbccc/HappyFace.jpg HappyFace.jpg --no-sign-request
download failed: s3://aaabbbccc/HappyFace.jpg to ./HappyFace.jpg An error occurred (InvalidArgument) when calling the GetObject operation: Requests specifying Server Side Encryption with AWS KMS managed keys require AWS Signature Version 4. You can enable AWS Signature Version 4 by running the command:
aws configure set s3.signature_version s3v4
[root@ip-10-0-0-64 ~]#

至此,我再总结一下:

  1. 如果使用了KMS加密,那无论KMS CMK的权限,当我把S3 object公开之后,通过浏览器或者wget都是不能访问的,会提示“无效参数”的错误。
  2. 如果使用了KMS加密,KMS的权限对外开放,那我如果使用CLI配置某一个用户之后是可以正常访问的
  3. 如果使用了KMS加密,KMS的权限对外开放,如果CLI使用–no-sign-request匿名执行命令也是不可以的

0x03 经过KMS加密之后还能通过pre-signed URL的方式将object公开访问吗?

容我先把KMS CMK的权限改回去,然后关闭该对象的公开访问,然后在生成pre-signed URL试试:

1
2
3
[root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbbccc/HappyFace.jpg --profile root
https://aaabbbccc.s3.cn-north-1.amazonaws.com.cn/HappyFace.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAXUIR4JOPO4YK27P7%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T142742Z&X-Amz-Signature=1bc979333e36b60d908f59811d9221303b1a44957821c5d35996300ede935cc6
[root@ip-10-0-0-64 ~]#

可以看到,是正常访问的:

image

这件事其实还是比较好理解的,因为生成临时的签名,我也是用当前用户凭证去签的,当前用户有权限就行,所以生产的pre-signed URL是可以正常访问的(用KMS加密的对象也没事)。

0x04 访问S3 pre-signed URL报认证错误应该怎么处理?

访问S3 pre-signed URL,报如下错误:

1
2
3
4
5
[root@ip-10-0-0-64 ~]# aws s3 presign s3://aaabbb/webwxgeticon.jpg
https://aaabbb.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAUKT6WCRV7BGA6LF3%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T143127Z&X-Amz-Signature=38ec063e7940823b351e9a9798f82087929bcff1f2dab7a1ad49a705fb74a8b4
[root@ip-10-0-0-64 ~]# curl "https://aaabbb.s3.cn-north-1.amazonaws.com.cn/webwxgeticon.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Credential=AKIAUKT6WCRV7BGA6LF3%2F20191123%2Fcn-north-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Date=20191123T143127Z&X-Amz-Signature=38ec063e7940823b351e9a9798f82087929bcff1f2dab7a1ad49a705fb74a8b4"
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>UnauthorizedAccess</Code><Message>You are not authorized to perform this operation</Message><RequestId>6116F30427B5AB43</RequestId><HostId>3wtTSiON9hssqGNCYfz+mCqigoFxl0s+DTJt3DKUEG9ABhGH5gOxCfOu7yJdKUHapN81r5uBUs0=</HostId></Error>[root@ip-10-0-0-64 ~]#

报这个错误的话,通常是由于当前账号未备案导致的(中国特色干啥都得备案),去找AWS备案就完事了。
当你公开对象的时候访问报这个错误,也是这个原因,挺那啥的这事。。。

0x05 总结

整理了一下S3 pre-signed URL与KMS加密的一些玩法,太不黑客了,不咋快乐。

0x06 参考链接

[1] presign, https://docs.aws.amazon.com/cli/latest/reference/s3/presign.html