Skip to content

aws_request_signing_filter hash payload by default#15846

Merged
lizan merged 21 commits intoenvoyproxy:mainfrom
jstewmon:aws-request-signing-payload-hash
May 12, 2021
Merged

aws_request_signing_filter hash payload by default#15846
lizan merged 21 commits intoenvoyproxy:mainfrom
jstewmon:aws-request-signing-payload-hash

Conversation

@jstewmon
Copy link
Copy Markdown
Contributor

@jstewmon jstewmon commented Apr 6, 2021

👋 Hi, I thought I'd have a crack at this one. It's the first time I've written C++ in nearly 20y, so feedback is more than welcome - I mostly waded though similar sources to figure out how to get something working.

Commit Message:
aws_request_signing_filter hash payload by default

canonical must include the hashed payload for most services. The prior
behavior of using UNSIGNED-PAYLOAD is an exception to the rule, which
select services like s3 support, since hashing the payload may be
impractical if the payload is very large.

A new filter option is introduced, so that the filter may be explicitly
configured to use the UNSIGNED-PAYLOAD string literal as specified
in the S3 signing docs:
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

fixes #13904

Additional Description:
The original implementation was seemingly very specific to S3 and was subsequently amended to extend the same niche singing behaviors for ES and Glacier. This changes the filter's default behavior to match the general SigV4 guidelines while providing a configuration option to enable the specialized UNSIGNED-PAYLOAD behavior.

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

Risk Level: Medium?

Deployments using the filter will now buffer requests by default, which could result in 413 responses for requests with bodies exceeding the buffer limit. These users can mitigate buffering by enabling the unsigned_payload option.

Testing:

I tested locally with a filter config. I anticipate updating the automated tests based on feedback from maintainers.

Docs Changes:
I think the .proto docstrings are sufficient, though I suppose a changelog entry may be needed somewhere? Pointers appreciated.

Release Notes:
Platform Specific Features:
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Deprecated:]
[Optional API Considerations:]

@jstewmon jstewmon requested a review from mattklein123 as a code owner April 6, 2021 03:44
@repokitteh-read-only
Copy link
Copy Markdown

Hi @jstewmon, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #15846 was opened by jstewmon.

see: more, trace.

@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to api/envoy/.
API shepherd assignee is @lizan
CC @envoyproxy/api-watchers: FYI only for changes made to api/envoy/.

🐱

Caused by: #15846 was opened by jstewmon.

see: more, trace.

Copy link
Copy Markdown
Member

@rojkov rojkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! Could you please add unit tests illustrating the new behavior introduced by the added Filter::decodeData() method?

To fix the formatting CI check you need to run ./tools/code_format/check_format.py fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better set the value in the init list right after host_rewrite_:

