aws

http://status.aws.amazon.com/ https://www.expeditedssl.com/aws-in-plain-english
kenny 2020-02-13T03:25:08.087700Z

Why would :Body be nil on a S3 GetObject request?

(aws-api/invoke
    s3-client
    {:op      :GetObject
     :request {:Bucket billing-s3-bucket
               :Key    "hourly-cur/hourly-cur/20200201-20200301/fc247861-fb77-460c-9f91/hourly-cur-7.csv.gz"}})
=>
{:LastModified #inst"2020-02-04T09:04:14.000-00:00",
 :ETag "\"d41d8cd98f00b204e9800998ecf8427e\"",
 :Metadata {},
 :ServerSideEncryption "AES256",
 :ContentLength 0,
 :ContentType "application/octet-stream",
 :AcceptRanges "bytes",
 :Body nil}

2020-02-13T13:21:29.091200Z

@kenny you can have keys that have no associated content. That :ContentLength is 0 suggests that is the case, in which case s3 sends back no body. You can see this if you eval (:http-response (meta *1)) (assuming your last exp was the invoke call you posted).

kenny 2020-02-13T15:49:29.091300Z

Ah yes, that was it. Thanks.

kenny 2020-02-13T16:06:52.095800Z

I believe there is an issue with S3 returning a key that looks like this // . If you create a CUR with no prefix set, AWS will store the CURs under the path //<report name>/<date range>/<assembly id>/<report item>. If I call :ListObjectsV2 on this bucket, I get a list of objects returned but the :Key is not correct. For example, one key returned looks like this "/test/20190501-20190601/7c0795d3-9b7e-438b-bb5d-de3ead37baad/test-1.csv.gz" when its actual key should be "//test/20190501-20190601/7c0795c3-9b7e-437b-bb5d-de3ead37baad/test-1.csv.gz" . If I call :GetObject on the key returned by aws-api, I get a NoSuchKey anomaly. If I try using the actual key with the //, I get a forbidden anomaly:

{:Error {:HostIdAttrs {},
         :StringToSignBytes "...",
         :CanonicalRequestBytes "...",
         :CanonicalRequestAttrs {},
         :Message "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
         :StringToSign "AWS4-HMAC-SHA256
                        20200213T160537Z
                        20200213/us-west-2/s3/aws4_request
                        ...",
         :CodeAttrs {},
         :RequestIdAttrs {},
         :SignatureProvidedAttrs {},
         :HostId "...",
         :StringToSignBytesAttrs {},
         :MessageAttrs {},
         :RequestId "E92073AA791F0EB6",
         :Code "SignatureDoesNotMatch",
         :SignatureProvided "...",
         :AWSAccessKeyIdAttrs {},
         :CanonicalRequest "GET
                            /compute-cost-reports//test/20190501-20190601/7c0795c3-9b7e-437b-bb5d-de3ead37baad/test-1.csv.gz
                            
                            host:<http://s3.us-west-2.amazonaws.com|s3.us-west-2.amazonaws.com>
                            x-amz-content-sha256:...
                            x-amz-date:20200213T160537Z
                            
                            host;x-amz-content-sha256;x-amz-date
                            ...",
         :CanonicalRequestBytesAttrs {},
         :StringToSignAttrs {},
         :AWSAccessKeyId "..."},
 :ErrorAttrs {},
 :cognitect.anomalies/category :cognitect.anomalies/forbidden}
The object does actually exist under that path -- I can see it in the S3 console.

2020-02-13T17:35:27.096200Z

What happens if you do the same with the aws cli?

kenny 2020-02-13T21:05:38.097100Z

Listing:

aws s3 ls <s3://my-bucket>
                           PRE /
2020-02-12 19:22:09          4 aws-programmatic-access-test-object
Get object:
aws s3 cp <s3://my-bucket//test/20190501-20190601/test-Manifest.json> .
download: <s3://my-bucket//test/20190501-20190601/test-Manifest.json> to ./test-Manifest.json

kenny 2020-02-13T21:05:59.097500Z

It appears to work. The file is downloaded on my computer.

kenny 2020-02-13T21:09:50.097700Z

This is what it looks like in the console. It's definitely a strange case. However, it is the default for creating a CUR without a prefix so I'd expect this to work.

2020-02-13T21:16:03.098200Z

@kenny would you kindly submit another issue?

1
ghadi 2020-02-13T21:21:46.098700Z

what is a CUR?

ghadi 2020-02-13T21:21:50.098900Z

