diff --git a/changelog/v1.29.3-patch2/base64url.yaml b/changelog/v1.29.3-patch2/base64url.yaml new file mode 100644 index 000000000..c6056bd1e --- /dev/null +++ b/changelog/v1.29.3-patch2/base64url.yaml @@ -0,0 +1,7 @@ +changelog: +- type: FIX + issueLink: https://github.com/solo-io/envoy-gloo/issues/329 + resolvesIssue: false + description: >- + Fall back to attempting to decode Base64Url if Base64 decoding fails in the Inja transformation + base64_decode callback. This can happen especially when attempting to decode JWTs. diff --git a/source/extensions/filters/http/transformation/inja_transformer.cc b/source/extensions/filters/http/transformation/inja_transformer.cc index c5022de7e..2d258e5df 100644 --- a/source/extensions/filters/http/transformation/inja_transformer.cc +++ b/source/extensions/filters/http/transformation/inja_transformer.cc @@ -10,6 +10,7 @@ #include "source/common/regex/regex.h" #include "source/common/common/utility.h" #include "source/common/config/metadata.h" +#include "source/common/common/empty_string.h" #include "source/extensions/filters/http/solo_well_known_names.h" @@ -238,9 +239,15 @@ TransformerInstance::TransformerInstance(ThreadLocal::Slot &tls, Envoy::Random:: env_.add_callback("base64_encode", 1, [this](Arguments &args) { return base64_encode_callback(args); }); + env_.add_callback("base64url_encode", 1, [this](Arguments &args) { + return base64url_encode_callback(args); + }); env_.add_callback("base64_decode", 1, [this](Arguments &args) { return base64_decode_callback(args); }); + env_.add_callback("base64url_decode", 1, [this](Arguments &args) { + return base64url_decode_callback(args); + }); // substring can be called with either two or three arguments -- // the first argument is the string to be modified, the second is the start position // of the substring, and the optional third argument is the length of the substring. @@ -388,11 +395,21 @@ json TransformerInstance::base64_encode_callback(const inja::Arguments &args) co return Base64::encode(input.c_str(), input.length()); } +json TransformerInstance::base64url_encode_callback(const inja::Arguments &args) const { + const std::string &input = args.at(0)->get_ref(); + return Base64Url::encode(input.c_str(), input.length()); +} + json TransformerInstance::base64_decode_callback(const inja::Arguments &args) const { const std::string &input = args.at(0)->get_ref(); return Base64::decode(input); } +json TransformerInstance::base64url_decode_callback(const inja::Arguments &args) const { + const std::string &input = args.at(0)->get_ref(); + return Base64Url::decode(input); +} + // return a substring of the input string, starting at the start position // and extending for length characters. If length is not provided, the // substring will extend to the end of the string. diff --git a/source/extensions/filters/http/transformation/inja_transformer.h b/source/extensions/filters/http/transformation/inja_transformer.h index 97861da47..829978780 100644 --- a/source/extensions/filters/http/transformation/inja_transformer.h +++ b/source/extensions/filters/http/transformation/inja_transformer.h @@ -66,7 +66,9 @@ class TransformerInstance { nlohmann::json env(const inja::Arguments &args) const; nlohmann::json cluster_metadata_callback(const inja::Arguments &args) const; nlohmann::json base64_encode_callback(const inja::Arguments &args) const; + nlohmann::json base64url_encode_callback(const inja::Arguments &args) const; nlohmann::json base64_decode_callback(const inja::Arguments &args) const; + nlohmann::json base64url_decode_callback(const inja::Arguments &args) const; nlohmann::json substring_callback(const inja::Arguments &args) const; nlohmann::json replace_with_random_callback(const inja::Arguments &args); std::string& random_for_pattern(const std::string& pattern); diff --git a/test/extensions/filters/http/transformation/inja_transformer_test.cc b/test/extensions/filters/http/transformation/inja_transformer_test.cc index f0fd7e6d0..3b57c9f9b 100644 --- a/test/extensions/filters/http/transformation/inja_transformer_test.cc +++ b/test/extensions/filters/http/transformation/inja_transformer_test.cc @@ -882,6 +882,28 @@ TEST_F(InjaTransformerTest, Base64DecodeTestString) { EXPECT_EQ(body.toString(), test_string); } +TEST_F(InjaTransformerTest, Base64UrlDecodeJWT) { + Http::TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/foo"}}; + TransformationTemplate transformation; + + // Test string that produced the base64url underscore + auto expect_string = "aaoĆ°"; + // JWT with an underscore which is an invalid base64 character + auto encoded_string = "eyJzdWIiOiJhYW_DsCJ9"; + + auto formatted_string = fmt::format("{{{{base64url_decode(\"{}\")}}}}", encoded_string); + + transformation.mutable_body()->set_text(formatted_string); + + InjaTransformer transformer(transformation, rng_, google::protobuf::BoolValue(), tls_); + + NiceMock callbacks; + + Buffer::OwnedImpl body(""); + transformer.transform(headers, &headers, body, callbacks); + EXPECT_NE(std::string::npos, body.toString().find(expect_string)); +} + TEST_F(InjaTransformerTest, Base64Composed) { Http::TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/foo"}}; TransformationTemplate transformation;