-      host_rewrite_(host_rewrite) {}
+      host_rewrite_(host_rewrite), unsigned_payload_{unsigned_payload} {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see where this member is initialized. Perhaps you wanted to use config_->isPayloadUnsigned() here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a really helpful comment for me! I wasn't paying close attention to what I was doing and completely failed to thread the config setting through to the filter implementation. I ended up grabbing the option from the config_ instance already available to the filter. Would appreciate another look to see if it looks ok now. 🙏

Copy link
Copy Markdown
Member

@rgs1 rgs1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. Could you add an entry to docs/root/version_history/current.rst and also some tests for the cases when hashing the body fails?

It'll be nice to update docs/root/configuration/http/http_filters/aws_request_signing_filter.rst with a note about this being supported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rojkov I started working on the tests tonight only to realize that the new sign overload can't be disambiguated by the mocks.

I wanted to get feedback on this strategy before updating everywhere. I didn't see Matcher or the other recommended techniques used elsewhere in the envoy code.

Is this a good approach? If not, do you have a recommendation?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be fine. testing::Matcher is actually used in some Envoy tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks - I now see Matcher and others used throughout the tests. I must have had a stray search filter enabled when I was looking before.

Copy link
Copy Markdown
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm api

@repokitteh-read-only repokitteh-read-only bot removed the api label Apr 7, 2021
@jstewmon jstewmon force-pushed the aws-request-signing-payload-hash branch from 35a7e2f to 0f70ce0 Compare April 8, 2021 03:55
@jstewmon
Copy link
Copy Markdown
Contributor Author

jstewmon commented Apr 8, 2021

I think this PR is in pretty good shape now. Though, I've just realized that none of the tests for decodeData are checking the stats - I can try to add that tomorrow.

I've tested the filter with GET and POST requests to SNS (without unsigned_payload). I've also tested GET and PUT requests to S3 with and without unsigned_payload. I also verified that PUTs to an S3 having a bucket policy which denies unsigned payloads succeeds without unsigned payloads and fails with a 403 with unsigned_payloads.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we stop iterating to avoid tampering the signature? Probably a comment could be helpful here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I inferred this as necessary because it's what the buffer filter and the aws lambda filter do. I supposed it was necessary to preclude the headers from being sent upstream before the stream ends.

I don't mind adding a comment, but the best I can honestly write with my current understanding is "stop iteration is what other filters do when the request is to be buffered."

Did you have something more concrete in mind?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My experience with filter flows is limited, but I think you are right: StopIteration is needed to buffer the request.

I don't have strong opinion if a comment is needed. Let's leave it as it is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new param needs to be documented in the doc string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: better put explicit type here instead of auto. It helps readers to disambiguate which variant of the overloaded sign() function is called with less mental effort.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it const.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method doesn't mutate the object's state -> can be declared as const.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I tried this, but it gives a compiler error:

 error: 'const' type qualifier on return type has no effect [-Werror,-Wignored-qualifiers]
const bool FilterConfigImpl::useUnsignedPayload() { return unsigned_payload_; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, I figured it out - was using const in the wrong place.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASSERT(request_headers_ != nullptr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: usually we initialize to null values with empty braces:

Http::RequestHeaderMap* request_headers_{};

@jstewmon jstewmon requested a review from rojkov April 8, 2021 13:37
rojkov
rojkov previously approved these changes Apr 8, 2021
Copy link
Copy Markdown
Member

@rojkov rojkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks good to me.

Please fix the CI checks. To run some checks (like DCO) automatically before pushing you can install the git hooks with

./support/bootstrap

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My experience with filter flows is limited, but I think you are right: StopIteration is needed to buffer the request.

I don't have strong opinion if a comment is needed. Let's leave it as it is.

@jstewmon jstewmon force-pushed the aws-request-signing-payload-hash branch from 26b6a01 to 7f03b7b Compare April 8, 2021 14:36
Copy link
Copy Markdown
Member

@rgs1 rgs1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, left a few more comments.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please link to the unsigned_payload field

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link to field and I don't think false needs to the inverted quotes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single inverted quotes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

single inverted quotes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: const bool

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

ENVOY_LOG(debug, "aws request signing from decodeHeaders unsigned_payload: {}", unsigned_payload);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are moving away from exceptions in the serving path, so maybe add a TODO about making sign() unexceptional

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm should we add a new stat signing_body_added or similar? To distinguish from the existing signing code path...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having separate counters could add fidelity when unsigned_payload is false. I don't actually know under what circumstances end_stream is true in decodeHeaders, so I suppose having discrete counters could be useful in diagnosing any unexpected behavior (like if there's some way end_stream is true when there was a request body).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call this use_unsigned_payload or skip_body_buffering?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change it to use_unsigned_payload. Unsigned payload is a very specific signing algorithm variant that isn't widely supported. I would say that using unsigned payload implies buffering is not required, but there could be some future case that warrants disabling buffering without using the unsigned payload variant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added two new counters that are only incremented when signing occurs in decodeData.

@jstewmon jstewmon requested a review from rgs1 April 8, 2021 20:47
@jstewmon
Copy link
Copy Markdown
Contributor Author

jstewmon commented Apr 9, 2021

I looked at the failed CI checks, and see two issues.

On windows:

//test/extensions/filters/http/aws_request_signing:aws_request_signing_filter_test FAILED TO BUILD

on linux_x64 tsan:

//test/integration:integration_test                                      FAILED in 2 out of 3 in 136.6s
  Stats over 3 runs: max = 136.6s, min = 132.1s, avg = 134.0s, dev = 1.9s
  /build/tmp/_bazel_envoybuild/b570b5ccd0454dc9af9f65ab1833764d/execroot/envoy/bazel-out/k8-dbg/testlogs/test/integration/integration_test/shard_1_of_2/test.log
  /build/tmp/_bazel_envoybuild/b570b5ccd0454dc9af9f65ab1833764d/execroot/envoy/bazel-out/k8-dbg/testlogs/test/integration/integration_test/shard_1_of_2/test_attempts/attempt_1.log

The windows build failure seems possibly related to this PR, but I don't see a way to get the detailed build output to figure out what the problem is.

I'm not sure how the integration test failure might be related to this PR. I don't see a way to get the test log output to see what the problem was.

I can't reproduce either of these locally (macOS).

@dragonbone81
Copy link
Copy Markdown

Hi, don't want to pollute this PR but not sure where else to ask.
I'm trying to proxy dynamoDB requests through envoy, and in an http lua filter modify the body of the request. This requires a re-sign of the request, however when using the signing_filter aws still sends back an error.
Does the fix in this PR have anything to do with what I'm seeing or are these unrelated?

@jstewmon
Copy link
Copy Markdown
Contributor Author

@dragonbone81 this PR should resolve your issue. The current aws request signing filter implementation does not sign request payloads, so it only works with requests which do not have a body or with a few services that support a special UNSIGNED PAYLOAD (e.g. s3).

@mattklein123 I've worked through all of the reviewer feedback thus far. There are basically two CI failures which I don't think I can resolve with the limited visibility I have. Could you please take a look and give any feedback you might have that can help me push this over the line? 🙏

@rgs1
Copy link
Copy Markdown
Member

rgs1 commented Apr 15, 2021

@jstewmon mind merging main and pushing? I can take a look at the CI failures.

canonical must include the hashed payload for most services. The prior
behavior of using UNSIGNED-PAYLOAD is an exception to the rule, which
select services like s3 support, since hashing the payload may be
impractical if the payload is very large.

A new filter option is introduced, so that the filter may be explicitly
configured to use the UNSIGNED-PAYLOAD string literal as specified
in the S3 signing docs:
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

fixes envoyproxy#13904

Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
@tonya11en
Copy link
Copy Markdown
Member

Overall this is looking good. Just added some minor comments.

Thanks!

Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
@jstewmon jstewmon requested review from lizan, rojkov and tonya11en April 23, 2021 03:22
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
lizan
lizan previously approved these changes Apr 29, 2021
@jstewmon
Copy link
Copy Markdown
Contributor Author

jstewmon commented May 3, 2021

@tonya11en thanks for the last round of feedback - I think I've addressed everything. Could you please take another look? 🙏

tonya11en
tonya11en previously approved these changes May 4, 2021
Copy link
Copy Markdown
Member

@tonya11en tonya11en left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm digging the thorough tests! Thanks for doing this and for all of the rigor in the implementation.

The explanations around the various decisions make sense. LGTM.

headers.setHost(host_rewrite);
}

if (!use_unsigned_payload && !end_stream) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Thanks for the really thorough answer-- made it really clear.

@lizan
Copy link
Copy Markdown
Member

lizan commented May 7, 2021

Can you merge main and resolve conflicts?

jstewmon added 2 commits May 7, 2021 10:32
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
@jstewmon jstewmon dismissed stale reviews from tonya11en and lizan via 7a964a5 May 7, 2021 15:36
@jstewmon
Copy link
Copy Markdown
Contributor Author

jstewmon commented May 7, 2021

@lizan merged main and fixed up the version history 👍

lizan
lizan previously approved these changes May 7, 2021
@repokitteh-read-only repokitteh-read-only bot removed the api label May 7, 2021
@jstewmon
Copy link
Copy Markdown
Contributor Author

@lizan is this ready to be merged? Anything else I need to do?

@tonya11en
Copy link
Copy Markdown
Member

tonya11en commented May 11, 2021

I think he was waiting on the tests to pass. Looks like one of them was cancelled, so it never happened.

/retest

@repokitteh-read-only
Copy link
Copy Markdown

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #15846 (comment) was created by @tonya11en.

see: more, trace.

Signed-off-by: Jonathan Stewmon <jstewmon@gmail.com>
@jstewmon
Copy link
Copy Markdown
Contributor Author

@tonya11en @lizan the retest passed, but in the meantime, the version history required another merge. If you can please approve the CI checks to run again, hopefully we'll get a clean run and can merge this. 🤞 🙏

@lizan lizan merged commit 80e1ca8 into envoyproxy:main May 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AWS Request Signing Documentation enhancement

6 participants