@kenny

kenny 2020-02-13T21:21:58.099100Z

cost and usage report

ghadi 2020-02-13T21:23:33.099800Z

GetObjects shouldn't have leading slashes in the keys

kenny 2020-02-13T21:25:04.100900Z

It appears to work :man-shrugging::skin-tone-2: Apparently the aws billing team thinks it's okay. It certainly seems very odd to me.

ghadi 2020-02-13T21:25:34.101400Z

i thought you're posting because it doesn't appear to work

ghadi 2020-02-13T21:26:15.102300Z

Note the aws cli is baroque & does some unspecified transforms of cmdline arguments, and is not a canonical guide

kenny 2020-02-13T21:27:13.103400Z

aws-api does not support keys with a path like this //test/20190501-20190601/test-Manifest.json. The aws billing team stores CURs under that path if you don't specify a prefix.

ghadi 2020-02-13T21:27:38.104Z

there's no such thing as that path AFAIK

kenny 2020-02-13T21:27:49.104400Z

There is. Create a CUR with no prefix.

ghadi 2020-02-13T21:27:49.104500Z

I think aws cli eats the //

ghadi 2020-02-13T21:28:23.105100Z

please create a CUR and then aws s3 ls the bucket

kenny 2020-02-13T21:28:42.105400Z

I think I did that above?

ghadi 2020-02-13T21:29:02.105600Z

the listing above says aws-programmatic-access-test-object

ghadi 2020-02-13T21:29:07.105900Z

is that the one?

kenny 2020-02-13T21:29:24.106300Z

Yes. There's 2 keys there. One that is / and one that is aws-programmatic-access-test-object

ghadi 2020-02-13T21:31:00.106700Z

I see

ghadi 2020-02-13T21:43:07.107200Z

thanks, I added to it

1
kenny 2020-02-13T21:47:05.107400Z

This is what it looks like in the S3 console folders list. The first time I saw this, I didn't even think it was a folder.

ghadi 2020-02-13T21:48:57.108300Z

storing stuff with the / prefix is not a great idea 🙂

kenny 2020-02-13T21:53:36.108500Z

Lovely

ghadi 2020-02-13T21:55:28.108800Z

not sure if that's related, but it's suspect

kenny 2020-02-13T22:00:07.108900Z

Curious what the potential solution is if that's the problem. Not use http://java.net.URI?

2020-02-13T22:10:28.109100Z

I'm sure that was chosen for a reason. I'm pretty sure I added the (str/replace #"//+" "/") when I was refactoring some things, but didn't question the choice of URI at the time.

2020-02-13T22:11:17.109300Z

More context:

(-&gt; uri
                         (str/replace #"//+" "/") ; (URI.) throws Exception on '//'.
                         (str/replace #"\s" "%20"); (URI.) throws Exception on space.
                         (URI.)
                         (.normalize)
                         (.getPath)
                         (uri-encode "/"))

2020-02-13T22:12:08.109500Z

So it looks like we're using URI to avoid having to do the work done in .normalize

ghadi 2020-02-13T22:13:19.109700Z

it might be a red herring

ghadi 2020-02-13T22:13:38.109900Z

or URI might be doing unwanted things

kenny 2020-02-13T22:14:54.110200Z

FWIW, it sounds like // is a legal path https://stackoverflow.com/questions/20523318/is-a-url-with-in-the-path-section-valid

2020-02-13T22:16:20.110500Z

"So: yes, it is valid, no, don't use it." 🙂

😝 1
ghadi 2020-02-13T22:19:03.110800Z

user=&gt; (.normalize (<http://java.net|java.net>.URI/create "<https://ghadi.net//fooo>"))
#object[<http://java.net|java.net>.URI 0x54e81b21 "<https://ghadi.net/fooo>"]

2020-02-13T22:20:00.111100Z

You spelled foo wrong

ghadi 2020-02-13T22:20:09.111300Z

my b

2020-02-13T22:23:32.111500Z

From https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html:

In exception to this, you do not normalize URI paths for requests to Amazon S3. For example, if you have a bucket with an object named my-object//example//photo.user, use that path. Normalizing the path to my-object/example/photo.user will cause the request to fail. For more information, see Task 1: Create a Canonical Request in the Amazon Simple Storage Service API Reference. 

2020-02-13T22:23:40.111800Z

We don't make that exception.

ghadi 2020-02-13T22:26:40.112Z

yup

2020-02-13T22:29:38.112200Z

We do need to do it everywhere else, however.